Added chat interface.

This commit is contained in:
Michael Mainguy 2025-12-20 11:24:31 -06:00
parent 8a78e45440
commit 54e5017c38
4 changed files with 146 additions and 7 deletions

View File

@ -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> -->

View File

@ -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);

View File

@ -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

View File

@ -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: "/"
};
}) });