Compare commits
2 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 38ad2ce256 | |||
| 65f8e44a88 |
@@ -174,6 +174,14 @@ _Avoid_: mock 先行堆积、前后端各自发散、先做排行榜 UI
|
||||
|
||||
## 项目开发对话(DirectProject)
|
||||
|
||||
**DirectProject 专属聊天模块**:
|
||||
AGC 普通项目聊天的独立容器,拥有 DirectProject 的聊天状态、运行态订阅、历史读取、发送队列、附件和中止交互,并把聊天投影交给专属表现层渲染;它不承接 Supervisor、Design Agent 或 Planning V2 的运行态。
|
||||
_Avoid_: 把 DirectProject 作为项目总控聊天的一个布尔分支、把四种 Agent 会话抽象成同一事实源
|
||||
|
||||
**项目工作台布局**:
|
||||
承载本地项目的资源工作区、项目级工具和独立聊天产品路径的外层界面;布局拥有跨面板的账户/钱包入口,聊天模块只负责项目对话,不嵌套账户展示。
|
||||
_Avoid_: 把钱包入口塞进聊天设置、让聊天组件拥有工作台级账户状态
|
||||
|
||||
**项目对话历史**:
|
||||
AGC 本地项目内 Codex 原始对话条目的持久集合,是聊天展示、工具卡片和线程恢复注入的唯一持久事实源。
|
||||
_Avoid_: 会话缓存、展示态历史、按 UI 需要另存的对话副本
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
+1
-1
@@ -2,7 +2,7 @@ import { useLexicalComposerContext } from '@lexical/react/LexicalComposerContext
|
||||
import { $getNodeByKey, type NodeKey } from 'lexical';
|
||||
import { Paperclip, X } from 'lucide-react';
|
||||
|
||||
import type { DirectCodexUserAttachmentReferencePart } from './generated';
|
||||
import type { DirectCodexUserAttachmentReferencePart } from '../../view/project-development/chat/generated/DirectCodexUserAttachmentReferencePart';
|
||||
|
||||
export function AttachmentReferenceChip({
|
||||
attachment,
|
||||
|
||||
+1
-1
@@ -9,8 +9,8 @@ import {
|
||||
} from 'lexical';
|
||||
import type { ReactNode } from 'react';
|
||||
|
||||
import type { DirectCodexUserAttachmentReferencePart } from '../../view/project-development/chat/generated/DirectCodexUserAttachmentReferencePart';
|
||||
import { AttachmentReferenceChip } from './AttachmentReferenceChip';
|
||||
import type { DirectCodexUserAttachmentReferencePart } from './generated';
|
||||
|
||||
export type SerializedAttachmentReferenceNode = Spread<
|
||||
{
|
||||
|
||||
+1
-17
@@ -1,5 +1,4 @@
|
||||
import { Settings2, ShieldCheck, Wallet, X } from 'lucide-react';
|
||||
import type { ReactNode } from 'react';
|
||||
import { Settings2, ShieldCheck, X } from 'lucide-react';
|
||||
import { useState } from 'react';
|
||||
|
||||
import {
|
||||
@@ -17,19 +16,16 @@ import { RuntimeConfigDialog } from '../runtime-config/RuntimeConfigDialog';
|
||||
* 组成:
|
||||
* - 「运行配置」打开既有 `RuntimeConfigDialog`(独立浮层,自己有 backdrop);
|
||||
* - 「操作权限」打开由 `ApprovalModeDialog` 提供的选择面板;
|
||||
* - 「泥点」直接渲染父级传入的 `walletEntry`(为空则整行不渲染)。
|
||||
*/
|
||||
export function ProjectSupervisorSettingsDialog({
|
||||
projectPath,
|
||||
currentApprovalLabel,
|
||||
onOpenApproval,
|
||||
walletEntry,
|
||||
onClose,
|
||||
}: {
|
||||
projectPath: string;
|
||||
currentApprovalLabel: string;
|
||||
onOpenApproval: () => void;
|
||||
walletEntry?: ReactNode;
|
||||
onClose: () => void;
|
||||
}) {
|
||||
const [runtimeConfigOpen, setRuntimeConfigOpen] = useState(false);
|
||||
@@ -90,18 +86,6 @@ export function ProjectSupervisorSettingsDialog({
|
||||
</span>
|
||||
</span>
|
||||
</button>
|
||||
{walletEntry ? (
|
||||
<div className="project-supervisor-settings-row is-static">
|
||||
<span className="project-supervisor-settings-row-main">
|
||||
<Wallet size={16} aria-hidden="true" />
|
||||
<span>
|
||||
<strong>泥点</strong>
|
||||
<small>当前余额与充值入口</small>
|
||||
</span>
|
||||
</span>
|
||||
<div className="game-workbench-chat-wallet">{walletEntry}</div>
|
||||
</div>
|
||||
) : null}
|
||||
</div>
|
||||
</section>
|
||||
</div>
|
||||
|
||||
+90
-474
File diff suppressed because it is too large
Load Diff
+1
-1
@@ -61,6 +61,7 @@ import {
|
||||
createProjectResourcePreviewRequestId,
|
||||
createProjectResourcePreviewScopeId,
|
||||
} from '../../services/projectResourcePreviewTransport';
|
||||
import type { DirectCodexUserContentPart } from '../../view/project-development/chat/generated/DirectCodexUserContentPart';
|
||||
import {
|
||||
$createAttachmentReferenceNode,
|
||||
$isAttachmentReferenceNode,
|
||||
@@ -73,7 +74,6 @@ import {
|
||||
writeChatPromptPolishReminderDisabled,
|
||||
} from './chatPromptPolish';
|
||||
import { ChatPromptPolishReminder } from './ChatPromptPolishReminder';
|
||||
import type { DirectCodexUserContentPart } from './generated';
|
||||
import {
|
||||
$createResourceReferenceNode,
|
||||
$isResourceReferenceNode,
|
||||
|
||||
@@ -5,7 +5,7 @@
|
||||
* 依次发出。队列项能在输入盒上方单独取消。这里只放与 React 无关的纯逻辑,便于单测。
|
||||
*/
|
||||
import type { GameCreationAppAssetManifestEntry } from '../../../../../packages/shared/src/contracts/gameCreationApp';
|
||||
import type { DirectCodexUserItem } from './generated';
|
||||
import type { DirectCodexUserItem } from '../../view/project-development/chat/generated/DirectCodexUserItem';
|
||||
import { directCodexContentToPromptText } from './resourceReferences';
|
||||
|
||||
/** 队列上限:满了以后拒绝入队并给出可读提示,而不是静默丢消息。 */
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { resolveTauriInvoke } from '../../app/tauri';
|
||||
import type { DirectCodexUserContentPart } from './generated';
|
||||
import type { DirectCodexUserContentPart } from '../../view/project-development/chat/generated/DirectCodexUserContentPart';
|
||||
|
||||
/**
|
||||
* 「不再提醒」偏好存本机 localStorage,不进 manifest、不进后端。
|
||||
|
||||
@@ -5,13 +5,11 @@
|
||||
* 入口转发:前端不再自己抄一份形状,字段增删必须改 Rust。
|
||||
*/
|
||||
|
||||
export type {
|
||||
DirectThreadConsumeResult,
|
||||
DirectThreadDeltaKind,
|
||||
DirectThreadEvent,
|
||||
DirectThreadFileChange,
|
||||
DirectThreadHistorySlice,
|
||||
DirectThreadItem,
|
||||
DirectThreadRequestKind,
|
||||
DirectThreadSubscriptionBootstrap,
|
||||
} from './generated';
|
||||
export type { DirectThreadConsumeResult } from '../../view/project-development/chat/generated/DirectThreadConsumeResult';
|
||||
export type { DirectThreadDeltaKind } from '../../view/project-development/chat/generated/DirectThreadDeltaKind';
|
||||
export type { DirectThreadEvent } from '../../view/project-development/chat/generated/DirectThreadEvent';
|
||||
export type { DirectThreadFileChange } from '../../view/project-development/chat/generated/DirectThreadFileChange';
|
||||
export type { DirectThreadHistorySlice } from '../../view/project-development/chat/generated/DirectThreadHistorySlice';
|
||||
export type { DirectThreadItem } from '../../view/project-development/chat/generated/DirectThreadItem';
|
||||
export type { DirectThreadRequestKind } from '../../view/project-development/chat/generated/DirectThreadRequestKind';
|
||||
export type { DirectThreadSubscriptionBootstrap } from '../../view/project-development/chat/generated/DirectThreadSubscriptionBootstrap';
|
||||
|
||||
@@ -1,15 +0,0 @@
|
||||
export type { DirectCodexUserContentPart } from './DirectCodexUserContentPart';
|
||||
export type { DirectCodexUserAttachmentReferencePart } from './DirectCodexUserAttachmentReferencePart';
|
||||
export type { DirectCodexUserItem } from './DirectCodexUserItem';
|
||||
export type { DirectCodexUserMessageItem } from './DirectCodexUserMessageItem';
|
||||
export type { DirectCodexUserRole } from './DirectCodexUserRole';
|
||||
export type { DirectCodexUserRuntimeRegionPart } from './DirectCodexUserRuntimeRegionPart';
|
||||
export type { DirectCodexUserMessageEnvelope } from './DirectCodexUserMessageEnvelope';
|
||||
export type { DirectThreadConsumeResult } from './DirectThreadConsumeResult';
|
||||
export type { DirectThreadDeltaKind } from './DirectThreadDeltaKind';
|
||||
export type { DirectThreadEvent } from './DirectThreadEvent';
|
||||
export type { DirectThreadFileChange } from './DirectThreadFileChange';
|
||||
export type { DirectThreadHistorySlice } from './DirectThreadHistorySlice';
|
||||
export type { DirectThreadItem } from './DirectThreadItem';
|
||||
export type { DirectThreadRequestKind } from './DirectThreadRequestKind';
|
||||
export type { DirectThreadSubscriptionBootstrap } from './DirectThreadSubscriptionBootstrap';
|
||||
@@ -10,10 +10,8 @@ import {
|
||||
buildGameCreationAppAssetTagLibrary,
|
||||
type GameCreationAppAssetTagLibraryEntry,
|
||||
} from '../../../../../packages/shared/src/contracts/gameCreationAppAssetTagLibrary';
|
||||
import type {
|
||||
DirectCodexUserContentPart,
|
||||
DirectCodexUserItem,
|
||||
} from './generated';
|
||||
import type { DirectCodexUserContentPart } from '../../view/project-development/chat/generated/DirectCodexUserContentPart';
|
||||
import type { DirectCodexUserItem } from '../../view/project-development/chat/generated/DirectCodexUserItem';
|
||||
|
||||
export type ResourceReferenceSource =
|
||||
| 'asset-picker'
|
||||
|
||||
@@ -5850,6 +5850,17 @@ iframe.preview-frame {
|
||||
min-height: 0;
|
||||
}
|
||||
|
||||
.game-workbench-layout-account {
|
||||
position: absolute;
|
||||
top: 8px;
|
||||
right: 12px;
|
||||
z-index: 4;
|
||||
}
|
||||
|
||||
.game-workbench-layout-account .launcher-account-bar {
|
||||
position: static;
|
||||
}
|
||||
|
||||
.game-workbench-layout.is-ui-editor {
|
||||
grid-template-columns: minmax(0, 1fr);
|
||||
}
|
||||
|
||||
+453
@@ -0,0 +1,453 @@
|
||||
import { ArrowUp, AtSign, Loader2, Settings } from 'lucide-react';
|
||||
import type { FormEventHandler, RefObject, UIEventHandler } from 'react';
|
||||
import { Fragment, useEffect, useRef, useState } from 'react';
|
||||
|
||||
import {
|
||||
AgentMessageContent,
|
||||
type AgentMessageTone,
|
||||
} from '../../../../../../packages/shared/src/components/AgentMessageContent';
|
||||
import type { ChatMessage } from '../../../app/types';
|
||||
import { ChatMarkdownMessage } from '../../../components/ChatMarkdownMessage';
|
||||
import { projectWorkspaceStatusForDisplay } from '../../../features/agent-runtime';
|
||||
import type { DirectCodexTurnAttachment } from '../../../features/app-shell/directCodexTurnAttachments';
|
||||
import type { QueuedChatTurn } from '../../../features/project-workspace/chatComposerQueue';
|
||||
import {
|
||||
ComposerAttachmentMenu,
|
||||
ComposerPendingAttachments,
|
||||
ComposerReasoningEffortSelect,
|
||||
ComposerStopButton,
|
||||
ComposerTurnQueue,
|
||||
ComposerVoiceButton,
|
||||
} from '../../../features/project-workspace/ComposerControls';
|
||||
import {
|
||||
ConversationModelSelect,
|
||||
type ConversationModelSelectHandle,
|
||||
} from '../../../features/project-workspace/ConversationModelSelect';
|
||||
import type { DirectChatEntry } from '../../../features/project-workspace/directThreadChat';
|
||||
import {
|
||||
buildDirectChatTurns,
|
||||
type DirectChatBlock,
|
||||
type DirectChatTurn,
|
||||
directMessageTimestamp,
|
||||
} from '../../../features/project-workspace/directTurnPresentation';
|
||||
import { ProjectSupervisorSettingsDialog } from '../../../features/project-workspace/ProjectSupervisorSettingsDialog';
|
||||
import {
|
||||
ResourceReferenceInput,
|
||||
type ResourceReferenceInputHandle,
|
||||
} from '../../../features/project-workspace/ResourceReferenceInput';
|
||||
import type {
|
||||
ChatComposerDraft,
|
||||
ChatReference,
|
||||
} from '../../../features/project-workspace/resourceReferences';
|
||||
import { ToolCallGroup } from '../../../features/project-workspace/ToolCallGroup';
|
||||
import {
|
||||
formatClockTime,
|
||||
formatTurnDuration,
|
||||
} from '../../../features/project-workspace/toolCallGroupPresentation';
|
||||
import { type ApprovalMode, approvalModeLabel } from '../approvalMode';
|
||||
import { ApprovalModeDialog } from '../ApprovalModeDialog';
|
||||
|
||||
type AssetManifestEntry =
|
||||
import('../../../../../../packages/shared/src/contracts/gameCreationApp').GameCreationAppAssetManifestEntry;
|
||||
type GameIterationVersion =
|
||||
import('../../../../../../packages/shared/src/contracts/gameCreationApp').GameIterationVersion;
|
||||
|
||||
export type DirectProjectChatViewProps = {
|
||||
activeVersionId?: string | null;
|
||||
attachments: DirectCodexTurnAttachment[];
|
||||
attachmentNotice: string;
|
||||
chatInput: string;
|
||||
chatReferences: ChatReference[];
|
||||
chatProjectAssets: AssetManifestEntry[];
|
||||
composerRef: RefObject<ResourceReferenceInputHandle | null>;
|
||||
composerNotice: string;
|
||||
conversationMessages: ChatMessage[];
|
||||
directEntries: DirectChatEntry[];
|
||||
directTurnRunning: boolean;
|
||||
hiddenConversationCount: number;
|
||||
hasEarlierConversationMessages: boolean;
|
||||
messagesRef: RefObject<HTMLDivElement | null>;
|
||||
onCancelQueuedTurn: (id: string) => void;
|
||||
onCancelTurn: () => void;
|
||||
onChatInputChange: (draft: ChatComposerDraft) => void;
|
||||
onRemoveAttachment: (index: number) => void;
|
||||
onScroll: UIEventHandler<HTMLDivElement>;
|
||||
onShowEarlierMessages: () => void;
|
||||
onSubmit: FormEventHandler<HTMLFormElement>;
|
||||
onUploadFiles: (files: readonly File[]) => void;
|
||||
projectPath: string;
|
||||
queuedTurns: QueuedChatTurn[];
|
||||
turnCancelling: boolean;
|
||||
versions?: GameIterationVersion[];
|
||||
workspaceStatus: string;
|
||||
controlBusy: boolean;
|
||||
};
|
||||
|
||||
/**
|
||||
* DirectProject 的独立表现容器。
|
||||
*
|
||||
* 这个组件只处理 DirectProject 的输入框、回合分区、运行中状态和设置表现;
|
||||
* Supervisor / Design Agent / Planning V2 的运行态不在这里出现。
|
||||
*/
|
||||
export function DirectProjectChatView({
|
||||
activeVersionId = null,
|
||||
attachments,
|
||||
attachmentNotice,
|
||||
chatInput,
|
||||
chatReferences,
|
||||
chatProjectAssets,
|
||||
composerRef,
|
||||
composerNotice,
|
||||
conversationMessages,
|
||||
directEntries,
|
||||
directTurnRunning,
|
||||
hiddenConversationCount,
|
||||
hasEarlierConversationMessages,
|
||||
messagesRef,
|
||||
onCancelQueuedTurn,
|
||||
onCancelTurn,
|
||||
onChatInputChange,
|
||||
onRemoveAttachment,
|
||||
onScroll,
|
||||
onShowEarlierMessages,
|
||||
onSubmit,
|
||||
onUploadFiles,
|
||||
projectPath,
|
||||
queuedTurns,
|
||||
turnCancelling,
|
||||
versions,
|
||||
workspaceStatus,
|
||||
controlBusy,
|
||||
}: DirectProjectChatViewProps) {
|
||||
const [settingsOpen, setSettingsOpen] = useState(false);
|
||||
const [turnUsageNow, setTurnUsageNow] = useState(() => Date.now());
|
||||
const [voiceNotice, setVoiceNotice] = useState('');
|
||||
const [approvalOpen, setApprovalOpen] = useState(false);
|
||||
const [approvalMode, setApprovalMode] = useState<ApprovalMode>('strict');
|
||||
const [approvalNotice, setApprovalNotice] = useState('');
|
||||
const [modelReady, setModelReady] = useState(false);
|
||||
const [modelValidating, setModelValidating] = useState(false);
|
||||
const modelSelectRef = useRef<ConversationModelSelectHandle>(null);
|
||||
const modelValidateInFlightRef = useRef(false);
|
||||
|
||||
useEffect(() => {
|
||||
if (!directTurnRunning) return;
|
||||
setTurnUsageNow(Date.now());
|
||||
const timer = setInterval(() => setTurnUsageNow(Date.now()), 1000);
|
||||
return () => clearInterval(timer);
|
||||
}, [directTurnRunning]);
|
||||
|
||||
const directTurns = buildDirectChatTurns({
|
||||
entries: directEntries,
|
||||
localMessages: conversationMessages,
|
||||
turnRunning: directTurnRunning,
|
||||
});
|
||||
const activeTurnStartedAt =
|
||||
directTurns.find((turn) => turn.active)?.startedAt ?? 0;
|
||||
|
||||
const renderTurnUsage = (turn: DirectChatTurn) => {
|
||||
if (turn.active || !turn.startedAt) return null;
|
||||
const endedAt = Math.max(turn.endedAt, turn.startedAt);
|
||||
return (
|
||||
<p
|
||||
className="message-turn-usage"
|
||||
data-testid="turn-usage"
|
||||
data-turn-running="false"
|
||||
>
|
||||
{`本轮结束于 ${new Date(endedAt).toLocaleTimeString('zh-CN', {
|
||||
hour12: false,
|
||||
})} · 耗时 ${formatTurnDuration(endedAt - turn.startedAt) ?? '0秒'}`}
|
||||
</p>
|
||||
);
|
||||
};
|
||||
|
||||
const submitLabel = controlBusy ? '思考中' : '发送';
|
||||
const submitButton = (
|
||||
<button
|
||||
type="submit"
|
||||
className="project-supervisor-submit-button"
|
||||
aria-label={submitLabel}
|
||||
title={submitLabel}
|
||||
disabled={controlBusy || !modelReady || modelValidating}
|
||||
>
|
||||
{controlBusy ? (
|
||||
<Loader2 size={16} aria-hidden="true" className="animate-spin" />
|
||||
) : (
|
||||
<ArrowUp size={16} aria-hidden="true" />
|
||||
)}
|
||||
</button>
|
||||
);
|
||||
|
||||
const renderBlock = (
|
||||
turn: DirectChatTurn,
|
||||
block: DirectChatBlock,
|
||||
tone: AgentMessageTone = 'body',
|
||||
streamingKey: string | null,
|
||||
) => {
|
||||
if (block.kind === 'tools') {
|
||||
return (
|
||||
<ToolCallGroup
|
||||
key={block.key}
|
||||
calls={block.calls}
|
||||
userSentAt={turn.startedAt}
|
||||
active={turn.active}
|
||||
className="message-tool-call"
|
||||
/>
|
||||
);
|
||||
}
|
||||
if (block.kind === 'reasoning') {
|
||||
return (
|
||||
<AgentMessageContent
|
||||
key={block.key}
|
||||
as="details"
|
||||
tone="process"
|
||||
className="design-agent-reasoning"
|
||||
aria-label="思考过程"
|
||||
>
|
||||
<summary>思考过程</summary>
|
||||
<pre>{block.text}</pre>
|
||||
</AgentMessageContent>
|
||||
);
|
||||
}
|
||||
const role = block.kind === 'user' ? 'user' : 'assistant';
|
||||
return (
|
||||
<div key={block.key} className={`message message--${role}`}>
|
||||
<AgentMessageContent tone={tone}>
|
||||
<ChatMarkdownMessage
|
||||
role={role}
|
||||
text={block.text}
|
||||
streaming={block.key === streamingKey}
|
||||
/>
|
||||
</AgentMessageContent>
|
||||
{block.kind === 'user' && block.at > 0 ? (
|
||||
<time
|
||||
className="message-sent-at"
|
||||
dateTime={new Date(directMessageTimestamp(block.at)).toISOString()}
|
||||
title={`发送于 ${new Date(block.at).toLocaleString('zh-CN', { hour12: false })}`}
|
||||
>
|
||||
{formatClockTime(block.at)}
|
||||
</time>
|
||||
) : null}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
return (
|
||||
<section
|
||||
className="project-supervisor-surface is-direct-codex"
|
||||
aria-label="陶泥儿项目对话"
|
||||
>
|
||||
<div className="project-supervisor-conversation">
|
||||
<header className="project-supervisor-topbar">
|
||||
<span className="project-supervisor-topbar-status" aria-live="polite">
|
||||
<span
|
||||
className={`project-supervisor-topbar-dot${controlBusy ? ' is-busy' : ''}`}
|
||||
aria-hidden="true"
|
||||
/>
|
||||
{controlBusy
|
||||
? '陶泥儿正在处理'
|
||||
: projectWorkspaceStatusForDisplay(workspaceStatus)}
|
||||
</span>
|
||||
<button
|
||||
type="button"
|
||||
className="project-supervisor-settings-trigger"
|
||||
aria-label="设置"
|
||||
title="设置"
|
||||
onClick={() => setSettingsOpen(true)}
|
||||
>
|
||||
<Settings size={16} aria-hidden="true" />
|
||||
</button>
|
||||
</header>
|
||||
<div
|
||||
ref={messagesRef}
|
||||
className="message-list project-supervisor-message-list"
|
||||
aria-label="陶泥儿消息"
|
||||
onScroll={onScroll}
|
||||
>
|
||||
{hiddenConversationCount > 0 || hasEarlierConversationMessages ? (
|
||||
<button
|
||||
type="button"
|
||||
className="message-history-more"
|
||||
onClick={onShowEarlierMessages}
|
||||
>
|
||||
{hiddenConversationCount > 0
|
||||
? `显示更早 · 还有 ${hiddenConversationCount} 条对话`
|
||||
: '显示更早的对话'}
|
||||
</button>
|
||||
) : null}
|
||||
{directTurns.map((turn) => {
|
||||
const streamingKey = turn.active
|
||||
? ([...turn.process]
|
||||
.reverse()
|
||||
.find((block) => block.kind === 'assistant')?.key ?? null)
|
||||
: null;
|
||||
return (
|
||||
<Fragment key={turn.key}>
|
||||
{turn.users.map((block) =>
|
||||
renderBlock(turn, block, 'body', streamingKey),
|
||||
)}
|
||||
{turn.process.length > 0 ? (
|
||||
turn.active ? (
|
||||
turn.process.map((block) =>
|
||||
renderBlock(turn, block, 'process', streamingKey),
|
||||
)
|
||||
) : (
|
||||
<details
|
||||
className="message-turn-process"
|
||||
data-testid="turn-process"
|
||||
>
|
||||
<summary>执行过程</summary>
|
||||
<div className="message-turn-process-body">
|
||||
{turn.process.map((block) =>
|
||||
renderBlock(turn, block, 'process', streamingKey),
|
||||
)}
|
||||
</div>
|
||||
</details>
|
||||
)
|
||||
) : null}
|
||||
{turn.finals.map((block) =>
|
||||
renderBlock(turn, block, 'body', streamingKey),
|
||||
)}
|
||||
{renderTurnUsage(turn)}
|
||||
</Fragment>
|
||||
);
|
||||
})}
|
||||
</div>
|
||||
{directTurnRunning ? (
|
||||
<AgentMessageContent
|
||||
as="section"
|
||||
tone="process"
|
||||
className="project-supervisor-process-card is-active"
|
||||
aria-label="陶泥儿执行过程"
|
||||
aria-live="polite"
|
||||
aria-atomic="false"
|
||||
role="status"
|
||||
data-runtime-owned="true"
|
||||
>
|
||||
<header>
|
||||
<span aria-hidden="true" />
|
||||
<strong>陶泥儿正在处理</strong>
|
||||
{activeTurnStartedAt > 0 ? (
|
||||
<em className="project-supervisor-process-elapsed">
|
||||
{`已耗时 ${
|
||||
formatTurnDuration(
|
||||
Math.max(0, turnUsageNow - activeTurnStartedAt),
|
||||
) ?? '0秒'
|
||||
}`}
|
||||
</em>
|
||||
) : null}
|
||||
</header>
|
||||
</AgentMessageContent>
|
||||
) : null}
|
||||
<form
|
||||
className="project-supervisor-composer is-direct-codex"
|
||||
onSubmit={(event) => {
|
||||
event.preventDefault();
|
||||
if (modelValidateInFlightRef.current) return;
|
||||
modelValidateInFlightRef.current = true;
|
||||
setModelValidating(true);
|
||||
void (async () => {
|
||||
try {
|
||||
const ready = modelSelectRef.current
|
||||
? await modelSelectRef.current.ensureUsable()
|
||||
: modelReady;
|
||||
if (ready) onSubmit(event);
|
||||
} finally {
|
||||
modelValidateInFlightRef.current = false;
|
||||
setModelValidating(false);
|
||||
}
|
||||
})();
|
||||
}}
|
||||
>
|
||||
<ComposerTurnQueue
|
||||
turns={queuedTurns}
|
||||
assets={chatProjectAssets}
|
||||
onCancel={onCancelQueuedTurn}
|
||||
/>
|
||||
<ComposerPendingAttachments
|
||||
attachments={attachments}
|
||||
onRemove={onRemoveAttachment}
|
||||
/>
|
||||
<ResourceReferenceInput
|
||||
ref={composerRef}
|
||||
ariaLabel="陶泥儿对话内容"
|
||||
activeVersionId={activeVersionId}
|
||||
versions={versions}
|
||||
assets={chatProjectAssets}
|
||||
projectPath={projectPath}
|
||||
disabled={modelValidating}
|
||||
rows={3}
|
||||
value={chatInput}
|
||||
references={chatReferences}
|
||||
placeholder="描述你的想法,或 @ 引用素材"
|
||||
onChange={onChatInputChange}
|
||||
/>
|
||||
<div className="project-supervisor-composer-controls">
|
||||
<div className="project-supervisor-composer-controls-left">
|
||||
<button
|
||||
type="button"
|
||||
className="project-supervisor-reference-trigger"
|
||||
aria-label="插入素材引用"
|
||||
title="插入素材引用"
|
||||
disabled={controlBusy}
|
||||
onClick={() => composerRef.current?.openPicker()}
|
||||
>
|
||||
<AtSign size={15} aria-hidden="true" />
|
||||
</button>
|
||||
<ComposerAttachmentMenu
|
||||
disabled={controlBusy || modelValidating}
|
||||
onPickFiles={onUploadFiles}
|
||||
onOpenReferencePicker={() => composerRef.current?.openPicker()}
|
||||
/>
|
||||
</div>
|
||||
<div className="project-supervisor-composer-controls-right">
|
||||
<ComposerReasoningEffortSelect disabled={controlBusy} />
|
||||
<ConversationModelSelect
|
||||
ref={modelSelectRef}
|
||||
disabled={controlBusy}
|
||||
onReady={setModelReady}
|
||||
projectPath={projectPath}
|
||||
/>
|
||||
<ComposerVoiceButton
|
||||
disabled={controlBusy || modelValidating}
|
||||
onTranscript={(text) => composerRef.current?.insertText(text)}
|
||||
onNotice={setVoiceNotice}
|
||||
/>
|
||||
{controlBusy ? (
|
||||
<ComposerStopButton
|
||||
cancelling={turnCancelling}
|
||||
onCancel={onCancelTurn}
|
||||
/>
|
||||
) : (
|
||||
submitButton
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
{composerNotice || attachmentNotice || voiceNotice ? (
|
||||
<p className="project-supervisor-composer-notice" role="status">
|
||||
{composerNotice || attachmentNotice || voiceNotice}
|
||||
</p>
|
||||
) : null}
|
||||
</form>
|
||||
</div>
|
||||
{settingsOpen ? (
|
||||
<ProjectSupervisorSettingsDialog
|
||||
projectPath={projectPath}
|
||||
currentApprovalLabel={approvalModeLabel(approvalMode)}
|
||||
onOpenApproval={() => setApprovalOpen(true)}
|
||||
onClose={() => setSettingsOpen(false)}
|
||||
/>
|
||||
) : null}
|
||||
{settingsOpen && approvalOpen ? (
|
||||
<ApprovalModeDialog
|
||||
approvalMode={approvalMode}
|
||||
notice={approvalNotice}
|
||||
onSelect={setApprovalMode}
|
||||
onNotice={setApprovalNotice}
|
||||
onClose={() => setApprovalOpen(false)}
|
||||
closeOnEscape={false}
|
||||
/>
|
||||
) : null}
|
||||
</section>
|
||||
);
|
||||
}
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user