illusion-code 0.1.0__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (214) hide show
  1. illusion/__init__.py +24 -0
  2. illusion/__main__.py +15 -0
  3. illusion/_frontend/dist/index.mjs +39208 -0
  4. illusion/_frontend/package.json +27 -0
  5. illusion/_frontend/src/App.tsx +624 -0
  6. illusion/_frontend/src/components/CommandPicker.tsx +98 -0
  7. illusion/_frontend/src/components/Composer.tsx +55 -0
  8. illusion/_frontend/src/components/ComposerController.tsx +128 -0
  9. illusion/_frontend/src/components/ConversationView.tsx +750 -0
  10. illusion/_frontend/src/components/Footer.tsx +25 -0
  11. illusion/_frontend/src/components/MarkdownContent.tsx +537 -0
  12. illusion/_frontend/src/components/MarkdownTable.tsx +245 -0
  13. illusion/_frontend/src/components/ModalHost.tsx +425 -0
  14. illusion/_frontend/src/components/MultilineTextInput.tsx +250 -0
  15. illusion/_frontend/src/components/PromptInput.tsx +64 -0
  16. illusion/_frontend/src/components/SelectModal.tsx +78 -0
  17. illusion/_frontend/src/components/SidePanel.tsx +175 -0
  18. illusion/_frontend/src/components/Spinner.tsx +77 -0
  19. illusion/_frontend/src/components/StatusBar.tsx +142 -0
  20. illusion/_frontend/src/components/SwarmPanel.tsx +141 -0
  21. illusion/_frontend/src/components/TodoPanel.tsx +126 -0
  22. illusion/_frontend/src/components/ToolCallDisplay.tsx +202 -0
  23. illusion/_frontend/src/components/TranscriptPane.tsx +79 -0
  24. illusion/_frontend/src/components/WelcomeBanner.tsx +37 -0
  25. illusion/_frontend/src/hooks/useBackendSession.ts +468 -0
  26. illusion/_frontend/src/hooks/useTerminalSize.ts +9 -0
  27. illusion/_frontend/src/i18n.ts +78 -0
  28. illusion/_frontend/src/index.tsx +42 -0
  29. illusion/_frontend/src/theme/ThemeContext.tsx +19 -0
  30. illusion/_frontend/src/theme/builtinThemes.ts +89 -0
  31. illusion/_frontend/src/types.ts +110 -0
  32. illusion/_frontend/src/utils/markdown.ts +33 -0
  33. illusion/_frontend/src/utils/thinking.ts +191 -0
  34. illusion/_frontend/tsconfig.json +13 -0
  35. illusion/_web_dist/assets/index-BseIw-ik.css +10 -0
  36. illusion/_web_dist/assets/index-C_0ZWMuW.js +82 -0
  37. illusion/_web_dist/index.html +16 -0
  38. illusion/api/__init__.py +36 -0
  39. illusion/api/client.py +568 -0
  40. illusion/api/codex_client.py +563 -0
  41. illusion/api/compat.py +138 -0
  42. illusion/api/effort.py +128 -0
  43. illusion/api/errors.py +57 -0
  44. illusion/api/openai_client.py +819 -0
  45. illusion/api/provider.py +148 -0
  46. illusion/api/registry.py +479 -0
  47. illusion/api/usage.py +45 -0
  48. illusion/auth/__init__.py +50 -0
  49. illusion/auth/copilot.py +419 -0
  50. illusion/auth/external.py +612 -0
  51. illusion/auth/flows.py +58 -0
  52. illusion/auth/manager.py +214 -0
  53. illusion/auth/storage.py +372 -0
  54. illusion/bridge/__init__.py +38 -0
  55. illusion/bridge/manager.py +190 -0
  56. illusion/bridge/session_runner.py +84 -0
  57. illusion/bridge/types.py +113 -0
  58. illusion/bridge/work_secret.py +131 -0
  59. illusion/cli.py +1228 -0
  60. illusion/commands/__init__.py +32 -0
  61. illusion/commands/registry.py +1934 -0
  62. illusion/config/__init__.py +39 -0
  63. illusion/config/i18n.py +522 -0
  64. illusion/config/paths.py +259 -0
  65. illusion/config/settings.py +564 -0
  66. illusion/coordinator/__init__.py +41 -0
  67. illusion/coordinator/agent_definitions.py +1093 -0
  68. illusion/coordinator/coordinator_mode.py +127 -0
  69. illusion/engine/__init__.py +95 -0
  70. illusion/engine/cost_tracker.py +55 -0
  71. illusion/engine/messages.py +369 -0
  72. illusion/engine/query.py +632 -0
  73. illusion/engine/query_engine.py +343 -0
  74. illusion/engine/stream_events.py +169 -0
  75. illusion/hooks/__init__.py +67 -0
  76. illusion/hooks/events.py +43 -0
  77. illusion/hooks/executor.py +397 -0
  78. illusion/hooks/hot_reload.py +74 -0
  79. illusion/hooks/loader.py +133 -0
  80. illusion/hooks/schemas.py +121 -0
  81. illusion/hooks/types.py +86 -0
  82. illusion/mcp/__init__.py +104 -0
  83. illusion/mcp/client.py +377 -0
  84. illusion/mcp/config.py +140 -0
  85. illusion/mcp/types.py +175 -0
  86. illusion/memory/__init__.py +36 -0
  87. illusion/memory/manager.py +94 -0
  88. illusion/memory/memdir.py +58 -0
  89. illusion/memory/paths.py +57 -0
  90. illusion/memory/scan.py +120 -0
  91. illusion/memory/search.py +83 -0
  92. illusion/memory/types.py +43 -0
  93. illusion/output_styles/__init__.py +15 -0
  94. illusion/output_styles/loader.py +64 -0
  95. illusion/permissions/__init__.py +39 -0
  96. illusion/permissions/checker.py +174 -0
  97. illusion/permissions/modes.py +38 -0
  98. illusion/platforms.py +148 -0
  99. illusion/plugins/__init__.py +71 -0
  100. illusion/plugins/bundled/__init__.py +0 -0
  101. illusion/plugins/installer.py +59 -0
  102. illusion/plugins/loader.py +301 -0
  103. illusion/plugins/schemas.py +51 -0
  104. illusion/plugins/types.py +56 -0
  105. illusion/prompts/__init__.py +29 -0
  106. illusion/prompts/claudemd.py +74 -0
  107. illusion/prompts/context.py +187 -0
  108. illusion/prompts/environment.py +189 -0
  109. illusion/prompts/system_prompt.py +155 -0
  110. illusion/py.typed +0 -0
  111. illusion/sandbox/__init__.py +29 -0
  112. illusion/sandbox/adapter.py +174 -0
  113. illusion/services/__init__.py +59 -0
  114. illusion/services/compact/__init__.py +1015 -0
  115. illusion/services/cron.py +338 -0
  116. illusion/services/cron_scheduler.py +715 -0
  117. illusion/services/file_history.py +258 -0
  118. illusion/services/lsp/__init__.py +455 -0
  119. illusion/services/session_storage.py +237 -0
  120. illusion/services/token_estimation.py +72 -0
  121. illusion/skills/__init__.py +60 -0
  122. illusion/skills/bundled/__init__.py +110 -0
  123. illusion/skills/bundled/content/batch.md +86 -0
  124. illusion/skills/bundled/content/coding-guidelines.md +70 -0
  125. illusion/skills/bundled/content/debug.md +38 -0
  126. illusion/skills/bundled/content/loop.md +82 -0
  127. illusion/skills/bundled/content/remember.md +105 -0
  128. illusion/skills/bundled/content/simplify.md +53 -0
  129. illusion/skills/bundled/content/skillify.md +113 -0
  130. illusion/skills/bundled/content/stuck.md +54 -0
  131. illusion/skills/bundled/content/update-config.md +329 -0
  132. illusion/skills/bundled/content/verify.md +74 -0
  133. illusion/skills/loader.py +219 -0
  134. illusion/skills/registry.py +40 -0
  135. illusion/skills/types.py +24 -0
  136. illusion/state/__init__.py +18 -0
  137. illusion/state/app_state.py +67 -0
  138. illusion/state/store.py +93 -0
  139. illusion/swarm/__init__.py +71 -0
  140. illusion/swarm/agent_executor.py +857 -0
  141. illusion/swarm/in_process.py +259 -0
  142. illusion/swarm/subprocess_backend.py +136 -0
  143. illusion/swarm/team_helpers.py +123 -0
  144. illusion/swarm/types.py +159 -0
  145. illusion/swarm/worktree.py +347 -0
  146. illusion/tasks/__init__.py +33 -0
  147. illusion/tasks/local_agent_task.py +42 -0
  148. illusion/tasks/local_shell_task.py +27 -0
  149. illusion/tasks/manager.py +377 -0
  150. illusion/tasks/stop_task.py +21 -0
  151. illusion/tasks/types.py +88 -0
  152. illusion/tools/__init__.py +126 -0
  153. illusion/tools/agent_tool.py +388 -0
  154. illusion/tools/ask_user_question_tool.py +186 -0
  155. illusion/tools/base.py +149 -0
  156. illusion/tools/bash_tool.py +413 -0
  157. illusion/tools/config_tool.py +90 -0
  158. illusion/tools/cron_tool.py +473 -0
  159. illusion/tools/enter_plan_mode_tool.py +147 -0
  160. illusion/tools/enter_worktree_tool.py +188 -0
  161. illusion/tools/exit_plan_mode_tool.py +69 -0
  162. illusion/tools/exit_worktree_tool.py +225 -0
  163. illusion/tools/file_edit_tool.py +283 -0
  164. illusion/tools/file_read_tool.py +294 -0
  165. illusion/tools/file_write_tool.py +184 -0
  166. illusion/tools/glob_tool.py +165 -0
  167. illusion/tools/grep_tool.py +190 -0
  168. illusion/tools/list_mcp_resources_tool.py +80 -0
  169. illusion/tools/lsp_tool.py +333 -0
  170. illusion/tools/mcp_auth_tool.py +100 -0
  171. illusion/tools/mcp_tool.py +75 -0
  172. illusion/tools/notebook_edit_tool.py +242 -0
  173. illusion/tools/powershell_tool.py +334 -0
  174. illusion/tools/read_mcp_resource_tool.py +63 -0
  175. illusion/tools/repl_tool.py +100 -0
  176. illusion/tools/send_message_tool.py +112 -0
  177. illusion/tools/shell_common.py +187 -0
  178. illusion/tools/skill_tool.py +86 -0
  179. illusion/tools/sleep_tool.py +62 -0
  180. illusion/tools/structured_output_tool.py +58 -0
  181. illusion/tools/task_create_tool.py +98 -0
  182. illusion/tools/task_get_tool.py +94 -0
  183. illusion/tools/task_list_tool.py +94 -0
  184. illusion/tools/task_output_tool.py +55 -0
  185. illusion/tools/task_stop_tool.py +52 -0
  186. illusion/tools/task_update_tool.py +224 -0
  187. illusion/tools/team_create_tool.py +236 -0
  188. illusion/tools/team_delete_tool.py +104 -0
  189. illusion/tools/todo_write_tool.py +198 -0
  190. illusion/tools/tool_search_tool.py +156 -0
  191. illusion/tools/web_fetch_tool.py +264 -0
  192. illusion/tools/web_search_tool.py +186 -0
  193. illusion/ui/__init__.py +23 -0
  194. illusion/ui/app.py +258 -0
  195. illusion/ui/backend_host.py +1180 -0
  196. illusion/ui/input.py +86 -0
  197. illusion/ui/output.py +363 -0
  198. illusion/ui/permission_dialog.py +47 -0
  199. illusion/ui/permission_store.py +99 -0
  200. illusion/ui/protocol.py +384 -0
  201. illusion/ui/react_launcher.py +280 -0
  202. illusion/ui/runtime.py +787 -0
  203. illusion/ui/textual_app.py +603 -0
  204. illusion/ui/web/__init__.py +10 -0
  205. illusion/ui/web/server.py +87 -0
  206. illusion/ui/web/ws_host.py +1197 -0
  207. illusion/utils/__init__.py +0 -0
  208. illusion/utils/ripgrep.py +299 -0
  209. illusion/utils/shell.py +248 -0
  210. illusion_code-0.1.0.dist-info/METADATA +1159 -0
  211. illusion_code-0.1.0.dist-info/RECORD +214 -0
  212. illusion_code-0.1.0.dist-info/WHEEL +4 -0
  213. illusion_code-0.1.0.dist-info/entry_points.txt +2 -0
  214. illusion_code-0.1.0.dist-info/licenses/LICENSE +21 -0
