Fix entity connections and add clear diagram tool
Connection fixes:
- Add chatResolveEntity event to resolve labels to entity IDs
- Update connectEntities to resolve from/to labels before creating connection
- Auto-generate connection labels as "{from label} to {to label}"
Clear diagram tool:
- Add clear_diagram tool with confirmation requirement
- Claude prompts user for confirmation before executing
- Clears all entities from diagram and resets session
- Syncs empty entity list to server after clearing
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
parent
7769910027
commit
2fd87b2d14
@ -179,6 +179,43 @@ export class DiagramManager {
|
||||
document.dispatchEvent(responseEvent);
|
||||
});
|
||||
|
||||
// Resolve entity label/ID to actual entity ID and label
|
||||
document.addEventListener('chatResolveEntity', (event: CustomEvent) => {
|
||||
const {target, requestId} = event.detail;
|
||||
this._logger.debug('chatResolveEntity', target);
|
||||
const entity = this.findEntityByIdOrLabel(target);
|
||||
const responseEvent = new CustomEvent('chatResolveEntityResponse', {
|
||||
detail: {
|
||||
requestId,
|
||||
target,
|
||||
entityId: entity?.id || null,
|
||||
entityLabel: entity?.text || null,
|
||||
found: !!entity
|
||||
},
|
||||
bubbles: true
|
||||
});
|
||||
document.dispatchEvent(responseEvent);
|
||||
});
|
||||
|
||||
// Clear all entities from the diagram
|
||||
document.addEventListener('chatClearDiagram', () => {
|
||||
this._logger.debug('chatClearDiagram - removing all entities');
|
||||
const entitiesToRemove = Array.from(this._diagramObjects.keys());
|
||||
for (const id of entitiesToRemove) {
|
||||
const diagramObject = this._diagramObjects.get(id);
|
||||
if (diagramObject) {
|
||||
const entity = diagramObject.diagramEntity;
|
||||
diagramObject.dispose();
|
||||
this._diagramObjects.delete(id);
|
||||
this.onDiagramEventObservable.notifyObservers({
|
||||
type: DiagramEventType.REMOVE,
|
||||
entity: entity
|
||||
}, DiagramEventObserverMask.TO_DB);
|
||||
}
|
||||
}
|
||||
this._logger.debug(`Cleared ${entitiesToRemove.length} entities`);
|
||||
});
|
||||
|
||||
this._logger.debug("DiagramManager constructed");
|
||||
}
|
||||
|
||||
|
||||
@ -1,5 +1,5 @@
|
||||
import {ChatMessage, CreateSessionResponse, DiagramSession, DiagramToolCall, SessionEntity, SyncEntitiesResponse, ToolResult} from "../types/chatTypes";
|
||||
import {connectEntities, createEntity, listEntities, modifyEntity, removeEntity} from "./entityBridge";
|
||||
import {clearDiagram, connectEntities, createEntity, listEntities, modifyEntity, removeEntity} from "./entityBridge";
|
||||
import {v4 as uuidv4} from 'uuid';
|
||||
|
||||
// Session management
|
||||
@ -204,6 +204,20 @@ const TOOLS = [
|
||||
},
|
||||
required: ["target"]
|
||||
}
|
||||
},
|
||||
{
|
||||
name: "clear_diagram",
|
||||
description: "DESTRUCTIVE: Permanently delete ALL entities from the diagram and clear the session. This cannot be undone. IMPORTANT: Before calling this tool, you MUST first ask the user to confirm by saying something like 'Are you sure you want to clear the entire diagram? This will permanently delete all entities and cannot be undone.' Only call this tool with confirmed=true AFTER the user explicitly confirms (e.g., says 'yes', 'confirm', 'do it', etc.).",
|
||||
input_schema: {
|
||||
type: "object",
|
||||
properties: {
|
||||
confirmed: {
|
||||
type: "boolean",
|
||||
description: "Must be true to execute. Only set to true after user has explicitly confirmed the deletion."
|
||||
}
|
||||
},
|
||||
required: ["confirmed"]
|
||||
}
|
||||
}
|
||||
];
|
||||
|
||||
@ -232,13 +246,22 @@ async function executeToolCall(toolCall: DiagramToolCall): Promise<ToolResult> {
|
||||
case 'create_entity':
|
||||
return createEntity(toolCall.input);
|
||||
case 'connect_entities':
|
||||
return connectEntities(toolCall.input);
|
||||
return await connectEntities(toolCall.input);
|
||||
case 'remove_entity':
|
||||
return removeEntity(toolCall.input);
|
||||
case 'modify_entity':
|
||||
return modifyEntity(toolCall.input);
|
||||
case 'list_entities':
|
||||
return await listEntities();
|
||||
case 'clear_diagram':
|
||||
const result = await clearDiagram(toolCall.input);
|
||||
// If successful, also clear the session (history and entities)
|
||||
if (result.success && currentSessionId) {
|
||||
await clearSessionHistory();
|
||||
// Sync empty entity list to clear server-side entity cache
|
||||
await syncEntitiesToSession([]);
|
||||
}
|
||||
return result;
|
||||
default:
|
||||
return {
|
||||
toolName: 'unknown',
|
||||
|
||||
@ -1,5 +1,6 @@
|
||||
import {DiagramEntity, DiagramEntityType, DiagramTemplates} from "../../diagram/types/diagramEntity";
|
||||
import {
|
||||
ClearDiagramParams,
|
||||
COLOR_NAME_TO_HEX,
|
||||
ConnectEntitiesParams,
|
||||
CreateEntityParams,
|
||||
@ -22,6 +23,42 @@ function resolveColor(color?: string): string {
|
||||
return '#0000ff';
|
||||
}
|
||||
|
||||
interface ResolvedEntity {
|
||||
id: string | null;
|
||||
label: string | null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Resolve an entity label or ID to actual entity ID and label
|
||||
*/
|
||||
function resolveEntity(target: string): Promise<ResolvedEntity> {
|
||||
return new Promise((resolve) => {
|
||||
const requestId = 'req-' + Date.now() + '-' + Math.random();
|
||||
|
||||
const responseHandler = (e: CustomEvent) => {
|
||||
if (e.detail.requestId !== requestId) return;
|
||||
document.removeEventListener('chatResolveEntityResponse', responseHandler as EventListener);
|
||||
resolve({
|
||||
id: e.detail.entityId,
|
||||
label: e.detail.entityLabel
|
||||
});
|
||||
};
|
||||
|
||||
document.addEventListener('chatResolveEntityResponse', responseHandler as EventListener);
|
||||
|
||||
const event = new CustomEvent('chatResolveEntity', {
|
||||
detail: {target, requestId},
|
||||
bubbles: true
|
||||
});
|
||||
document.dispatchEvent(event);
|
||||
|
||||
setTimeout(() => {
|
||||
document.removeEventListener('chatResolveEntityResponse', responseHandler as EventListener);
|
||||
resolve({id: null, label: null});
|
||||
}, 5000);
|
||||
});
|
||||
}
|
||||
|
||||
export function createEntity(params: CreateEntityParams): ToolResult {
|
||||
const id = 'id' + uuidv4();
|
||||
const template = SHAPE_TO_TEMPLATE[params.shape];
|
||||
@ -53,17 +90,43 @@ export function createEntity(params: CreateEntityParams): ToolResult {
|
||||
};
|
||||
}
|
||||
|
||||
export function connectEntities(params: ConnectEntitiesParams): ToolResult {
|
||||
export async function connectEntities(params: ConnectEntitiesParams): Promise<ToolResult> {
|
||||
// Resolve labels to actual entity IDs and get their labels
|
||||
const fromEntity = await resolveEntity(params.from);
|
||||
const toEntity = await resolveEntity(params.to);
|
||||
|
||||
if (!fromEntity.id) {
|
||||
return {
|
||||
toolName: 'connect_entities',
|
||||
success: false,
|
||||
message: `Could not find entity "${params.from}"`
|
||||
};
|
||||
}
|
||||
|
||||
if (!toEntity.id) {
|
||||
return {
|
||||
toolName: 'connect_entities',
|
||||
success: false,
|
||||
message: `Could not find entity "${params.to}"`
|
||||
};
|
||||
}
|
||||
|
||||
const id = 'id' + uuidv4();
|
||||
const color = resolveColor(params.color);
|
||||
|
||||
// Generate default label from entity labels: "{from label} to {to label}"
|
||||
const fromLabel = fromEntity.label || params.from;
|
||||
const toLabel = toEntity.label || params.to;
|
||||
const connectionLabel = `${fromLabel} to ${toLabel}`;
|
||||
|
||||
const entity: DiagramEntity = {
|
||||
id,
|
||||
template: DiagramTemplates.CONNECTION,
|
||||
type: DiagramEntityType.ENTITY,
|
||||
color,
|
||||
from: params.from,
|
||||
to: params.to,
|
||||
text: connectionLabel,
|
||||
from: fromEntity.id,
|
||||
to: toEntity.id,
|
||||
};
|
||||
|
||||
const event = new CustomEvent('chatCreateEntity', {
|
||||
@ -75,7 +138,7 @@ export function connectEntities(params: ConnectEntitiesParams): ToolResult {
|
||||
return {
|
||||
toolName: 'connect_entities',
|
||||
success: true,
|
||||
message: `Connected "${params.from}" to "${params.to}"`,
|
||||
message: `Connected "${fromLabel}" to "${toLabel}"`,
|
||||
entityId: id
|
||||
};
|
||||
}
|
||||
@ -195,3 +258,39 @@ export function getEntitiesForSync(): Promise<Array<{
|
||||
}, 5000);
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Clear all entities from the diagram
|
||||
*/
|
||||
export async function clearDiagram(params: ClearDiagramParams): Promise<ToolResult> {
|
||||
if (!params.confirmed) {
|
||||
return {
|
||||
toolName: 'clear_diagram',
|
||||
success: false,
|
||||
message: 'Clearing the diagram requires confirmation. Please ask the user to confirm before proceeding.'
|
||||
};
|
||||
}
|
||||
|
||||
// Get all entities first
|
||||
const entities = await getEntitiesForSync();
|
||||
|
||||
if (entities.length === 0) {
|
||||
return {
|
||||
toolName: 'clear_diagram',
|
||||
success: true,
|
||||
message: 'The diagram is already empty.'
|
||||
};
|
||||
}
|
||||
|
||||
// Dispatch clear event to remove all entities at once
|
||||
const event = new CustomEvent('chatClearDiagram', {
|
||||
bubbles: true
|
||||
});
|
||||
document.dispatchEvent(event);
|
||||
|
||||
return {
|
||||
toolName: 'clear_diagram',
|
||||
success: true,
|
||||
message: `Cleared ${entities.length} entities from the diagram.`
|
||||
};
|
||||
}
|
||||
|
||||
@ -42,12 +42,17 @@ export interface ModifyEntityParams {
|
||||
position?: { x: number; y: number; z: number };
|
||||
}
|
||||
|
||||
export interface ClearDiagramParams {
|
||||
confirmed: boolean;
|
||||
}
|
||||
|
||||
export type DiagramToolCall =
|
||||
| { name: 'create_entity'; input: CreateEntityParams }
|
||||
| { name: 'connect_entities'; input: ConnectEntitiesParams }
|
||||
| { name: 'remove_entity'; input: RemoveEntityParams }
|
||||
| { name: 'modify_entity'; input: ModifyEntityParams }
|
||||
| { name: 'list_entities'; input: Record<string, never> };
|
||||
| { name: 'list_entities'; input: Record<string, never> }
|
||||
| { name: 'clear_diagram'; input: ClearDiagramParams };
|
||||
|
||||
export const SHAPE_TO_TEMPLATE: Record<CreateEntityParams['shape'], DiagramTemplates> = {
|
||||
box: DiagramTemplates.BOX,
|
||||
|
||||
Loading…
Reference in New Issue
Block a user