Added chat interface.
This commit is contained in:
parent
8a78e45440
commit
54e5017c38
@ -10,7 +10,7 @@
|
|||||||
<link href="/assets/dasfad/favicon-32x32.png" rel="icon" sizes="32x32" type="image/png">
|
<link href="/assets/dasfad/favicon-32x32.png" rel="icon" sizes="32x32" type="image/png">
|
||||||
<link href="/assets/dasfad/favicon-16x16.png" rel="icon" sizes="16x16" type="image/png">
|
<link href="/assets/dasfad/favicon-16x16.png" rel="icon" sizes="16x16" type="image/png">
|
||||||
<link href="/assets/dasfad/favicon-96x96.png" rel="icon" sizes="96x96" type="image/png">
|
<link href="/assets/dasfad/favicon-96x96.png" rel="icon" sizes="96x96" type="image/png">
|
||||||
<link rel="preload" href="/node_modules/.vite/deps/HavokPhysics.wasm" as="fetch">
|
<link as="fetch" href="/node_modules/.vite/deps/HavokPhysics.wasm" rel="preload">
|
||||||
<title>DASFAD</title>
|
<title>DASFAD</title>
|
||||||
<!-- <link as="script" href="/newRelic.js" rel="preload">
|
<!-- <link as="script" href="/newRelic.js" rel="preload">
|
||||||
<script defer src="/newRelic.js"></script> -->
|
<script defer src="/newRelic.js"></script> -->
|
||||||
|
|||||||
@ -102,6 +102,74 @@ export class DiagramManager {
|
|||||||
}
|
}
|
||||||
|
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// Chat event listeners for AI-powered diagram creation
|
||||||
|
document.addEventListener('chatCreateEntity', (event: CustomEvent) => {
|
||||||
|
const {entity} = event.detail;
|
||||||
|
this._logger.debug('chatCreateEntity', entity);
|
||||||
|
const object = new DiagramObject(this._scene, this.onDiagramEventObservable, {
|
||||||
|
diagramEntity: entity,
|
||||||
|
actionManager: this._diagramEntityActionManager
|
||||||
|
});
|
||||||
|
this._diagramObjects.set(entity.id, object);
|
||||||
|
this.onDiagramEventObservable.notifyObservers({
|
||||||
|
type: DiagramEventType.ADD,
|
||||||
|
entity: entity
|
||||||
|
}, DiagramEventObserverMask.TO_DB);
|
||||||
|
});
|
||||||
|
|
||||||
|
document.addEventListener('chatRemoveEntity', (event: CustomEvent) => {
|
||||||
|
const {target} = event.detail;
|
||||||
|
this._logger.debug('chatRemoveEntity', target);
|
||||||
|
const entity = this.findEntityByIdOrLabel(target);
|
||||||
|
if (entity) {
|
||||||
|
const diagramObject = this._diagramObjects.get(entity.id);
|
||||||
|
if (diagramObject) {
|
||||||
|
diagramObject.dispose();
|
||||||
|
this._diagramObjects.delete(entity.id);
|
||||||
|
this.onDiagramEventObservable.notifyObservers({
|
||||||
|
type: DiagramEventType.REMOVE,
|
||||||
|
entity: entity
|
||||||
|
}, DiagramEventObserverMask.TO_DB);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
document.addEventListener('chatModifyEntity', (event: CustomEvent) => {
|
||||||
|
const {target, updates} = event.detail;
|
||||||
|
this._logger.debug('chatModifyEntity', target, updates);
|
||||||
|
const entity = this.findEntityByIdOrLabel(target);
|
||||||
|
if (entity) {
|
||||||
|
const diagramObject = this._diagramObjects.get(entity.id);
|
||||||
|
if (diagramObject) {
|
||||||
|
if (updates.text !== undefined) {
|
||||||
|
diagramObject.text = updates.text;
|
||||||
|
}
|
||||||
|
// Note: color and position updates would require additional DiagramObject methods
|
||||||
|
const updatedEntity = {...entity, ...updates};
|
||||||
|
this.onDiagramEventObservable.notifyObservers({
|
||||||
|
type: DiagramEventType.MODIFY,
|
||||||
|
entity: updatedEntity
|
||||||
|
}, DiagramEventObserverMask.TO_DB);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
document.addEventListener('chatListEntities', () => {
|
||||||
|
this._logger.debug('chatListEntities');
|
||||||
|
const entities = Array.from(this._diagramObjects.values()).map(obj => ({
|
||||||
|
id: obj.diagramEntity.id,
|
||||||
|
template: obj.diagramEntity.template,
|
||||||
|
text: obj.diagramEntity.text || '',
|
||||||
|
position: obj.diagramEntity.position
|
||||||
|
}));
|
||||||
|
const responseEvent = new CustomEvent('chatListEntitiesResponse', {
|
||||||
|
detail: {entities},
|
||||||
|
bubbles: true
|
||||||
|
});
|
||||||
|
document.dispatchEvent(responseEvent);
|
||||||
|
});
|
||||||
|
|
||||||
this._logger.debug("DiagramManager constructed");
|
this._logger.debug("DiagramManager constructed");
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -135,6 +203,21 @@ export class DiagramManager {
|
|||||||
return appConfigInstance;
|
return appConfigInstance;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private findEntityByIdOrLabel(target: string): DiagramEntity | null {
|
||||||
|
// First try direct ID match
|
||||||
|
const byId = this._diagramObjects.get(target);
|
||||||
|
if (byId) {
|
||||||
|
return byId.diagramEntity;
|
||||||
|
}
|
||||||
|
// Then try label match (case-insensitive)
|
||||||
|
const targetLower = target.toLowerCase();
|
||||||
|
for (const [, obj] of this._diagramObjects) {
|
||||||
|
if (obj.diagramEntity.text?.toLowerCase() === targetLower) {
|
||||||
|
return obj.diagramEntity;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
private onDiagramEvent(event: DiagramEvent) {
|
private onDiagramEvent(event: DiagramEvent) {
|
||||||
let diagramObject = this._diagramObjects.get(event?.entity?.id);
|
let diagramObject = this._diagramObjects.get(event?.entity?.id);
|
||||||
|
|||||||
@ -2,7 +2,7 @@ import VrApp from '../../vrApp';
|
|||||||
import React, {useEffect, useState} from "react";
|
import React, {useEffect, useState} from "react";
|
||||||
import {Affix, Burger, Group, Menu, Alert, Button, Text} from "@mantine/core";
|
import {Affix, Burger, Group, Menu, Alert, Button, Text} from "@mantine/core";
|
||||||
import VrTemplate from "../vrTemplate";
|
import VrTemplate from "../vrTemplate";
|
||||||
import {IconStar, IconInfoCircle} from "@tabler/icons-react";
|
import {IconStar, IconInfoCircle, IconMessageCircle} from "@tabler/icons-react";
|
||||||
import VrMenuItem from "../components/vrMenuItem";
|
import VrMenuItem from "../components/vrMenuItem";
|
||||||
import CreateDiagramModal from "./createDiagramModal";
|
import CreateDiagramModal from "./createDiagramModal";
|
||||||
import ManageDiagramsModal from "./manageDiagramsModal";
|
import ManageDiagramsModal from "./manageDiagramsModal";
|
||||||
@ -19,6 +19,7 @@ import {DefaultScene} from "../../defaultScene";
|
|||||||
import VREntryPrompt from "../components/VREntryPrompt";
|
import VREntryPrompt from "../components/VREntryPrompt";
|
||||||
import ComingSoonBadge from "../components/ComingSoonBadge";
|
import ComingSoonBadge from "../components/ComingSoonBadge";
|
||||||
import UpgradeBadge from "../components/UpgradeBadge";
|
import UpgradeBadge from "../components/UpgradeBadge";
|
||||||
|
import ChatPanel from "../components/ChatPanel";
|
||||||
|
|
||||||
let vrApp: VrApp = null;
|
let vrApp: VrApp = null;
|
||||||
|
|
||||||
@ -118,6 +119,7 @@ export default function VrExperience() {
|
|||||||
const [rerender, setRerender] = useState(0);
|
const [rerender, setRerender] = useState(0);
|
||||||
const [dbName, setDbName] = useState(params.db);
|
const [dbName, setDbName] = useState(params.db);
|
||||||
const [showVRPrompt, setShowVRPrompt] = useState(false);
|
const [showVRPrompt, setShowVRPrompt] = useState(false);
|
||||||
|
const [chatOpen, setChatOpen] = useState(!isMobileVRDevice()); // Show chat by default on desktop
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const canvas = document.getElementById('vrCanvas');
|
const canvas = document.getElementById('vrCanvas');
|
||||||
@ -340,10 +342,30 @@ export default function VrExperience() {
|
|||||||
onClick={getClickHandler(configState, openConfig)}
|
onClick={getClickHandler(configState, openConfig)}
|
||||||
availableIcon={getFeatureIndicator(configState)}/>
|
availableIcon={getFeatureIndicator(configState)}/>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
|
<Menu.Divider/>
|
||||||
|
<VrMenuItem
|
||||||
|
tip="Toggle AI chat assistant for creating entities"
|
||||||
|
label={chatOpen ? "Hide Chat" : "Show Chat"}
|
||||||
|
onClick={() => setChatOpen(!chatOpen)}
|
||||||
|
availableIcon={<IconMessageCircle size={16}/>}/>
|
||||||
</Menu.Dropdown>
|
</Menu.Dropdown>
|
||||||
</Menu>
|
</Menu>
|
||||||
</Affix>
|
</Affix>
|
||||||
<canvas id="vrCanvas" style={{zIndex: 1000, width: '100%', height: '100vh'}}/>
|
|
||||||
|
<div style={{display: 'flex', height: '100vh', width: '100vw', overflow: 'hidden'}}>
|
||||||
|
<div style={{flex: 1, position: 'relative', minWidth: 0}}>
|
||||||
|
<canvas id="vrCanvas" style={{
|
||||||
|
position: 'absolute',
|
||||||
|
top: 0,
|
||||||
|
left: 0,
|
||||||
|
width: '100%',
|
||||||
|
height: '100%',
|
||||||
|
zIndex: 1000
|
||||||
|
}}/>
|
||||||
|
</div>
|
||||||
|
{chatOpen && <ChatPanel onClose={() => setChatOpen(false)}/>}
|
||||||
|
</div>
|
||||||
|
|
||||||
{/* VR Entry Prompt - Rendered AFTER canvas to ensure it's on top in DOM order */}
|
{/* VR Entry Prompt - Rendered AFTER canvas to ensure it's on top in DOM order */}
|
||||||
<VREntryPrompt
|
<VREntryPrompt
|
||||||
|
|||||||
@ -1,8 +1,10 @@
|
|||||||
/// <reference types="vitest" />
|
/// <reference types="vitest" />
|
||||||
import {defineConfig} from "vite";
|
import {defineConfig, loadEnv} from "vite";
|
||||||
|
|
||||||
/** @type {import('vite').UserConfig} */
|
/** @type {import('vite').UserConfig} */
|
||||||
export default defineConfig({
|
export default defineConfig(({mode}) => {
|
||||||
|
const env = loadEnv(mode, process.cwd(), '');
|
||||||
|
return {
|
||||||
test: {},
|
test: {},
|
||||||
define: {},
|
define: {},
|
||||||
build: {
|
build: {
|
||||||
@ -23,6 +25,7 @@ export default defineConfig({
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
server: {
|
server: {
|
||||||
|
allowedHosts: true,
|
||||||
port: 3001,
|
port: 3001,
|
||||||
proxy: {
|
proxy: {
|
||||||
'^/sync/.*': {
|
'^/sync/.*': {
|
||||||
@ -36,6 +39,22 @@ export default defineConfig({
|
|||||||
'^/api/images': {
|
'^/api/images': {
|
||||||
target: 'https://www.deepdiagram.com/',
|
target: 'https://www.deepdiagram.com/',
|
||||||
changeOrigin: true,
|
changeOrigin: true,
|
||||||
|
},
|
||||||
|
'^/api/claude': {
|
||||||
|
target: 'https://api.anthropic.com',
|
||||||
|
changeOrigin: true,
|
||||||
|
rewrite: (path) => path.replace(/^\/api\/claude/, ''),
|
||||||
|
configure: (proxy) => {
|
||||||
|
proxy.on('proxyReq', (proxyReq) => {
|
||||||
|
|
||||||
|
const apiKey = env.ANTHROPIC_API_KEY;
|
||||||
|
console.log(` API KEY: ${apiKey}`);
|
||||||
|
if (apiKey) {
|
||||||
|
proxyReq.setHeader('x-api-key', apiKey);
|
||||||
|
proxyReq.setHeader('anthropic-version', '2023-06-01');
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -54,9 +73,24 @@ export default defineConfig({
|
|||||||
'^/api/images': {
|
'^/api/images': {
|
||||||
target: 'https://www.deepdiagram.com/',
|
target: 'https://www.deepdiagram.com/',
|
||||||
changeOrigin: true,
|
changeOrigin: true,
|
||||||
|
},
|
||||||
|
'^/api/claude': {
|
||||||
|
target: 'https://api.anthropic.com',
|
||||||
|
changeOrigin: true,
|
||||||
|
rewrite: (path) => path.replace(/^\/api\/claude/, ''),
|
||||||
|
configure: (proxy) => {
|
||||||
|
proxy.on('proxyReq', (proxyReq) => {
|
||||||
|
const apiKey = env.ANTHROPIC_API_KEY;
|
||||||
|
console.log(` API KEY: ${apiKey}`);
|
||||||
|
if (apiKey) {
|
||||||
|
proxyReq.setHeader('x-api-key', apiKey);
|
||||||
|
proxyReq.setHeader('anthropic-version', '2023-06-01');
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
base: "/"
|
base: "/"
|
||||||
|
};
|
||||||
})
|
});
|
||||||
Loading…
Reference in New Issue
Block a user