feat: 支持岗位会商任务和意图编辑
This commit is contained in:
1 parent
790267ac43
commit
7bd032ec77
2 files changed
+408
-12
No files matched your search
@@ -128,7 +128,7 @@ function toNode(node: ServerNode): PositionRoundtableNode {
|
||||
}
|
||||
|
||||
function toSession(session: ServerSession): PositionRoundtableSession {
|
||||
return {
|
||||
const result: PositionRoundtableSession = {
|
||||
id: session.id,
|
||||
externalTaskId: session.external_task_id,
|
||||
taskSnapshot: session.task_snapshot ?? {},
|
||||
@@ -143,8 +143,11 @@ function toSession(session: ServerSession): PositionRoundtableSession {
|
||||
status: session.status,
|
||||
createdAt: session.created_at,
|
||||
updatedAt: session.updated_at,
|
||||
nodes: session.nodes?.map(toNode),
|
||||
};
|
||||
if (Array.isArray(session.nodes)) {
|
||||
result.nodes = session.nodes.map(toNode);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
export async function listPositionSessions(externalTaskId?: string): Promise<PositionRoundtableSession[]> {
|
||||
@@ -182,6 +185,21 @@ export async function getPositionSession(sessionId: string): Promise<PositionRou
|
||||
return toSession(result);
|
||||
}
|
||||
|
||||
export async function updatePositionTaskSnapshot(
|
||||
sessionId: string,
|
||||
taskSnapshot: Record<string, unknown>,
|
||||
): Promise<PositionRoundtableSession> {
|
||||
const result = await readJson<ServerSession>(
|
||||
await apiFetch(`${BASE()}/sessions/${encodeURIComponent(sessionId)}/task`, {
|
||||
method: "PUT",
|
||||
headers: { "Content-Type": "application/json" },
|
||||
body: JSON.stringify({ task_snapshot: taskSnapshot }),
|
||||
}),
|
||||
"update position task",
|
||||
);
|
||||
return toSession(result);
|
||||
}
|
||||
|
||||
export async function savePositionIntent(
|
||||
sessionId: string,
|
||||
input: { intentThreadId?: string | null; intentSnapshot: object | null },
|
||||
|
||||
@@ -19,6 +19,7 @@ import {
|
||||
Layers3,
|
||||
Loader2,
|
||||
MessageSquareText,
|
||||
PencilLine,
|
||||
Plus,
|
||||
RotateCcw,
|
||||
Search,
|
||||
@@ -86,6 +87,7 @@ import {
|
||||
preparePositionSummary,
|
||||
rejectPositionNode,
|
||||
savePositionIntent,
|
||||
updatePositionTaskSnapshot,
|
||||
type PositionRoundtableNode,
|
||||
type PositionRoundtableSession,
|
||||
} from "@/position-roundtable/api/sessions";
|
||||
@@ -116,6 +118,11 @@ type PositionArtifactPreview = {
|
||||
mimeType: string | null;
|
||||
auto?: boolean;
|
||||
};
|
||||
type IntentEditDraft = {
|
||||
objective: string;
|
||||
constraints: string;
|
||||
assumptions: string;
|
||||
};
|
||||
|
||||
function sandboxThreadSignature(thread: BaseStream<AgentThreadState>): string {
|
||||
const messages = thread.messages ?? [];
|
||||
@@ -347,6 +354,14 @@ function artifactName(artifact: Record<string, unknown> | null | undefined): str
|
||||
return path.split("/").filter(Boolean).at(-1) ?? "";
|
||||
}
|
||||
|
||||
function mergePositionSession(
|
||||
current: PositionRoundtableSession | null,
|
||||
next: PositionRoundtableSession,
|
||||
): PositionRoundtableSession {
|
||||
if (!current || current.id !== next.id || Array.isArray(next.nodes)) return next;
|
||||
return { ...current, ...next, nodes: current.nodes };
|
||||
}
|
||||
|
||||
function parseIntentSummary(snapshot: Record<string, unknown> | null | undefined): IntentSummary | null {
|
||||
if (!snapshot || typeof snapshot.objective !== "string" || !Array.isArray(snapshot.constraints)) {
|
||||
return null;
|
||||
@@ -430,6 +445,17 @@ export default function PositionRoundtablePage({
|
||||
signature: string;
|
||||
} | null>(null);
|
||||
const [flowDetailItem, setFlowDetailItem] = useState<ResultItem | null>(null);
|
||||
const [taskEditorOpen, setTaskEditorOpen] = useState(false);
|
||||
const [taskDraft, setTaskDraft] = useState<PositionTask>(fallbackTask);
|
||||
const [taskSaving, setTaskSaving] = useState(false);
|
||||
const [intentEditorOpen, setIntentEditorOpen] = useState(false);
|
||||
const [intentDraft, setIntentDraft] = useState<IntentEditDraft>({
|
||||
objective: "",
|
||||
constraints: "",
|
||||
assumptions: "",
|
||||
});
|
||||
const [intentSaving, setIntentSaving] = useState(false);
|
||||
const [intentCompletedAt, setIntentCompletedAt] = useState("");
|
||||
const activePosition = POSITIONS.find((item) => item.id === positionId)!;
|
||||
useEffect(() => {
|
||||
artifactsCtxRef.current = artifactsCtx;
|
||||
@@ -482,6 +508,140 @@ export default function PositionRoundtablePage({
|
||||
});
|
||||
const hydratedIntentSessionRef = useRef<string | null>(null);
|
||||
const persistedIntentRef = useRef<string | null>(null);
|
||||
const isArchivedSession = positionSession?.status === "archived";
|
||||
|
||||
const syncSessionUrl = useCallback(
|
||||
(sessionId: string, nextTaskId: string) => {
|
||||
const next = new URLSearchParams(searchParamsString);
|
||||
next.set("taskId", nextTaskId);
|
||||
next.set("sessionId", sessionId);
|
||||
next.delete("new");
|
||||
setSearchParams(next, { replace: true });
|
||||
},
|
||||
[searchParamsString, setSearchParams],
|
||||
);
|
||||
|
||||
const ensureEditableSession = useCallback(
|
||||
async (taskSnapshot: PositionTask): Promise<PositionRoundtableSession> => {
|
||||
if (positionSession) return positionSession;
|
||||
const created = await createPositionSession({
|
||||
externalTaskId: taskSnapshot.id || undefined,
|
||||
taskSnapshot: { ...taskSnapshot },
|
||||
});
|
||||
setPositionSession(created);
|
||||
syncSessionUrl(created.id, taskSnapshot.id);
|
||||
return created;
|
||||
},
|
||||
[positionSession, syncSessionUrl],
|
||||
);
|
||||
|
||||
const openTaskEditor = useCallback(() => {
|
||||
if (isArchivedSession) return;
|
||||
setTaskDraft({ ...task });
|
||||
setTaskEditorOpen(true);
|
||||
}, [isArchivedSession, task]);
|
||||
|
||||
const handleSaveTaskEdit = useCallback(async () => {
|
||||
if (isArchivedSession) {
|
||||
toast.warning("已归档的会话不能编辑任务信息");
|
||||
return;
|
||||
}
|
||||
const title = taskDraft.title.trim();
|
||||
if (!title) {
|
||||
toast.warning("请填写任务名称");
|
||||
return;
|
||||
}
|
||||
const nextTask: PositionTask = {
|
||||
...task,
|
||||
title,
|
||||
description: taskDraft.description.trim(),
|
||||
direction: taskDraft.direction.trim() || task.direction,
|
||||
};
|
||||
setTaskSaving(true);
|
||||
try {
|
||||
const saved = positionSession
|
||||
? await updatePositionTaskSnapshot(positionSession.id, { ...nextTask })
|
||||
: await createPositionSession({
|
||||
externalTaskId: nextTask.id || undefined,
|
||||
taskSnapshot: { ...nextTask },
|
||||
});
|
||||
setPositionSession((current) => mergePositionSession(current, saved));
|
||||
syncSessionUrl(saved.id, nextTask.id);
|
||||
setTaskEditorOpen(false);
|
||||
toast.success("任务信息已更新");
|
||||
} catch (error) {
|
||||
toast.error(`任务信息保存失败:${error instanceof Error ? error.message : String(error)}`);
|
||||
} finally {
|
||||
setTaskSaving(false);
|
||||
}
|
||||
}, [isArchivedSession, positionSession, syncSessionUrl, task, taskDraft]);
|
||||
|
||||
const openIntentEditor = useCallback(() => {
|
||||
if (isArchivedSession) return;
|
||||
const currentIntent = frozenIntent ?? intent.intentReady;
|
||||
setIntentDraft({
|
||||
objective: currentIntent?.objective ?? "",
|
||||
constraints: (currentIntent?.constraints ?? []).join("\n"),
|
||||
assumptions: (currentIntent?.assumptions ?? []).join("\n"),
|
||||
});
|
||||
setIntentEditorOpen(true);
|
||||
}, [frozenIntent, intent.intentReady, isArchivedSession]);
|
||||
|
||||
const handleSaveIntentEdit = useCallback(async () => {
|
||||
if (isArchivedSession) {
|
||||
toast.warning("已归档的会话不能编辑意图理解");
|
||||
return;
|
||||
}
|
||||
const objective = intentDraft.objective.trim();
|
||||
if (!objective) {
|
||||
toast.warning("请填写任务意图");
|
||||
return;
|
||||
}
|
||||
const splitLines = (value: string) =>
|
||||
value
|
||||
.split(/\r?\n/)
|
||||
.map((item) => item.trim())
|
||||
.filter(Boolean);
|
||||
const nextIntent: IntentSummary = {
|
||||
objective,
|
||||
constraints: splitLines(intentDraft.constraints),
|
||||
assumptions: splitLines(intentDraft.assumptions),
|
||||
};
|
||||
|
||||
setIntentSaving(true);
|
||||
try {
|
||||
const baseSession = await ensureEditableSession(task);
|
||||
const intentThreadId = baseSession.intentThreadId ?? intent.intentThreadId;
|
||||
const saved = await savePositionIntent(baseSession.id, {
|
||||
intentThreadId,
|
||||
intentSnapshot: nextIntent,
|
||||
});
|
||||
persistedIntentRef.current = `${saved.id}:${intentThreadId ?? ""}:${JSON.stringify(nextIntent)}`;
|
||||
setPositionSession((current) => mergePositionSession(current, saved));
|
||||
intent.hydrateFromDraft({
|
||||
intentThreadId: intentThreadId ?? intent.intentThreadId,
|
||||
intentReady: nextIntent,
|
||||
interactiveMessages: intent.interactiveMessages,
|
||||
timelineEntries: [],
|
||||
});
|
||||
if (!intentCompletedAt) setIntentCompletedAt(timeLabel());
|
||||
setIntentEditorOpen(false);
|
||||
toast.success("意图理解已更新");
|
||||
} catch (error) {
|
||||
toast.error(`意图理解保存失败:${error instanceof Error ? error.message : String(error)}`);
|
||||
} finally {
|
||||
setIntentSaving(false);
|
||||
}
|
||||
}, [
|
||||
ensureEditableSession,
|
||||
intent,
|
||||
intentCompletedAt,
|
||||
intentDraft.assumptions,
|
||||
intentDraft.constraints,
|
||||
intentDraft.objective,
|
||||
isArchivedSession,
|
||||
task,
|
||||
]);
|
||||
|
||||
const { data: chains = [], isLoading: chainsLoading } = useQuery({
|
||||
queryKey: ["position-roundtable", "chains"],
|
||||
@@ -550,6 +710,25 @@ export default function PositionRoundtablePage({
|
||||
const nextNodeKey = activeNode?.nodeKey ?? null;
|
||||
if (activeNodeKey !== nextNodeKey) setActiveNodeKey(nextNodeKey);
|
||||
}, [activeNode?.nodeKey, activeNodeKey]);
|
||||
useEffect(() => {
|
||||
if (
|
||||
!sessionIdFromUrl ||
|
||||
!positionSession ||
|
||||
Array.isArray(positionSession.nodes) ||
|
||||
positionSession.status === "intent_pending"
|
||||
) {
|
||||
return;
|
||||
}
|
||||
let cancelled = false;
|
||||
void getPositionSession(sessionIdFromUrl)
|
||||
.then((session) => {
|
||||
if (!cancelled) setPositionSession(session);
|
||||
})
|
||||
.catch(() => undefined);
|
||||
return () => {
|
||||
cancelled = true;
|
||||
};
|
||||
}, [positionSession, sessionIdFromUrl]);
|
||||
const handleNodeUpdate = useCallback((updatedNode: PositionRoundtableNode) => {
|
||||
setPositionSession((current) => {
|
||||
if (!current?.nodes) return current;
|
||||
@@ -597,7 +776,7 @@ export default function PositionRoundtablePage({
|
||||
});
|
||||
if (cancelled) return;
|
||||
persistedIntentRef.current = `${saved.id}:${intentThreadId}:${snapshotKey}`;
|
||||
setPositionSession((current) => (current?.id === saved.id ? { ...current, ...saved } : saved));
|
||||
setPositionSession((current) => mergePositionSession(current, saved));
|
||||
if (!sessionIdFromUrl) {
|
||||
const next = new URLSearchParams(searchParamsString);
|
||||
next.set("taskId", task.id);
|
||||
@@ -756,7 +935,6 @@ export default function PositionRoundtablePage({
|
||||
}, [sessionIdFromUrl]);
|
||||
|
||||
const { agent: selectedAgent } = useAgent(selectedAgentId);
|
||||
const [intentCompletedAt, setIntentCompletedAt] = useState("");
|
||||
useEffect(() => {
|
||||
if (intent.intentReady && !intentCompletedAt) setIntentCompletedAt(timeLabel());
|
||||
if (!intent.intentReady && intentCompletedAt) setIntentCompletedAt("");
|
||||
@@ -857,7 +1035,7 @@ export default function PositionRoundtablePage({
|
||||
if (!window.confirm("归档后该会话将只读,是否继续?")) return;
|
||||
void archivePositionSession(positionSession.id)
|
||||
.then((session) => {
|
||||
setPositionSession((current) => (current ? { ...current, ...session } : session));
|
||||
setPositionSession((current) => mergePositionSession(current, session));
|
||||
toast.success("会话已归档,可新建会话继续研讨");
|
||||
})
|
||||
.catch((error) => {
|
||||
@@ -1163,11 +1341,19 @@ export default function PositionRoundtablePage({
|
||||
|
||||
<div className="flex min-h-0 flex-1 gap-3 overflow-hidden p-3">
|
||||
<aside className="custom-scrollbar flex w-[360px] shrink-0 flex-col gap-3 overflow-y-auto rounded-2xl border border-slate-200 bg-white p-3 shadow-sm dark:border-[#2b8ffe]/45 dark:bg-[#032650]/80 dark:text-[#c5cfe0] dark:shadow-none dark:backdrop-blur-md">
|
||||
<TaskInformationCard task={task} />
|
||||
<TaskInformationCard
|
||||
task={task}
|
||||
editable={!isArchivedSession}
|
||||
onEdit={openTaskEditor}
|
||||
/>
|
||||
|
||||
{activePosition.isIntentPosition ? (
|
||||
isIntelligenceDeliveryOverview ? (
|
||||
<IntentUnderstandingCard intent={frozenIntent ?? intent.intentReady} />
|
||||
<IntentUnderstandingCard
|
||||
intent={frozenIntent ?? intent.intentReady}
|
||||
editable={!isArchivedSession}
|
||||
onEdit={openIntentEditor}
|
||||
/>
|
||||
) : (
|
||||
<BusinessChainSelector
|
||||
chains={chains}
|
||||
@@ -1179,7 +1365,11 @@ export default function PositionRoundtablePage({
|
||||
)
|
||||
) : (
|
||||
<div className="contents">
|
||||
<IntentUnderstandingCard intent={intent.intentReady} />
|
||||
<IntentUnderstandingCard
|
||||
intent={frozenIntent ?? intent.intentReady}
|
||||
editable={!isArchivedSession}
|
||||
onEdit={openIntentEditor}
|
||||
/>
|
||||
<BusinessChainFlow
|
||||
chain={activeChain}
|
||||
nodes={positionSession?.nodes ?? []}
|
||||
@@ -1368,6 +1558,22 @@ export default function PositionRoundtablePage({
|
||||
showReject={false}
|
||||
showBuiltinAsk={false}
|
||||
/>
|
||||
<TaskInformationEditDialog
|
||||
open={taskEditorOpen}
|
||||
task={taskDraft}
|
||||
saving={taskSaving}
|
||||
onOpenChange={setTaskEditorOpen}
|
||||
onChange={setTaskDraft}
|
||||
onSubmit={handleSaveTaskEdit}
|
||||
/>
|
||||
<IntentUnderstandingEditDialog
|
||||
open={intentEditorOpen}
|
||||
draft={intentDraft}
|
||||
saving={intentSaving}
|
||||
onOpenChange={setIntentEditorOpen}
|
||||
onChange={setIntentDraft}
|
||||
onSubmit={handleSaveIntentEdit}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
@@ -1732,10 +1938,39 @@ function DeliveryOverviewCard({ item }: { item: DeliveryOverviewItem }) {
|
||||
);
|
||||
}
|
||||
|
||||
/** 仅展示岗位会商任务,不在会商页编辑任务。 */
|
||||
function TaskInformationCard({ task }: { task: PositionTask }) {
|
||||
/** 岗位会商内的任务快照展示,可按当前 Session 独立编辑。 */
|
||||
function HoverEditButton({
|
||||
title,
|
||||
onClick,
|
||||
}: {
|
||||
title: string;
|
||||
onClick: () => void;
|
||||
}) {
|
||||
return (
|
||||
<section className="rounded-2xl border border-[#ECE7E4] bg-white p-4 shadow-[0_4px_20px_rgba(0,0,0,0.02)] dark:border-[#2b8ffe]/30 dark:bg-[#042b59]/55 dark:shadow-none">
|
||||
<button
|
||||
type="button"
|
||||
title={title}
|
||||
aria-label={title}
|
||||
onClick={onClick}
|
||||
className="absolute right-3 top-3 z-10 flex size-7 items-center justify-center rounded-full border border-slate-200 bg-white/95 text-slate-500 opacity-0 shadow-sm transition-all hover:border-primary/40 hover:bg-primary/5 hover:text-primary focus-visible:opacity-100 focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-primary/30 group-hover:opacity-100 dark:border-[#2b8ffe]/35 dark:bg-[#063363]/95 dark:text-[#9ebfe2] dark:hover:border-[#2b8ffe]/80 dark:hover:bg-[#0b4fae]/45 dark:hover:text-[#83c1ff]"
|
||||
>
|
||||
<PencilLine className="size-3.5" strokeWidth={2.4} />
|
||||
</button>
|
||||
);
|
||||
}
|
||||
|
||||
function TaskInformationCard({
|
||||
task,
|
||||
editable,
|
||||
onEdit,
|
||||
}: {
|
||||
task: PositionTask;
|
||||
editable?: boolean;
|
||||
onEdit?: () => void;
|
||||
}) {
|
||||
return (
|
||||
<section className="group relative rounded-2xl border border-[#ECE7E4] bg-white p-4 shadow-[0_4px_20px_rgba(0,0,0,0.02)] dark:border-[#2b8ffe]/30 dark:bg-[#042b59]/55 dark:shadow-none">
|
||||
{editable && onEdit && <HoverEditButton title="编辑任务信息" onClick={onEdit} />}
|
||||
<div className="mb-3 flex items-center gap-2">
|
||||
<span className="flex size-5 items-center justify-center rounded-md bg-primary text-primary-foreground">
|
||||
<Target className="size-3" strokeWidth={2.5} />
|
||||
@@ -1774,6 +2009,72 @@ function TaskInformationCard({ task }: { task: PositionTask }) {
|
||||
);
|
||||
}
|
||||
|
||||
function TaskInformationEditDialog({
|
||||
open,
|
||||
task,
|
||||
saving,
|
||||
onOpenChange,
|
||||
onChange,
|
||||
onSubmit,
|
||||
}: {
|
||||
open: boolean;
|
||||
task: PositionTask;
|
||||
saving: boolean;
|
||||
onOpenChange: (open: boolean) => void;
|
||||
onChange: (task: PositionTask) => void;
|
||||
onSubmit: () => void;
|
||||
}) {
|
||||
return (
|
||||
<Dialog
|
||||
open={open}
|
||||
onOpenChange={(nextOpen) => {
|
||||
if (!saving) onOpenChange(nextOpen);
|
||||
}}
|
||||
>
|
||||
<DialogContent className="sm:max-w-[560px] dark:border-[#2b8ffe]/35 dark:bg-[#042b59]">
|
||||
<DialogHeader>
|
||||
<DialogTitle>编辑任务信息</DialogTitle>
|
||||
</DialogHeader>
|
||||
<div className="space-y-4 py-1">
|
||||
<label className="block space-y-1.5">
|
||||
<span className="text-xs font-semibold text-slate-500 dark:text-[#9ebfe2]">任务名称</span>
|
||||
<TInput
|
||||
value={task.title}
|
||||
onChange={(value) => onChange({ ...task, title: String(value) })}
|
||||
placeholder="请输入任务名称"
|
||||
/>
|
||||
</label>
|
||||
<label className="block space-y-1.5">
|
||||
<span className="text-xs font-semibold text-slate-500 dark:text-[#9ebfe2]">任务描述</span>
|
||||
<Textarea
|
||||
value={task.description}
|
||||
onChange={(event) => onChange({ ...task, description: event.target.value })}
|
||||
className="min-h-28 resize-none dark:border-[#2b8ffe]/35 dark:bg-[#032650]/80"
|
||||
placeholder="请输入任务描述"
|
||||
/>
|
||||
</label>
|
||||
<label className="block space-y-1.5">
|
||||
<span className="text-xs font-semibold text-slate-500 dark:text-[#9ebfe2]">任务方向</span>
|
||||
<TInput
|
||||
value={task.direction}
|
||||
onChange={(value) => onChange({ ...task, direction: String(value) })}
|
||||
placeholder="请输入任务方向"
|
||||
/>
|
||||
</label>
|
||||
</div>
|
||||
<DialogFooter>
|
||||
<TButton variant="outline" disabled={saving} onClick={() => onOpenChange(false)}>
|
||||
取消
|
||||
</TButton>
|
||||
<TButton theme="primary" loading={saving} onClick={onSubmit}>
|
||||
保存
|
||||
</TButton>
|
||||
</DialogFooter>
|
||||
</DialogContent>
|
||||
</Dialog>
|
||||
);
|
||||
}
|
||||
|
||||
function BusinessChainSelector({
|
||||
chains,
|
||||
loading,
|
||||
@@ -1976,11 +2277,16 @@ function BusinessChainSelector({
|
||||
|
||||
function IntentUnderstandingCard({
|
||||
intent,
|
||||
editable,
|
||||
onEdit,
|
||||
}: {
|
||||
intent: ReturnType<typeof useStep1Intent>["intentReady"];
|
||||
editable?: boolean;
|
||||
onEdit?: () => void;
|
||||
}) {
|
||||
return (
|
||||
<section className="rounded-2xl border border-[#ECE7E4] bg-white p-4 shadow-[0_4px_20px_rgba(0,0,0,0.02)] dark:border-[#2b8ffe]/30 dark:bg-[#042b59]/55 dark:shadow-none">
|
||||
<section className="group relative rounded-2xl border border-[#ECE7E4] bg-white p-4 shadow-[0_4px_20px_rgba(0,0,0,0.02)] dark:border-[#2b8ffe]/30 dark:bg-[#042b59]/55 dark:shadow-none">
|
||||
{editable && onEdit && <HoverEditButton title="编辑意图理解" onClick={onEdit} />}
|
||||
<h2 className="mb-3 flex items-center gap-2 text-sm font-semibold tracking-wide text-slate-900 dark:text-[#e8edf5]">
|
||||
<span className="flex size-5 items-center justify-center rounded-md bg-primary text-primary-foreground">
|
||||
<Sparkles className="size-3" strokeWidth={2.5} />
|
||||
@@ -2011,6 +2317,78 @@ function IntentUnderstandingCard({
|
||||
);
|
||||
}
|
||||
|
||||
function IntentUnderstandingEditDialog({
|
||||
open,
|
||||
draft,
|
||||
saving,
|
||||
onOpenChange,
|
||||
onChange,
|
||||
onSubmit,
|
||||
}: {
|
||||
open: boolean;
|
||||
draft: IntentEditDraft;
|
||||
saving: boolean;
|
||||
onOpenChange: (open: boolean) => void;
|
||||
onChange: (draft: IntentEditDraft) => void;
|
||||
onSubmit: () => void;
|
||||
}) {
|
||||
return (
|
||||
<Dialog
|
||||
open={open}
|
||||
onOpenChange={(nextOpen) => {
|
||||
if (!saving) onOpenChange(nextOpen);
|
||||
}}
|
||||
>
|
||||
<DialogContent className="sm:max-w-[600px] dark:border-[#2b8ffe]/35 dark:bg-[#042b59]">
|
||||
<DialogHeader>
|
||||
<DialogTitle>编辑意图理解</DialogTitle>
|
||||
</DialogHeader>
|
||||
<div className="space-y-4 py-1">
|
||||
<label className="block space-y-1.5">
|
||||
<span className="text-xs font-semibold text-slate-500 dark:text-[#9ebfe2]">任务意图</span>
|
||||
<Textarea
|
||||
value={draft.objective}
|
||||
onChange={(event) => onChange({ ...draft, objective: event.target.value })}
|
||||
className="min-h-24 resize-none dark:border-[#2b8ffe]/35 dark:bg-[#032650]/80"
|
||||
placeholder="请输入识别后的任务意图"
|
||||
/>
|
||||
</label>
|
||||
<label className="block space-y-1.5">
|
||||
<span className="text-xs font-semibold text-slate-500 dark:text-[#9ebfe2]">
|
||||
关键约束
|
||||
</span>
|
||||
<Textarea
|
||||
value={draft.constraints}
|
||||
onChange={(event) => onChange({ ...draft, constraints: event.target.value })}
|
||||
className="min-h-28 resize-none dark:border-[#2b8ffe]/35 dark:bg-[#032650]/80"
|
||||
placeholder="每行一条关键约束"
|
||||
/>
|
||||
</label>
|
||||
<label className="block space-y-1.5">
|
||||
<span className="text-xs font-semibold text-slate-500 dark:text-[#9ebfe2]">
|
||||
补充假设 / 说明
|
||||
</span>
|
||||
<Textarea
|
||||
value={draft.assumptions}
|
||||
onChange={(event) => onChange({ ...draft, assumptions: event.target.value })}
|
||||
className="min-h-20 resize-none dark:border-[#2b8ffe]/35 dark:bg-[#032650]/80"
|
||||
placeholder="可选,每行一条"
|
||||
/>
|
||||
</label>
|
||||
</div>
|
||||
<DialogFooter>
|
||||
<TButton variant="outline" disabled={saving} onClick={() => onOpenChange(false)}>
|
||||
取消
|
||||
</TButton>
|
||||
<TButton theme="primary" loading={saving} onClick={onSubmit}>
|
||||
保存
|
||||
</TButton>
|
||||
</DialogFooter>
|
||||
</DialogContent>
|
||||
</Dialog>
|
||||
);
|
||||
}
|
||||
|
||||
function chainStages(chain: BusinessChain) {
|
||||
const seatById = new Map(chain.seats.map((seat) => [seat.agent_id, seat]));
|
||||
if (chain.stages?.length) {
|
||||
|
||||
Reference in new issue
Block a user