mirror of
https://github.com/multica-ai/multica.git
synced 2026-08-02 18:13:27 +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>
117 lines
3.9 KiB
TypeScript
117 lines
3.9 KiB
TypeScript
"use client";
|
|
|
|
import { useRef, useState } from "react";
|
|
import { ChevronDown, FileText, X } from "lucide-react";
|
|
import { ContentEditor, type ContentEditorRef } from "../../editor";
|
|
import { Button } from "@multica/ui/components/ui/button";
|
|
import { cn } from "@multica/ui/lib/utils";
|
|
import { useT } from "../../i18n";
|
|
|
|
interface InstructionsEditorProps {
|
|
/** Markdown source. Used both as default value when expanded and as
|
|
* preview text when collapsed. */
|
|
value: string;
|
|
/** Fires on every keystroke (debounced inside ContentEditor). */
|
|
onChange: (value: string) => void;
|
|
/** Optional placeholder override. Defaults to the i18n "click to write"
|
|
* copy; the create dialog passes the duplicate-specific string for
|
|
* agents being cloned. */
|
|
placeholder?: string;
|
|
}
|
|
|
|
/**
|
|
* Collapsible Instructions field for the create-agent dialog. Stays compact
|
|
* until the user wants to write — most agents only need instructions when
|
|
* they're being authored carefully, not on every quick-create.
|
|
*
|
|
* Two states:
|
|
* collapsed → small clickable card, shows a preview of `value` (or the
|
|
* placeholder when empty). One click expands.
|
|
* expanded → full ContentEditor (markdown, bubble menu, mention support,
|
|
* attachment upload). "Collapse" button on the right of the
|
|
* header tucks it back; value is preserved.
|
|
*/
|
|
export function InstructionsEditor({
|
|
value,
|
|
onChange,
|
|
placeholder,
|
|
}: InstructionsEditorProps) {
|
|
const { t } = useT("agents");
|
|
const [expanded, setExpanded] = useState(false);
|
|
const editorRef = useRef<ContentEditorRef>(null);
|
|
|
|
const label = t(($) => $.create_dialog.instructions.label);
|
|
const resolvedPlaceholder =
|
|
placeholder ?? t(($) => $.create_dialog.instructions.placeholder_blank);
|
|
|
|
const expand = () => {
|
|
setExpanded(true);
|
|
// Focus on next tick so the editor mounts first.
|
|
setTimeout(() => editorRef.current?.focus(), 0);
|
|
};
|
|
|
|
if (!expanded) {
|
|
return (
|
|
<div>
|
|
<div className="text-xs font-medium uppercase tracking-wider text-muted-foreground">
|
|
{label}
|
|
</div>
|
|
<button
|
|
type="button"
|
|
onClick={expand}
|
|
className="mt-1.5 flex w-full items-start gap-2.5 rounded-lg border bg-card px-3 py-3 text-left transition-colors hover:border-primary/40 hover:bg-accent/40"
|
|
>
|
|
<FileText className="mt-0.5 h-3.5 w-3.5 shrink-0 text-muted-foreground" />
|
|
<div className="min-w-0 flex-1">
|
|
{value.trim() ? (
|
|
// Preview: first 2 lines of markdown, ellipsised.
|
|
<div className="line-clamp-2 whitespace-pre-wrap text-sm text-foreground/80">
|
|
{value}
|
|
</div>
|
|
) : (
|
|
<div className="text-sm text-muted-foreground">{resolvedPlaceholder}</div>
|
|
)}
|
|
</div>
|
|
<ChevronDown className="h-3.5 w-3.5 shrink-0 text-muted-foreground/40" />
|
|
</button>
|
|
</div>
|
|
);
|
|
}
|
|
|
|
return (
|
|
<div>
|
|
<div className="flex items-center justify-between">
|
|
<div className="text-xs font-medium uppercase tracking-wider text-muted-foreground">
|
|
{label}
|
|
</div>
|
|
<Button
|
|
type="button"
|
|
variant="ghost"
|
|
size="sm"
|
|
onClick={() => setExpanded(false)}
|
|
className="h-6 gap-1 px-2 text-xs"
|
|
>
|
|
<X className="h-3 w-3" />
|
|
{t(($) => $.create_dialog.instructions.collapse)}
|
|
</Button>
|
|
</div>
|
|
<div
|
|
className={cn(
|
|
"mt-1.5 rounded-lg border bg-card",
|
|
"focus-within:border-primary/40",
|
|
)}
|
|
>
|
|
<ContentEditor
|
|
ref={editorRef}
|
|
defaultValue={value}
|
|
onUpdate={onChange}
|
|
placeholder={t(($) => $.create_dialog.instructions.editor_placeholder)}
|
|
className="min-h-[160px] max-h-[320px] overflow-y-auto px-3 py-2.5 text-sm"
|
|
showBubbleMenu={true}
|
|
disableMentions={true}
|
|
/>
|
|
</div>
|
|
</div>
|
|
);
|
|
}
|