(props: {
project: SourceRecord | null;
detail: McpSessionDetail | null;
input: string;
isRunning: boolean;
pendingUserMessage: string;
streamingAssistantText: string;
runningMcpSearches: RunningMcpSearch[];
onInputChange: (value: string) => void;
onClearSession: () => void;
onDeleteSession: () => void;
onOpenCitation: (citation: AnswerCitation) => void;
onStop: () => void;
onSend: () => void;
})
| 1634 | } |
| 1635 | |
| 1636 | function ConversationWorkspace(props: { |
| 1637 | project: SourceRecord | null; |
| 1638 | detail: McpSessionDetail | null; |
| 1639 | input: string; |
| 1640 | isRunning: boolean; |
| 1641 | pendingUserMessage: string; |
| 1642 | streamingAssistantText: string; |
| 1643 | runningMcpSearches: RunningMcpSearch[]; |
| 1644 | onInputChange: (value: string) => void; |
| 1645 | onClearSession: () => void; |
| 1646 | onDeleteSession: () => void; |
| 1647 | onOpenCitation: (citation: AnswerCitation) => void; |
| 1648 | onStop: () => void; |
| 1649 | onSend: () => void; |
| 1650 | }) { |
| 1651 | const { t } = useI18n(); |
| 1652 | const scrollAnchorRef = useRef<HTMLDivElement | null>(null); |
| 1653 | |
| 1654 | useEffect(() => { |
| 1655 | scrollAnchorRef.current?.scrollIntoView({ block: "end" }); |
| 1656 | }, [props.detail?.messages.length, props.pendingUserMessage, props.streamingAssistantText, props.isRunning, props.runningMcpSearches.length]); |
| 1657 | |
| 1658 | if (!props.project) { |
| 1659 | return ( |
| 1660 | <section className="flex min-h-0 flex-1 items-center justify-center px-6"> |
| 1661 | <EmptyState title={t("先创建项目", "Create a project first")} description={t("项目是文档、切片、事件、实体和 MCP 对话的共同归属。", "A project contains documents, chunks, events, entities, and MCP chats.")} /> |
| 1662 | </section> |
| 1663 | ); |
| 1664 | } |
| 1665 | |
| 1666 | return ( |
| 1667 | <section className="flex min-h-0 flex-1 flex-col"> |
| 1668 | <div className="flex shrink-0 flex-wrap items-start justify-between gap-3 border-b border-border px-4 py-3 md:flex-nowrap md:items-center md:px-6"> |
| 1669 | <div className="min-w-0"> |
| 1670 | <h1 className="truncate text-base font-semibold">{props.detail?.session.title ?? t("新对话", "New chat")}</h1> |
| 1671 | <p className="truncate text-xs text-muted-foreground"> |
| 1672 | {props.detail ? `${formatModelName(props.detail.session.model, t)} · ${shortId(props.detail.session.id)}` : t("新建会话后开始测试 MCP 工具", "Create a chat to test MCP tools")} |
| 1673 | </p> |
| 1674 | </div> |
| 1675 | <div className="flex min-w-0 flex-wrap items-center justify-end gap-2"> |
| 1676 | <Button variant="outline" size="sm" onClick={props.onClearSession} disabled={!props.detail || props.isRunning}> |
| 1677 | <RotateCcw className="h-4 w-4" /> |
| 1678 | {t("清空记录", "Clear history")} |
| 1679 | </Button> |
| 1680 | <Button variant="outline" size="sm" onClick={props.onDeleteSession} disabled={!props.detail || props.isRunning}> |
| 1681 | <Trash2 className="h-4 w-4" /> |
| 1682 | {t("删除对话", "Delete chat")} |
| 1683 | </Button> |
| 1684 | </div> |
| 1685 | </div> |
| 1686 | |
| 1687 | <div className="min-h-0 flex-1 overflow-y-auto px-4 py-4 md:px-6"> |
| 1688 | <div className="mx-auto flex max-w-3xl flex-col gap-3"> |
| 1689 | {!props.detail || props.detail.messages.length === 0 ? ( |
| 1690 | <EmptyState title={t("还没有对话", "No conversation yet")} description={t("输入问题后,系统会通过 MCP 工具检索当前项目资料。", "Ask a question and the system will retrieve current project documents through MCP tools.")} /> |
| 1691 | ) : props.detail.messages.map((message) => { |
| 1692 | const citations = getMessageCitations(message); |
| 1693 | return ( |
nothing calls this directly
no test coverage detected