mirror of
https://github.com/multica-ai/multica.git
synced 2026-07-31 17:10:43 +02:00
Frontend half of Phase 1. CreateAgentDialog becomes a state machine
spanning four steps:
chooser → Start blank / From template cards
blank-form → existing manual form (post-chooser)
duplicate-form → existing form pre-filled from a duplicated agent
template-picker → grid of templates, click navigates to detail
template-detail → instructions + skill list preview + one-click Use
Picking a template never lands on the form: name auto-deduped against
existingAgentNames, runtime = first usable one, visibility = private.
Refinement happens on the agent detail page if needed. Same rationale
the doc spells out — templates exist precisely to skip configuration.
New components, all collapsible-by-default so quick-create stays fast:
- template-picker.tsx — categorised grid, lucide icons + semantic
accent tokens resolved through static maps so Tailwind's JIT picks
up every variant (dynamic class strings would silently miss).
- template-detail.tsx — instructions preview, skill list with cached
descriptions, Use CTA. Renders the failedURLs banner when a 422
fires — the only step that can trigger that response.
- instructions-editor.tsx — collapsed preview-card / expanded full
ContentEditor.
- skill-multi-select.tsx + skill-picker-list.tsx — shared multi-
select surface, also adopted by the existing skill-add-dialog.
- avatar-picker.tsx — agent avatar upload, mirrors the inspector's
visual language.
Schema-defended client (CLAUDE.md → API Response Compatibility): the
three new endpoints are wired through parseWithFallback with lenient
zod schemas. Desktop builds outlive any given server — a future
field rename / wrapping must not white-screen older installs.
listAgentTemplates accepts both the current bare array and a future
{templates: [...]} envelope. Coverage: 7 new schema-test cases in
schema.test.ts (null body, missing skills/instructions, malformed
create response, envelope migration).
Catalog + detail go through TanStack Query with staleTime: Infinity —
workspace-independent static data, no per-mount refetch.
Other:
- skill-add-dialog becomes a true multi-select (Confirm button +
checkbox list); attached skills are filtered out of the list.
- agents-page hands the freshly-created Agent back to the dialog so a
follow-up setAgentSkills can attach the form-selected skills.
- agent-overview-pane drops the mx-auto/max-w-2xl frame on config-
tab content; the wider dialog visual language reads better with
tabs filling the column.
- Every new UI string lives in both en/agents.json and
zh-Hans/agents.json under create_dialog.* / tab_body.skills.* —
locales/parity.test.ts blocks drift in CI.
Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
112 lines
4.1 KiB
TypeScript
112 lines
4.1 KiB
TypeScript
import { queryOptions } from "@tanstack/react-query";
|
|
import { api } from "../api";
|
|
|
|
export const agentTaskSnapshotKeys = {
|
|
all: (wsId: string) => ["workspaces", wsId, "agent-task-snapshot"] as const,
|
|
list: (wsId: string) => [...agentTaskSnapshotKeys.all(wsId), "list"] as const,
|
|
};
|
|
|
|
export const agentActivityKeys = {
|
|
all: (wsId: string) => ["workspaces", wsId, "agent-activity"] as const,
|
|
last30d: (wsId: string) => [...agentActivityKeys.all(wsId), "30d"] as const,
|
|
};
|
|
|
|
export const agentRunCountsKeys = {
|
|
all: (wsId: string) => ["workspaces", wsId, "agent-run-counts"] as const,
|
|
last30d: (wsId: string) => [...agentRunCountsKeys.all(wsId), "30d"] as const,
|
|
};
|
|
|
|
// Workspace-scoped agent task snapshot — every active task plus each agent's
|
|
// most recent terminal task. This is the single shared source of truth that
|
|
// powers per-agent presence derivation across the app. One fetch per
|
|
// workspace; all agent dots / hover cards / list rows derive presence from
|
|
// this cache with zero additional network traffic.
|
|
//
|
|
// The 30s staleTime is a safety net only; the primary freshness signal is
|
|
// WS task events, which invalidate this query immediately. Without WS,
|
|
// presence still updates within 30s on focus / mount.
|
|
export function agentTaskSnapshotOptions(wsId: string) {
|
|
return queryOptions({
|
|
queryKey: agentTaskSnapshotKeys.list(wsId),
|
|
queryFn: () => api.getAgentTaskSnapshot(),
|
|
staleTime: 30 * 1000,
|
|
gcTime: 5 * 60 * 1000,
|
|
refetchOnWindowFocus: true,
|
|
});
|
|
}
|
|
|
|
// Workspace-wide daily task activity for the last 30 days, anchored on
|
|
// completed_at. One fetch backs both the Agents-list sparkline (which
|
|
// only uses the trailing 7 buckets via `summarizeActivityWindow`) and
|
|
// the agent detail "Last 30 days" panel. WS task lifecycle events
|
|
// invalidate this query in useRealtimeSync; the staleTime is a
|
|
// tab-focus safety net.
|
|
export function agentActivity30dOptions(wsId: string) {
|
|
return queryOptions({
|
|
queryKey: agentActivityKeys.last30d(wsId),
|
|
queryFn: () => api.getWorkspaceAgentActivity30d(),
|
|
staleTime: 60 * 1000,
|
|
gcTime: 5 * 60 * 1000,
|
|
refetchOnWindowFocus: true,
|
|
});
|
|
}
|
|
|
|
// Workspace-wide 30-day run counts for the Agents-list RUNS column. Same
|
|
// single-fetch / WS-invalidate pattern as activity24hOptions.
|
|
export function agentRunCounts30dOptions(wsId: string) {
|
|
return queryOptions({
|
|
queryKey: agentRunCountsKeys.last30d(wsId),
|
|
queryFn: () => api.getWorkspaceAgentRunCounts(),
|
|
staleTime: 60 * 1000,
|
|
gcTime: 5 * 60 * 1000,
|
|
refetchOnWindowFocus: true,
|
|
});
|
|
}
|
|
|
|
export const agentTasksKeys = {
|
|
all: (wsId: string) => ["workspaces", wsId, "agent-tasks"] as const,
|
|
detail: (wsId: string, agentId: string) =>
|
|
[...agentTasksKeys.all(wsId), agentId] as const,
|
|
};
|
|
|
|
// All tasks for a single agent (the agent detail page consumer). Powers both
|
|
// the inspector's 7-day throughput stats and the Tasks tab list — shared so
|
|
// they don't fetch twice. WS task events invalidate this via the existing
|
|
// task-prefix invalidation in useRealtimeSync.
|
|
export function agentTasksOptions(wsId: string, agentId: string) {
|
|
return queryOptions({
|
|
queryKey: agentTasksKeys.detail(wsId, agentId),
|
|
queryFn: () => api.listAgentTasks(agentId),
|
|
staleTime: 30 * 1000,
|
|
gcTime: 5 * 60 * 1000,
|
|
refetchOnWindowFocus: true,
|
|
});
|
|
}
|
|
|
|
// Agent templates are workspace-independent: a static catalog served from
|
|
// the server's embedded JSON. Cache effectively forever — the only way the
|
|
// list / detail change is a server deploy, and a hard reload picks that up.
|
|
export const agentTemplateKeys = {
|
|
all: () => ["agent-templates"] as const,
|
|
list: () => [...agentTemplateKeys.all(), "list"] as const,
|
|
detail: (slug: string) => [...agentTemplateKeys.all(), "detail", slug] as const,
|
|
};
|
|
|
|
export function agentTemplateListOptions() {
|
|
return queryOptions({
|
|
queryKey: agentTemplateKeys.list(),
|
|
queryFn: () => api.listAgentTemplates(),
|
|
staleTime: Infinity,
|
|
gcTime: 30 * 60 * 1000,
|
|
});
|
|
}
|
|
|
|
export function agentTemplateDetailOptions(slug: string) {
|
|
return queryOptions({
|
|
queryKey: agentTemplateKeys.detail(slug),
|
|
queryFn: () => api.getAgentTemplate(slug),
|
|
staleTime: Infinity,
|
|
gcTime: 30 * 60 * 1000,
|
|
});
|
|
}
|