diff --git a/apps/ai-game-creator-shell/package.json b/apps/ai-game-creator-shell/package.json index c08cb6ac9..650ebf029 100644 --- a/apps/ai-game-creator-shell/package.json +++ b/apps/ai-game-creator-shell/package.json @@ -57,6 +57,7 @@ "react-colorful": "^5.8.0", "react-dom": "^19.0.0", "react-markdown": "^10.1.0", + "rehype-highlight": "^7.0.2", "remark-gfm": "^4.0.1", "vite": "^6.2.0", "zustand": "^5.0.14" diff --git a/apps/ai-game-creator-shell/src-tauri/src/agent/direct_runtime/mod.rs b/apps/ai-game-creator-shell/src-tauri/src/agent/direct_runtime/mod.rs index 08c7f56be..a515954cb 100644 --- a/apps/ai-game-creator-shell/src-tauri/src/agent/direct_runtime/mod.rs +++ b/apps/ai-game-creator-shell/src-tauri/src/agent/direct_runtime/mod.rs @@ -444,7 +444,8 @@ pub(crate) fn direct_taonier_active_invocation_id_at(root: &Path) -> Result Result, String> { @@ -475,16 +477,6 @@ pub(crate) fn read_direct_taonier_active_invocation_at( })) } -/// 只读命令:读取该项目当前登记的 Direct 活跃回合,供前端重进会话时恢复。 -/// -/// 不进入、不抢占、不释放守卫;没有回合时返回 `null`。 -#[tauri::command] -pub(crate) fn read_direct_codex_active_turn( - project_path: String, -) -> Result, String> { - read_direct_taonier_active_invocation_at(Path::new(project_path.trim())) -} - /// "终止"拿不到可中断句柄时的分类,决定是否允许强制释放本地守卫。 #[derive(Clone, Copy, Debug, Eq, PartialEq)] pub(crate) enum DirectTaonierStaleGuardReason { diff --git a/apps/ai-game-creator-shell/src-tauri/src/main.rs b/apps/ai-game-creator-shell/src-tauri/src/main.rs index 9a6c48a3b..bec55a981 100644 --- a/apps/ai-game-creator-shell/src-tauri/src/main.rs +++ b/apps/ai-game-creator-shell/src-tauri/src/main.rs @@ -2677,7 +2677,6 @@ fn main() { chat_with_game_creator_role_agent, chat_with_game_creator_role_agent_stream, chat_with_game_creator_direct_codex, - read_direct_codex_active_turn, cancel_direct_codex_turn, select_game_creator_reasoning_effort, start_planning_session_v2, diff --git a/apps/ai-game-creator-shell/src/app/types.ts b/apps/ai-game-creator-shell/src/app/types.ts index b71988918..1a8e48d72 100644 --- a/apps/ai-game-creator-shell/src/app/types.ts +++ b/apps/ai-game-creator-shell/src/app/types.ts @@ -1204,19 +1204,6 @@ interface TurnStreamItemBase { */ export type TurnStreamItem = TurnStreamTextItem | TurnStreamToolItem; -/** - * `read_direct_codex_active_turn` 的返回值:Rust 进程内当前登记的 Direct 活跃回合。 - * - * 重进会话时用它把前端的"当前活跃回合"接管回来,否则界面不知道有回合在跑, - * 既不显示过程卡也不给终止入口,用户再发消息只会被守卫拒绝。 - */ -export interface DirectActiveTurnView { - /** 与回合事件的 `turnId` 同一个身份。 */ - clientTurnId: string; - /** 这一轮登记的时刻(Unix 毫秒)。 */ - startedAt: number; -} - /** `cancel_direct_codex_turn` 的返回值。 */ export interface DirectTurnCancelView { /** diff --git a/apps/ai-game-creator-shell/src/components/ChatMarkdownMessage/codeHighlight.css b/apps/ai-game-creator-shell/src/components/ChatMarkdownMessage/codeHighlight.css new file mode 100644 index 000000000..d212361a5 --- /dev/null +++ b/apps/ai-game-creator-shell/src/components/ChatMarkdownMessage/codeHighlight.css @@ -0,0 +1,37 @@ +/* 只作用于共享 Markdown 渲染器,不改变普通正文及用户消息的字体颜色。 */ +.agc-markdown-code .hljs-comment, +.agc-markdown-code .hljs-quote { + color: #6a737d; +} + +.agc-markdown-code .hljs-keyword, +.agc-markdown-code .hljs-name, +.agc-markdown-code .hljs-selector-tag, +.agc-markdown-code .hljs-literal, +.agc-markdown-code .hljs-deletion { + color: #a6264c; +} + +.agc-markdown-code .hljs-string, +.agc-markdown-code .hljs-regexp, +.agc-markdown-code .hljs-addition { + color: #276438; +} + +.agc-markdown-code .hljs-number, +.agc-markdown-code .hljs-attr, +.agc-markdown-code .hljs-variable, +.agc-markdown-code .hljs-built_in { + color: #075a9c; +} + +.agc-markdown-code .hljs-title, +.agc-markdown-code .hljs-type, +.agc-markdown-code .hljs-section { + color: #6f42a0; +} + +.agc-markdown-code .hljs-meta, +.agc-markdown-code .hljs-symbol { + color: #8a4c0a; +} diff --git a/apps/ai-game-creator-shell/src/components/ChatMarkdownMessage/index.tsx b/apps/ai-game-creator-shell/src/components/ChatMarkdownMessage/index.tsx index d743dc2c2..d8acce153 100644 --- a/apps/ai-game-creator-shell/src/components/ChatMarkdownMessage/index.tsx +++ b/apps/ai-game-creator-shell/src/components/ChatMarkdownMessage/index.tsx @@ -1,3 +1,5 @@ +import './codeHighlight.css'; + import type { ErrorInfo, ReactNode } from 'react'; import { Children, @@ -7,14 +9,20 @@ import { useContext, } from 'react'; import ReactMarkdown, { type Components } from 'react-markdown'; +import rehypeHighlight from 'rehype-highlight'; import remarkGfm from 'remark-gfm'; export type ChatMarkdownMessageProps = { text: string; role: 'assistant' | 'user'; streaming?: boolean; + /** 文件预览不压缩正文空行,保留源码与文档的原始排版。 */ + preserveBlankLines?: boolean; }; +const MAX_HIGHLIGHT_CHARACTERS = 100_000; +const CodeBlockContext = createContext(false); + /** 只压缩普通 Markdown 正文里多余的空行;代码块中的换行必须原样保留。 */ function normalizeMarkdownBlankLines(text: string) { return text @@ -183,22 +191,50 @@ const markdownComponents: Components = { a: ({ children }) => children, img: ({ alt }) => (alt?.trim() ? `图片:${alt}` : '图片已省略'), h1: ({ children }) => ( -

