foliko 1.0.74 → 1.0.76

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 (238) hide show
  1. package/.agent/.shared/ui-ux-pro-max/data/charts.csv +26 -0
  2. package/.agent/.shared/ui-ux-pro-max/data/colors.csv +97 -0
  3. package/.agent/.shared/ui-ux-pro-max/data/icons.csv +101 -0
  4. package/.agent/.shared/ui-ux-pro-max/data/landing.csv +31 -0
  5. package/.agent/.shared/ui-ux-pro-max/data/products.csv +97 -0
  6. package/.agent/.shared/ui-ux-pro-max/data/prompts.csv +24 -0
  7. package/.agent/.shared/ui-ux-pro-max/data/react-performance.csv +45 -0
  8. package/.agent/.shared/ui-ux-pro-max/data/stacks/flutter.csv +53 -0
  9. package/.agent/.shared/ui-ux-pro-max/data/stacks/html-tailwind.csv +56 -0
  10. package/.agent/.shared/ui-ux-pro-max/data/stacks/jetpack-compose.csv +53 -0
  11. package/.agent/.shared/ui-ux-pro-max/data/stacks/nextjs.csv +53 -0
  12. package/.agent/.shared/ui-ux-pro-max/data/stacks/nuxt-ui.csv +51 -0
  13. package/.agent/.shared/ui-ux-pro-max/data/stacks/nuxtjs.csv +59 -0
  14. package/.agent/.shared/ui-ux-pro-max/data/stacks/react-native.csv +52 -0
  15. package/.agent/.shared/ui-ux-pro-max/data/stacks/react.csv +54 -0
  16. package/.agent/.shared/ui-ux-pro-max/data/stacks/shadcn.csv +61 -0
  17. package/.agent/.shared/ui-ux-pro-max/data/stacks/svelte.csv +54 -0
  18. package/.agent/.shared/ui-ux-pro-max/data/stacks/swiftui.csv +51 -0
  19. package/.agent/.shared/ui-ux-pro-max/data/stacks/vue.csv +50 -0
  20. package/.agent/.shared/ui-ux-pro-max/data/styles.csv +59 -0
  21. package/.agent/.shared/ui-ux-pro-max/data/typography.csv +58 -0
  22. package/.agent/.shared/ui-ux-pro-max/data/ui-reasoning.csv +101 -0
  23. package/.agent/.shared/ui-ux-pro-max/data/ux-guidelines.csv +100 -0
  24. package/.agent/.shared/ui-ux-pro-max/data/web-interface.csv +31 -0
  25. package/.agent/.shared/ui-ux-pro-max/scripts/__pycache__/core.cpython-313.pyc +0 -0
  26. package/.agent/.shared/ui-ux-pro-max/scripts/__pycache__/design_system.cpython-313.pyc +0 -0
  27. package/.agent/.shared/ui-ux-pro-max/scripts/core.py +258 -0
  28. package/.agent/.shared/ui-ux-pro-max/scripts/design_system.py +1067 -0
  29. package/.agent/.shared/ui-ux-pro-max/scripts/search.py +106 -0
  30. package/.agent/ARCHITECTURE.md +288 -0
  31. package/.agent/agents/ambient-agent.md +57 -0
  32. package/.agent/agents/debugger.md +55 -0
  33. package/.agent/agents/email-assistant.md +49 -0
  34. package/.agent/agents/file-manager.md +42 -0
  35. package/.agent/agents/python-developer.md +60 -0
  36. package/.agent/agents/scheduler.md +59 -0
  37. package/.agent/agents/web-developer.md +45 -0
  38. package/.agent/data/default.json +29 -0
  39. package/.agent/data/plugins-state.json +255 -0
  40. package/.agent/mcp_config.json +4 -0
  41. package/.agent/mcp_config_updated.json +12 -0
  42. package/.agent/plugins.json +5 -0
  43. package/.agent/rules/GEMINI.md +273 -0
  44. package/.agent/rules/allow-rule.md +77 -0
  45. package/.agent/rules/log-rule.md +83 -0
  46. package/.agent/rules/security-rule.md +93 -0
  47. package/.agent/scripts/auto_preview.py +148 -0
  48. package/.agent/scripts/checklist.py +217 -0
  49. package/.agent/scripts/session_manager.py +120 -0
  50. package/.agent/scripts/verify_all.py +327 -0
  51. package/.agent/skills/api-patterns/SKILL.md +81 -0
  52. package/.agent/skills/api-patterns/api-style.md +42 -0
  53. package/.agent/skills/api-patterns/auth.md +24 -0
  54. package/.agent/skills/api-patterns/documentation.md +26 -0
  55. package/.agent/skills/api-patterns/graphql.md +41 -0
  56. package/.agent/skills/api-patterns/rate-limiting.md +31 -0
  57. package/.agent/skills/api-patterns/response.md +37 -0
  58. package/.agent/skills/api-patterns/rest.md +40 -0
  59. package/.agent/skills/api-patterns/scripts/api_validator.py +211 -0
  60. package/.agent/skills/api-patterns/security-testing.md +122 -0
  61. package/.agent/skills/api-patterns/trpc.md +41 -0
  62. package/.agent/skills/api-patterns/versioning.md +22 -0
  63. package/.agent/skills/app-builder/SKILL.md +75 -0
  64. package/.agent/skills/app-builder/agent-coordination.md +71 -0
  65. package/.agent/skills/app-builder/feature-building.md +53 -0
  66. package/.agent/skills/app-builder/project-detection.md +34 -0
  67. package/.agent/skills/app-builder/scaffolding.md +118 -0
  68. package/.agent/skills/app-builder/tech-stack.md +40 -0
  69. package/.agent/skills/app-builder/templates/SKILL.md +39 -0
  70. package/.agent/skills/app-builder/templates/astro-static/TEMPLATE.md +76 -0
  71. package/.agent/skills/app-builder/templates/chrome-extension/TEMPLATE.md +92 -0
  72. package/.agent/skills/app-builder/templates/cli-tool/TEMPLATE.md +88 -0
  73. package/.agent/skills/app-builder/templates/electron-desktop/TEMPLATE.md +88 -0
  74. package/.agent/skills/app-builder/templates/express-api/TEMPLATE.md +83 -0
  75. package/.agent/skills/app-builder/templates/flutter-app/TEMPLATE.md +90 -0
  76. package/.agent/skills/app-builder/templates/monorepo-turborepo/TEMPLATE.md +90 -0
  77. package/.agent/skills/app-builder/templates/nextjs-fullstack/TEMPLATE.md +122 -0
  78. package/.agent/skills/app-builder/templates/nextjs-saas/TEMPLATE.md +122 -0
  79. package/.agent/skills/app-builder/templates/nextjs-static/TEMPLATE.md +169 -0
  80. package/.agent/skills/app-builder/templates/nuxt-app/TEMPLATE.md +134 -0
  81. package/.agent/skills/app-builder/templates/python-fastapi/TEMPLATE.md +83 -0
  82. package/.agent/skills/app-builder/templates/react-native-app/TEMPLATE.md +119 -0
  83. package/.agent/skills/architecture/SKILL.md +55 -0
  84. package/.agent/skills/architecture/context-discovery.md +43 -0
  85. package/.agent/skills/architecture/examples.md +94 -0
  86. package/.agent/skills/architecture/pattern-selection.md +68 -0
  87. package/.agent/skills/architecture/patterns-reference.md +50 -0
  88. package/.agent/skills/architecture/trade-off-analysis.md +77 -0
  89. package/.agent/skills/clean-code/SKILL.md +201 -0
  90. package/.agent/skills/doc.md +177 -0
  91. package/.agent/skills/frontend-design/SKILL.md +418 -0
  92. package/.agent/skills/frontend-design/animation-guide.md +331 -0
  93. package/.agent/skills/frontend-design/color-system.md +311 -0
  94. package/.agent/skills/frontend-design/decision-trees.md +418 -0
  95. package/.agent/skills/frontend-design/motion-graphics.md +306 -0
  96. package/.agent/skills/frontend-design/scripts/accessibility_checker.py +183 -0
  97. package/.agent/skills/frontend-design/scripts/ux_audit.py +722 -0
  98. package/.agent/skills/frontend-design/typography-system.md +345 -0
  99. package/.agent/skills/frontend-design/ux-psychology.md +1116 -0
  100. package/.agent/skills/frontend-design/visual-effects.md +383 -0
  101. package/.agent/skills/i18n-localization/SKILL.md +154 -0
  102. package/.agent/skills/i18n-localization/scripts/i18n_checker.py +241 -0
  103. package/.agent/skills/mcp-builder/SKILL.md +176 -0
  104. package/.agent/skills/web-design-guidelines/SKILL.md +57 -0
  105. package/.agent/workflows/brainstorm.md +113 -0
  106. package/.agent/workflows/create.md +59 -0
  107. package/.agent/workflows/debug.md +103 -0
  108. package/.agent/workflows/deploy.md +176 -0
  109. package/.agent/workflows/enhance.md +63 -0
  110. package/.agent/workflows/orchestrate.md +237 -0
  111. package/.agent/workflows/plan.md +89 -0
  112. package/.agent/workflows/preview.md +81 -0
  113. package/.agent/workflows/simple-test.md +42 -0
  114. package/.agent/workflows/status.md +86 -0
  115. package/.agent/workflows/structured-orchestrate.md +180 -0
  116. package/.agent/workflows/test.md +144 -0
  117. package/.agent/workflows/ui-ux-pro-max.md +296 -0
  118. package/.claude/settings.local.json +11 -1
  119. package/.editorconfig +56 -0
  120. package/.husky/pre-commit +4 -0
  121. package/.lintstagedrc +7 -0
  122. package/.prettierignore +29 -0
  123. package/.prettierrc +11 -0
  124. package/CLAUDE.md +2 -0
  125. package/README.md +64 -55
  126. package/SPEC.md +102 -61
  127. package/cli/bin/foliko.js +11 -11
  128. package/cli/src/commands/chat.js +143 -141
  129. package/cli/src/commands/list.js +93 -90
  130. package/cli/src/index.js +75 -75
  131. package/cli/src/ui/chat-ui.js +201 -199
  132. package/cli/src/utils/ansi.js +40 -40
  133. package/cli/src/utils/markdown.js +292 -296
  134. package/docker-compose.yml +1 -1
  135. package/docs/ai-sdk-optimization.md +655 -643
  136. package/docs/features.md +80 -80
  137. package/docs/quick-reference.md +49 -46
  138. package/docs/user-manual.md +411 -380
  139. package/examples/ambient-example.js +194 -196
  140. package/examples/basic.js +50 -45
  141. package/examples/bootstrap.js +121 -112
  142. package/examples/mcp-example.js +19 -16
  143. package/examples/skill-example.js +20 -20
  144. package/examples/test-chat.js +137 -135
  145. package/examples/test-mcp.js +85 -79
  146. package/examples/test-reload.js +59 -61
  147. package/examples/test-telegram.js +50 -50
  148. package/examples/test-tg-bot.js +45 -42
  149. package/examples/test-tg-simple.js +47 -46
  150. package/examples/test-tg.js +62 -62
  151. package/examples/test-think.js +43 -37
  152. package/examples/test-web-plugin.js +103 -98
  153. package/examples/test-weixin-feishu.js +103 -100
  154. package/examples/workflow.js +158 -158
  155. package/package.json +37 -3
  156. package/plugins/ai-plugin.js +102 -100
  157. package/plugins/ambient-agent/EventWatcher.js +113 -0
  158. package/plugins/ambient-agent/ExplorerLoop.js +640 -0
  159. package/plugins/ambient-agent/GoalManager.js +197 -0
  160. package/plugins/ambient-agent/Reflector.js +95 -0
  161. package/plugins/ambient-agent/StateStore.js +90 -0
  162. package/plugins/ambient-agent/constants.js +101 -0
  163. package/plugins/ambient-agent/index.js +579 -0
  164. package/plugins/audit-plugin.js +187 -187
  165. package/plugins/default-plugins.js +662 -649
  166. package/plugins/email/constants.js +64 -0
  167. package/plugins/email/handlers.js +461 -0
  168. package/plugins/email/index.js +278 -0
  169. package/plugins/email/monitor.js +269 -0
  170. package/plugins/email/parser.js +138 -0
  171. package/plugins/email/reply.js +151 -0
  172. package/plugins/email/utils.js +124 -0
  173. package/plugins/feishu-plugin.js +481 -477
  174. package/plugins/file-system-plugin.js +826 -476
  175. package/plugins/install-plugin.js +199 -197
  176. package/plugins/python-executor-plugin.js +367 -365
  177. package/plugins/python-plugin-loader.js +481 -479
  178. package/plugins/rules-plugin.js +294 -292
  179. package/plugins/scheduler-plugin.js +691 -689
  180. package/plugins/session-plugin.js +369 -367
  181. package/plugins/shell-executor-plugin.js +197 -197
  182. package/plugins/storage-plugin.js +240 -238
  183. package/plugins/subagent-plugin.js +845 -785
  184. package/plugins/telegram-plugin.js +482 -475
  185. package/plugins/think-plugin.js +345 -343
  186. package/plugins/tools-plugin.js +196 -194
  187. package/plugins/web-plugin.js +606 -604
  188. package/plugins/weixin-plugin.js +545 -538
  189. package/reports/system-health-report-20260401.md +79 -0
  190. package/skills/ambient-agent/SKILL.md +49 -39
  191. package/skills/foliko-dev/AGENTS.md +64 -61
  192. package/skills/foliko-dev/SKILL.md +125 -119
  193. package/skills/mcp-usage/SKILL.md +19 -17
  194. package/skills/python-plugin-dev/SKILL.md +16 -15
  195. package/skills/skill-guide/SKILL.md +12 -12
  196. package/skills/subagent-guide/SKILL.md +237 -0
  197. package/skills/workflow-guide/SKILL.md +90 -45
  198. package/skills/workflow-troubleshooting/DEBUGGING.md +36 -21
  199. package/skills/workflow-troubleshooting/SKILL.md +156 -79
  200. package/src/capabilities/index.js +11 -11
  201. package/src/capabilities/skill-manager.js +609 -595
  202. package/src/capabilities/workflow-engine.js +1109 -1195
  203. package/src/core/agent-chat.js +882 -735
  204. package/src/core/agent.js +892 -688
  205. package/src/core/framework.js +465 -431
  206. package/src/core/index.js +19 -19
  207. package/src/core/plugin-base.js +219 -219
  208. package/src/core/plugin-manager.js +863 -767
  209. package/src/core/provider.js +114 -111
  210. package/src/core/sub-agent-config.js +264 -0
  211. package/src/core/system-prompt-builder.js +120 -0
  212. package/src/core/tool-registry.js +517 -134
  213. package/src/core/tool-router.js +297 -216
  214. package/src/executors/executor-base.js +12 -12
  215. package/src/executors/mcp-executor.js +741 -729
  216. package/src/index.js +25 -37
  217. package/src/utils/circuit-breaker.js +301 -0
  218. package/src/utils/error-boundary.js +363 -0
  219. package/src/utils/error.js +374 -0
  220. package/src/utils/event-emitter.js +97 -97
  221. package/src/utils/id.js +133 -0
  222. package/src/utils/index.js +217 -3
  223. package/src/utils/logger.js +181 -0
  224. package/src/utils/plugin-helpers.js +90 -0
  225. package/src/utils/retry.js +122 -0
  226. package/src/utils/sandbox.js +292 -0
  227. package/test/tool-registry-validation.test.js +218 -0
  228. package/test_report.md +70 -0
  229. package/website/docs/api.html +169 -107
  230. package/website/docs/configuration.html +296 -144
  231. package/website/docs/plugin-development.html +154 -85
  232. package/website/docs/project-structure.html +110 -109
  233. package/website/docs/skill-development.html +117 -61
  234. package/website/index.html +209 -205
  235. package/website/script.js +136 -133
  236. package/website/styles.css +1 -1
  237. package/plugins/ambient-agent-plugin.js +0 -1565
  238. package/plugins/email.js +0 -1142
