diff --git a/apps/presentation/dashboard/src/data/chat.ts b/apps/presentation/dashboard/src/data/chat.ts index 1a77682671..cb80a308a9 100644 --- a/apps/presentation/dashboard/src/data/chat.ts +++ b/apps/presentation/dashboard/src/data/chat.ts @@ -642,6 +642,7 @@ export async function createChatSession( agentId?: string, mode: "resume_latest" | "new" = "resume_latest", contextKind: "goal" | "manager" = "goal", + signal?: AbortSignal, ) { return requestJson<{ agent_id: string; @@ -651,7 +652,7 @@ export async function createChatSession( session_id: string; session: ChatSessionSummary; }>("/api/chat/sessions", { - method: "POST", + method: "POST", signal, // An omitted ``agent_id`` means "no explicit executor pick": the channel // owner resolves its own default. Sending this client's own default would // silently re-point the steward channel away from its configured executor. diff --git a/apps/presentation/dashboard/src/features/personal-workspace/channel-timeline.tsx b/apps/presentation/dashboard/src/features/personal-workspace/channel-timeline.tsx index 2f543b125c..19a9a59bf1 100644 --- a/apps/presentation/dashboard/src/features/personal-workspace/channel-timeline.tsx +++ b/apps/presentation/dashboard/src/features/personal-workspace/channel-timeline.tsx @@ -1,9 +1,9 @@ +import { MessageActivity } from "./message-activity"; import { GoalDraftCard } from "./goal-draft-card"; import type { GoalDraft } from "../../../../../../loopx/control_plane/collaboration/goal_draft.js"; -import {Fragment, useRef, useState} from "react"; -import { ChatApiError } from "../../data/chat.js"; +import {Fragment} from "react"; import { CollaborationCard } from "./collaboration-card"; -import { Activity, Bot, Sparkles, Square } from "lucide-react"; +import { Activity, Bot, Sparkles } from "lucide-react"; import { AttentionRow } from "./cards/attention-row"; import { MIN_SEPARATE_ANSWER_LENGTH } from "./answer-text"; @@ -15,7 +15,7 @@ import { useWorkspaceI18n } from "./i18n"; import { ReturnDeliveryStatus } from "./return-delivery-status"; import {ManagerTeamResult} from "./manager-team-result"; import { compareProposalRecency } from "./proposal-recency"; -import type { WorkspaceDrawerSelection, WorkspaceGoal, WorkspaceMessage, WorkspaceTimelineItem } from "./personal-workspace-model"; +import type { WorkspaceDrawerSelection, WorkspaceGoal, WorkspaceTimelineItem } from "./personal-workspace-model"; function answerLink(sessionId: string, messageId: string) { const url = new URL(window.location.href); @@ -26,80 +26,6 @@ function answerLink(sessionId: string, messageId: string) { return url.toString(); } -function MessageActivity({ message, onInterruptTurn, onSteerTurn }: { - message: WorkspaceMessage; - onInterruptTurn?: (turnId: string) => Promise; - onSteerTurn?: (turnId: string, text: string, ingressId: string) => Promise; -}) { - const { locale, t } = useWorkspaceI18n(); - const zh = locale === "zh-CN"; - const [stopping, setStopping] = useState(false); - const [error, setError] = useState(null); - const [editing, setEditing] = useState(false); - const [draft, setDraft] = useState(""); - const [steering, setSteering] = useState(false); - const [steerError, setSteerError] = useState(null); - const [steerReceipt, setSteerReceipt] = useState(false); - const request = useRef<{ text: string; id: string } | null>(null); - const activity = message.activity ?? []; - async function steer() { - const text = draft.trim(); - if (!text || !message.pending || !message.sourceTurnId || !onSteerTurn || steering) return; - // Retain the operation identity after a lost response; retry cannot deliver twice. - if (request.current?.text !== text) request.current = { text, id: crypto.randomUUID() }; - setSteering(true); - setSteerError(null); - try { - await onSteerTurn(message.sourceTurnId, text, request.current.id); - setDraft(""); setEditing(false); setSteerReceipt(true); request.current = null; - } catch (cause) { - const definitelyNotDelivered = cause instanceof ChatApiError && cause.payload.delivery_state === "not_delivered"; - if (definitelyNotDelivered) request.current = null; - const message = cause instanceof Error ? cause.message : (zh ? "未确认接收,草稿已保留。" : "Delivery unconfirmed. Draft retained."); - setSteerError(definitelyNotDelivered - ? (zh ? "本次未送达;请检查当前回合与执行器,条件恢复后可重试原文。" : "Not delivered; check the current turn and executor, then retry the unchanged draft.") - : message); - } finally { setSteering(false); } - } - async function interrupt() { - if (!message.sourceTurnId || !onInterruptTurn || stopping) return; - setStopping(true); - setError(null); - try { await onInterruptTurn(message.sourceTurnId); } - catch (cause) { setError(cause instanceof Error ? cause.message : (zh ? "中断失败,请重试。" : "Could not interrupt. Try again.")); } - finally { setStopping(false); } - } - return
- {message.pending ?
- {activity.at(-1) || t("timeline.pending")} - - {message.sourceTurnId && onSteerTurn ? : null} - {message.sourceTurnId && onInterruptTurn ? : null} - -
: null} - {editing ?
{ event.preventDefault(); void steer(); }}> -