feat(agent-ui): add inline skill and canvas references
This commit is contained in:
@@ -18,7 +18,7 @@ type AgentSkillStore = {
|
||||
loaded: boolean;
|
||||
errors: string[];
|
||||
loadSkills: (endpoint: string, token: string, forceReload?: boolean) => Promise<void>;
|
||||
selectSkill: (skill: AgentSkillSummary | null) => void;
|
||||
selectSkill: (skill: AgentSkillSummary | null, prompt?: string) => void;
|
||||
clearSelection: (expectedRevision?: number) => void;
|
||||
setDraft: (draft: AgentSkillDraft | null) => void;
|
||||
setGeneratingSource: (source: "conversation" | "canvas" | null) => void;
|
||||
@@ -47,24 +47,41 @@ export const useAgentSkillStore = create<AgentSkillStore>((set, get) => ({
|
||||
const selected = current.selectedSkill;
|
||||
const selectedSkill = selected ? skills.find((item) => item.name === selected.name && item.path === selected.path && item.enabled) || null : null;
|
||||
const selectionChanged = Boolean(selected && !selectedSkill);
|
||||
if (selectionChanged) clearSelectedSkillPrompt(current);
|
||||
const autoPrompt = selectedSkill
|
||||
? replaceAutoPrompt(current.autoPrompt, selectedSkill.interface?.defaultPrompt?.trim() || "", false)
|
||||
: replaceAutoPrompt(current.autoPrompt, "", false);
|
||||
? replaceAutoPrompt(current.autoPrompt, selectedSkill.interface?.defaultPrompt?.trim() || `$${selectedSkill.name}`, false)
|
||||
: "";
|
||||
set({ skills, selectedSkill, autoPrompt, selectionRevision: current.selectionRevision + (selectionChanged ? 1 : 0), loading: false, loaded: true, errors: (response.errors || []).map(skillErrorText) });
|
||||
} catch (error) {
|
||||
if (sequence !== loadSequence) return;
|
||||
set({ loading: false, loaded: true, errors: [error instanceof Error ? error.message : i18n.t("agent.state.skillReadFailed")] });
|
||||
}
|
||||
},
|
||||
selectSkill: (selectedSkill) => {
|
||||
selectSkill: (selectedSkill, prompt) => {
|
||||
const current = get();
|
||||
const autoPrompt = replaceAutoPrompt(current.autoPrompt, selectedSkill?.interface?.defaultPrompt?.trim() || "", true);
|
||||
const agent = useAgentStore.getState();
|
||||
const marker = selectedSkill ? `$${selectedSkill.name}` : "";
|
||||
const previousMarker = current.selectedSkill ? `$${current.selectedSkill.name}` : "";
|
||||
let nextPrompt = prompt;
|
||||
let autoPrompt = "";
|
||||
if (nextPrompt === undefined && selectedSkill) {
|
||||
const defaultPrompt = selectedSkill.interface?.defaultPrompt?.trim() || marker;
|
||||
if (!agent.prompt.trim() || Boolean(current.autoPrompt && agent.prompt === current.autoPrompt)) {
|
||||
nextPrompt = defaultPrompt;
|
||||
autoPrompt = defaultPrompt;
|
||||
} else if (previousMarker && agent.prompt.includes(previousMarker)) {
|
||||
nextPrompt = agent.prompt.replace(previousMarker, marker);
|
||||
} else {
|
||||
nextPrompt = agent.prompt.includes(marker) ? agent.prompt : `${agent.prompt}${/\s$/.test(agent.prompt) ? "" : " "}${marker}`;
|
||||
}
|
||||
}
|
||||
if (nextPrompt !== undefined && agent.prompt !== nextPrompt) agent.setAgentState({ prompt: nextPrompt });
|
||||
set({ selectedSkill, autoPrompt, selectionRevision: current.selectionRevision + 1 });
|
||||
},
|
||||
clearSelection: (expectedRevision) => {
|
||||
const current = get();
|
||||
if (expectedRevision !== undefined && expectedRevision !== current.selectionRevision) return;
|
||||
replaceAutoPrompt(current.autoPrompt, "", false);
|
||||
clearSelectedSkillPrompt(current);
|
||||
set({ selectedSkill: null, autoPrompt: "", selectionRevision: current.selectionRevision + 1 });
|
||||
},
|
||||
setDraft: (draft) => set({ draft }),
|
||||
@@ -72,7 +89,7 @@ export const useAgentSkillStore = create<AgentSkillStore>((set, get) => ({
|
||||
reset: () => {
|
||||
loadSequence += 1;
|
||||
const current = get();
|
||||
replaceAutoPrompt(current.autoPrompt, "", false);
|
||||
clearSelectedSkillPrompt(current);
|
||||
set({ skills: [], selectedSkill: null, selectionRevision: current.selectionRevision + 1, connectionRevision: current.connectionRevision + 1, autoPrompt: "", draft: null, generatingSource: null, loading: false, loaded: false, errors: [] });
|
||||
},
|
||||
}));
|
||||
@@ -85,6 +102,17 @@ function replaceAutoPrompt(previous: string, next: string, fillEmpty: boolean) {
|
||||
return next;
|
||||
}
|
||||
|
||||
function clearSelectedSkillPrompt(selection: Pick<AgentSkillStore, "selectedSkill" | "autoPrompt">) {
|
||||
const agent = useAgentStore.getState();
|
||||
if (selection.autoPrompt && agent.prompt === selection.autoPrompt) {
|
||||
agent.setAgentState({ prompt: "" });
|
||||
return;
|
||||
}
|
||||
if (!selection.selectedSkill) return;
|
||||
const marker = `$${selection.selectedSkill.name}`;
|
||||
if (agent.prompt.includes(marker)) agent.setAgentState({ prompt: agent.prompt.replace(marker, "") });
|
||||
}
|
||||
|
||||
function skillErrorText(value: unknown) {
|
||||
if (typeof value === "string") return value;
|
||||
if (!value || typeof value !== "object") return i18n.t("agent.state.skillParseFailed");
|
||||
|
||||
@@ -2,10 +2,14 @@ import { create } from "zustand";
|
||||
import i18n from "@/i18n";
|
||||
|
||||
import type { CanvasAgentOp, CanvasAgentSnapshot } from "@/lib/canvas/canvas-agent-ops";
|
||||
import type { CanvasResourceReference } from "@/lib/canvas/canvas-resource-references";
|
||||
|
||||
export type AgentChatRole = "user" | "assistant" | "system" | "tool" | "error";
|
||||
export type AgentAttachment = { id: string; name: string; type: string; size: number; width: number; height: number; url: string; dataUrl: string };
|
||||
export type AgentChatItem = { id: string; itemId?: string; clientMessageId?: string; threadId?: string; turnId?: string; role: AgentChatRole; title?: string; text: string; historyText?: string; meta?: string; detail?: unknown; attachments?: AgentAttachment[]; streamId?: string; activityItems?: Record<string, string> };
|
||||
export type AgentMessageAttachment = Pick<AgentAttachment, "id" | "name" | "url"> & Partial<Pick<AgentAttachment, "type" | "size" | "width" | "height" | "dataUrl">>;
|
||||
export type AgentCanvasReference = Pick<CanvasResourceReference, "nodeId" | "label" | "title" | "kind" | "previewUrl" | "text">;
|
||||
export type AgentSkillReference = { name: string; path: string; displayName?: string };
|
||||
export type AgentChatItem = { id: string; itemId?: string; clientMessageId?: string; threadId?: string; turnId?: string; role: AgentChatRole; title?: string; text: string; meta?: string; detail?: unknown; attachments?: AgentMessageAttachment[]; canvasReferences?: AgentCanvasReference[]; skill?: AgentSkillReference; streamId?: string; activityItems?: Record<string, string> };
|
||||
export type AgentEventLog = { id: string; time: string; title: string; text: string; raw?: unknown };
|
||||
export type AgentPendingToolCall = { requestId: string; name: string; input?: { ops?: CanvasAgentOp[]; path?: string } & Record<string, unknown> };
|
||||
export type AgentPermissionMode = "request" | "automatic" | "full";
|
||||
@@ -52,6 +56,7 @@ type AgentStore = {
|
||||
silentConnect: boolean;
|
||||
prompt: string;
|
||||
attachments: AgentAttachment[];
|
||||
canvasReferences: CanvasResourceReference[];
|
||||
sending: boolean;
|
||||
waiting: boolean;
|
||||
messages: AgentChatItem[];
|
||||
@@ -102,6 +107,7 @@ export const useAgentStore = create<AgentStore>((set, get) => ({
|
||||
silentConnect: false,
|
||||
prompt: "",
|
||||
attachments: [],
|
||||
canvasReferences: [],
|
||||
sending: false,
|
||||
waiting: false,
|
||||
messages: [],
|
||||
|
||||
Reference in New Issue
Block a user