{children}

+

+ {children} +

), h2: ({ children }) => ( -

{children}

+

+ {children} +

), h3: ({ children }) => ( -

{children}

+

+ {children} +

), h4: ({ children }) => ( -

{children}

+

+ {children} +

), h5: ({ children }) => ( -
{children}
+
+ {children} +
), h6: ({ children }) => ( -
+
{children}
), @@ -213,15 +249,18 @@ const markdownComponents: Components = { ), pre: ({ children }) => (
-      {children}
+      
+        {children}
+      
     
), - code: ({ className, children, node: _node, ...props }) => { - const isBlock = - Boolean(className?.includes('language-')) || - String(children).includes('\n'); + code: function MarkdownCode({ className, children, node: _node, ...props }) { + const isBlock = useContext(CodeBlockContext); return isBlock ? ( - + {children} ) : ( @@ -235,7 +274,10 @@ const markdownComponents: Components = { }, table: ({ children }) => (
- +
{children}
@@ -264,6 +306,7 @@ export function ChatMarkdownMessage({ text, role, streaming = false, + preserveBlankLines = false, }: ChatMarkdownMessageProps) { if (role === 'user') { return {text}; @@ -274,11 +317,14 @@ export function ChatMarkdownMessage({ - {normalizeMarkdownBlankLines(text)} + {preserveBlankLines ? text : normalizeMarkdownBlankLines(text)} ); diff --git a/apps/ai-game-creator-shell/src/features/project-workspace/ProjectSupervisorView.tsx b/apps/ai-game-creator-shell/src/features/project-workspace/ProjectSupervisorView.tsx index b2a9a0728..4124f3a5a 100644 --- a/apps/ai-game-creator-shell/src/features/project-workspace/ProjectSupervisorView.tsx +++ b/apps/ai-game-creator-shell/src/features/project-workspace/ProjectSupervisorView.tsx @@ -8,6 +8,10 @@ import type { } from 'react'; import { Fragment, useEffect, useRef, useState } from 'react'; +import { + AgentMessageContent, + type AgentMessageTone, +} from '../../../../../packages/shared/src/components/AgentMessageContent'; import type { AgentStatusCard, ChatMessage, @@ -131,6 +135,7 @@ function TurnStreamSequence({ active, userSentAt, className, + tone = 'body', }: { items: readonly TurnStreamItem[]; toolCalls: readonly GameCreatorDirectToolCall[]; @@ -138,6 +143,7 @@ function TurnStreamSequence({ active: boolean; userSentAt: number; className?: string; + tone?: AgentMessageTone; }) { const runs = turnStreamRuns(items); const callsById = new Map(); @@ -159,11 +165,13 @@ function TurnStreamSequence({ : 'message message--assistant' } > - + + + ) : ( + 思考过程 +
{text}
+ + ); +} + type RuntimePanelProps = ComponentProps; function directStatusTitle(status: string | null | undefined) { @@ -437,7 +469,11 @@ export function ProjectSupervisorView({

); }; - const renderMessage = (message: ChatMessage, index: number) => { + const renderMessageContent = ( + message: ChatMessage, + index: number, + tone: AgentMessageTone, + ) => { const sentAt = message.role === 'user' ? directMessageTimestamp(message.updatedAt) : 0; return ( @@ -445,10 +481,12 @@ export function ProjectSupervisorView({ key={message.messageId ?? `${message.role}-${index}`} className={`message message--${message.role}`} > - + + + {sentAt > 0 ? (