From d4bd01ec040148f407abc4e1bed707973ef92c82 Mon Sep 17 00:00:00 2001 From: Guilhem Lemouel Date: Tue, 22 Sep 2026 09:05:36 +0200 Subject: [PATCH] feat(agents): mockup of agents as a standalone frontend kind Frontend-only exploration: saved AI agents (`ai_agent` resources) appear as their own kind, with pages of their own instead of the resources drawer. - home page lists agents as a kind, with an Agents filter and a create entry - /agents/edit/ hosts the existing agent editor with a Chat/Form toggle - /agents/get/ is the deployed agent's page: chat or single-shot form - /agent_chats lists every agent conversation, grouped by agent - a new agent starts from a minted draft path, named in the editor itself No backend change: a turn is still a preview of a one-step flow. Co-Authored-By: Claude Opus 5 (1M context) --- .../src/lib/components/DropdownV2Inner.svelte | 26 +- .../components/common/table/AgentRow.svelte | 201 +++++++++ .../lib/components/common/table/Row.svelte | 1 + .../components/common/table/RowIcon.svelte | 4 + .../src/lib/components/flows/agentChatFlow.ts | 75 ++++ .../lib/components/flows/agentDraft.svelte.ts | 53 ++- .../flows/content/AgentEditorHost.svelte | 117 ++++- .../flows/content/AgentRunPane.svelte | 164 +++++++ .../conversations/ConversationRow.svelte | 100 +++++ .../conversations/FlowChatInterface.svelte | 14 +- .../flows/conversations/FlowChatPanel.svelte | 22 +- .../FlowConversationsSidebar.svelte | 62 +-- .../flows/conversations/flowChatProps.ts | 13 + .../components/home/CreateActionsMenu.svelte | 16 + frontend/src/lib/components/home/Item.svelte | 20 +- .../src/lib/components/home/ItemsList.svelte | 127 +++++- .../src/lib/components/home/treeViewUtils.ts | 11 +- .../components/sidebar/SidebarContent.svelte | 8 + frontend/src/lib/utils.ts | 2 + .../src/routes/(root)/(logged)/+page.svelte | 2 +- .../(root)/(logged)/agent_chats/+page.svelte | 397 +++++++++++++++++ .../(root)/(logged)/agents/add/+page.svelte | 5 + .../(root)/(logged)/agents/add/+page.ts | 6 + .../agents/edit/[...path]/+page.svelte | 356 +++++++++++++++ .../agents/get/[...path]/+page.svelte | 416 ++++++++++++++++++ 25 files changed, 2113 insertions(+), 105 deletions(-) create mode 100644 frontend/src/lib/components/common/table/AgentRow.svelte create mode 100644 frontend/src/lib/components/flows/agentChatFlow.ts create mode 100644 frontend/src/lib/components/flows/content/AgentRunPane.svelte create mode 100644 frontend/src/lib/components/flows/conversations/ConversationRow.svelte create mode 100644 frontend/src/routes/(root)/(logged)/agent_chats/+page.svelte create mode 100644 frontend/src/routes/(root)/(logged)/agents/add/+page.svelte create mode 100644 frontend/src/routes/(root)/(logged)/agents/add/+page.ts create mode 100644 frontend/src/routes/(root)/(logged)/agents/edit/[...path]/+page.svelte create mode 100644 frontend/src/routes/(root)/(logged)/agents/get/[...path]/+page.svelte diff --git a/frontend/src/lib/components/DropdownV2Inner.svelte b/frontend/src/lib/components/DropdownV2Inner.svelte index 5f8b96c32f..f961dcb6ea 100644 --- a/frontend/src/lib/components/DropdownV2Inner.svelte +++ b/frontend/src/lib/components/DropdownV2Inner.svelte @@ -53,12 +53,26 @@ {#if item.icon} {/if} -

- {item.displayName} -