@@ -1,296 +1,292 @@
1
- /**
2
- * 简单的流式 Markdown 渲染器
3
- * 支持: **粗体**, *斜体*, `代码`, ```代码块```, # 标题, 列表, 引用, <think>思考
4
- */
5
-
6
- const {
7
- BOLD, DIM, CLEAR, RED, GREEN, YELLOW, CYAN, BLUE, MAGENTA, WHITE
8
- } = require('./ansi')
9
-
10
- // Markdown 标记对应的 ANSI 样式
11
- const STYLES = {
12
- bold: { prefix: BOLD, suffix: CLEAR },
13
- italic: { prefix: DIM, suffix: CLEAR },
14
- code: { prefix: CYAN, suffix: CLEAR }, // dim = 暗色
15
- codeBlock: { prefix: YELLOW, suffix: CLEAR }, // yellow = 黄色
16
- h1: { prefix: BOLD, suffix: CLEAR }, // bold white
17
- h2: { prefix: BOLD, suffix: CLEAR }, // bold white
18
- h3: { prefix: BOLD, suffix: CLEAR },
19
- link: { prefix: '\x1B[34m\x1B[4m', suffix: CLEAR }, // blue underline
20
- list: { prefix: DIM, suffix: CLEAR }, // dim = 暗色
21
- blockquote: { prefix: DIM, suffix: CLEAR }, // dim = 暗色
22
- think: { prefix: `\x1B[38;5;240m`, suffix: CLEAR }, // yellow = 黄色
23
- }
24
-
25
- /**
26
- * 验证并修复UTF-16代理对
27
- */
28
- function fixSurrogates(text) {
29
- if (!text) return ''
30
- const result = []
31
- let i = 0
32
- while (i < text.length) {
33
- const char = text[i]
34
- const code = text.charCodeAt(i)
35
- // 高代理
36
- if (code >= 0xD800 && code <= 0xDBFF) {
37
- const nextCode = text.charCodeAt(i + 1)
38
- // 如果有低代理,配对
39
- if (nextCode >= 0xDC00 && nextCode <= 0xDFFF) {
40
- result.push(char + text[i + 1])
41
- i += 2
42
- continue
43
- }
44
- // 孤立的高代理,替换为方框
45
- result.push('\uFFFD')
46
- i += 1
47
- continue
48
- }
49
- // 低代理(不应该单独出现)
50
- if (code >= 0xDC00 && code <= 0xDFFF) {
51
- result.push('\uFFFD')
52
- i += 1
53
- continue
54
- }
55
- result.push(char)
56
- i++
57
- }
58
- return result.join('')
59
- }
60
-
61
- /**
62
- * 渲染单行 markdown(流式友好,简单处理)
63
- */
64
- function renderInline(text) {
65
- if (!text) return ''
66
-
67
- // 修复不完整的代理对
68
- const fixed = fixSurrogates(text)
69
- let result = fixed
70
-
71
-
72
- // 处理行内代码 `code`
73
- result = result.replace(/`([^`]+)`/g, (match, code) => {
74
- return `${STYLES.code.prefix}${code}${STYLES.code.suffix}`
75
- })
76
-
77
- // 处理 **粗体**
78
- result = result.replace(/\*\*([^*]+)\*\*/g, (match, bold) => {
79
- return `${STYLES.bold.prefix}${bold}${STYLES.bold.suffix}`
80
- })
81
-
82
- // 处理 *斜体*(不匹配已处理的粗体内部)
83
- result = result.replace(/(?<!\*)\*([^*\n]+)\*(?!\*)/g, (match, italic) => {
84
- return `${STYLES.italic.prefix}${italic}${STYLES.italic.suffix}`
85
- })
86
-
87
- // 处理 ~~删除线~~ (简单处理)
88
- result = result.replace(/~~([^~]+)~~/g, (match, strikethrough) => {
89
- return `${DIM}${strikethrough}${CLEAR}`
90
- })
91
-
92
- // 处理行内链接 [text](url) -> text
93
- result = result.replace(/\[([^\]]+)\]\([^)]+\)/g, (match, text) => {
94
- return `${STYLES.link.prefix}${text}${STYLES.link.suffix}`
95
- })
96
-
97
- return result
98
- }
99
-
100
- /**
101
- * 渲染包含思考标签的行
102
- */
103
- function renderThink(line, state = { inThink: false }) {
104
- // <think>思考内容</think> -> 思考内容用暗色显示
105
- let result = ''
106
-
107
- let i = 0
108
- let lastEnd = 0
109
-
110
- // 如果在思考块中但这行没有开始标签,在行首添加黄色前缀
111
- if (state.inThink && !line.includes('<think>')) {
112
- result += STYLES.think.prefix
113
- }
114
-
115
- while (i < line.length) {
116
- // 检查 <think> (7个字符)
117
- if (line.substring(i, i + 7) === '<think>') {
118
- if (i > lastEnd) {
119
- result += renderInline(line.substring(lastEnd, i))
120
- }
121
- result += STYLES.think.prefix + '<think>'
122
- i += 7
123
- lastEnd = i
124
- state.inThink = true
125
- // 如果这行只有<think>标签,不要在这里添加CLEAR
126
- // 后续行会继续使用黄色
127
- if (i >= line.length) {
128
- // 没有更多内容,return时不添加CLEAR,保持黄色状态
129
- return result
130
- }
131
- continue
132
- }
133
-
134
- // 检查</think> (8个字符)
135
- if (line.substring(i, i + 8) === '</think>') {
136
- if (i > lastEnd) {
137
- result += line.substring(lastEnd, i) + STYLES.think.suffix
138
- }
139
- result += '</think>' + CLEAR
140
- i += 8
141
- lastEnd = i
142
- state.inThink = false
143
- continue
144
- }
145
-
146
- i++
147
- }
148
-
149
- if (lastEnd < line.length) {
150
- result += renderInline(line.substring(lastEnd))
151
- }
152
-
153
- return result
154
- }
155
-
156
- /**
157
- * 渲染一行 markdown(处理标题、列表等)
158
- * state: { inThink: boolean, inCodeBlock: boolean } - 状态,会被修改
159
- */
160
- function renderLine(line, state = { inThink: false, inCodeBlock: false }) {
161
- // 思考标签 - 保留首尾换行符
162
- const leadingNL = line.match(/^\n*/)[0]
163
- const trailingNL = line.match(/\n*$/)[0]
164
- const trimmed = line.trim()
165
-
166
- // 代码块处理
167
- if (trimmed.startsWith('```')) {
168
- if (state.inCodeBlock) {
169
- // 结束代码块
170
- state.inCodeBlock = false
171
- return `\`\`\`${trimmed.slice(3) ? ' ' + trimmed.slice(3) : ''}${STYLES.codeBlock.suffix}`
172
- } else {
173
- // 开始代码块
174
- state.inCodeBlock = true
175
- const lang = trimmed.slice(3).trim()
176
- return `${STYLES.codeBlock.prefix}\`\`\`${lang ? ' ' + lang : ''}`
177
- }
178
- }
179
-
180
- // 如果在代码块中,整行用代码样式
181
- if (state.inCodeBlock) {
182
- return `${STYLES.codeBlock.prefix}${line}${STYLES.codeBlock.suffix}`
183
- }
184
-
185
- // 如果在思考块中或者有思考标签
186
- if (state.inThink || trimmed.includes('<think>') || trimmed.includes('</think>')) {
187
- const result = leadingNL + renderThink(trimmed, state) + trailingNL
188
- return result
189
- }
190
-
191
- // 标题
192
- if (line.startsWith('### ')) {
193
- return `${STYLES.h3.prefix}${renderInline(line.substring(4))}${STYLES.h3.suffix}`
194
- }
195
- if (line.startsWith('## ')) {
196
- return `${STYLES.h2.prefix}${renderInline(line.substring(3))}${STYLES.h2.suffix}`
197
- }
198
- if (line.startsWith('# ')) {
199
- return `${STYLES.h1.prefix}${renderInline(line.substring(2))}${STYLES.h1.suffix}`
200
- }
201
-
202
- // 无序列表
203
- if (line.match(/^[-*+] /)) {
204
- return line.replace(/^([-*+] )(.*)/, (match, bullet, content) => {
205
- return `${STYLES.list.prefix}${bullet}${CLEAR}${renderInline(content)}`
206
- })
207
- }
208
-
209
- // 有序列表
210
- if (line.match(/^\d+\. /)) {
211
- return line.replace(/^(\d+\. )(.*)/, (match, bullet, content) => {
212
- return `${STYLES.list.prefix}${bullet}${CLEAR}${renderInline(content)}`
213
- })
214
- }
215
-
216
- // 引用
217
- if (line.startsWith('> ')) {
218
- return line.replace(/^> (.*)/, (match, content) => {
219
- return `${STYLES.blockquote.prefix}| ${renderInline(content)}${STYLES.blockquote.suffix}`
220
- })
221
- }
222
-
223
- return renderInline(line)
224
- }
225
-
226
- /**
227
- * 渲染完整的 markdown 文本
228
- */
229
- function render(text) {
230
- if (!text) return ''
231
-
232
- const lines = text.split('\n')
233
- const rendered = []
234
-
235
- let inCodeBlock = false
236
-
237
- for (const line of lines) {
238
- // 代码块
239
- if (line.startsWith('```')) {
240
- if (inCodeBlock) {
241
- rendered.push(`\`\`\`${STYLES.codeBlock.suffix}`)
242
- inCodeBlock = false
243
- } else {
244
- const lang = line.substring(3).trim() || ''
245
- rendered.push(`${STYLES.codeBlock.prefix}\`\`\`${lang ? ' ' + lang : ''}`)
246
- inCodeBlock = true
247
- }
248
- continue
249
- }
250
-
251
- if (inCodeBlock) {
252
- rendered.push(`${STYLES.codeBlock.prefix}${line}${STYLES.codeBlock.suffix}`)
253
- } else {
254
- rendered.push(renderLine(line))
255
- }
256
- }
257
-
258
- return rendered.join('\n')
259
- }
260
-
261
- /**
262
- * 流式渲染 - 每次返回一个完整的行
263
- * 返回 { done: boolean, line: string }
264
- */
265
- function* streamRender(text) {
266
- if (!text) return
267
-
268
- const lines = text.split('\n')
269
- let inCodeBlock = false
270
-
271
- for (const line of lines) {
272
- // 代码块
273
- if (line.startsWith('```')) {
274
- if (inCodeBlock) {
275
- yield { done: false, line: `${STYLES.codeBlock.suffix}` }
276
- inCodeBlock = false
277
- } else {
278
- const lang = line.substring(3).trim() || ''
279
- yield { done: false, line: `${STYLES.codeBlock.prefix}\`\`\`${lang ? ' ' + lang : ''}` }
280
- inCodeBlock = true
281
- }
282
- continue
283
- }
284
-
285
-
286
- if (inCodeBlock) {
287
- yield { done: false, line: `${STYLES.codeBlock.prefix}${line}${STYLES.codeBlock.suffix}` }
288
- } else {
289
- yield { done: false, line: renderLine(line) }
290
- }
291
- }
292
-
293
- yield { done: true }
294
- }
295
-
296
- module.exports = { render, renderInline, renderLine, streamRender, STYLES }
1
+ /**
2
+ * 简单的流式 Markdown 渲染器
3
+ * 支持: **粗体**, *斜体*, `代码`, ```代码块```, # 标题, 列表, 引用, <think>思考
4
+ */
5
+
6
+ const { BOLD, DIM, CLEAR, RED, GREEN, YELLOW, CYAN, BLUE, MAGENTA, WHITE } = require('./ansi');
7
+
8
+ // Markdown 标记对应的 ANSI 样式
9
+ const STYLES = {
10
+ bold: { prefix: BOLD, suffix: CLEAR },
11
+ italic: { prefix: DIM, suffix: CLEAR },
12
+ code: { prefix: CYAN, suffix: CLEAR }, // dim = 暗色
13
+ codeBlock: { prefix: YELLOW, suffix: CLEAR }, // yellow = 黄色
14
+ h1: { prefix: BOLD, suffix: CLEAR }, // bold white
15
+ h2: { prefix: BOLD, suffix: CLEAR }, // bold white
16
+ h3: { prefix: BOLD, suffix: CLEAR },
17
+ link: { prefix: '\x1B[34m\x1B[4m', suffix: CLEAR }, // blue underline
18
+ list: { prefix: DIM, suffix: CLEAR }, // dim = 暗色
19
+ blockquote: { prefix: DIM, suffix: CLEAR }, // dim = 暗色
20
+ think: { prefix: `\x1B[38;5;240m`, suffix: CLEAR }, // yellow = 黄色
21
+ };
22
+
23
+ /**
24
+ * 验证并修复UTF-16代理对
25
+ */
26
+ function fixSurrogates(text) {
27
+ if (!text) return '';
28
+ const result = [];
29
+ let i = 0;
30
+ while (i < text.length) {
31
+ const char = text[i];
32
+ const code = text.charCodeAt(i);
33
+ // 高代理
34
+ if (code >= 0xd800 && code <= 0xdbff) {
35
+ const nextCode = text.charCodeAt(i + 1);
36
+ // 如果有低代理,配对
37
+ if (nextCode >= 0xdc00 && nextCode <= 0xdfff) {
38
+ result.push(char + text[i + 1]);
39
+ i += 2;
40
+ continue;
41
+ }
42
+ // 孤立的高代理,替换为方框
43
+ result.push('\uFFFD');
44
+ i += 1;
45
+ continue;
46
+ }
47
+ // 低代理(不应该单独出现)
48
+ if (code >= 0xdc00 && code <= 0xdfff) {
49
+ result.push('\uFFFD');
50
+ i += 1;
51
+ continue;
52
+ }
53
+ result.push(char);
54
+ i++;
55
+ }
56
+ return result.join('');
57
+ }
58
+
59
+ /**
60
+ * 渲染单行 markdown(流式友好,简单处理)
61
+ */
62
+ function renderInline(text) {
63
+ if (!text) return '';
64
+
65
+ // 修复不完整的代理对
66
+ const fixed = fixSurrogates(text);
67
+ let result = fixed;
68
+
69
+ // 处理行内代码 `code`
70
+ result = result.replace(/`([^`]+)`/g, (match, code) => {
71
+ return `${STYLES.code.prefix}${code}${STYLES.code.suffix}`;
72
+ });
73
+
74
+ // 处理 **粗体**
75
+ result = result.replace(/\*\*([^*]+)\*\*/g, (match, bold) => {
76
+ return `${STYLES.bold.prefix}${bold}${STYLES.bold.suffix}`;
77
+ });
78
+
79
+ // 处理 *斜体*(不匹配已处理的粗体内部)
80
+ result = result.replace(/(?<!\*)\*([^*\n]+)\*(?!\*)/g, (match, italic) => {
81
+ return `${STYLES.italic.prefix}${italic}${STYLES.italic.suffix}`;
82
+ });
83
+
84
+ // 处理 ~~删除线~~ (简单处理)
85
+ result = result.replace(/~~([^~]+)~~/g, (match, strikethrough) => {
86
+ return `${DIM}${strikethrough}${CLEAR}`;
87
+ });
88
+
89
+ // 处理行内链接 [text](url) -> text
90
+ result = result.replace(/\[([^\]]+)\]\([^)]+\)/g, (match, text) => {
91
+ return `${STYLES.link.prefix}${text}${STYLES.link.suffix}`;
92
+ });
93
+
94
+ return result;
95
+ }
96
+
97
+ /**
98
+ * 渲染包含思考标签的行
99
+ */
100
+ function renderThink(line, state = { inThink: false }) {
101
+ // <think>思考内容</think> -> 思考内容用暗色显示
102
+ let result = '';
103
+
104
+ let i = 0;
105
+ let lastEnd = 0;
106
+
107
+ // 如果在思考块中但这行没有开始标签,在行首添加黄色前缀
108
+ if (state.inThink && !line.includes('<think>')) {
109
+ result += STYLES.think.prefix;
110
+ }
111
+
112
+ while (i < line.length) {
113
+ // 检查 <think> (7个字符)
114
+ if (line.substring(i, i + 7) === '<think>') {
115
+ if (i > lastEnd) {
116
+ result += renderInline(line.substring(lastEnd, i));
117
+ }
118
+ result += STYLES.think.prefix + '<think>';
119
+ i += 7;
120
+ lastEnd = i;
121
+ state.inThink = true;
122
+ // 如果这行只有<think>标签,不要在这里添加CLEAR
123
+ // 后续行会继续使用黄色
124
+ if (i >= line.length) {
125
+ // 没有更多内容,return时不添加CLEAR,保持黄色状态
126
+ return result;
127
+ }
128
+ continue;
129
+ }
130
+
131
+ // 检查</think> (8个字符)
132
+ if (line.substring(i, i + 8) === '</think>') {
133
+ if (i > lastEnd) {
134
+ result += line.substring(lastEnd, i) + STYLES.think.suffix;
135
+ }
136
+ result += '</think>' + CLEAR;
137
+ i += 8;
138
+ lastEnd = i;
139
+ state.inThink = false;
140
+ continue;
141
+ }
142
+
143
+ i++;
144
+ }
145
+
146
+ if (lastEnd < line.length) {
147
+ result += renderInline(line.substring(lastEnd));
148
+ }
149
+
150
+ return result;
151
+ }
152
+
153
+ /**
154
+ * 渲染一行 markdown(处理标题、列表等)
155
+ * state: { inThink: boolean, inCodeBlock: boolean } - 状态,会被修改
156
+ */
157
+ function renderLine(line, state = { inThink: false, inCodeBlock: false }) {
158
+ // 思考标签 - 保留首尾换行符
159
+ const leadingNL = line.match(/^\n*/)[0];
160
+ const trailingNL = line.match(/\n*$/)[0];
161
+ const trimmed = line.trim();
162
+
163
+ // 代码块处理
164
+ if (trimmed.startsWith('```')) {
165
+ if (state.inCodeBlock) {
166
+ // 结束代码块
167
+ state.inCodeBlock = false;
168
+ return `\`\`\`${trimmed.slice(3) ? ' ' + trimmed.slice(3) : ''}${STYLES.codeBlock.suffix}`;
169
+ } else {
170
+ // 开始代码块
171
+ state.inCodeBlock = true;
172
+ const lang = trimmed.slice(3).trim();
173
+ return `${STYLES.codeBlock.prefix}\`\`\`${lang ? ' ' + lang : ''}`;
174
+ }
175
+ }
176
+
177
+ // 如果在代码块中,整行用代码样式
178
+ if (state.inCodeBlock) {
179
+ return `${STYLES.codeBlock.prefix}${line}${STYLES.codeBlock.suffix}`;
180
+ }
181
+
182
+ // 如果在思考块中或者有思考标签
183
+ if (state.inThink || trimmed.includes('<think>') || trimmed.includes('</think>')) {
184
+ const result = leadingNL + renderThink(trimmed, state) + trailingNL;
185
+ return result;
186
+ }
187
+
188
+ // 标题
189
+ if (line.startsWith('### ')) {
190
+ return `${STYLES.h3.prefix}${renderInline(line.substring(4))}${STYLES.h3.suffix}`;
191
+ }
192
+ if (line.startsWith('## ')) {
193
+ return `${STYLES.h2.prefix}${renderInline(line.substring(3))}${STYLES.h2.suffix}`;
194
+ }
195
+ if (line.startsWith('# ')) {
196
+ return `${STYLES.h1.prefix}${renderInline(line.substring(2))}${STYLES.h1.suffix}`;
197
+ }
198
+
199
+ // 无序列表
200
+ if (line.match(/^[-*+] /)) {
201
+ return line.replace(/^([-*+] )(.*)/, (match, bullet, content) => {
202
+ return `${STYLES.list.prefix}${bullet}${CLEAR}${renderInline(content)}`;
203
+ });
204
+ }
205
+
206
+ // 有序列表
207
+ if (line.match(/^\d+\. /)) {
208
+ return line.replace(/^(\d+\. )(.*)/, (match, bullet, content) => {
209
+ return `${STYLES.list.prefix}${bullet}${CLEAR}${renderInline(content)}`;
210
+ });
211
+ }
212
+
213
+ // 引用
214
+ if (line.startsWith('> ')) {
215
+ return line.replace(/^> (.*)/, (match, content) => {
216
+ return `${STYLES.blockquote.prefix}| ${renderInline(content)}${STYLES.blockquote.suffix}`;
217
+ });
218
+ }
219
+
220
+ return renderInline(line);
221
+ }
222
+
223
+ /**
224
+ * 渲染完整的 markdown 文本
225
+ */
226
+ function render(text) {
227
+ if (!text) return '';
228
+
229
+ const lines = text.split('\n');
230
+ const rendered = [];
231
+
232
+ let inCodeBlock = false;
233
+
234
+ for (const line of lines) {
235
+ // 代码块
236
+ if (line.startsWith('```')) {
237
+ if (inCodeBlock) {
238
+ rendered.push(`\`\`\`${STYLES.codeBlock.suffix}`);
239
+ inCodeBlock = false;
240
+ } else {
241
+ const lang = line.substring(3).trim() || '';
242
+ rendered.push(`${STYLES.codeBlock.prefix}\`\`\`${lang ? ' ' + lang : ''}`);
243
+ inCodeBlock = true;
244
+ }
245
+ continue;
246
+ }
247
+
248
+ if (inCodeBlock) {
249
+ rendered.push(`${STYLES.codeBlock.prefix}${line}${STYLES.codeBlock.suffix}`);
250
+ } else {
251
+ rendered.push(renderLine(line));
252
+ }
253
+ }
254
+
255
+ return rendered.join('\n');
256
+ }
257
+
258
+ /**
259
+ * 流式渲染 - 每次返回一个完整的行
260
+ * 返回 { done: boolean, line: string }
261
+ */
262
+ function* streamRender(text) {
263
+ if (!text) return;
264
+
265
+ const lines = text.split('\n');
266
+ let inCodeBlock = false;
267
+
268
+ for (const line of lines) {
269
+ // 代码块
270
+ if (line.startsWith('```')) {
271
+ if (inCodeBlock) {
272
+ yield { done: false, line: `${STYLES.codeBlock.suffix}` };
273
+ inCodeBlock = false;
274
+ } else {
275
+ const lang = line.substring(3).trim() || '';
276
+ yield { done: false, line: `${STYLES.codeBlock.prefix}\`\`\`${lang ? ' ' + lang : ''}` };
277
+ inCodeBlock = true;
278
+ }
279
+ continue;
280
+ }
281
+
282
+ if (inCodeBlock) {
283
+ yield { done: false, line: `${STYLES.codeBlock.prefix}${line}${STYLES.codeBlock.suffix}` };
284
+ } else {
285
+ yield { done: false, line: renderLine(line) };
286
+ }
287
+ }
288
+
289
+ yield { done: true };
290
+ }
291
+
292
+ module.exports = { render, renderInline, renderLine, streamRender, STYLES };
@@ -7,7 +7,7 @@ services:
7
7
  container_name: foliko-agent
8
8
  restart: unless-stopped
9
9
  ports:
10
- - "3000:3000"
10
+ - '3000:3000'
11
11
  environment:
12
12
  # AI 配置
13
13
  - FOLIKO_PROVIDER=${FOLIKO_PROVIDER:-minimax}