完善客户端模块与工作区能力
Some checks failed
Electron E2E / Electron E2E (macos-latest) (push) Has been cancelled
Electron E2E / Electron E2E (ubuntu-latest) (push) Has been cancelled
Electron E2E / Electron E2E (windows-latest) (push) Has been cancelled

This commit is contained in:
inman
2026-08-13 19:45:30 +08:00
parent 0148b91a15
commit 22add3f01f
97 changed files with 4756 additions and 3226 deletions

View File

@@ -671,10 +671,10 @@ function MessageBubble({
<span>{deliveryLabel}</span>
</div>
)}
<p className="whitespace-pre-wrap break-words break-all text-sm">{text}</p>
<p className="chat-message-body whitespace-pre-wrap break-words break-all">{text}</p>
</>
) : (
<div className="chat-assistant-message-markdown prose prose-sm max-w-none break-words break-all text-foreground">
<div className="chat-message-body chat-assistant-message-markdown prose max-w-none break-words break-all text-foreground">
<ReactMarkdown
remarkPlugins={[remarkGfm, remarkMath]}
rehypePlugins={[[rehypeKatex, { strict: false, throwOnError: false, output: 'html' }]]}

View File

@@ -934,6 +934,7 @@ export function OpencodeChatPanel({ variant = 'main', navigationDraft, onOpenPro
const [composerStopArmed, setComposerStopArmed] = useState(true);
const [selectedAgentId, setSelectedAgentId] = useState<string | null>(null);
const [startingAgentId, setStartingAgentId] = useState<string | null>(null);
const agentSelectionRequestRef = useRef(0);
const transcriptScrollRef = useRef<HTMLDivElement | null>(null);
const messageElementsRef = useRef(new Map<string, HTMLDivElement>());
const undoOriginatingDraftRef = useRef('');
@@ -1233,7 +1234,7 @@ export function OpencodeChatPanel({ variant = 'main', navigationDraft, onOpenPro
const responseWaitElapsedSeconds = responseWaitStartedAt
? Math.max(0, Math.floor((responseWaitNow - responseWaitStartedAt) / 1000))
: 0;
const selectedProjectAgentAvatarSrc = selectedProjectAgent ? getAgentAvatarSrc(selectedProjectAgent.avatarId) : undefined;
const selectedProjectAgentAvatarSrc = selectedProjectAgent ? getAgentAvatarSrc(selectedProjectAgent.avatarId, selectedProjectAgent.avatarDataUrl) : undefined;
const selectedProjectAgentAvatarAlt = selectedProjectAgent ? `${selectedProjectAgent.name || selectedProjectAgent.roleName}头像` : '';
const selectedSessionDiff = useMemo(() => {
if (!selectedDiffPath) return null;
@@ -2326,7 +2327,9 @@ export function OpencodeChatPanel({ variant = 'main', navigationDraft, onOpenPro
await abortSession(selectedSessionId);
}, [abortSession, composerStopArmed, selectedSessionId]);
const handleSelectAgent = useCallback(async (agentId: string) => {
const handleSelectAgent = useCallback((agentId: string) => {
const requestId = agentSelectionRequestRef.current + 1;
agentSelectionRequestRef.current = requestId;
if (!agentId) {
setSelectedAgentId(null);
clearSessionSelection();
@@ -2339,18 +2342,27 @@ export function OpencodeChatPanel({ variant = 'main', navigationDraft, onOpenPro
}
setSelectedAgentId(agentId);
setStartingAgentId(agentId);
try {
const latestSession = conversationState.sessions
.filter((item) => item.agentId === agentId && !item.archivedAt)
.sort((left, right) => right.updatedAt.localeCompare(left.updatedAt))[0];
if (latestSession) {
await selectSession(latestSession.sessionId);
} else {
clearSessionSelection();
}
} finally {
setStartingAgentId((current) => current === agentId ? null : current);
}
// Let the selected card paint before loading the next transcript. The
// synchronous store update otherwise makes the sidebar wait behind the
// chat panel's session swap during the same click task.
window.setTimeout(() => {
if (agentSelectionRequestRef.current !== requestId) return;
void (async () => {
try {
const latestSession = conversationState.sessions
.filter((item) => item.agentId === agentId && !item.archivedAt)
.sort((left, right) => right.updatedAt.localeCompare(left.updatedAt))[0];
if (agentSelectionRequestRef.current !== requestId) return;
if (latestSession) {
await selectSession(latestSession.sessionId);
} else {
clearSessionSelection();
}
} finally {
setStartingAgentId((current) => current === agentId ? null : current);
}
})();
}, 0);
}, [clearSessionSelection, conversationState.sessions, selectedAgentId, selectSession]);
const handleSelectConversation = useCallback(async (sessionId: string) => {
@@ -2370,6 +2382,7 @@ export function OpencodeChatPanel({ variant = 'main', navigationDraft, onOpenPro
const agent: ProjectAgentConfig = {
id: `agent-${Date.now()}-${Math.random().toString(36).slice(2, 8)}`,
avatarId: input.avatarId,
avatarDataUrl: input.avatarDataUrl,
roleName: '项目伙伴',
name: input.name,
builtIn: false,
@@ -2705,10 +2718,25 @@ export function OpencodeChatPanel({ variant = 'main', navigationDraft, onOpenPro
return (
<Fragment key={messageKey}>
{startingExecutionRun && graphScopedAssistant ? executionTranscriptNode : (
<>
{executionTranscriptNode}
{messageTranscriptNode}
</>
executionTranscriptNode && messageTranscriptNode ? (
<div
className="space-y-0"
data-testid="chat-execution-reply-group"
>
{executionTranscriptNode}
<div
className="mt-1 h-px w-full bg-border/70"
data-testid="chat-execution-reply-divider"
aria-hidden="true"
/>
{messageTranscriptNode}
</div>
) : (
<>
{executionTranscriptNode}
{messageTranscriptNode}
</>
)
)}
{assetReviewInvocation && (
<div className="mt-3 max-w-[80%] sm:ml-11">