+ {#if item.subtitle} +
+

+ {item.displayName} +

+

+ {item.subtitle} +

+
+ {:else} +

+ {item.displayName} +

+ {/if} {@render item.extra?.()} {#if item.shortcut || item.selected || item.toggle !== undefined} - +
- - - -
+ + +
+ {#if runPane === 'chat' && chatReady} + +
+ +
+ {:else if runPane === 'chat'} +
+ {AGENT_CHAT_BLOCKED_REASON} +
+ {:else} + +
- - - - - +
+ +
+ {/if} +
diff --git a/frontend/src/lib/components/flows/content/AgentRunPane.svelte b/frontend/src/lib/components/flows/content/AgentRunPane.svelte new file mode 100644 index 0000000000..50f40cb58b --- /dev/null +++ b/frontend/src/lib/components/flows/content/AgentRunPane.svelte @@ -0,0 +1,164 @@ + + +{#if agentModule} + +
+
+ +
+
+ +
+
+{/if} diff --git a/frontend/src/lib/components/flows/conversations/ConversationRow.svelte b/frontend/src/lib/components/flows/conversations/ConversationRow.svelte new file mode 100644 index 0000000000..068eb45212 --- /dev/null +++ b/frontend/src/lib/components/flows/conversations/ConversationRow.svelte @@ -0,0 +1,100 @@ + + + diff --git a/frontend/src/lib/components/flows/conversations/FlowChatInterface.svelte b/frontend/src/lib/components/flows/conversations/FlowChatInterface.svelte index 7a35e6ea1f..756032fb86 100644 --- a/frontend/src/lib/components/flows/conversations/FlowChatInterface.svelte +++ b/frontend/src/lib/components/flows/conversations/FlowChatInterface.svelte @@ -1,6 +1,7 @@ + + +{#snippet agentChip(interactive: boolean)} + +{/snippet} + + +{#snippet noHint()}{/snippet} + +{#snippet talkingTo()} +
+ Talking to + + {#snippet buttonReplacement()} + {@render agentChip(true)} + {/snippet} + +
+{/snippet} + +
+ + + +
+ {#if currentResource} +
+ {title} + {#if !newChat || chatStarted} + +
+ Talking to + {@render agentChip(false)} +
+ {/if} +
+
+
+ {#if chatReady} + + {#key `${newChat}:${currentResource.path}`} +
+ +
+ {/key} + {#if newChat && !chatStarted && currentResource.description} +
+ +
+ {/if} + {:else if agentResource.current !== undefined} +
+ {#if newChat && !chatStarted} + {@render talkingTo()} + {/if} + {AGENT_CHAT_BLOCKED_REASON} +
+ {/if} +
+ {:else if !catalog.loading && (newChat || agents.length === 0)} +
+ {agents.length === 0 + ? 'No saved agent in this workspace. Create one from the home page to chat with it.' + : 'No agent can chat yet: set its memory to auto from its editor.'} +
+ {/if} +
+
diff --git a/frontend/src/routes/(root)/(logged)/agents/add/+page.svelte b/frontend/src/routes/(root)/(logged)/agents/add/+page.svelte new file mode 100644 index 0000000000..e54568a9ed --- /dev/null +++ b/frontend/src/routes/(root)/(logged)/agents/add/+page.svelte @@ -0,0 +1,5 @@ + diff --git a/frontend/src/routes/(root)/(logged)/agents/add/+page.ts b/frontend/src/routes/(root)/(logged)/agents/add/+page.ts new file mode 100644 index 0000000000..38697b7210 --- /dev/null +++ b/frontend/src/routes/(root)/(logged)/agents/add/+page.ts @@ -0,0 +1,6 @@ +import { makeDraftAddLoad } from '$lib/draftAddRedirect' +import type { PageLoad } from './$types' + +export const prerender = false + +export const load: PageLoad = makeDraftAddLoad('agents/edit') diff --git a/frontend/src/routes/(root)/(logged)/agents/edit/[...path]/+page.svelte b/frontend/src/routes/(root)/(logged)/agents/edit/[...path]/+page.svelte new file mode 100644 index 0000000000..2ef16c6bc0 --- /dev/null +++ b/frontend/src/routes/(root)/(logged)/agents/edit/[...path]/+page.svelte @@ -0,0 +1,356 @@ + + +
+
+
+
+ { + if (draft?.state) draft.state.description = v.trim() + }} + textClass="text-xs font-semibold text-emphasis leading-tight" + class="max-w-full" + /> +
+
+ {#if version != undefined && !refused} + + v{version} + + {/if} + + {#if ws && !refused} + draft?.sync.resetToDeployed(path)} + /> + {/if} + {#if readOnly} + + Read only + + {/if} +
+ +
+ {#if !refused} + (runPane = v as 'chat' | 'form')} + noWFull + > + {#snippet children({ item })} + + + {/snippet} + +
+ + {#if !draftOnly} + + + {/if} + + +
+ draft?.deployed} + getCurrent={() => draft?.state} + onDiscard={() => draft?.sync.resetToDeployed(path)} + title="Deployed <> Unsaved agent changes" + /> +
+ +
+ {#key `${ws}:${path}:${restores}`} + (toolId = id)} + {onSaved} + {runPane} + {isNew} + /> + {/key} +
+ + + +{#snippet pathField(autofocus: boolean)} + {#if pathEditable} + draft?.state?.draft_path ?? '', + (v) => { + if (draft?.state) draft.state.draft_path = v + } + } + initialPath={draft?.state?.draft_path ?? ''} + namePlaceholder="agent" + kind="resource" + size="sm" + drawerOffset={4000} + /> + {:else} + {shownPath} +

+ Rename a deployed agent with Move, from its page, so the flows linking it follow. +

+ {/if} +{/snippet} + + + + (settingsOpen = false)}> +
+
+ + +
+
+
+
+ + + versionDrawer?.closeDrawer()} noPadding> + { + versionDrawer?.closeDrawer() + if (ws) markAgentWritten(ws, path) + restores++ + }} + /> + + diff --git a/frontend/src/routes/(root)/(logged)/agents/get/[...path]/+page.svelte b/frontend/src/routes/(root)/(logged)/agents/get/[...path]/+page.svelte new file mode 100644 index 0000000000..55204a25b9 --- /dev/null +++ b/frontend/src/routes/(root)/(logged)/agents/get/[...path]/+page.svelte @@ -0,0 +1,416 @@ + + + + + { + await goto(`${base}/agents/get/${e.detail}?workspace=${ws}`) + }} +/> + (deleteOpen = false)} + on:confirmed={() => { + deleteOpen = false + deleteAgent() + }} +> + Every flow linking {path} will fail at its agent step once it is gone. + + + versionDrawer?.closeDrawer()} noPadding> + { + versionDrawer?.closeDrawer() + if (ws) markAgentWritten(ws, path) + }} + /> + + + +
+
+
+
+ +
+ + {agent?.description || path} + + {#if agent?.description} + {path} + {/if} +
+ {#if loaded?.version != undefined} + + v{loaded.version} + + {/if} + {#if agent?.labels?.length} + + {/if} + +
+ {#if agent && !refusal} + (leftPane = v as 'chat' | 'form')} + noWFull + > + {#snippet children({ item })} + + + {/snippet} + +
+ {/if} +
+ {#if menuItems.length > 0} + + {/if} + {#if can_write && !$userStore?.operator} + + {/if} +
+
+
+ + {#if loadError} +
+ {loadError} +
+ {:else if refusal} +
+ {refusal} +
+ {:else if agent} +
+ + +
+ + {#if agent.description && leftPane === 'form'} +
+
+ +
+
+ {/if} +
+ {#if leftPane === 'chat' && chatReady} + + {:else if leftPane === 'chat'} +
+ {chatBlockedReason} +
+ {:else} + + {#key `${ws}:${path}:${writes}`} + + {/key} + {/if} +
+
+
+ +
+ + + + {#snippet content()} +
+ +
+

Model

+
+ {#each brain.filter((p) => p.label !== AGENT_BRAIN_LABELS['system_prompt']) as param (param.label)} +
+
{param.label}
+
{param.value}
+
+ {/each} +
+
+
+

System message

+ {#if systemPrompt} +
{systemPrompt}
+ {:else} + None + {/if} +
+
+

+ Tools ({tools.length}) +

+ {#if tools.length === 0} + No tools. + {:else} +
    + {#each tools as tool (tool.id)} +
  • + + {toolDisplayName(tool) ?? tool.id} + + {toolKind(tool)} +
  • + {/each} +
+ {/if} +
+
+ + + +
+ {/snippet} +
+
+
+
+
+ {:else} +
Loading agent...
+ {/if} +