@@ -0,0 +1,89 @@
1
+ export type ThemeConfig = {
2
+ name: string;
3
+ colors: {
4
+ primary: string;
5
+ secondary: string;
6
+ accent: string;
7
+ foreground: string;
8
+ background: string;
9
+ muted: string;
10
+ success: string;
11
+ warning: string;
12
+ error: string;
13
+ info: string;
14
+ illusion: string;
15
+ illusionShimmer: string;
16
+ text: string;
17
+ subtle: string;
18
+ highlight: string;
19
+ promptBorder: string;
20
+ suggestion: string;
21
+ permission: string;
22
+ };
23
+ icons: {
24
+ spinner: string[];
25
+ tool: string;
26
+ assistant: string;
27
+ user: string;
28
+ system: string;
29
+ success: string;
30
+ error: string;
31
+ pending: string;
32
+ inProgress: string;
33
+ completed: string;
34
+ bullet: string;
35
+ arrow: string;
36
+ check: string;
37
+ cross: string;
38
+ chevron: string;
39
+ dot: string;
40
+ pointer: string;
41
+ middleDot: string;
42
+ resultPrefix: string;
43
+ };
44
+ };
45
+
46
+ export const defaultTheme: ThemeConfig = {
47
+ name: 'default',
48
+ colors: {
49
+ primary: '#56d4dd',
50
+ secondary: 'white',
51
+ accent: 'magenta',
52
+ foreground: 'white',
53
+ background: 'black',
54
+ muted: '#9ca3af',
55
+ success: 'green',
56
+ warning: 'yellow',
57
+ error: 'red',
58
+ info: '#89ddff',
59
+ illusion: '#f0c8b0',
60
+ illusionShimmer: '#e07070',
61
+ text: 'white',
62
+ subtle: '#a8b2c1',
63
+ highlight: '#56d4dd',
64
+ promptBorder: '#8b949e',
65
+ suggestion: '#89ddff',
66
+ permission: '#bb9af7',
67
+ },
68
+ icons: {
69
+ spinner: ['·', '◌', '◎', '◌'],
70
+ tool: '●',
71
+ assistant: '●',
72
+ user: '❯',
73
+ system: '✻',
74
+ success: '✓',
75
+ error: '✗',
76
+ pending: '○',
77
+ inProgress: '◐',
78
+ completed: '●',
79
+ bullet: '•',
80
+ arrow: '→',
81
+ check: '✓',
82
+ cross: '✗',
83
+ chevron: '›',
84
+ dot: '●',
85
+ pointer: '❯',
86
+ middleDot: '·',
87
+ resultPrefix: '⎿',
88
+ },
89
+ };
@@ -0,0 +1,110 @@
1
+ export type PendingToolCall = {
2
+ tool_name: string;
3
+ tool_use_id: string;
4
+ tool_input?: Record<string, unknown>;
5
+ };
6
+
7
+ export type FrontendConfig = {
8
+ backend_command: string[];
9
+ initial_prompt?: string | null;
10
+ };
11
+
12
+ export type TranscriptItem = {
13
+ role: 'system' | 'user' | 'assistant' | 'assistant_streaming' | 'tool' | 'tool_result' | 'log';
14
+ text: string;
15
+ tool_name?: string;
16
+ tool_input?: Record<string, unknown>;
17
+ is_error?: boolean;
18
+ reasoning?: string;
19
+ tool_use_id?: string;
20
+ };
21
+
22
+ export type TaskSnapshot = {
23
+ id: string;
24
+ type: string;
25
+ status: string;
26
+ description: string;
27
+ metadata: Record<string, string>;
28
+ };
29
+
30
+ export type McpServerSnapshot = {
31
+ name: string;
32
+ state: string;
33
+ detail?: string;
34
+ transport?: string;
35
+ auth_configured?: boolean;
36
+ tool_count?: number;
37
+ resource_count?: number;
38
+ };
39
+
40
+ export type BridgeSessionSnapshot = {
41
+ session_id: string;
42
+ command: string;
43
+ cwd: string;
44
+ pid: number;
45
+ status: string;
46
+ started_at: number;
47
+ output_path: string;
48
+ };
49
+
50
+ export type SelectOptionPayload = {
51
+ value: string;
52
+ label: string;
53
+ description?: string;
54
+ active?: boolean;
55
+ };
56
+
57
+ export type SelectRequestPayload = {
58
+ title: string;
59
+ command: string;
60
+ options: SelectOptionPayload[];
61
+ };
62
+
63
+ export type TodoItemSnapshot = {
64
+ content: string;
65
+ status: 'pending' | 'in_progress' | 'completed';
66
+ activeForm: string;
67
+ };
68
+
69
+ export type SwarmTeammateSnapshot = {
70
+ name: string;
71
+ status: 'running' | 'idle' | 'done' | 'error';
72
+ duration?: number;
73
+ task?: string;
74
+ };
75
+
76
+ export type SwarmNotificationSnapshot = {
77
+ from: string;
78
+ message: string;
79
+ timestamp: number;
80
+ };
81
+
82
+ export type BackendEvent = {
83
+ type: string;
84
+ message?: string | null;
85
+ item?: TranscriptItem | null;
86
+ state?: Record<string, unknown> | null;
87
+ tasks?: TaskSnapshot[] | null;
88
+ mcp_servers?: McpServerSnapshot[] | null;
89
+ bridge_sessions?: BridgeSessionSnapshot[] | null;
90
+ commands?: string[] | null;
91
+ modal?: Record<string, unknown> | null;
92
+ select_options?: SelectOptionPayload[] | null;
93
+ tool_name?: string | null;
94
+ tool_input?: Record<string, unknown> | null;
95
+ tool_use_id?: string | null;
96
+ output?: string | null;
97
+ is_error?: boolean | null;
98
+ // New event payloads
99
+ todo_items?: TodoItemSnapshot[] | null;
100
+ todo_markdown?: string | null;
101
+ plan_mode?: string | null;
102
+ swarm_teammates?: SwarmTeammateSnapshot[] | null;
103
+ swarm_notifications?: SwarmNotificationSnapshot[] | null;
104
+ reasoning?: string | null;
105
+ command_result_data?: {
106
+ message: string;
107
+ type: 'success' | 'error' | 'info';
108
+ } | null;
109
+ items?: TranscriptItem[] | null;
110
+ };
@@ -0,0 +1,33 @@
1
+ import stripAnsi from 'strip-ansi';
2
+ import stringWidth from 'string-width';
3
+ import wrapAnsi from 'wrap-ansi';
4
+
5
+ export {stripAnsi, stringWidth, wrapAnsi};
6
+
7
+ export function padAligned(
8
+ content: string,
9
+ displayWidth: number,
10
+ targetWidth: number,
11
+ align: 'left' | 'center' | 'right' | null | undefined,
12
+ ): string {
13
+ const padding = Math.max(0, targetWidth - displayWidth);
14
+ if (align === 'center') {
15
+ const leftPad = Math.floor(padding / 2);
16
+ return ' '.repeat(leftPad) + content + ' '.repeat(padding - leftPad);
17
+ }
18
+ if (align === 'right') {
19
+ return ' '.repeat(padding) + content;
20
+ }
21
+ return content + ' '.repeat(padding);
22
+ }
23
+
24
+ export function wrapText(text: string, width: number, options?: {hard?: boolean}): string[] {
25
+ if (width <= 0) return [text];
26
+ const trimmedText = text.trimEnd();
27
+ const wrapped = wrapAnsi(trimmedText, width, {
28
+ hard: options?.hard ?? false,
29
+ trim: false,
30
+ });
31
+ const lines = wrapped.split('\n').filter((line) => line.length > 0);
32
+ return lines.length > 0 ? lines : [''];
33
+ }
@@ -0,0 +1,191 @@
1
+ /**
2
+ * 思考过程处理工具
3
+ *
4
+ * 处理两种思考过程来源:
5
+ * 1. XML 标签包裹:`<think...</think` (DeepSeek、MiniMax 等模型)
6
+ * 2. 独立 reasoning 文本:通过后端 reasoning_content 字段传输(Kimi k2.5 等 OpenAI 兼容模型)
7
+ */
8
+
9
+ // 匹配完整的 <think...> 和 </think...> 标签
10
+ const THINK_OPEN_TAG = /<think\b[^>]*>/gi;
11
+ const THINK_CLOSE_TAG = /<\/think\b[^>]*>/gi;
12
+ // 匹配未闭合的 <think 开头标签(流式传输时标签可能被截断)
13
+ const THINK_OPEN_INCOMPLETE = /<th(?:i(?:n(?:k)?)?)?\s*$/i;
14
+ // 匹配完整的开闭标签对及其内容
15
+ const THINK_BLOCK_FULL = /<think\b[^>]*>[\s\S]*?<\/think\b[^>]*>/gi;
16
+ // DeepSeek 工具调用残留标记(例如 <|DSML|tool_calls)
17
+ const DSML_TOOL_CALL_PREFIX = /<\s*[||]\s*DSML\s*[||]\s*tool_calls[^\n>]*>?/gi;
18
+ // 兼容常见工具调用 XML 片段
19
+ const TOOL_CALL_XML_BLOCK = /<tool_call\b[^>]*>[\s\S]*?<\/tool_call\b[^>]*>/gi;
20
+ const TOOL_CALL_XML_TAG = /<\/?(?:tool_call|arg_key|arg_value)\b[^>]*>/gi;
21
+
22
+ /**
23
+ * 清除 `<think` 标签包裹的思考块(保留标签外的内容)
24
+ * 处理完整标签、嵌套标签和流式截断的不完整标签
25
+ */
26
+ export function stripThinkTags(raw: string): string {
27
+ if (!raw) return '';
28
+
29
+ // 先移除完整的 <think...</think 块(包括内容)
30
+ let result = raw.replace(THINK_BLOCK_FULL, '');
31
+
32
+ // 移除孤立的闭合标签
33
+ result = result.replace(THINK_CLOSE_TAG, '');
34
+
35
+ // 移除孤立的开标签(可能是不完整的块)
36
+ result = result.replace(THINK_OPEN_TAG, '');
37
+
38
+ // 移除被截断的不完整开标签(如 "<th"、"<thi"、"<thin")
39
+ result = result.replace(THINK_OPEN_INCOMPLETE, '');
40
+
41
+ return result.trim();
42
+ }
43
+
44
+ /**
45
+ * 从包含 `<think` 标签的文本中提取思考过程内容
46
+ * 返回标签内的文本(去除标签本身)
47
+ */
48
+ export function extractThinkContent(raw: string): string {
49
+ if (!raw) return '';
50
+
51
+ const parts: string[] = [];
52
+ let remaining = raw;
53
+
54
+ // 提取所有完整 <think...</think 块的内容
55
+ let match: RegExpExecArray | null;
56
+ THINK_BLOCK_FULL.lastIndex = 0;
57
+ let lastIndex = 0;
58
+
59
+ while ((match = THINK_BLOCK_FULL.exec(remaining)) !== null) {
60
+ // 标签前的内容中检查是否有孤立的开标签
61
+ const before = remaining.slice(lastIndex, match.index);
62
+ if (THINK_OPEN_TAG.test(before)) {
63
+ // 孤立开标签后的内容也是思考过程
64
+ THINK_OPEN_TAG.lastIndex = 0;
65
+ const openMatch = THINK_OPEN_TAG.exec(before);
66
+ if (openMatch) {
67
+ parts.push(before.slice(openMatch.index + openMatch[0].length));
68
+ }
69
+ }
70
+
71
+ // 提取标签对内的内容
72
+ const inner = match[0].replace(THINK_OPEN_TAG, '').replace(THINK_CLOSE_TAG, '');
73
+ parts.push(inner.trim());
74
+
75
+ lastIndex = match.index + match[0].length;
76
+ THINK_OPEN_TAG.lastIndex = 0;
77
+ THINK_CLOSE_TAG.lastIndex = 0;
78
+ }
79
+
80
+ // 检查剩余文本中是否有未闭合的开标签(流式截断场景)
81
+ const tail = remaining.slice(lastIndex);
82
+ THINK_OPEN_TAG.lastIndex = 0;
83
+ const openInTail = THINK_OPEN_TAG.exec(tail);
84
+ if (openInTail) {
85
+ parts.push(tail.slice(openInTail.index + openInTail[0].length).trim());
86
+ } else if (THINK_OPEN_INCOMPLETE.test(tail)) {
87
+ // 不完整标签,忽略
88
+ } else {
89
+ // 如果没有匹配到任何 think 块,检查是否有推理文本
90
+ // (不会走到这里如果没有 think 标签)
91
+ }
92
+
93
+ return parts.filter(Boolean).join('\n');
94
+ }
95
+
96
+ export function mergeReasoning(a: string | undefined, b: string): string {
97
+ const parts: string[] = [];
98
+ if (a) appendUnique(parts, a);
99
+ if (b) appendUnique(parts, b);
100
+ return parts.join('\n').trim();
101
+ }
102
+
103
+ /**
104
+ * 判断文本是否包含 `<think` 标签
105
+ */
106
+ export function hasThinkTags(raw: string): boolean {
107
+ if (!raw) return false;
108
+ return /<think\b/i.test(raw);
109
+ }
110
+
111
+ export function stripToolCallArtifacts(raw: string): string {
112
+ if (!raw) return '';
113
+ return raw
114
+ .replace(DSML_TOOL_CALL_PREFIX, '')
115
+ .replace(TOOL_CALL_XML_BLOCK, '')
116
+ .replace(TOOL_CALL_XML_TAG, '');
117
+ }
118
+
119
+ function normalizeCompareText(raw: string): string {
120
+ return stripToolCallArtifacts(raw)
121
+ .replace(/\s+/g, ' ')
122
+ .trim();
123
+ }
124
+
125
+ function appendUnique(parts: string[], value: string): void {
126
+ const cleaned = stripToolCallArtifacts(value).trim();
127
+ if (!cleaned) {
128
+ return;
129
+ }
130
+
131
+ const candidateNorm = normalizeCompareText(cleaned);
132
+ if (!candidateNorm) {
133
+ return;
134
+ }
135
+
136
+ for (const existing of parts) {
137
+ const existingNorm = normalizeCompareText(existing);
138
+ if (!existingNorm) {
139
+ continue;
140
+ }
141
+ if (existingNorm === candidateNorm || existingNorm.includes(candidateNorm)) {
142
+ return;
143
+ }
144
+ }
145
+
146
+ for (let i = parts.length - 1; i >= 0; i -= 1) {
147
+ const existingNorm = normalizeCompareText(parts[i]);
148
+ if (candidateNorm.includes(existingNorm)) {
149
+ parts.splice(i, 1);
150
+ }
151
+ }
152
+
153
+ parts.push(cleaned);
154
+ }
155
+
156
+ /**
157
+ * 渲染助手消息文本,根据 showThinking 决定是否显示思考过程
158
+ *
159
+ * @param raw 原始文本(可能包含 `<think` 标签)
160
+ * @param showThinking 是否显示思考过程
161
+ * @param reasoning 独立的推理文本(来自 reasoning_content 字段,无标签)
162
+ * @returns 显示给用户的文本
163
+ */
164
+ export function renderAssistantText(
165
+ raw: string,
166
+ showThinking: boolean,
167
+ reasoning?: string,
168
+ ): string {
169
+ if (!raw && !reasoning) return '';
170
+
171
+ const sanitizedRaw = stripToolCallArtifacts(raw);
172
+ const sanitizedReasoning = stripToolCallArtifacts(reasoning ?? '');
173
+ const hasTags = hasThinkTags(sanitizedRaw);
174
+ let cleanText = sanitizedRaw;
175
+ let thinkContent = '';
176
+
177
+ if (hasTags) {
178
+ thinkContent = stripToolCallArtifacts(extractThinkContent(sanitizedRaw));
179
+ cleanText = stripToolCallArtifacts(stripThinkTags(sanitizedRaw));
180
+ }
181
+
182
+ if (showThinking) {
183
+ const parts: string[] = [];
184
+ if (sanitizedReasoning) appendUnique(parts, sanitizedReasoning);
185
+ if (thinkContent) appendUnique(parts, thinkContent);
186
+ if (cleanText) appendUnique(parts, cleanText);
187
+ return parts.filter(Boolean).join('\n\n').trim();
188
+ }
189
+
190
+ return cleanText.trim();
191
+ }
@@ -0,0 +1,13 @@
1
+ {
2
+ "compilerOptions": {
3
+ "target": "ES2022",
4
+ "module": "ESNext",
5
+ "moduleResolution": "Bundler",
6
+ "jsx": "react-jsx",
7
+ "esModuleInterop": true,
8
+ "strict": false,
9
+ "skipLibCheck": true,
10
+ "types": ["node"]
11
+ },
12
+ "include": ["src/**/*.ts", "src/**/*.tsx"]
13
+ }
@@ -0,0 +1,10 @@
1
+ pre code.hljs{display:block;overflow-x:auto;padding:1em}code.hljs{padding:3px 5px}/*!
2
+ Theme: GitHub
3
+ Description: Light theme as seen on github.com
4
+ Author: github.com
5
+ Maintainer: @Hirse
6
+ Updated: 2021-05-15
7
+
8
+ Outdated base version: https://github.com/primer/github-syntax-light
9
+ Current colors taken from GitHub's CSS
10
+ */.hljs{color:#24292e;background:#fff}.hljs-doctag,.hljs-keyword,.hljs-meta .hljs-keyword,.hljs-template-tag,.hljs-template-variable,.hljs-type,.hljs-variable.language_{color:#d73a49}.hljs-title,.hljs-title.class_,.hljs-title.class_.inherited__,.hljs-title.function_{color:#6f42c1}.hljs-attr,.hljs-attribute,.hljs-literal,.hljs-meta,.hljs-number,.hljs-operator,.hljs-variable,.hljs-selector-attr,.hljs-selector-class,.hljs-selector-id{color:#005cc5}.hljs-regexp,.hljs-string,.hljs-meta .hljs-string{color:#032f62}.hljs-built_in,.hljs-symbol{color:#e36209}.hljs-comment,.hljs-code,.hljs-formula{color:#6a737d}.hljs-name,.hljs-quote,.hljs-selector-tag,.hljs-selector-pseudo{color:#22863a}.hljs-subst{color:#24292e}.hljs-section{color:#005cc5;font-weight:700}.hljs-bullet{color:#735c0f}.hljs-emphasis{color:#24292e;font-style:italic}.hljs-strong{color:#24292e;font-weight:700}.hljs-addition{color:#22863a;background-color:#f0fff4}.hljs-deletion{color:#b31d28;background-color:#ffeef0}*,:before,:after{--tw-border-spacing-x: 0;--tw-border-spacing-y: 0;--tw-translate-x: 0;--tw-translate-y: 0;--tw-rotate: 0;--tw-skew-x: 0;--tw-skew-y: 0;--tw-scale-x: 1;--tw-scale-y: 1;--tw-pan-x: ;--tw-pan-y: ;--tw-pinch-zoom: ;--tw-scroll-snap-strictness: proximity;--tw-gradient-from-position: ;--tw-gradient-via-position: ;--tw-gradient-to-position: ;--tw-ordinal: ;--tw-slashed-zero: ;--tw-numeric-figure: ;--tw-numeric-spacing: ;--tw-numeric-fraction: ;--tw-ring-inset: ;--tw-ring-offset-width: 0px;--tw-ring-offset-color: #fff;--tw-ring-color: rgb(59 130 246 / .5);--tw-ring-offset-shadow: 0 0 #0000;--tw-ring-shadow: 0 0 #0000;--tw-shadow: 0 0 #0000;--tw-shadow-colored: 0 0 #0000;--tw-blur: ;--tw-brightness: ;--tw-contrast: ;--tw-grayscale: ;--tw-hue-rotate: ;--tw-invert: ;--tw-saturate: ;--tw-sepia: ;--tw-drop-shadow: ;--tw-backdrop-blur: ;--tw-backdrop-brightness: ;--tw-backdrop-contrast: ;--tw-backdrop-grayscale: ;--tw-backdrop-hue-rotate: ;--tw-backdrop-invert: ;--tw-backdrop-opacity: ;--tw-backdrop-saturate: ;--tw-backdrop-sepia: ;--tw-contain-size: ;--tw-contain-layout: ;--tw-contain-paint: ;--tw-contain-style: }::backdrop{--tw-border-spacing-x: 0;--tw-border-spacing-y: 0;--tw-translate-x: 0;--tw-translate-y: 0;--tw-rotate: 0;--tw-skew-x: 0;--tw-skew-y: 0;--tw-scale-x: 1;--tw-scale-y: 1;--tw-pan-x: ;--tw-pan-y: ;--tw-pinch-zoom: ;--tw-scroll-snap-strictness: proximity;--tw-gradient-from-position: ;--tw-gradient-via-position: ;--tw-gradient-to-position: ;--tw-ordinal: ;--tw-slashed-zero: ;--tw-numeric-figure: ;--tw-numeric-spacing: ;--tw-numeric-fraction: ;--tw-ring-inset: ;--tw-ring-offset-width: 0px;--tw-ring-offset-color: #fff;--tw-ring-color: rgb(59 130 246 / .5);--tw-ring-offset-shadow: 0 0 #0000;--tw-ring-shadow: 0 0 #0000;--tw-shadow: 0 0 #0000;--tw-shadow-colored: 0 0 #0000;--tw-blur: ;--tw-brightness: ;--tw-contrast: ;--tw-grayscale: ;--tw-hue-rotate: ;--tw-invert: ;--tw-saturate: ;--tw-sepia: ;--tw-drop-shadow: ;--tw-backdrop-blur: ;--tw-backdrop-brightness: ;--tw-backdrop-contrast: ;--tw-backdrop-grayscale: ;--tw-backdrop-hue-rotate: ;--tw-backdrop-invert: ;--tw-backdrop-opacity: ;--tw-backdrop-saturate: ;--tw-backdrop-sepia: ;--tw-contain-size: ;--tw-contain-layout: ;--tw-contain-paint: ;--tw-contain-style: }*,:before,:after{box-sizing:border-box;border-width:0;border-style:solid;border-color:#e5e7eb}:before,:after{--tw-content: ""}html,:host{line-height:1.5;-webkit-text-size-adjust:100%;-moz-tab-size:4;-o-tab-size:4;tab-size:4;font-family:ui-sans-serif,system-ui,sans-serif,"Apple Color Emoji","Segoe UI Emoji",Segoe UI Symbol,"Noto Color Emoji";font-feature-settings:normal;font-variation-settings:normal;-webkit-tap-highlight-color:transparent}body{margin:0;line-height:inherit}hr{height:0;color:inherit;border-top-width:1px}abbr:where([title]){-webkit-text-decoration:underline dotted;text-decoration:underline dotted}h1,h2,h3,h4,h5,h6{font-size:inherit;font-weight:inherit}a{color:inherit;text-decoration:inherit}b,strong{font-weight:bolder}code,kbd,samp,pre{font-family:JetBrains Mono,Fira Code,monospace;font-feature-settings:normal;font-variation-settings:normal;font-size:1em}small{font-size:80%}sub,sup{font-size:75%;line-height:0;position:relative;vertical-align:baseline}sub{bottom:-.25em}sup{top:-.5em}table{text-indent:0;border-color:inherit;border-collapse:collapse}button,input,optgroup,select,textarea{font-family:inherit;font-feature-settings:inherit;font-variation-settings:inherit;font-size:100%;font-weight:inherit;line-height:inherit;letter-spacing:inherit;color:inherit;margin:0;padding:0}button,select{text-transform:none}button,input:where([type=button]),input:where([type=reset]),input:where([type=submit]){-webkit-appearance:button;background-color:transparent;background-image:none}:-moz-focusring{outline:auto}:-moz-ui-invalid{box-shadow:none}progress{vertical-align:baseline}::-webkit-inner-spin-button,::-webkit-outer-spin-button{height:auto}[type=search]{-webkit-appearance:textfield;outline-offset:-2px}::-webkit-search-decoration{-webkit-appearance:none}::-webkit-file-upload-button{-webkit-appearance:button;font:inherit}summary{display:list-item}blockquote,dl,dd,h1,h2,h3,h4,h5,h6,hr,figure,p,pre{margin:0}fieldset{margin:0;padding:0}legend{padding:0}ol,ul,menu{list-style:none;margin:0;padding:0}dialog{padding:0}textarea{resize:vertical}input::-moz-placeholder,textarea::-moz-placeholder{opacity:1;color:#9ca3af}input::placeholder,textarea::placeholder{opacity:1;color:#9ca3af}button,[role=button]{cursor:pointer}:disabled{cursor:default}img,svg,video,canvas,audio,iframe,embed,object{display:block;vertical-align:middle}img,video{max-width:100%;height:auto}[hidden]:where(:not([hidden=until-found])){display:none}.fixed{position:fixed}.absolute{position:absolute}.relative{position:relative}.inset-0{top:0;right:0;bottom:0;left:0}.bottom-20{bottom:5rem}.bottom-full{bottom:100%}.left-0{left:0}.left-4{left:1rem}.right-0{right:0}.right-4{right:1rem}.right-6{right:1.5rem}.top-full{top:100%}.z-10{z-index:10}.z-20{z-index:20}.z-50{z-index:50}.m-1\.5{margin:.375rem}.mx-0\.5{margin-left:.125rem;margin-right:.125rem}.mx-auto{margin-left:auto;margin-right:auto}.my-3{margin-top:.75rem;margin-bottom:.75rem}.mb-1{margin-bottom:.25rem}.mb-1\.5{margin-bottom:.375rem}.mb-2{margin-bottom:.5rem}.mb-3{margin-bottom:.75rem}.mb-4{margin-bottom:1rem}.ml-0\.5{margin-left:.125rem}.ml-3\.5{margin-left:.875rem}.ml-auto{margin-left:auto}.mt-0\.5{margin-top:.125rem}.mt-1{margin-top:.25rem}.mt-1\.5{margin-top:.375rem}.mt-12{margin-top:3rem}.mt-2{margin-top:.5rem}.mt-3{margin-top:.75rem}.mt-4{margin-top:1rem}.mt-8{margin-top:2rem}.mt-auto{margin-top:auto}.line-clamp-2{overflow:hidden;display:-webkit-box;-webkit-box-orient:vertical;-webkit-line-clamp:2}.block{display:block}.inline-block{display:inline-block}.flex{display:flex}.hidden{display:none}.h-1\.5{height:.375rem}.h-2{height:.5rem}.h-2\.5{height:.625rem}.h-3{height:.75rem}.h-3\.5{height:.875rem}.h-4{height:1rem}.h-5{height:1.25rem}.h-7{height:1.75rem}.h-8{height:2rem}.h-9{height:2.25rem}.h-full{height:100%}.h-screen{height:100vh}.max-h-40{max-height:10rem}.max-h-56{max-height:14rem}.max-h-60{max-height:15rem}.max-h-64{max-height:16rem}.max-h-\[140px\]{max-height:140px}.max-h-\[40vh\]{max-height:40vh}.max-h-\[50vh\]{max-height:50vh}.max-h-\[70vh\]{max-height:70vh}.min-h-\[36px\]{min-height:36px}.w-0\.5{width:.125rem}.w-1{width:.25rem}.w-1\.5{width:.375rem}.w-12{width:3rem}.w-14{width:3.5rem}.w-2{width:.5rem}.w-2\.5{width:.625rem}.w-3{width:.75rem}.w-3\.5{width:.875rem}.w-4{width:1rem}.w-5{width:1.25rem}.w-7{width:1.75rem}.w-8{width:2rem}.w-9{width:2.25rem}.w-\[420px\]{width:420px}.w-full{width:100%}.min-w-0{min-width:0px}.min-w-\[160px\]{min-width:160px}.max-w-5xl{max-width:64rem}.max-w-\[min\(82\%\,64ch\)\]{max-width:min(82%,64ch)}.max-w-full{max-width:100%}.max-w-sm{max-width:24rem}.flex-1{flex:1 1 0%}.shrink-0{flex-shrink:0}.rotate-180{--tw-rotate: 180deg;transform:translate(var(--tw-translate-x),var(--tw-translate-y)) rotate(var(--tw-rotate)) skew(var(--tw-skew-x)) skewY(var(--tw-skew-y)) scaleX(var(--tw-scale-x)) scaleY(var(--tw-scale-y))}.rotate-90{--tw-rotate: 90deg;transform:translate(var(--tw-translate-x),var(--tw-translate-y)) rotate(var(--tw-rotate)) skew(var(--tw-skew-x)) skewY(var(--tw-skew-y)) scaleX(var(--tw-scale-x)) scaleY(var(--tw-scale-y))}@keyframes bounce{0%,to{transform:translateY(-25%);animation-timing-function:cubic-bezier(.8,0,1,1)}50%{transform:none;animation-timing-function:cubic-bezier(0,0,.2,1)}}.animate-bounce{animation:bounce 1s infinite}@keyframes fade-in{0%{opacity:0;transform:translateY(8px)}to{opacity:1;transform:translateY(0)}}.animate-fade-in{animation:fade-in .2s ease-out}@keyframes pulse{50%{opacity:.5}}.animate-pulse{animation:pulse 2s cubic-bezier(.4,0,.6,1) infinite}.cursor-col-resize{cursor:col-resize}.cursor-default{cursor:default}.cursor-pointer{cursor:pointer}.select-none{-webkit-user-select:none;-moz-user-select:none;user-select:none}.select-text{-webkit-user-select:text;-moz-user-select:text;user-select:text}.resize-none{resize:none}.flex-col{flex-direction:column}.items-start{align-items:flex-start}.items-end{align-items:flex-end}.items-center{align-items:center}.justify-end{justify-content:flex-end}.justify-center{justify-content:center}.justify-between{justify-content:space-between}.gap-0\.5{gap:.125rem}.gap-1{gap:.25rem}.gap-1\.5{gap:.375rem}.gap-2{gap:.5rem}.gap-2\.5{gap:.625rem}.gap-3{gap:.75rem}.space-y-0\.5>:not([hidden])~:not([hidden]){--tw-space-y-reverse: 0;margin-top:calc(.125rem * calc(1 - var(--tw-space-y-reverse)));margin-bottom:calc(.125rem * var(--tw-space-y-reverse))}.space-y-1\.5>:not([hidden])~:not([hidden]){--tw-space-y-reverse: 0;margin-top:calc(.375rem * calc(1 - var(--tw-space-y-reverse)));margin-bottom:calc(.375rem * var(--tw-space-y-reverse))}.overflow-hidden{overflow:hidden}.overflow-y-auto{overflow-y:auto}.truncate{overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.whitespace-nowrap{white-space:nowrap}.whitespace-pre-wrap{white-space:pre-wrap}.break-words{overflow-wrap:break-word}.rounded{border-radius:.25rem}.rounded-2xl{border-radius:1rem}.rounded-\[12px\]{border-radius:12px}.rounded-\[6px\]{border-radius:6px}.rounded-full{border-radius:9999px}.rounded-lg{border-radius:20px}.rounded-md{border-radius:12px}.rounded-xl{border-radius:.75rem}.border{border-width:1px}.border-2{border-width:2px}.border-b{border-bottom-width:1px}.border-l{border-left-width:1px}.border-r{border-right-width:1px}.border-t{border-top-width:1px}.border-dashed{border-style:dashed}.border-\[\#e5e5e5\]{--tw-border-opacity: 1;border-color:rgb(229 229 229 / var(--tw-border-opacity, 1))}.border-border-light{border-color:#94a3b833}.border-primary{--tw-border-opacity: 1;border-color:rgb(99 102 241 / var(--tw-border-opacity, 1))}.border-primary\/20{border-color:#6366f133}.border-red-200{--tw-border-opacity: 1;border-color:rgb(254 202 202 / var(--tw-border-opacity, 1))}.bg-\[rgba\(0\,0\,0\,0\.031\)\]{background-color:#00000008}.bg-black\/30{background-color:#0000004d}.bg-content-secondary{--tw-bg-opacity: 1;background-color:rgb(100 116 139 / var(--tw-bg-opacity, 1))}.bg-danger{--tw-bg-opacity: 1;background-color:rgb(239 68 68 / var(--tw-bg-opacity, 1))}.bg-green-500{--tw-bg-opacity: 1;background-color:rgb(34 197 94 / var(--tw-bg-opacity, 1))}.bg-primary{--tw-bg-opacity: 1;background-color:rgb(99 102 241 / var(--tw-bg-opacity, 1))}.bg-primary-light{background-color:#6366f11a}.bg-red-100{--tw-bg-opacity: 1;background-color:rgb(254 226 226 / var(--tw-bg-opacity, 1))}.bg-red-50{--tw-bg-opacity: 1;background-color:rgb(254 242 242 / var(--tw-bg-opacity, 1))}.bg-red-500{--tw-bg-opacity: 1;background-color:rgb(239 68 68 / var(--tw-bg-opacity, 1))}.bg-success{--tw-bg-opacity: 1;background-color:rgb(16 185 129 / var(--tw-bg-opacity, 1))}.bg-surface-card{--tw-bg-opacity: 1;background-color:rgb(255 255 255 / var(--tw-bg-opacity, 1))}.bg-surface-card-alt{--tw-bg-opacity: 1;background-color:rgb(241 245 249 / var(--tw-bg-opacity, 1))}.bg-surface-hover{--tw-bg-opacity: 1;background-color:rgb(226 232 240 / var(--tw-bg-opacity, 1))}.bg-surface-main{--tw-bg-opacity: 1;background-color:rgb(248 250 252 / var(--tw-bg-opacity, 1))}.bg-transparent{background-color:transparent}.bg-warning{--tw-bg-opacity: 1;background-color:rgb(245 158 11 / var(--tw-bg-opacity, 1))}.bg-white{--tw-bg-opacity: 1;background-color:rgb(255 255 255 / var(--tw-bg-opacity, 1))}.p-2\.5{padding:.625rem}.px-1{padding-left:.25rem;padding-right:.25rem}.px-1\.5{padding-left:.375rem;padding-right:.375rem}.px-2{padding-left:.5rem;padding-right:.5rem}.px-3{padding-left:.75rem;padding-right:.75rem}.px-4{padding-left:1rem;padding-right:1rem}.px-5{padding-left:1.25rem;padding-right:1.25rem}.px-6{padding-left:1.5rem;padding-right:1.5rem}.py-0\.5{padding-top:.125rem;padding-bottom:.125rem}.py-1{padding-top:.25rem;padding-bottom:.25rem}.py-1\.5{padding-top:.375rem;padding-bottom:.375rem}.py-2{padding-top:.5rem;padding-bottom:.5rem}.py-2\.5{padding-top:.625rem;padding-bottom:.625rem}.py-3{padding-top:.75rem;padding-bottom:.75rem}.py-4{padding-top:1rem;padding-bottom:1rem}.py-6{padding-top:1.5rem;padding-bottom:1.5rem}.py-8{padding-top:2rem;padding-bottom:2rem}.pb-1{padding-bottom:.25rem}.pb-1\.5{padding-bottom:.375rem}.pb-2\.5{padding-bottom:.625rem}.pb-3{padding-bottom:.75rem}.pb-4{padding-bottom:1rem}.pl-3{padding-left:.75rem}.pr-2{padding-right:.5rem}.pt-2{padding-top:.5rem}.pt-3{padding-top:.75rem}.text-left{text-align:left}.text-center{text-align:center}.align-middle{vertical-align:middle}.font-display{font-family:Playfair Display,Georgia,serif}.font-mono{font-family:JetBrains Mono,Fira Code,monospace}.text-\[10px\]{font-size:10px}.text-\[11px\]{font-size:11px}.text-base{font-size:1rem;line-height:1.5rem}.text-lg{font-size:1.125rem;line-height:1.75rem}.text-sm{font-size:.875rem;line-height:1.25rem}.text-xs{font-size:.75rem;line-height:1rem}.font-bold{font-weight:700}.font-medium{font-weight:500}.font-semibold{font-weight:600}.uppercase{text-transform:uppercase}.italic{font-style:italic}.tabular-nums{--tw-numeric-spacing: tabular-nums;font-variant-numeric:var(--tw-ordinal) var(--tw-slashed-zero) var(--tw-numeric-figure) var(--tw-numeric-spacing) var(--tw-numeric-fraction)}.leading-normal{line-height:1.5}.leading-relaxed{line-height:1.625}.tracking-wide{letter-spacing:.025em}.tracking-wider{letter-spacing:.05em}.text-amber-500{--tw-text-opacity: 1;color:rgb(245 158 11 / var(--tw-text-opacity, 1))}.text-content-disabled{--tw-text-opacity: 1;color:rgb(148 163 184 / var(--tw-text-opacity, 1))}.text-content-primary{--tw-text-opacity: 1;color:rgb(30 41 59 / var(--tw-text-opacity, 1))}.text-content-secondary{--tw-text-opacity: 1;color:rgb(100 116 139 / var(--tw-text-opacity, 1))}.text-danger{--tw-text-opacity: 1;color:rgb(239 68 68 / var(--tw-text-opacity, 1))}.text-green-500{--tw-text-opacity: 1;color:rgb(34 197 94 / var(--tw-text-opacity, 1))}.text-primary{--tw-text-opacity: 1;color:rgb(99 102 241 / var(--tw-text-opacity, 1))}.text-warning{--tw-text-opacity: 1;color:rgb(245 158 11 / var(--tw-text-opacity, 1))}.text-white{--tw-text-opacity: 1;color:rgb(255 255 255 / var(--tw-text-opacity, 1))}.line-through{text-decoration-line:line-through}.placeholder-content-disabled::-moz-placeholder{--tw-placeholder-opacity: 1;color:rgb(148 163 184 / var(--tw-placeholder-opacity, 1))}.placeholder-content-disabled::placeholder{--tw-placeholder-opacity: 1;color:rgb(148 163 184 / var(--tw-placeholder-opacity, 1))}.accent-danger{accent-color:#EF4444}.opacity-75{opacity:.75}.shadow-2xl{--tw-shadow: 0 25px 50px -12px rgb(0 0 0 / .25);--tw-shadow-colored: 0 25px 50px -12px var(--tw-shadow-color);box-shadow:var(--tw-ring-offset-shadow, 0 0 #0000),var(--tw-ring-shadow, 0 0 #0000),var(--tw-shadow)}.shadow-lg{--tw-shadow: 0 10px 15px -3px rgb(0 0 0 / .1), 0 4px 6px -4px rgb(0 0 0 / .1);--tw-shadow-colored: 0 10px 15px -3px var(--tw-shadow-color), 0 4px 6px -4px var(--tw-shadow-color);box-shadow:var(--tw-ring-offset-shadow, 0 0 #0000),var(--tw-ring-shadow, 0 0 #0000),var(--tw-shadow)}.shadow-soft{--tw-shadow: 0 1px 2px rgba(0, 0, 0, .04), 0 1px 3px rgba(0, 0, 0, .06);--tw-shadow-colored: 0 1px 2px var(--tw-shadow-color), 0 1px 3px var(--tw-shadow-color);box-shadow:var(--tw-ring-offset-shadow, 0 0 #0000),var(--tw-ring-shadow, 0 0 #0000),var(--tw-shadow)}.outline-none{outline:2px solid transparent;outline-offset:2px}.filter{filter:var(--tw-blur) var(--tw-brightness) var(--tw-contrast) var(--tw-grayscale) var(--tw-hue-rotate) var(--tw-invert) var(--tw-saturate) var(--tw-sepia) var(--tw-drop-shadow)}.backdrop-blur-sm{--tw-backdrop-blur: blur(4px);-webkit-backdrop-filter:var(--tw-backdrop-blur) var(--tw-backdrop-brightness) var(--tw-backdrop-contrast) var(--tw-backdrop-grayscale) var(--tw-backdrop-hue-rotate) var(--tw-backdrop-invert) var(--tw-backdrop-opacity) var(--tw-backdrop-saturate) var(--tw-backdrop-sepia);backdrop-filter:var(--tw-backdrop-blur) var(--tw-backdrop-brightness) var(--tw-backdrop-contrast) var(--tw-backdrop-grayscale) var(--tw-backdrop-hue-rotate) var(--tw-backdrop-invert) var(--tw-backdrop-opacity) var(--tw-backdrop-saturate) var(--tw-backdrop-sepia)}.transition-all{transition-property:all;transition-timing-function:cubic-bezier(.4,0,.2,1);transition-duration:.15s}.transition-colors{transition-property:color,background-color,border-color,text-decoration-color,fill,stroke;transition-timing-function:cubic-bezier(.4,0,.2,1);transition-duration:.15s}.transition-transform{transition-property:transform;transition-timing-function:cubic-bezier(.4,0,.2,1);transition-duration:.15s}.duration-150{transition-duration:.15s}.duration-200{transition-duration:.2s}.duration-500{transition-duration:.5s}:root{--primary: #6366F1;--primary-hover: #4F46E5;--primary-light: rgba(99, 102, 241, .1);--secondary: #8B5CF6;--accent: #F59E0B;--success: #10B981;--warning: #F59E0B;--danger: #EF4444;--bg-main: #F8FAFC;--bg-card: #FFFFFF;--bg-card-alt: #F1F5F9;--bg-hover: #E2E8F0;--bg-input: #FFFFFF;--border-light: rgba(148, 163, 184, .2);--border-medium: rgba(148, 163, 184, .4);--text-primary: #1E293B;--text-secondary: #64748B;--text-disabled: #94A3B8}*{margin:0;padding:0;box-sizing:border-box}html{font-size:15px;-webkit-font-smoothing:antialiased;-moz-osx-font-smoothing:grayscale}body{font-family:Inter,Inter Fallback,system-ui,-apple-system,BlinkMacSystemFont,PingFang SC,Hiragino Sans GB,Microsoft YaHei,Helvetica Neue,Helvetica,Arial,sans-serif;font-feature-settings:"cv01","ss01","ss03";background:#f8fafc;color:#1e293b;min-height:100vh}pre code.hljs,.prose pre code{border-radius:6px;font-size:13px;line-height:1.7;font-family:JetBrains Mono,Fira Code,monospace;background:#f1f5f9;border:1px solid rgba(148,163,184,.2)}pre code.hljs{display:block;padding:8px 12px;overflow-x:auto}.prose code{font-family:JetBrains Mono,Fira Code,monospace;background:transparent;padding:0;border-radius:0;font-size:.85em;color:#6366f1;font-weight:500}.prose code:before,.prose code:after{content:""!important}.prose h1 code,.prose h2 code,.prose h3 code,.prose h4 code{font-size:inherit;color:inherit;font-weight:inherit}.prose sup{font-size:.75em;vertical-align:super;line-height:0}.prose pre{border-radius:6px;border:1px solid rgba(148,163,184,.2);background:#f1f5f9;margin:1em 0;overflow:hidden}.prose pre code{background:transparent;padding:0;border:none;font-size:13px;color:#1e293b}.prose table{width:auto;border-collapse:collapse;font-size:.875rem;margin:1em 0;border:1px solid rgba(148,163,184,.3)}.prose thead{background:#f1f5f9}.prose th,.prose th[align]{padding:.5rem .75rem;text-align:center!important;font-weight:600;color:#1e293b;border-bottom:1px solid rgba(148,163,184,.3);border-right:1px solid rgba(148,163,184,.3);font-size:.8125rem}.prose th:last-child{border-right:none}.prose td{padding:.5rem .75rem;border-bottom:1px solid rgba(148,163,184,.15);border-right:1px solid rgba(148,163,184,.15);color:#64748b}.prose td:last-child{border-right:none}.prose tbody tr:hover{background:#6366f10d}.prose tbody tr:last-child td{border-bottom:none}.prose blockquote{border-left:4px solid #6366F1;padding:.75em 1em;margin:1em 0;background:#6366f10d;color:#4f46e5;border-radius:0 6px 6px 0}.prose blockquote p{margin:0}.prose ul,.prose ol{padding-left:1.5em;margin:.5em 0;list-style-type:disc}.prose ol{list-style-type:decimal}.prose li{margin:.25em 0;color:#1e293b}.prose li::marker{color:#6366f1;font-size:.85em}.prose ul ul{list-style-type:circle}.prose ul ul ul{list-style-type:square}.prose a{color:#6366f1;text-decoration:underline;text-underline-offset:2px}.prose a:hover{color:#4f46e5}.prose hr{border:none;border-top:1px solid rgba(148,163,184,.2);margin:1.5em 0}.prose h1,.prose h2,.prose h3,.prose h4{color:#1e293b;font-weight:700;margin-top:1.5em;margin-bottom:.5em}.prose h1{font-size:1.5em;border-bottom:2px solid rgba(148,163,184,.2);padding-bottom:.3em}.prose h2{font-size:1.25em;border-bottom:1px solid rgba(148,163,184,.1);padding-bottom:.3em}.prose h3{font-size:1.125em}.prose img{max-width:100%;border-radius:6px;margin:1em 0;border:1px solid rgba(148,163,184,.2)}.prose{font-size:1rem;line-height:1.8;font-family:Inter,sans-serif;color:#1e293b}::-webkit-scrollbar{width:6px;height:6px}::-webkit-scrollbar-track{background:transparent}::-webkit-scrollbar-thumb{background:#cbd5e1;border-radius:3px}::-webkit-scrollbar-thumb:hover{background:#94a3b8}::-moz-selection{background:#6366f133;color:#1e293b}::selection{background:#6366f133;color:#1e293b}*:focus-visible{outline:2px solid #6366F1;outline-offset:2px}@keyframes pulse-scale{0%,to{transform:scale(1);opacity:1}50%{transform:scale(.5);opacity:.5}}.animate-pulse-scale{animation:pulse-scale 1.2s ease-in-out infinite}@keyframes blink{0%,to{opacity:1}50%{opacity:0}}.animate-blink{animation:blink .8s ease-in-out infinite}.hover\:bg-danger-hover:hover{--tw-bg-opacity: 1;background-color:rgb(220 38 38 / var(--tw-bg-opacity, 1))}.hover\:bg-primary-hover:hover{--tw-bg-opacity: 1;background-color:rgb(79 70 229 / var(--tw-bg-opacity, 1))}.hover\:bg-primary\/20:hover{background-color:#6366f133}.hover\:bg-red-200:hover{--tw-bg-opacity: 1;background-color:rgb(254 202 202 / var(--tw-bg-opacity, 1))}.hover\:bg-red-50:hover{--tw-bg-opacity: 1;background-color:rgb(254 242 242 / var(--tw-bg-opacity, 1))}.hover\:bg-surface-hover:hover{--tw-bg-opacity: 1;background-color:rgb(226 232 240 / var(--tw-bg-opacity, 1))}.hover\:text-content-primary:hover{--tw-text-opacity: 1;color:rgb(30 41 59 / var(--tw-text-opacity, 1))}.hover\:text-content-secondary:hover{--tw-text-opacity: 1;color:rgb(100 116 139 / var(--tw-text-opacity, 1))}.focus\:border-content-disabled:focus{--tw-border-opacity: 1;border-color:rgb(148 163 184 / var(--tw-border-opacity, 1))}.active\:bg-primary\/30:active{background-color:#6366f14d}.disabled\:cursor-not-allowed:disabled{cursor:not-allowed}.disabled\:opacity-40:disabled{opacity:.4}.disabled\:opacity-50:disabled{opacity:.5}@media(min-width:768px){.md\:left-5{left:1.25rem}.md\:right-5{right:1.25rem}.md\:px-10{padding-left:2.5rem;padding-right:2.5rem}.md\:px-5{padding-left:1.25rem;padding-right:1.25rem}}@media(min-width:1024px){.lg\:px-16{padding-left:4rem;padding-right:4rem}}