@astro-minimax/ai 0.9.0 → 0.9.3

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 (251) hide show
  1. package/README.md +108 -18
  2. package/dist/cache/global-cache.d.ts +6 -2
  3. package/dist/cache/global-cache.d.ts.map +1 -1
  4. package/dist/cache/global-cache.js +24 -9
  5. package/dist/cache/index.d.ts +7 -6
  6. package/dist/cache/index.d.ts.map +1 -1
  7. package/dist/cache/index.js +12 -4
  8. package/dist/cache/injection-cache.d.ts +36 -0
  9. package/dist/cache/injection-cache.d.ts.map +1 -0
  10. package/dist/cache/injection-cache.js +90 -0
  11. package/dist/cache/kv-adapter.d.ts.map +1 -1
  12. package/dist/cache/kv-adapter.js +2 -1
  13. package/dist/cache/memory-adapter.d.ts.map +1 -1
  14. package/dist/cache/memory-adapter.js +2 -1
  15. package/dist/cache/response-cache.d.ts +10 -5
  16. package/dist/cache/response-cache.d.ts.map +1 -1
  17. package/dist/cache/response-cache.js +18 -6
  18. package/dist/components/AIChatContainer.d.ts +2 -2
  19. package/dist/components/AIChatContainer.d.ts.map +1 -1
  20. package/dist/components/AIChatContainer.js +8 -920
  21. package/dist/components/ChatInput.d.ts +15 -0
  22. package/dist/components/ChatInput.d.ts.map +1 -0
  23. package/dist/components/ChatInput.js +72 -0
  24. package/dist/components/ChatPanel.d.ts +1 -1
  25. package/dist/components/ChatPanel.d.ts.map +1 -1
  26. package/dist/components/ChatPanel.js +210 -672
  27. package/dist/components/CodeBlock.d.ts +31 -0
  28. package/dist/components/CodeBlock.d.ts.map +1 -0
  29. package/dist/components/CodeBlock.js +143 -0
  30. package/dist/components/MarkmapBlock.d.ts +4 -0
  31. package/dist/components/MarkmapBlock.d.ts.map +1 -0
  32. package/dist/components/MarkmapBlock.js +180 -0
  33. package/dist/components/MermaidBlock.d.ts +4 -0
  34. package/dist/components/MermaidBlock.d.ts.map +1 -0
  35. package/dist/components/MermaidBlock.js +193 -0
  36. package/dist/components/MessageBubble.d.ts +21 -0
  37. package/dist/components/MessageBubble.d.ts.map +1 -0
  38. package/dist/components/MessageBubble.js +233 -0
  39. package/dist/components/ReasoningBlock.d.ts +6 -0
  40. package/dist/components/ReasoningBlock.d.ts.map +1 -0
  41. package/dist/components/ReasoningBlock.js +11 -0
  42. package/dist/components/RichText.d.ts +41 -0
  43. package/dist/components/RichText.d.ts.map +1 -0
  44. package/dist/components/RichText.js +202 -0
  45. package/dist/components/VizShared.d.ts +57 -0
  46. package/dist/components/VizShared.d.ts.map +1 -0
  47. package/dist/components/VizShared.js +233 -0
  48. package/dist/components/tool-auto-continue.d.ts +5 -0
  49. package/dist/components/tool-auto-continue.d.ts.map +1 -0
  50. package/dist/components/tool-auto-continue.js +33 -0
  51. package/dist/constants.d.ts +61 -0
  52. package/dist/constants.d.ts.map +1 -0
  53. package/dist/constants.js +72 -0
  54. package/dist/data/index.d.ts +4 -3
  55. package/dist/data/index.d.ts.map +1 -1
  56. package/dist/data/index.js +4 -10
  57. package/dist/data/knowledge-types.d.ts +8 -0
  58. package/dist/data/knowledge-types.d.ts.map +1 -0
  59. package/dist/data/knowledge-types.js +14 -0
  60. package/dist/data/metadata-loader.d.ts +4 -28
  61. package/dist/data/metadata-loader.d.ts.map +1 -1
  62. package/dist/data/metadata-loader.js +11 -34
  63. package/dist/data/types.d.ts +17 -2
  64. package/dist/data/types.d.ts.map +1 -1
  65. package/dist/extensions/index.d.ts +5 -0
  66. package/dist/extensions/index.d.ts.map +1 -0
  67. package/dist/extensions/index.js +24 -0
  68. package/dist/extensions/injector.d.ts +14 -0
  69. package/dist/extensions/injector.d.ts.map +1 -0
  70. package/dist/extensions/injector.js +146 -0
  71. package/dist/extensions/loader.d.ts +5 -0
  72. package/dist/extensions/loader.d.ts.map +1 -0
  73. package/dist/extensions/loader.js +45 -0
  74. package/dist/extensions/registry.d.ts +4 -0
  75. package/dist/extensions/registry.d.ts.map +1 -0
  76. package/dist/extensions/registry.js +144 -0
  77. package/dist/extensions/types.d.ts +126 -0
  78. package/dist/extensions/types.d.ts.map +1 -0
  79. package/dist/extensions/types.js +0 -0
  80. package/dist/fact-registry/prompt-injector.d.ts +1 -1
  81. package/dist/fact-registry/prompt-injector.d.ts.map +1 -1
  82. package/dist/fact-registry/prompt-injector.js +2 -1
  83. package/dist/index.d.ts +3 -2
  84. package/dist/index.d.ts.map +1 -1
  85. package/dist/index.js +3 -2
  86. package/dist/intelligence/citation-guard.d.ts +2 -13
  87. package/dist/intelligence/citation-guard.d.ts.map +1 -1
  88. package/dist/intelligence/citation-guard.js +52 -23
  89. package/dist/intelligence/evidence-analysis.d.ts +24 -16
  90. package/dist/intelligence/evidence-analysis.d.ts.map +1 -1
  91. package/dist/intelligence/evidence-analysis.js +118 -20
  92. package/dist/intelligence/evidence-budget.d.ts +13 -0
  93. package/dist/intelligence/evidence-budget.d.ts.map +1 -0
  94. package/dist/intelligence/evidence-budget.js +49 -0
  95. package/dist/intelligence/index.d.ts +10 -4
  96. package/dist/intelligence/index.d.ts.map +1 -1
  97. package/dist/intelligence/index.js +27 -3
  98. package/dist/intelligence/keyword-extract.d.ts +1 -1
  99. package/dist/intelligence/keyword-extract.d.ts.map +1 -1
  100. package/dist/intelligence/keyword-extract.js +5 -9
  101. package/dist/intelligence/request-interpretation.d.ts +40 -0
  102. package/dist/intelligence/request-interpretation.d.ts.map +1 -0
  103. package/dist/intelligence/request-interpretation.js +71 -0
  104. package/dist/intelligence/response-templates.d.ts +1 -0
  105. package/dist/intelligence/response-templates.d.ts.map +1 -1
  106. package/dist/intelligence/response-templates.js +13 -0
  107. package/dist/prompt/dynamic-layer.d.ts +1 -5
  108. package/dist/prompt/dynamic-layer.d.ts.map +1 -1
  109. package/dist/prompt/dynamic-layer.js +145 -9
  110. package/dist/prompt/prompt-builder.d.ts +1 -1
  111. package/dist/prompt/prompt-builder.d.ts.map +1 -1
  112. package/dist/prompt/prompt-builder.js +5 -1
  113. package/dist/prompt/semi-static-layer.d.ts +1 -1
  114. package/dist/prompt/semi-static-layer.d.ts.map +1 -1
  115. package/dist/prompt/semi-static-layer.js +22 -12
  116. package/dist/prompt/static-layer.d.ts.map +1 -1
  117. package/dist/prompt/static-layer.js +37 -4
  118. package/dist/prompt/types.d.ts +9 -4
  119. package/dist/prompt/types.d.ts.map +1 -1
  120. package/dist/provider-manager/base.d.ts +5 -1
  121. package/dist/provider-manager/base.d.ts.map +1 -1
  122. package/dist/provider-manager/base.js +22 -2
  123. package/dist/provider-manager/config.d.ts.map +1 -1
  124. package/dist/provider-manager/config.js +3 -2
  125. package/dist/provider-manager/index.d.ts +1 -1
  126. package/dist/provider-manager/index.d.ts.map +1 -1
  127. package/dist/provider-manager/index.js +1 -2
  128. package/dist/provider-manager/manager.d.ts +10 -1
  129. package/dist/provider-manager/manager.d.ts.map +1 -1
  130. package/dist/provider-manager/manager.js +26 -10
  131. package/dist/provider-manager/openai.d.ts +2 -2
  132. package/dist/provider-manager/openai.d.ts.map +1 -1
  133. package/dist/provider-manager/openai.js +19 -4
  134. package/dist/provider-manager/types.d.ts +18 -38
  135. package/dist/provider-manager/types.d.ts.map +1 -1
  136. package/dist/provider-manager/workers.d.ts +2 -2
  137. package/dist/provider-manager/workers.d.ts.map +1 -1
  138. package/dist/provider-manager/workers.js +15 -4
  139. package/dist/query/followup.d.ts +7 -0
  140. package/dist/query/followup.d.ts.map +1 -0
  141. package/dist/query/followup.js +46 -0
  142. package/dist/query/intent.d.ts +6 -0
  143. package/dist/query/intent.d.ts.map +1 -0
  144. package/dist/query/intent.js +137 -0
  145. package/dist/query/types.d.ts +8 -0
  146. package/dist/query/types.d.ts.map +1 -0
  147. package/dist/query/types.js +0 -0
  148. package/dist/search/hybrid-search.d.ts +111 -0
  149. package/dist/search/hybrid-search.d.ts.map +1 -0
  150. package/dist/search/hybrid-search.js +326 -0
  151. package/dist/search/index.d.ts +11 -9
  152. package/dist/search/index.d.ts.map +1 -1
  153. package/dist/search/index.js +46 -10
  154. package/dist/search/scoring.d.ts +18 -0
  155. package/dist/search/scoring.d.ts.map +1 -0
  156. package/dist/search/{search-utils.js → scoring.js} +14 -27
  157. package/dist/search/search-api.d.ts +16 -1
  158. package/dist/search/search-api.d.ts.map +1 -1
  159. package/dist/search/search-api.js +118 -15
  160. package/dist/search/search-index.d.ts +2 -2
  161. package/dist/search/search-index.d.ts.map +1 -1
  162. package/dist/search/search-index.js +4 -2
  163. package/dist/search/session-cache.d.ts +4 -10
  164. package/dist/search/session-cache.d.ts.map +1 -1
  165. package/dist/search/session-cache.js +12 -45
  166. package/dist/search/types.d.ts +28 -0
  167. package/dist/search/types.d.ts.map +1 -1
  168. package/dist/search/vector-reranker.d.ts +3 -3
  169. package/dist/search/vector-reranker.d.ts.map +1 -1
  170. package/dist/search/vector-reranker.js +14 -2
  171. package/dist/server/chat-handler.d.ts +86 -1
  172. package/dist/server/chat-handler.d.ts.map +1 -1
  173. package/dist/server/chat-handler.js +835 -401
  174. package/dist/server/chat-message-utils.d.ts +6 -0
  175. package/dist/server/chat-message-utils.d.ts.map +1 -0
  176. package/dist/server/chat-message-utils.js +40 -0
  177. package/dist/server/chat-utils.d.ts +30 -0
  178. package/dist/server/chat-utils.d.ts.map +1 -0
  179. package/dist/server/chat-utils.js +88 -0
  180. package/dist/server/dev-server.js +238 -101
  181. package/dist/server/env-config.d.ts +22 -0
  182. package/dist/server/env-config.d.ts.map +1 -0
  183. package/dist/server/env-config.js +25 -0
  184. package/dist/server/errors.d.ts +1 -0
  185. package/dist/server/errors.d.ts.map +1 -1
  186. package/dist/server/errors.js +14 -7
  187. package/dist/server/index.d.ts +2 -4
  188. package/dist/server/index.d.ts.map +1 -1
  189. package/dist/server/index.js +4 -25
  190. package/dist/server/metadata-init.d.ts +10 -5
  191. package/dist/server/metadata-init.d.ts.map +1 -1
  192. package/dist/server/metadata-init.js +78 -34
  193. package/dist/server/notify.d.ts +12 -11
  194. package/dist/server/notify.d.ts.map +1 -1
  195. package/dist/server/notify.js +46 -48
  196. package/dist/server/prompt-runtime.d.ts +60 -0
  197. package/dist/server/prompt-runtime.d.ts.map +1 -0
  198. package/dist/server/prompt-runtime.js +284 -0
  199. package/dist/server/stream-helpers.d.ts +30 -16
  200. package/dist/server/stream-helpers.d.ts.map +1 -1
  201. package/dist/server/stream-helpers.js +152 -15
  202. package/dist/server/types.d.ts +47 -12
  203. package/dist/server/types.d.ts.map +1 -1
  204. package/dist/structured-output/generator.d.ts +6 -0
  205. package/dist/structured-output/generator.d.ts.map +1 -0
  206. package/dist/structured-output/generator.js +164 -0
  207. package/dist/structured-output/index.d.ts +4 -0
  208. package/dist/structured-output/index.d.ts.map +1 -0
  209. package/dist/structured-output/index.js +6 -0
  210. package/dist/structured-output/schemas/evidence.d.ts +88 -0
  211. package/dist/structured-output/schemas/evidence.d.ts.map +1 -0
  212. package/dist/structured-output/schemas/evidence.js +65 -0
  213. package/dist/structured-output/types.d.ts +69 -0
  214. package/dist/structured-output/types.d.ts.map +1 -0
  215. package/dist/structured-output/types.js +0 -0
  216. package/dist/tools/action-tools.d.ts +63 -0
  217. package/dist/tools/action-tools.d.ts.map +1 -0
  218. package/dist/tools/action-tools.js +158 -0
  219. package/dist/tools/index.d.ts +2 -0
  220. package/dist/tools/index.d.ts.map +1 -0
  221. package/dist/tools/index.js +30 -0
  222. package/dist/utils/i18n.d.ts +1 -1
  223. package/dist/utils/i18n.d.ts.map +1 -1
  224. package/dist/utils/i18n.js +1 -1
  225. package/dist/utils/logger.d.ts +11 -0
  226. package/dist/utils/logger.d.ts.map +1 -0
  227. package/dist/utils/logger.js +36 -0
  228. package/dist/utils/text.d.ts +11 -0
  229. package/dist/utils/text.d.ts.map +1 -0
  230. package/dist/utils/text.js +87 -0
  231. package/dist/utils/url.d.ts +19 -0
  232. package/dist/utils/url.d.ts.map +1 -0
  233. package/dist/utils/url.js +13 -0
  234. package/package.json +46 -12
  235. package/dist/intelligence/intent-detect.d.ts +0 -40
  236. package/dist/intelligence/intent-detect.d.ts.map +0 -1
  237. package/dist/intelligence/intent-detect.js +0 -93
  238. package/dist/providers/index.d.ts +0 -2
  239. package/dist/providers/index.d.ts.map +0 -1
  240. package/dist/providers/index.js +0 -5
  241. package/dist/search/search-utils.d.ts +0 -47
  242. package/dist/search/search-utils.d.ts.map +0 -1
  243. package/dist/stream/index.d.ts +0 -3
  244. package/dist/stream/index.d.ts.map +0 -1
  245. package/dist/stream/index.js +0 -8
  246. package/dist/stream/mock-stream.d.ts +0 -12
  247. package/dist/stream/mock-stream.d.ts.map +0 -1
  248. package/dist/stream/mock-stream.js +0 -26
  249. package/dist/stream/response.d.ts +0 -10
  250. package/dist/stream/response.d.ts.map +0 -1
  251. package/dist/stream/response.js +0 -21
@@ -1,926 +1,14 @@
1
- // src/components/AIChatContainer.tsx
2
- import { useState as useState2, useCallback as useCallback2 } from "preact/hooks";
1
+ import { useState, useCallback, useEffect } from "preact/hooks";
2
+ import { h } from 'preact';
3
3
 
4
- import { h, Fragment } from 'preact';
5
-
6
- // src/components/ChatPanel.tsx
7
- import { useCallback, useEffect, useMemo, useRef, useState } from "preact/hooks";
8
- import { useChat } from "@ai-sdk/react";
9
- import { DefaultChatTransport } from "ai";
10
-
11
- // src/providers/mock.ts
12
- var MOCK_RESPONSES = [
13
- {
14
- patterns: [/astro/i, /框架/],
15
- zh: `Astro \u662F\u4E00\u4E2A\u73B0\u4EE3\u5316\u7684\u9759\u6001\u7AD9\u70B9\u751F\u6210\u5668\uFF0C\u6838\u5FC3\u4F18\u52BF\u662F"\u5C9B\u5C7F\u67B6\u6784"\u2014\u2014\u9ED8\u8BA4\u96F6 JS\uFF0C\u53EA\u5728\u4EA4\u4E92\u7EC4\u4EF6\u4E0A\u52A0\u8F7D\u811A\u672C\u3002\u672C\u535A\u5BA2\u57FA\u4E8E Astro \u6784\u5EFA\u3002
16
-
17
- \u63A8\u8350\u9605\u8BFB\uFF1A
18
- - [\u5FEB\u901F\u4E0A\u624B\uFF1A\u4E24\u79CD\u96C6\u6210\u65B9\u5F0F](/zh/posts/getting-started) \u2014 \u4E86\u89E3\u5982\u4F55\u642D\u5EFA astro-minimax \u535A\u5BA2
19
- - [\u5982\u4F55\u914D\u7F6E\u4E3B\u9898](/zh/posts/how-to-configure-astro-minimax-theme) \u2014 \u81EA\u5B9A\u4E49\u4F60\u7684\u535A\u5BA2\u5916\u89C2
20
-
21
- \u5916\u90E8\u8D44\u6E90\uFF1A
22
- - [Astro \u5B98\u65B9\u6587\u6863](https://docs.astro.build) \u2014 \u6DF1\u5165\u5B66\u4E60 Astro \u6846\u67B6
23
- - [Astro \u4E3B\u9898\u5E02\u573A](https://astro.build/themes/) \u2014 \u53D1\u73B0\u66F4\u591A Astro \u4E3B\u9898`,
24
- en: `Astro is a modern static site generator with an "Islands Architecture" \u2014 zero JS by default, loading scripts only for interactive components. This blog is built with Astro.
25
-
26
- Recommended reading:
27
- - [Getting Started: Two Integration Methods](/en/posts/getting-started) \u2014 Learn how to set up an astro-minimax blog
28
- - [How to Configure the Theme](/en/posts/how-to-configure-astro-minimax-theme) \u2014 Customize your blog
29
-
30
- External resources:
31
- - [Astro Documentation](https://docs.astro.build) \u2014 Learn Astro in depth
32
- - [Astro Themes](https://astro.build/themes/) \u2014 Discover more themes`
33
- },
34
- {
35
- patterns: [/推荐|文章|看什么|读什么|recommend/i],
36
- zh: `\u4EE5\u4E0B\u662F\u4E00\u4E9B\u70ED\u95E8\u6587\u7AE0\u63A8\u8350\uFF1A
37
-
38
- **\u5165\u95E8\u7CFB\u5217\uFF1A**
39
- - [\u5FEB\u901F\u4E0A\u624B\uFF1A\u4E24\u79CD\u96C6\u6210\u65B9\u5F0F](/zh/posts/getting-started) \u2014 \u642D\u5EFA\u4F60\u7684\u7B2C\u4E00\u4E2A\u535A\u5BA2
40
- - [\u5982\u4F55\u6DFB\u52A0\u65B0\u6587\u7AE0](/zh/posts/adding-new-post) \u2014 \u5185\u5BB9\u521B\u4F5C\u6307\u5357
41
- - [\u9884\u5B9A\u4E49\u914D\u8272\u65B9\u6848](/zh/posts/predefined-color-schemes) \u2014 \u9009\u4E00\u4E2A\u4F60\u559C\u6B22\u7684\u4E3B\u9898\u8272
42
-
43
- **\u6280\u672F\u6DF1\u5EA6\uFF1A**
44
- - [\u5982\u4F55\u5728\u535A\u5BA2\u4E2D\u4F7F\u7528 LaTeX \u516C\u5F0F](/zh/posts/how-to-add-latex-equations-in-blog-posts) \u2014 \u6570\u5B66\u516C\u5F0F\u652F\u6301
45
- - [\u52A8\u6001 OG \u56FE\u7247\u751F\u6210](/zh/posts/dynamic-og-images) \u2014 \u81EA\u52A8\u751F\u6210\u793E\u4EA4\u5206\u4EAB\u56FE
46
-
47
- \u4F60\u5BF9\u54EA\u4E2A\u65B9\u5411\u7684\u5185\u5BB9\u66F4\u611F\u5174\u8DA3\uFF1F\u6211\u53EF\u4EE5\u505A\u66F4\u7CBE\u51C6\u7684\u63A8\u8350\u3002`,
48
- en: `Here are some recommended articles:
49
-
50
- **Getting Started:**
51
- - [Getting Started: Two Integration Methods](/en/posts/getting-started) \u2014 Build your first blog
52
- - [Adding New Posts](/en/posts/adding-new-post) \u2014 Content creation guide
53
- - [Predefined Color Schemes](/en/posts/predefined-color-schemes) \u2014 Pick your favorite theme color
54
-
55
- **Technical Deep Dives:**
56
- - [LaTeX Equations in Blog Posts](/en/posts/how-to-add-latex-equations-in-blog-posts) \u2014 Math formula support
57
- - [Dynamic OG Images](/en/posts/dynamic-og-images) \u2014 Auto-generate social share images
58
-
59
- What direction interests you more? I can provide more specific recommendations.`
60
- },
61
- {
62
- patterns: [/博客|blog|功能|feature/i],
63
- zh: `\u8FD9\u4E2A\u535A\u5BA2\u57FA\u4E8E **astro-minimax** \u4E3B\u9898\uFF0C\u529F\u80FD\u4E30\u5BCC\uFF1A
64
-
65
- \u6838\u5FC3\u529F\u80FD\uFF1AMarkdown/MDX\u3001\u4EE3\u7801\u9AD8\u4EAE\u3001[\u6570\u5B66\u516C\u5F0F(KaTeX)](/zh/posts/how-to-add-latex-equations-in-blog-posts)\u3001[Mermaid \u56FE\u8868](/zh/posts/mermaid-diagrams)\u3001\u6807\u7B7E\u5206\u7C7B\u3001\u5168\u6587\u641C\u7D22(Pagefind)\u3001[Waline \u8BC4\u8BBA](https://waline.js.org)\u3001\u6DF1\u8272\u6A21\u5F0F\u3002
66
-
67
- \u4E86\u89E3\u66F4\u591A\uFF1A
68
- - [\u914D\u7F6E\u6307\u5357](/zh/posts/how-to-configure-astro-minimax-theme) \u2014 \u5B8C\u6574\u914D\u7F6E\u9009\u9879
69
- - [Markdown \u6269\u5C55\u8BED\u6CD5](/zh/posts/markdown-extended) \u2014 \u6240\u6709\u652F\u6301\u7684\u8BED\u6CD5\u7279\u6027
70
-
71
- \u5F00\u6E90\u5730\u5740\uFF1A[souloss/astro-minimax](https://github.com/souloss/astro-minimax)`,
72
- en: `This blog uses the **astro-minimax** theme with rich features:
73
-
74
- Core features: Markdown/MDX, syntax highlighting, [math equations (KaTeX)](/en/posts/how-to-add-latex-equations-in-blog-posts), [Mermaid diagrams](/en/posts/mermaid-diagrams), tags & categories, full-text search (Pagefind), [Waline comments](https://waline.js.org), dark mode.
75
-
76
- Learn more:
77
- - [Configuration Guide](/en/posts/how-to-configure-astro-minimax-theme) \u2014 Full config options
78
- - [Extended Markdown](/en/posts/markdown-extended) \u2014 All supported syntax features
79
-
80
- Open source: [souloss/astro-minimax](https://github.com/souloss/astro-minimax)`
81
- },
82
- {
83
- patterns: [/主题|theme|暗色|dark|颜色|color|配色/i],
84
- zh: `\u535A\u5BA2\u652F\u6301\u4EAE\u8272\u548C\u6697\u8272\u4E3B\u9898\uFF0C\u53F3\u4E0B\u89D2\u6309\u94AE\u5373\u53EF\u5207\u6362\uFF0C\u4E5F\u4F1A\u81EA\u52A8\u68C0\u6D4B\u7CFB\u7EDF\u504F\u597D\u3002
85
-
86
- \u914D\u8272\u65B9\u6848\u53EF\u4EE5\u5728\u914D\u7F6E\u4E2D\u81EA\u5B9A\u4E49\uFF0C\u76EE\u524D\u63D0\u4F9B\u591A\u79CD\u9884\u8BBE\uFF1A
87
- - [\u9884\u5B9A\u4E49\u914D\u8272\u65B9\u6848](/zh/posts/predefined-color-schemes) \u2014 \u67E5\u770B\u6240\u6709\u53EF\u7528\u914D\u8272
88
- - [\u4E3B\u9898\u914D\u7F6E\u6307\u5357](/zh/posts/how-to-configure-astro-minimax-theme) \u2014 \u521B\u5EFA\u4F60\u81EA\u5DF1\u7684\u914D\u8272
89
-
90
- \u53C2\u8003 [Tailwind CSS \u8C03\u8272\u677F](https://tailwindcss.com/docs/customizing-colors) \u83B7\u53D6\u7075\u611F\u3002`,
91
- en: `The blog supports light and dark themes \u2014 toggle with the bottom-right button or auto-detect system preference.
92
-
93
- Color schemes are customizable:
94
- - [Predefined Color Schemes](/en/posts/predefined-color-schemes) \u2014 See all available schemes
95
- - [Theme Configuration Guide](/en/posts/how-to-configure-astro-minimax-theme) \u2014 Create your own
96
-
97
- Check [Tailwind CSS Color Palette](https://tailwindcss.com/docs/customizing-colors) for inspiration.`
98
- },
99
- {
100
- patterns: [/搭建|部署|deploy|build|install|安装|搭/i],
101
- zh: `\u642D\u5EFA\u7C7B\u4F3C\u7684\u535A\u5BA2\u975E\u5E38\u7B80\u5355\uFF01\u6709\u4E24\u79CD\u65B9\u5F0F\uFF1A
102
-
103
- 1. **GitHub \u6A21\u677F**\uFF08\u63A8\u8350\u65B0\u624B\uFF09\u2014 \u4E00\u952E Fork\uFF0C\u5F00\u7BB1\u5373\u7528
104
- 2. **NPM \u5305\u96C6\u6210** \u2014 \u9002\u5408\u5185\u5BB9\u4E0E\u7CFB\u7EDF\u5206\u79BB\u7684\u8FDB\u9636\u7528\u6CD5
105
-
106
- \u8BE6\u7EC6\u6B65\u9AA4\u8BF7\u770B [\u5FEB\u901F\u4E0A\u624B](/zh/posts/getting-started)\u3002
107
-
108
- \u90E8\u7F72\u63A8\u8350 [Cloudflare Pages](https://pages.cloudflare.com)\uFF08\u514D\u8D39\u3001\u5168\u7403 CDN\uFF09\uFF0C\u4E5F\u652F\u6301 [Vercel](https://vercel.com) \u548C [Netlify](https://netlify.com)\u3002`,
109
- en: `Setting up a similar blog is easy! Two methods:
110
-
111
- 1. **GitHub Template** (recommended for beginners) \u2014 One-click fork, ready to use
112
- 2. **NPM Package Integration** \u2014 For advanced content/system separation
113
-
114
- See [Getting Started](/en/posts/getting-started) for detailed steps.
115
-
116
- Deploy with [Cloudflare Pages](https://pages.cloudflare.com) (free, global CDN), or [Vercel](https://vercel.com) / [Netlify](https://netlify.com).`
117
- },
118
- {
119
- patterns: [/rust/i],
120
- zh: `\u535A\u5BA2\u4E2D\u6709\u4E00\u7CFB\u5217 Rust \u6587\u7AE0\uFF1A
121
- - [Rust \u5165\u95E8\u4ECB\u7ECD](/zh/posts/rust-series-01-introduction) \u2014 \u8BED\u8A00\u57FA\u7840
122
- - [\u6240\u6709\u6743\u7CFB\u7EDF](/zh/posts/rust-series-02-ownership) \u2014 Rust \u6838\u5FC3\u6982\u5FF5
123
- - [\u9519\u8BEF\u5904\u7406](/zh/posts/rust-series-03-error-handling) \u2014 Result \u548C Option
124
- - [\u5E76\u53D1\u7F16\u7A0B](/zh/posts/rust-series-04-concurrency) \u2014 \u5B89\u5168\u7684\u591A\u7EBF\u7A0B
125
-
126
- \u5916\u90E8\u5B66\u4E60\u8D44\u6E90\uFF1A
127
- - [The Rust Book](https://doc.rust-lang.org/book/) \u2014 \u5B98\u65B9\u6559\u7A0B
128
- - [Rust by Example](https://doc.rust-lang.org/rust-by-example/) \u2014 \u5B9E\u4F8B\u5B66\u4E60`,
129
- en: `The blog has a Rust series:
130
- - [Rust Introduction](/en/posts/rust-series-01-introduction) \u2014 Language basics
131
- - [Ownership System](/en/posts/rust-series-02-ownership) \u2014 Core Rust concept
132
- - [Error Handling](/en/posts/rust-series-03-error-handling) \u2014 Result and Option
133
- - [Concurrency](/en/posts/rust-series-04-concurrency) \u2014 Safe multithreading
134
-
135
- External resources:
136
- - [The Rust Book](https://doc.rust-lang.org/book/) \u2014 Official tutorial
137
- - [Rust by Example](https://doc.rust-lang.org/rust-by-example/) \u2014 Learn by examples`
138
- },
139
- {
140
- patterns: [/ai|人工智能|助手|assistant|chat/i],
141
- zh: `\u6211\u662F\u8FD9\u4E2A\u535A\u5BA2\u7684 AI \u52A9\u624B\uFF01\u5F53\u524D\u8FD0\u884C\u5728 Demo \u6A21\u5F0F\uFF0C\u53EF\u4EE5\uFF1A
142
- - \u6839\u636E\u4F60\u7684\u95EE\u9898\u63A8\u8350\u76F8\u5173\u535A\u5BA2\u6587\u7AE0
143
- - \u63A8\u8350\u6709\u7528\u7684\u5916\u90E8\u5B66\u4E60\u8D44\u6E90
144
- - \u89E3\u7B54\u5173\u4E8E\u535A\u5BA2\u6280\u672F\u6808\u7684\u95EE\u9898
145
-
146
- \u542F\u7528\u5B8C\u6574 AI \u529F\u80FD\uFF08RAG \u641C\u7D22\u589E\u5F3A\uFF09\u9700\u8981\u914D\u7F6E \`AI_BASE_URL\` \u548C \`AI_API_KEY\` \u73AF\u5883\u53D8\u91CF\u3002
147
-
148
- \u8BD5\u8BD5\u95EE\u6211\uFF1A"\u6709\u54EA\u4E9B\u6587\u7AE0\u63A8\u8350\uFF1F" \u6216 "\u600E\u4E48\u642D\u5EFA\u7C7B\u4F3C\u7684\u535A\u5BA2\uFF1F"`,
149
- en: `I'm the blog AI assistant! Currently in Demo mode, I can:
150
- - Recommend relevant blog articles based on your questions
151
- - Suggest useful external learning resources
152
- - Answer questions about the blog's tech stack
153
-
154
- For full AI features (RAG search enhancement), configure \`AI_BASE_URL\` and \`AI_API_KEY\` environment variables.
155
-
156
- Try asking: "Recommend some articles?" or "How to build a similar blog?"`
157
- },
158
- {
159
- patterns: [/搜索|search|pagefind/i],
160
- zh: `\u535A\u5BA2\u96C6\u6210\u4E86 [Pagefind](https://pagefind.app) \u5168\u6587\u641C\u7D22\u5F15\u64CE\uFF0C\u6784\u5EFA\u65F6\u81EA\u52A8\u7D22\u5F15\u3002\u70B9\u51FB\u9875\u9762\u9876\u90E8\u641C\u7D22\u56FE\u6807\u5373\u53EF\u4F7F\u7528\u3002
161
-
162
- \u4E86\u89E3\u66F4\u591A\u641C\u7D22\u529F\u80FD\uFF1A
163
- - [Pagefind \u5B98\u65B9\u6587\u6863](https://pagefind.app/docs/) \u2014 \u5B8C\u6574\u914D\u7F6E\u6307\u5357
164
- - \u641C\u7D22\u652F\u6301\u4E2D\u6587\u548C\u82F1\u6587\u5185\u5BB9`,
165
- en: `The blog integrates [Pagefind](https://pagefind.app) for full-text search, auto-indexed at build time. Click the search icon at the top to use it.
166
-
167
- Learn more:
168
- - [Pagefind Documentation](https://pagefind.app/docs/) \u2014 Complete configuration guide
169
- - Search supports both Chinese and English content`
170
- },
171
- {
172
- patterns: [/markdown|mdx|语法|syntax|公式|latex|mermaid|图表/i],
173
- zh: `\u535A\u5BA2\u652F\u6301\u4E30\u5BCC\u7684\u5185\u5BB9\u8BED\u6CD5\uFF1A
174
-
175
- - [Markdown \u57FA\u7840\u8BED\u6CD5](/zh/posts/markdown-basics) \u2014 \u6807\u9898\u3001\u5217\u8868\u3001\u8868\u683C\u7B49
176
- - [Markdown \u6269\u5C55\u8BED\u6CD5](/zh/posts/markdown-extended) \u2014 \u811A\u6CE8\u3001\u9AD8\u4EAE\u3001\u6298\u53E0\u7B49
177
- - [LaTeX \u6570\u5B66\u516C\u5F0F](/zh/posts/how-to-add-latex-equations-in-blog-posts) \u2014 KaTeX \u6E32\u67D3
178
- - [Mermaid \u56FE\u8868](/zh/posts/mermaid-diagrams) \u2014 \u6D41\u7A0B\u56FE\u3001\u65F6\u5E8F\u56FE
179
- - [Markmap \u601D\u7EF4\u5BFC\u56FE](/zh/posts/markmap-mindmaps) \u2014 \u4EA4\u4E92\u5F0F\u601D\u7EF4\u5BFC\u56FE
180
-
181
- \u5916\u90E8\u53C2\u8003\uFF1A[GitHub Flavored Markdown](https://github.github.com/gfm/)`,
182
- en: `The blog supports rich content syntax:
183
-
184
- - [Markdown Basics](/en/posts/markdown-basics) \u2014 Headings, lists, tables
185
- - [Extended Markdown](/en/posts/markdown-extended) \u2014 Footnotes, highlights, collapsible
186
- - [LaTeX Equations](/en/posts/how-to-add-latex-equations-in-blog-posts) \u2014 KaTeX rendering
187
- - [Mermaid Diagrams](/en/posts/mermaid-diagrams) \u2014 Flowcharts, sequence diagrams
188
- - [Markmap Mind Maps](/en/posts/markmap-mindmaps) \u2014 Interactive mind maps
189
-
190
- Reference: [GitHub Flavored Markdown](https://github.github.com/gfm/)`
191
- }
192
- ];
193
- var FALLBACK = {
194
- zh: `\u611F\u8C22\u63D0\u95EE\uFF01\u6211\u76EE\u524D\u5728 Demo \u6A21\u5F0F\u4E0B\uFF0C\u53EF\u4EE5\u63A8\u8350\u535A\u5BA2\u6587\u7AE0\u548C\u5916\u90E8\u8D44\u6E90\u3002
195
-
196
- \u8BD5\u8BD5\u8FD9\u4E9B\u8BDD\u9898\uFF1A
197
- - "\u6709\u54EA\u4E9B\u6587\u7AE0\u63A8\u8350\uFF1F"
198
- - "Astro \u6846\u67B6\u662F\u4EC0\u4E48\uFF1F"
199
- - "\u600E\u4E48\u642D\u5EFA\u7C7B\u4F3C\u7684\u535A\u5BA2\uFF1F"
200
- - "\u652F\u6301\u54EA\u4E9B Markdown \u8BED\u6CD5\uFF1F"`,
201
- en: `Thanks for asking! I'm in Demo mode and can recommend blog articles and external resources.
202
-
203
- Try these topics:
204
- - "Recommend some articles?"
205
- - "What is Astro?"
206
- - "How to build a similar blog?"
207
- - "What Markdown syntax is supported?"`
208
- };
209
- function getMockResponse(question, lang = "zh") {
210
- const q = question.toLowerCase();
211
- const isZh = lang !== "en";
212
- for (const { patterns, zh, en } of MOCK_RESPONSES) {
213
- if (patterns.some((p) => p.test(q))) {
214
- return isZh ? zh : en;
215
- }
216
- }
217
- return isZh ? FALLBACK.zh : FALLBACK.en;
218
- }
219
- function createMockStream(text) {
220
- let index = 0;
221
- return new ReadableStream({
222
- async pull(controller) {
223
- if (index >= text.length) {
224
- controller.close();
225
- return;
226
- }
227
- const chunkSize = Math.random() < 0.3 ? 2 : 1;
228
- const chunk = text.slice(index, index + chunkSize);
229
- index += chunkSize;
230
- controller.enqueue(chunk);
231
- await new Promise((resolve) => setTimeout(resolve, 12 + Math.random() * 23));
232
- }
233
- });
234
- }
235
-
236
- // src/server/types.ts
237
- function isChatStatusData(value) {
238
- if (!value || typeof value !== "object") return false;
239
- const v = value;
240
- return typeof v.stage === "string" && typeof v.message === "string" && typeof v.progress === "number";
241
- }
242
-
243
- // src/utils/i18n.ts
244
- var translations = {
245
- en: {
246
- // Reasoning UI
247
- "ai.reasoning.thinking": "Thinking...",
248
- "ai.reasoning.viewReasoning": "View reasoning",
249
- "ai.reasoning.waiting": "Waiting for thoughts...",
250
- // Error messages
251
- "ai.error.network": "Network connection failed. Please check your connection.",
252
- "ai.error.aborted": "Request was cancelled.",
253
- "ai.error.rateLimit": "Too many requests. Please try again later.",
254
- "ai.error.unavailable": "AI service is temporarily unavailable.",
255
- "ai.error.generic": "Something went wrong. Please try again later.",
256
- "ai.error.format": "Invalid request format.",
257
- // UI labels
258
- "ai.placeholder": "Ask a question...",
259
- "ai.clear": "Clear",
260
- "ai.clearConversation": "Clear conversation",
261
- "ai.close": "Close",
262
- "ai.closeChat": "Close chat",
263
- "ai.retry": "Retry",
264
- "ai.status.searching": "Searching...",
265
- "ai.status.generating": "Generating response...",
266
- "ai.status.found": "Found {count} related items",
267
- "ai.status.citation": "Answered from public records",
268
- "ai.status.fallback": "AI service unavailable, using demo mode",
269
- // Quick prompts
270
- "ai.prompt.techStack": "What tech stack is used?",
271
- "ai.prompt.recommend": "Recommend some articles?",
272
- "ai.prompt.build": "How to build a similar blog?",
273
- "ai.prompt.summarize": 'Summarize the key points of "{title}"',
274
- "ai.prompt.explain": 'Explain "{point}"',
275
- "ai.prompt.related": "What related content should I read next?",
276
- // Welcome messages
277
- "ai.welcome.reading": `I'm reading "{title}" with you.
278
- Ask me to summarize, explain a concept, or explore related topics.`,
279
- "ai.welcome.canHelp": "Hi! I'm the blog AI assistant. Ask me anything and I'll help you find related articles.",
280
- "ai.welcome.greeting": "Hi! I'm the blog AI assistant.",
281
- "ai.welcome.demo": "I'm running in demo mode. I can recommend blog articles and external resources.",
282
- "ai.welcome.demoHint": "For full AI features (RAG search), configure AI_BASE_URL and AI_API_KEY.",
283
- "ai.welcome.demoPrompt": 'Try: "Recommend articles?" or "How to build this blog?"',
284
- // Header
285
- "ai.header.reading": "Reading:",
286
- "ai.header.mode": "Demo",
287
- // Assistant branding
288
- "ai.assistantName": "Blog Avatar",
289
- "ai.status.live": "Live",
290
- // Additional error messages
291
- "ai.error.emptyMessage": "Message cannot be empty.",
292
- "ai.error.emptyContent": "Message content cannot be empty.",
293
- "ai.error.inputTooLong": "Message too long, max {max} characters.",
294
- "ai.error.timeout": "Response timeout, please retry or simplify your question.",
295
- // Rate limit messages
296
- "ai.error.rateLimit.burst": "Too many requests, please try again later.",
297
- "ai.error.rateLimit.sustained": "Too many requests, please wait a minute.",
298
- "ai.error.rateLimit.daily": "Daily limit reached, please come back tomorrow.",
299
- "ai.error.noOutput": "Sorry, I could not generate a valid response. Please try rephrasing your question.",
300
- "ai.prompt.section.responsibilities": "Your Responsibilities",
301
- "ai.prompt.section.format": "Response Format",
302
- "ai.prompt.section.principles": "Recommendation Principles",
303
- "ai.prompt.section.constraints": "Constraints",
304
- "ai.prompt.section.sourceLayers": "Source Priority Protocol (must follow)",
305
- "ai.prompt.section.privacy": "Privacy Protection",
306
- "ai.prompt.section.answerModes": "Answer Mode Guide (follow detected mode)",
307
- "ai.prompt.section.preOutputChecks": "Pre-Output Checks (execute mentally, do not output steps)",
308
- "ai.semiStatic.blogOverview": "Blog Overview",
309
- "ai.semiStatic.totalPosts": "{count} posts total",
310
- "ai.semiStatic.mainCategories": "Main categories: {categories}",
311
- "ai.semiStatic.latestArticles": "Latest Posts"
312
- },
313
- zh: {
314
- // Reasoning UI
315
- "ai.reasoning.thinking": "\u601D\u8003\u4E2D...",
316
- "ai.reasoning.viewReasoning": "\u67E5\u770B\u601D\u8003\u8FC7\u7A0B",
317
- "ai.reasoning.waiting": "\u7B49\u5F85\u601D\u8003...",
318
- // Error messages
319
- "ai.error.network": "\u7F51\u7EDC\u8FDE\u63A5\u5931\u8D25\uFF0C\u8BF7\u68C0\u67E5\u7F51\u7EDC",
320
- "ai.error.aborted": "\u8BF7\u6C42\u5DF2\u53D6\u6D88",
321
- "ai.error.rateLimit": "\u8BF7\u6C42\u592A\u9891\u7E41\uFF0C\u8BF7\u7A0D\u540E\u518D\u8BD5",
322
- "ai.error.unavailable": "AI \u670D\u52A1\u6682\u65F6\u4E0D\u53EF\u7528",
323
- "ai.error.generic": "\u51FA\u4E86\u70B9\u95EE\u9898\uFF0C\u8BF7\u7A0D\u540E\u518D\u8BD5",
324
- "ai.error.format": "\u8BF7\u6C42\u683C\u5F0F\u9519\u8BEF",
325
- // UI labels
326
- "ai.placeholder": "\u8F93\u5165\u4F60\u7684\u95EE\u9898...",
327
- "ai.clear": "\u6E05\u9664",
328
- "ai.clearConversation": "\u6E05\u9664\u5BF9\u8BDD",
329
- "ai.close": "\u5173\u95ED",
330
- "ai.closeChat": "\u5173\u95ED\u804A\u5929",
331
- "ai.retry": "\u91CD\u8BD5",
332
- "ai.status.searching": "\u641C\u7D22\u4E2D...",
333
- "ai.status.generating": "\u6B63\u5728\u751F\u6210\u56DE\u7B54...",
334
- "ai.status.found": "\u627E\u5230 {count} \u7BC7\u76F8\u5173\u5185\u5BB9",
335
- "ai.status.citation": "\u5DF2\u57FA\u4E8E\u516C\u5F00\u8BB0\u5F55\u76F4\u63A5\u7ED9\u51FA\u56DE\u7B54",
336
- "ai.status.fallback": "AI \u670D\u52A1\u4E0D\u53EF\u7528\uFF0C\u4F7F\u7528\u6F14\u793A\u6A21\u5F0F\u56DE\u590D",
337
- // Quick prompts
338
- "ai.prompt.techStack": "\u8FD9\u4E2A\u535A\u5BA2\u7528\u4E86\u4EC0\u4E48\u6280\u672F\uFF1F",
339
- "ai.prompt.recommend": "\u6709\u54EA\u4E9B\u6587\u7AE0\u63A8\u8350\uFF1F",
340
- "ai.prompt.build": "\u600E\u4E48\u642D\u5EFA\u7C7B\u4F3C\u7684\u535A\u5BA2\uFF1F",
341
- "ai.prompt.summarize": "\u603B\u7ED3\u4E00\u4E0B\u300A{title}\u300B\u7684\u6838\u5FC3\u89C2\u70B9",
342
- "ai.prompt.explain": "\u89E3\u91CA\u4E00\u4E0B\u300C{point}\u300D",
343
- "ai.prompt.related": "\u8FD9\u7BC7\u6587\u7AE0\u548C\u54EA\u4E9B\u5185\u5BB9\u76F8\u5173\uFF1F",
344
- // Welcome messages
345
- "ai.welcome.reading": "\u6211\u5728\u7ED3\u5408\u300A{title}\u300B\u966A\u4F60\u9605\u8BFB\u3002\n\u4F60\u53EF\u4EE5\u8BA9\u6211\u603B\u7ED3\u8FD9\u7BC7\u6587\u7AE0\u3001\u89E3\u91CA\u67D0\u4E2A\u89C2\u70B9\uFF0C\u6216\u8005\u987A\u7740\u8FD9\u7BC7\u6587\u7AE0\u7EE7\u7EED\u5EF6\u4F38\u5230\u76F8\u5173\u4E3B\u9898\u3002",
346
- "ai.welcome.canHelp": "\u4F60\u597D\uFF01\u6211\u662F\u535A\u5BA2 AI \u52A9\u624B\uFF0C\u95EE\u6211\u4EFB\u4F55\u5173\u4E8E\u535A\u5BA2\u5185\u5BB9\u7684\u95EE\u9898\uFF0C\u6211\u53EF\u4EE5\u5E2E\u4F60\u627E\u5230\u76F8\u5173\u6587\u7AE0\u3002",
347
- "ai.welcome.greeting": "\u4F60\u597D\uFF01\u6211\u662F\u535A\u5BA2 AI \u52A9\u624B\u3002",
348
- "ai.welcome.demo": "\u6211\u76EE\u524D\u5728 Demo \u6A21\u5F0F\u4E0B\uFF0C\u53EF\u4EE5\u63A8\u8350\u535A\u5BA2\u6587\u7AE0\u548C\u5916\u90E8\u8D44\u6E90\u3002",
349
- "ai.welcome.demoHint": "\u542F\u7528\u5B8C\u6574 AI \u529F\u80FD\uFF08RAG \u641C\u7D22\u589E\u5F3A\uFF09\u9700\u8981\u914D\u7F6E AI_BASE_URL \u548C AI_API_KEY \u73AF\u5883\u53D8\u91CF\u3002",
350
- "ai.welcome.demoPrompt": "\u8BD5\u8BD5\uFF1A\u300C\u6709\u54EA\u4E9B\u6587\u7AE0\u63A8\u8350\uFF1F\u300D\u6216\u300C\u600E\u4E48\u642D\u5EFA\u7C7B\u4F3C\u7684\u535A\u5BA2\uFF1F\u300D",
351
- // Header
352
- "ai.header.reading": "\u6B63\u5728\u9605\u8BFB\uFF1A",
353
- "ai.header.mode": "\u6F14\u793A",
354
- // Assistant branding
355
- "ai.assistantName": "\u535A\u5BA2\u5206\u8EAB",
356
- "ai.status.live": "\u5728\u7EBF",
357
- // Additional error messages
358
- "ai.error.emptyMessage": "\u6D88\u606F\u4E0D\u80FD\u4E3A\u7A7A\u3002",
359
- "ai.error.emptyContent": "\u6D88\u606F\u5185\u5BB9\u4E0D\u80FD\u4E3A\u7A7A\u3002",
360
- "ai.error.inputTooLong": "\u6D88\u606F\u8FC7\u957F\uFF0C\u6700\u591A {max} \u5B57\u3002",
361
- "ai.error.timeout": "\u54CD\u5E94\u8D85\u65F6\uFF0C\u8BF7\u91CD\u8BD5\u6216\u7B80\u5316\u95EE\u9898\u3002",
362
- // Rate limit messages
363
- "ai.error.rateLimit.burst": "\u8BF7\u6C42\u592A\u9891\u7E41\uFF0C\u8BF7\u7A0D\u540E\u518D\u8BD5\u3002",
364
- "ai.error.rateLimit.sustained": "\u8BF7\u6C42\u6B21\u6570\u8FC7\u591A\uFF0C\u8BF7\u4E00\u5206\u949F\u540E\u518D\u8BD5\u3002",
365
- "ai.error.rateLimit.daily": "\u4ECA\u65E5\u5BF9\u8BDD\u6B21\u6570\u5DF2\u8FBE\u4E0A\u9650\uFF0C\u8BF7\u660E\u5929\u518D\u6765\u3002",
366
- "ai.error.noOutput": "\u62B1\u6B49\uFF0C\u6211\u65E0\u6CD5\u751F\u6210\u6709\u6548\u7684\u56DE\u7B54\u3002\u8BF7\u5C1D\u8BD5\u6362\u4E00\u79CD\u65B9\u5F0F\u63D0\u95EE\u3002",
367
- "ai.prompt.section.responsibilities": "\u4F60\u7684\u804C\u8D23",
368
- "ai.prompt.section.format": "\u56DE\u7B54\u683C\u5F0F",
369
- "ai.prompt.section.principles": "\u63A8\u8350\u539F\u5219",
370
- "ai.prompt.section.constraints": "\u7EA6\u675F",
371
- "ai.prompt.section.sourceLayers": "\u6765\u6E90\u5206\u5C42\u534F\u8BAE\uFF08\u5FC5\u987B\u9075\u5B88\uFF09",
372
- "ai.prompt.section.privacy": "\u9690\u79C1\u4FDD\u62A4",
373
- "ai.prompt.section.answerModes": "\u56DE\u7B54\u6A21\u5F0F\u6307\u5BFC\uFF08\u6309\u68C0\u6D4B\u5230\u7684\u6A21\u5F0F\u6267\u884C\uFF09",
374
- "ai.prompt.section.preOutputChecks": "\u8F93\u51FA\u524D\u68C0\u67E5\uFF08\u5728\u5FC3\u91CC\u6267\u884C\uFF0C\u4E0D\u8F93\u51FA\u6B65\u9AA4\uFF09",
375
- "ai.semiStatic.blogOverview": "\u535A\u5BA2\u6982\u51B5",
376
- "ai.semiStatic.totalPosts": "\u5171\u6709 {count} \u7BC7\u6587\u7AE0",
377
- "ai.semiStatic.mainCategories": "\u4E3B\u8981\u5206\u7C7B\uFF1A{categories}",
378
- "ai.semiStatic.latestArticles": "\u6700\u65B0\u6587\u7AE0"
379
- }
380
- };
381
- function t(key, lang = "zh", vars) {
382
- const l = lang === "zh" ? "zh" : "en";
383
- let text = translations[l]?.[key] ?? translations["en"][key] ?? key;
384
- if (vars) {
385
- for (const [k, v] of Object.entries(vars)) {
386
- text = text.replace(new RegExp(`\\{${k}\\}`, "g"), String(v));
387
- }
388
- }
389
- return text;
390
- }
391
- function getLang(lang) {
392
- return lang === "zh" ? "zh" : "en";
393
- }
394
-
395
- // src/components/ChatPanel.tsx
396
- var MIN_SEND_INTERVAL_MS = 500;
397
- var TYPEWRITER_SPEED_MS = 25;
398
- var TYPEWRITER_BATCH_SIZE = 1;
399
- function useTypewriter(fullText, isStreaming) {
400
- const [displayedLength, setDisplayedLength] = useState(0);
401
- const prevFullTextRef = useRef(fullText);
402
- const prevStreamingRef = useRef(isStreaming);
403
- const animationRef = useRef(null);
404
- useEffect(() => {
405
- if (fullText !== prevFullTextRef.current && !fullText.startsWith(prevFullTextRef.current)) {
406
- setDisplayedLength(0);
407
- }
408
- prevFullTextRef.current = fullText;
409
- }, [fullText]);
410
- useEffect(() => {
411
- if (!isStreaming && prevStreamingRef.current) {
412
- setDisplayedLength(fullText.length);
413
- if (animationRef.current) {
414
- cancelAnimationFrame(animationRef.current);
415
- animationRef.current = null;
416
- }
417
- }
418
- prevStreamingRef.current = isStreaming;
419
- }, [isStreaming, fullText.length]);
420
- useEffect(() => {
421
- if (!isStreaming) return;
422
- let lastTime = performance.now();
423
- const animate = (currentTime) => {
424
- const elapsed = currentTime - lastTime;
425
- if (elapsed >= TYPEWRITER_SPEED_MS) {
426
- setDisplayedLength((prev) => {
427
- const targetLength = fullText.length;
428
- if (prev >= targetLength) return prev;
429
- const behind = targetLength - prev;
430
- const speed = behind > 20 ? Math.min(behind, 5) : TYPEWRITER_BATCH_SIZE;
431
- return Math.min(prev + speed, targetLength);
432
- });
433
- lastTime = currentTime;
434
- }
435
- animationRef.current = requestAnimationFrame(animate);
436
- };
437
- animationRef.current = requestAnimationFrame(animate);
438
- return () => {
439
- if (animationRef.current) {
440
- cancelAnimationFrame(animationRef.current);
441
- animationRef.current = null;
442
- }
443
- };
444
- }, [isStreaming, fullText]);
445
- return fullText.slice(0, displayedLength) || (isStreaming ? "" : fullText);
446
- }
447
- function generateSessionId(articleContext) {
448
- if (articleContext?.slug) return `article:${articleContext.slug}`;
449
- if (typeof crypto !== "undefined" && crypto.randomUUID) return crypto.randomUUID();
450
- return `${Date.now().toString(36)}-${Math.random().toString(36).slice(2)}`;
451
- }
452
- function getTextFromMessage(message) {
453
- const parts = message.parts ?? [];
454
- return Array.isArray(parts) ? parts.filter((p) => p.type === "text").map((p) => p.text).join("") : "";
455
- }
456
- var QUICK_PROMPTS_ZH = [
457
- t("ai.prompt.techStack", "zh"),
458
- t("ai.prompt.recommend", "zh"),
459
- t("ai.prompt.build", "zh")
460
- ];
461
- var QUICK_PROMPTS_EN = [
462
- t("ai.prompt.techStack", "en"),
463
- t("ai.prompt.recommend", "en"),
464
- t("ai.prompt.build", "en")
465
- ];
466
- function getQuickPrompts(lang, articleContext) {
467
- const l = getLang(lang);
468
- if (!articleContext) return l === "zh" ? QUICK_PROMPTS_ZH : QUICK_PROMPTS_EN;
469
- if (l === "zh") {
470
- const prompts2 = [t("ai.prompt.summarize", "zh", { title: articleContext.title })];
471
- if (articleContext.keyPoints?.length) {
472
- prompts2.push(t("ai.prompt.explain", "zh", { point: articleContext.keyPoints[0] }));
473
- }
474
- prompts2.push(t("ai.prompt.related", "zh"));
475
- return prompts2;
476
- }
477
- const prompts = [t("ai.prompt.summarize", "en", { title: articleContext.title })];
478
- if (articleContext.keyPoints?.length) {
479
- prompts.push(t("ai.prompt.explain", "en", { point: articleContext.keyPoints[0] }));
480
- }
481
- prompts.push(t("ai.prompt.related", "en"));
482
- return prompts;
483
- }
484
- function buildWelcomeMessage(config, articleContext) {
485
- const lang = getLang(config.lang);
486
- let text;
487
- if (articleContext) {
488
- text = config.welcomeMessage ?? t("ai.welcome.reading", lang, { title: articleContext.title });
489
- } else {
490
- text = config.welcomeMessage ?? t("ai.welcome.canHelp", lang);
491
- }
492
- return {
493
- id: "welcome",
494
- role: "assistant",
495
- parts: [{ type: "text", text }]
496
- };
497
- }
498
- function parseErrorMessage(error, lang = "zh") {
499
- const l = getLang(lang);
500
- try {
501
- const parsed = JSON.parse(error.message);
502
- if (parsed?.error) return parsed.error;
503
- } catch {
504
- }
505
- const msg = error.message;
506
- if (msg.includes("Failed to fetch") || msg.includes("NetworkError")) return t("ai.error.network", l);
507
- if (msg.includes("aborted")) return t("ai.error.aborted", l);
508
- if (msg.includes("429") || msg.includes("rate")) return t("ai.error.rateLimit", l);
509
- if (msg.includes("503") || msg.includes("unavailable")) return t("ai.error.unavailable", l);
510
- return t("ai.error.generic", l);
511
- }
512
- function isRetryable(error) {
513
- try {
514
- const parsed = JSON.parse(error.message);
515
- if (typeof parsed?.retryable === "boolean") return parsed.retryable;
516
- } catch {
517
- }
518
- return true;
519
- }
520
- function parseInlineMarkdown(text) {
521
- const parts = [];
522
- const re = /\[([^\]]+)\]\(([^)]+)\)|\*\*([^*]+)\*\*|`([^`]+)`/g;
523
- let lastIndex = 0;
524
- let match;
525
- while ((match = re.exec(text)) !== null) {
526
- if (match.index > lastIndex) {
527
- parts.push({ type: "text", text: text.slice(lastIndex, match.index) });
528
- }
529
- if (match[1] && match[2]) parts.push({ type: "link", label: match[1], url: match[2] });
530
- else if (match[3]) parts.push({ type: "bold", text: match[3] });
531
- else if (match[4]) parts.push({ type: "code", text: match[4] });
532
- lastIndex = match.index + match[0].length;
533
- }
534
- if (lastIndex < text.length) parts.push({ type: "text", text: text.slice(lastIndex) });
535
- return parts;
536
- }
537
- function InlineRichText({ text }) {
538
- const parts = useMemo(() => parseInlineMarkdown(text), [text]);
539
- return /* @__PURE__ */ h("span", null, parts.map((p, i) => {
540
- if (p.type === "link") {
541
- const isExternal = p.url.startsWith("http");
542
- return /* @__PURE__ */ h(
543
- "a",
544
- {
545
- key: i,
546
- href: p.url,
547
- class: "inline-flex items-center gap-0.5 font-medium text-accent underline decoration-accent/30 underline-offset-2 transition-colors hover:decoration-accent",
548
- target: isExternal ? "_blank" : void 0,
549
- rel: isExternal ? "noopener noreferrer" : void 0
550
- },
551
- p.label,
552
- isExternal && /* @__PURE__ */ h(ExternalLinkIcon, null)
553
- );
554
- }
555
- if (p.type === "bold") return /* @__PURE__ */ h("strong", { key: i, class: "font-semibold" }, p.text);
556
- if (p.type === "code") return /* @__PURE__ */ h("code", { key: i, class: "rounded bg-muted/60 px-1 py-0.5 text-[13px] font-mono" }, p.text);
557
- return /* @__PURE__ */ h("span", { key: i }, p.text);
558
- }));
559
- }
560
- function parseBlocks(text) {
561
- const lines = text.split("\n");
562
- const blocks = [];
563
- let i = 0;
564
- while (i < lines.length) {
565
- const line = lines[i];
566
- if (line.startsWith("```")) {
567
- const lang = line.slice(3).trim();
568
- const codeLines = [];
569
- i++;
570
- while (i < lines.length && !lines[i].startsWith("```")) {
571
- codeLines.push(lines[i]);
572
- i++;
573
- }
574
- if (i < lines.length) i++;
575
- blocks.push({ type: "code-block", content: codeLines.join("\n"), lang: lang || void 0 });
576
- continue;
577
- }
578
- if (line.startsWith("> ") || line === ">") {
579
- const quoteLines = [];
580
- while (i < lines.length && (lines[i].startsWith("> ") || lines[i] === ">")) {
581
- quoteLines.push(lines[i].replace(/^>\s?/, ""));
582
- i++;
583
- }
584
- blocks.push({ type: "blockquote", content: quoteLines.join("\n") });
585
- continue;
586
- }
587
- if (/^[-*]\s/.test(line)) {
588
- const items = [];
589
- while (i < lines.length && /^[-*]\s/.test(lines[i])) {
590
- items.push(lines[i].replace(/^[-*]\s/, ""));
591
- i++;
592
- }
593
- blocks.push({ type: "list", content: "", ordered: false, items });
594
- continue;
595
- }
596
- if (/^\d+\.\s/.test(line)) {
597
- const items = [];
598
- while (i < lines.length && /^\d+\.\s/.test(lines[i])) {
599
- items.push(lines[i].replace(/^\d+\.\s/, ""));
600
- i++;
601
- }
602
- blocks.push({ type: "list", content: "", ordered: true, items });
603
- continue;
604
- }
605
- if (!line.trim()) {
606
- i++;
607
- continue;
608
- }
609
- const paraLines = [];
610
- while (i < lines.length && lines[i].trim() && !lines[i].startsWith("```") && !lines[i].startsWith("> ") && lines[i] !== ">" && !/^[-*]\s/.test(lines[i]) && !/^\d+\.\s/.test(lines[i])) {
611
- paraLines.push(lines[i]);
612
- i++;
613
- }
614
- if (paraLines.length) {
615
- blocks.push({ type: "paragraph", content: paraLines.join("\n") });
616
- }
617
- }
618
- return blocks;
619
- }
620
- function RichText({ text }) {
621
- const blocks = useMemo(() => parseBlocks(text), [text]);
622
- return /* @__PURE__ */ h("div", { class: "space-y-2" }, blocks.map((block, i) => {
623
- switch (block.type) {
624
- case "code-block":
625
- return /* @__PURE__ */ h("pre", { key: i, class: "overflow-x-auto rounded-md bg-muted/60 px-3 py-2 text-[12px] leading-relaxed font-mono" }, /* @__PURE__ */ h("code", null, block.content));
626
- case "blockquote":
627
- return /* @__PURE__ */ h("blockquote", { key: i, class: "border-l-2 border-accent/40 pl-3 text-foreground-soft italic" }, /* @__PURE__ */ h(InlineRichText, { text: block.content }));
628
- case "list":
629
- if (block.ordered) {
630
- return /* @__PURE__ */ h("ol", { key: i, class: "list-decimal space-y-0.5 pl-5" }, block.items?.map((item, j) => /* @__PURE__ */ h("li", { key: j }, /* @__PURE__ */ h(InlineRichText, { text: item }))));
631
- }
632
- return /* @__PURE__ */ h("ul", { key: i, class: "list-disc space-y-0.5 pl-5" }, block.items?.map((item, j) => /* @__PURE__ */ h("li", { key: j }, /* @__PURE__ */ h(InlineRichText, { text: item }))));
633
- case "paragraph":
634
- default:
635
- return /* @__PURE__ */ h("p", { key: i, class: "whitespace-pre-wrap" }, /* @__PURE__ */ h(InlineRichText, { text: block.content }));
636
- }
637
- }));
638
- }
639
- function ReasoningBlock({ text, isStreaming, lang = "zh" }) {
640
- const isEmpty = text.length === 0;
641
- const l = getLang(lang);
642
- return /* @__PURE__ */ h("details", { class: "group rounded-lg border border-border/50 bg-muted/30 overflow-hidden", open: isStreaming || !isEmpty }, /* @__PURE__ */ h("summary", { class: "flex cursor-pointer items-center gap-1.5 px-2.5 py-1.5 text-[11px] font-medium text-foreground-soft transition-colors hover:bg-muted/50 hover:text-foreground" }, /* @__PURE__ */ h("svg", { class: "size-3.5 transition-transform group-open:rotate-90", viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", "stroke-width": "2", "stroke-linecap": "round", "stroke-linejoin": "round" }, /* @__PURE__ */ h("path", { d: "m9 18 6-6-6-6" })), /* @__PURE__ */ h("span", { class: "flex items-center gap-1" }, isStreaming ? /* @__PURE__ */ h("svg", { class: "size-3 animate-spin", viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", "stroke-width": "2" }, /* @__PURE__ */ h("path", { d: "M21 12a9 9 0 1 1-6.219-8.56" })) : /* @__PURE__ */ h("svg", { class: "size-3", viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", "stroke-width": "2", "stroke-linecap": "round", "stroke-linejoin": "round" }, /* @__PURE__ */ h("circle", { cx: "12", cy: "12", r: "10" }), /* @__PURE__ */ h("path", { d: "M12 16v-4" }), /* @__PURE__ */ h("path", { d: "M12 8h.01" })), isStreaming && isEmpty ? t("ai.reasoning.thinking", l) : isStreaming ? t("ai.reasoning.thinking", l).replace("...", "") : t("ai.reasoning.viewReasoning", l))), /* @__PURE__ */ h("div", { class: "border-t border-border/30 bg-background/50 px-2.5 py-2" }, isEmpty && isStreaming ? /* @__PURE__ */ h("div", { class: "flex items-center gap-2 text-[11px] text-foreground-soft" }, /* @__PURE__ */ h("span", { class: "inline-flex gap-1" }, /* @__PURE__ */ h("span", { class: "size-1.5 animate-bounce rounded-full bg-foreground-soft [animation-delay:0ms]" }), /* @__PURE__ */ h("span", { class: "size-1.5 animate-bounce rounded-full bg-foreground-soft [animation-delay:150ms]" }), /* @__PURE__ */ h("span", { class: "size-1.5 animate-bounce rounded-full bg-foreground-soft [animation-delay:300ms]" })), /* @__PURE__ */ h("span", null, t("ai.reasoning.waiting", l))) : /* @__PURE__ */ h("pre", { class: "whitespace-pre-wrap text-[11px] leading-relaxed text-foreground-soft font-mono" }, text)));
643
- }
644
- function AssistantMessage({ message, isStreaming, lang = "zh" }) {
645
- const fullText = getTextFromMessage(message);
646
- const displayedText = useTypewriter(fullText, isStreaming ?? false);
647
- const reasoningParts = message.parts.filter((p) => p.type === "reasoning");
648
- const reasoningFullText = reasoningParts.map((p) => p.text).join("");
649
- const reasoningDisplayed = useTypewriter(reasoningFullText, isStreaming ?? false);
650
- const hasReasoning = reasoningFullText.length > 0;
651
- const isWaitingForContent = isStreaming && !fullText && !reasoningFullText;
652
- const sources = message.parts.filter((p) => p.type === "source-url" || p.type === "source-document");
653
- if (isWaitingForContent) {
654
- return /* @__PURE__ */ h("div", { class: "space-y-1.5" }, /* @__PURE__ */ h(ReasoningBlock, { text: "", isStreaming: true, lang }));
655
- }
656
- if (!fullText && !hasReasoning) return null;
657
- return /* @__PURE__ */ h("div", { class: "space-y-1.5" }, hasReasoning && /* @__PURE__ */ h(ReasoningBlock, { text: reasoningDisplayed, isStreaming, lang }), displayedText && /* @__PURE__ */ h(RichText, { text: displayedText }), !isStreaming && sources.length > 0 && /* @__PURE__ */ h("div", { class: "mt-2 flex flex-wrap gap-1.5" }, sources.map((s, i) => {
658
- const part = s;
659
- return /* @__PURE__ */ h(
660
- "a",
661
- {
662
- key: i,
663
- href: part.url ?? "#",
664
- class: "inline-flex items-center gap-1 rounded-md border border-border bg-muted/30 px-2 py-0.5 text-[11px] text-foreground-soft transition-colors hover:border-accent/40 hover:text-foreground",
665
- target: "_blank",
666
- rel: "noopener noreferrer"
667
- },
668
- /* @__PURE__ */ h("svg", { class: "size-2.5 opacity-50", viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", "stroke-width": "2" }, /* @__PURE__ */ h("path", { d: "M10 13a5 5 0 0 0 7.54.54l3-3a5 5 0 0 0-7.07-7.07l-1.72 1.71" }), /* @__PURE__ */ h("path", { d: "M14 11a5 5 0 0 0-7.54-.54l-3 3a5 5 0 0 0 7.07 7.07l1.71-1.71" })),
669
- part.title ?? "Source"
670
- );
671
- })));
672
- }
673
- function ExternalLinkIcon() {
674
- return /* @__PURE__ */ h("svg", { class: "inline-block size-3 shrink-0 opacity-50", viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", "stroke-width": "2", "stroke-linecap": "round", "stroke-linejoin": "round" }, /* @__PURE__ */ h("path", { d: "M18 13v6a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2V8a2 2 0 0 1 2-2h6" }), /* @__PURE__ */ h("polyline", { points: "15 3 21 3 21 9" }), /* @__PURE__ */ h("line", { x1: "10", y1: "14", x2: "21", y2: "3" }));
675
- }
676
- function BotAvatar() {
677
- return /* @__PURE__ */ h("div", { class: "flex size-5.5 shrink-0 items-center justify-center rounded-full bg-accent/15 mt-0.5" }, /* @__PURE__ */ h(BotIcon, { class: "size-3 text-accent" }));
678
- }
679
- function BotIcon({ class: cls }) {
680
- return /* @__PURE__ */ h("svg", { class: cls, viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", "stroke-width": "2", "stroke-linecap": "round", "stroke-linejoin": "round" }, /* @__PURE__ */ h("path", { d: "M12 8V4H8" }), /* @__PURE__ */ h("rect", { width: "16", height: "12", x: "4", y: "8", rx: "2" }), /* @__PURE__ */ h("path", { d: "M2 14h2" }), /* @__PURE__ */ h("path", { d: "M20 14h2" }), /* @__PURE__ */ h("path", { d: "M15 13v2" }), /* @__PURE__ */ h("path", { d: "M9 13v2" }));
681
- }
682
- function TypingDots({ statusMessage }) {
683
- return /* @__PURE__ */ h("div", { class: "flex items-center gap-2" }, /* @__PURE__ */ h("span", { class: "inline-flex gap-1" }, /* @__PURE__ */ h("span", { class: "size-1.5 animate-bounce rounded-full bg-foreground-soft [animation-delay:0ms]" }), /* @__PURE__ */ h("span", { class: "size-1.5 animate-bounce rounded-full bg-foreground-soft [animation-delay:150ms]" }), /* @__PURE__ */ h("span", { class: "size-1.5 animate-bounce rounded-full bg-foreground-soft [animation-delay:300ms]" })), statusMessage && /* @__PURE__ */ h("span", { class: "text-[11px] text-foreground-soft" }, statusMessage));
684
- }
685
- function useMockChat(lang) {
686
- const [messages, setMessages] = useState([]);
687
- const [isStreaming, setIsStreaming] = useState(false);
688
- const sendMessage = useCallback(async (text) => {
689
- const userMsg = { id: `u-${Date.now()}`, role: "user", text };
690
- const assistantId = `a-${Date.now()}`;
691
- const assistantMsg = { id: assistantId, role: "assistant", text: "", streaming: true };
692
- setMessages((prev) => [...prev, userMsg, assistantMsg]);
693
- setIsStreaming(true);
694
- const stream = createMockStream(getMockResponse(text, lang));
695
- const reader = stream.getReader();
696
- let accumulated = "";
697
- try {
698
- while (true) {
699
- const { done, value } = await reader.read();
700
- if (done) break;
701
- accumulated += value;
702
- setMessages((prev) => prev.map((m) => m.id === assistantId ? { ...m, text: accumulated } : m));
703
- }
704
- } finally {
705
- setMessages((prev) => prev.map((m) => m.id === assistantId ? { ...m, streaming: false } : m));
706
- setIsStreaming(false);
707
- }
708
- }, [lang]);
709
- const clear = useCallback(() => setMessages([]), []);
710
- return { messages, isStreaming, sendMessage, clear };
711
- }
712
- function ChatPanel({ open, onClose, config, articleContext }) {
713
- const isMockMode = config.mockMode || !config.apiEndpoint;
714
- const lang = getLang(config.lang);
715
- const placeholder = config.placeholder ?? t("ai.placeholder", lang);
716
- const sessionId = useMemo(() => generateSessionId(articleContext), [articleContext]);
717
- const panelRef = useRef(null);
718
- const inputRef = useRef(null);
719
- const messagesEndRef = useRef(null);
720
- const lastSendRef = useRef(0);
721
- const [inputValue, setInputValue] = useState("");
722
- const [cooldown, setCooldown] = useState(false);
723
- const [statusMessage, setStatusMessage] = useState();
724
- const quickPrompts = useMemo(() => getQuickPrompts(lang, articleContext), [lang, articleContext]);
725
- const welcomeMessage = useMemo(() => buildWelcomeMessage(config, articleContext), [config, articleContext]);
726
- const transport = useMemo(() => new DefaultChatTransport({
727
- api: config.apiEndpoint ?? "/api/chat",
728
- prepareSendMessagesRequest: ({ id, messages: msgs }) => ({
729
- headers: { "x-session-id": sessionId },
730
- body: {
731
- id,
732
- messages: msgs,
733
- lang,
734
- context: articleContext ? { scope: "article", article: articleContext } : { scope: "global" }
735
- }
736
- })
737
- }), [config.apiEndpoint, sessionId, articleContext, lang]);
738
- const {
739
- messages: liveMessages,
740
- sendMessage: liveSendMessage,
741
- setMessages: liveSetMessages,
742
- regenerate,
743
- status: liveStatus,
744
- error: liveError
745
- } = useChat({
746
- transport,
747
- onError: (err) => {
748
- console.error("[ChatPanel] Chat error:", err.message);
749
- }
750
- });
751
- useEffect(() => {
752
- if (liveMessages.length === 0) {
753
- liveSetMessages([welcomeMessage]);
754
- }
755
- }, []);
756
- const mockChat = useMockChat(lang);
757
- const isStreaming = isMockMode ? mockChat.isStreaming : liveStatus === "streaming" || liveStatus === "submitted";
758
- const error = isMockMode ? null : liveError;
759
- useEffect(() => {
760
- if (isMockMode || !liveMessages.length) return;
761
- for (let i = liveMessages.length - 1; i >= 0; i--) {
762
- const msg = liveMessages[i];
763
- if (msg.role === "assistant" && isChatStatusData(msg.metadata)) {
764
- setStatusMessage(msg.metadata.message);
765
- return;
766
- }
767
- }
768
- setStatusMessage(void 0);
769
- }, [liveMessages, isMockMode]);
770
- useEffect(() => {
771
- messagesEndRef.current?.scrollIntoView({ behavior: "smooth" });
772
- }, [liveMessages, mockChat.messages]);
773
- useEffect(() => {
774
- if (open) setTimeout(() => inputRef.current?.focus(), 150);
775
- }, [open]);
776
- const autoResize = useCallback(() => {
777
- const el = inputRef.current;
778
- if (!el) return;
779
- el.style.height = "auto";
780
- el.style.height = Math.min(el.scrollHeight, 96) + "px";
781
- }, []);
782
- const doSend = useCallback((text) => {
783
- const trimmed = text.trim();
784
- if (!trimmed || isStreaming || cooldown) return;
785
- const now = Date.now();
786
- if (now - lastSendRef.current < MIN_SEND_INTERVAL_MS) return;
787
- lastSendRef.current = now;
788
- setCooldown(true);
789
- setTimeout(() => setCooldown(false), MIN_SEND_INTERVAL_MS);
790
- setInputValue("");
791
- if (inputRef.current) inputRef.current.style.height = "auto";
792
- if (isMockMode) {
793
- mockChat.sendMessage(trimmed);
794
- } else {
795
- liveSendMessage({ text: trimmed });
796
- }
797
- }, [isStreaming, cooldown, isMockMode, mockChat, liveSendMessage]);
798
- const handleSend = useCallback(() => doSend(inputValue), [doSend, inputValue]);
799
- const handleKeyDown = useCallback((e) => {
800
- if (e.key === "Enter" && !e.shiftKey) {
801
- e.preventDefault();
802
- handleSend();
803
- }
804
- }, [handleSend]);
805
- const handleClear = useCallback(() => {
806
- if (isMockMode) {
807
- mockChat.clear();
808
- } else {
809
- liveSetMessages([welcomeMessage]);
810
- }
811
- setInputValue("");
812
- setStatusMessage(void 0);
813
- }, [isMockMode, mockChat, liveSetMessages, welcomeMessage]);
814
- if (!open) return null;
815
- const renderMockMessages = () => /* @__PURE__ */ h(Fragment, null, mockChat.messages.length === 0 && /* @__PURE__ */ h("div", { class: "space-y-3" }, /* @__PURE__ */ h("div", { class: "flex items-start gap-2.5" }, /* @__PURE__ */ h(BotAvatar, null), /* @__PURE__ */ h("p", { class: "min-w-0 flex-1 pt-0.5 text-[13px] leading-relaxed text-foreground" }, getTextFromMessage(welcomeMessage))), /* @__PURE__ */ h("div", { class: "flex flex-wrap gap-1.5 pl-8" }, quickPrompts.map((q) => /* @__PURE__ */ h(
816
- "button",
817
- {
818
- key: q,
819
- type: "button",
820
- onClick: () => doSend(q),
821
- class: "rounded-lg border border-border bg-muted/30 px-2.5 py-1 text-[12px] text-foreground-soft transition-colors hover:border-accent/40 hover:bg-accent/10 hover:text-foreground"
822
- },
823
- q
824
- )))), mockChat.messages.map((msg) => /* @__PURE__ */ h("div", { key: msg.id, class: msg.role === "user" ? "flex justify-end" : "flex items-start gap-2.5" }, msg.role === "assistant" && /* @__PURE__ */ h(BotAvatar, null), /* @__PURE__ */ h("div", { class: msg.role === "user" ? "max-w-[82%] rounded-2xl rounded-br-md bg-accent px-3 py-2 text-[13px] leading-relaxed text-background" : "min-w-0 flex-1 pt-0.5 text-[13px] leading-relaxed text-foreground" }, msg.text ? msg.role === "assistant" ? /* @__PURE__ */ h(RichText, { text: msg.text }) : msg.text : msg.streaming ? /* @__PURE__ */ h(TypingDots, null) : null))));
825
- const renderLiveMessages = () => {
826
- const showQuickPrompts = liveMessages.length <= 1;
827
- const lastAssistantMsgId = [...liveMessages].reverse().find((m) => m.role === "assistant")?.id;
828
- const lastMessage = liveMessages[liveMessages.length - 1];
829
- const isWaitingForAssistant = isStreaming && lastMessage?.role === "user";
830
- return /* @__PURE__ */ h(Fragment, null, liveMessages.map((msg) => {
831
- if (msg.id === "welcome" && showQuickPrompts) {
832
- return /* @__PURE__ */ h("div", { key: msg.id, class: "space-y-3" }, /* @__PURE__ */ h("div", { class: "flex items-start gap-2.5" }, /* @__PURE__ */ h(BotAvatar, null), /* @__PURE__ */ h("p", { class: "min-w-0 flex-1 pt-0.5 text-[13px] leading-relaxed text-foreground" }, getTextFromMessage(msg))), /* @__PURE__ */ h("div", { class: "flex flex-wrap gap-1.5 pl-8" }, quickPrompts.map((q) => /* @__PURE__ */ h(
833
- "button",
834
- {
835
- key: q,
836
- type: "button",
837
- onClick: () => doSend(q),
838
- class: "rounded-lg border border-border bg-muted/30 px-2.5 py-1 text-[12px] text-foreground-soft transition-colors hover:border-accent/40 hover:bg-accent/10 hover:text-foreground"
839
- },
840
- q
841
- ))));
842
- }
843
- const text = getTextFromMessage(msg);
844
- const isAssistant = msg.role === "assistant";
845
- const isLastAssistantStreaming = isStreaming && msg.id === lastAssistantMsgId;
846
- return /* @__PURE__ */ h("div", { key: msg.id, class: msg.role === "user" ? "flex justify-end" : "flex items-start gap-2.5" }, isAssistant && /* @__PURE__ */ h(BotAvatar, null), /* @__PURE__ */ h("div", { class: msg.role === "user" ? "max-w-[82%] rounded-2xl rounded-br-md bg-accent px-3 py-2 text-[13px] leading-relaxed text-background" : "min-w-0 flex-1 pt-0.5 text-[13px] leading-relaxed text-foreground" }, isAssistant ? /* @__PURE__ */ h(AssistantMessage, { message: msg, isStreaming: isLastAssistantStreaming, lang }) : text));
847
- }), isWaitingForAssistant && /* @__PURE__ */ h("div", { class: "flex items-start gap-2.5" }, /* @__PURE__ */ h(BotAvatar, null), /* @__PURE__ */ h("div", { class: "min-w-0 flex-1 pt-0.5" }, /* @__PURE__ */ h(ReasoningBlock, { text: "", isStreaming: true, lang }))));
848
- };
849
- return /* @__PURE__ */ h(
850
- "div",
851
- {
852
- ref: panelRef,
853
- id: "ai-chat-panel",
854
- "data-ai-chat-panel": true,
855
- class: "fixed right-4 bottom-20 z-[90] flex w-[370px] max-w-[calc(100vw-2rem)] flex-col overflow-hidden rounded-2xl border border-border bg-background shadow-2xl sm:right-6 sm:bottom-20",
856
- style: { height: "min(520px, calc(100vh - 7rem))" }
857
- },
858
- /* @__PURE__ */ h("div", { class: "flex shrink-0 items-center justify-between border-b border-border px-3.5 py-2.5" }, /* @__PURE__ */ h("div", { class: "flex items-center gap-2" }, /* @__PURE__ */ h("div", { class: "flex size-6 shrink-0 items-center justify-center rounded-full bg-accent/15" }, /* @__PURE__ */ h(BotIcon, { class: "size-3 text-accent" })), /* @__PURE__ */ h("div", { class: "flex flex-col" }, /* @__PURE__ */ h("span", { class: "text-[13px] font-semibold text-foreground" }, t("ai.assistantName", lang)), articleContext && /* @__PURE__ */ h("span", { class: "max-w-[180px] truncate text-[10px] text-foreground-soft" }, t("ai.header.reading", lang), articleContext.title)), /* @__PURE__ */ h("span", { class: `rounded-full px-1.5 py-px text-[10px] font-medium ${isMockMode ? "bg-amber-500/15 text-amber-600 dark:text-amber-400" : "bg-green-500/15 text-green-600 dark:text-green-400"}` }, isMockMode ? t("ai.header.mode", lang) : t("ai.status.live", lang))), /* @__PURE__ */ h("div", { class: "flex items-center gap-0.5" }, /* @__PURE__ */ h(
859
- "button",
860
- {
861
- type: "button",
862
- onClick: handleClear,
863
- class: "rounded-md p-1 text-foreground-soft transition-colors hover:bg-muted/60 hover:text-foreground",
864
- title: t("ai.clear", lang)
865
- },
866
- /* @__PURE__ */ h("svg", { class: "size-3.5", viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", "stroke-width": "2", "stroke-linecap": "round", "stroke-linejoin": "round" }, /* @__PURE__ */ h("path", { d: "M3 6h18" }), /* @__PURE__ */ h("path", { d: "M19 6v14c0 1-1 2-2 2H7c-1 0-2-1-2-2V6" }), /* @__PURE__ */ h("path", { d: "M8 6V4c0-1 1-2 2-2h4c1 0 2 1 2 2v2" }))
867
- ), /* @__PURE__ */ h(
868
- "button",
869
- {
870
- type: "button",
871
- onClick: onClose,
872
- class: "rounded-md p-1 text-foreground-soft transition-colors hover:bg-muted/60 hover:text-foreground",
873
- title: t("ai.close", lang)
874
- },
875
- /* @__PURE__ */ h("svg", { class: "size-3.5", viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", "stroke-width": "2", "stroke-linecap": "round", "stroke-linejoin": "round" }, /* @__PURE__ */ h("path", { d: "M18 6 6 18" }), /* @__PURE__ */ h("path", { d: "m6 6 12 12" }))
876
- ))),
877
- /* @__PURE__ */ h("div", { class: "min-h-0 flex-1 overflow-y-auto overscroll-contain px-3.5 py-3 [scrollbar-width:thin]" }, /* @__PURE__ */ h("div", { class: "space-y-4" }, isMockMode ? renderMockMessages() : renderLiveMessages(), error && /* @__PURE__ */ h("div", { class: "flex items-start gap-2.5" }, /* @__PURE__ */ h(BotAvatar, null), /* @__PURE__ */ h("div", { class: "flex flex-col gap-1 pt-0.5" }, /* @__PURE__ */ h("p", { class: "text-[13px] text-amber-600 dark:text-amber-400" }, parseErrorMessage(error, lang)), isRetryable(error) && /* @__PURE__ */ h(
878
- "button",
879
- {
880
- type: "button",
881
- onClick: () => regenerate(),
882
- class: "self-start rounded-md border border-amber-500/30 px-2 py-0.5 text-[11px] text-amber-600 transition-colors hover:bg-amber-500/10 dark:text-amber-400"
883
- },
884
- t("ai.retry", lang)
885
- ))), /* @__PURE__ */ h("div", { ref: messagesEndRef }))),
886
- /* @__PURE__ */ h("div", { class: "shrink-0 border-t border-border px-3 pb-2.5 pt-2" }, /* @__PURE__ */ h("div", { class: "flex items-end gap-1.5 rounded-xl border border-border bg-muted/30 px-2.5 py-1.5 transition-colors focus-within:border-accent/40 focus-within:bg-background" }, /* @__PURE__ */ h(
887
- "textarea",
888
- {
889
- id: "ai-chat-input",
890
- ref: inputRef,
891
- rows: 1,
892
- value: inputValue,
893
- onInput: (e) => {
894
- setInputValue(e.target.value);
895
- autoResize();
896
- },
897
- onKeyDown: handleKeyDown,
898
- placeholder,
899
- maxLength: 500,
900
- class: "min-w-0 flex-1 resize-none bg-transparent py-0.5 text-[13px] leading-snug text-foreground outline-none placeholder:text-foreground-soft",
901
- style: { maxHeight: "96px" }
902
- }
903
- ), /* @__PURE__ */ h(
904
- "button",
905
- {
906
- type: "button",
907
- onClick: handleSend,
908
- disabled: !inputValue.trim() || isStreaming || cooldown,
909
- class: "mb-0.5 flex size-6 shrink-0 items-center justify-center rounded-md bg-accent text-background transition-all hover:bg-accent/90 disabled:cursor-not-allowed disabled:opacity-30"
910
- },
911
- isStreaming ? /* @__PURE__ */ h("svg", { class: "size-3 animate-spin", viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", "stroke-width": "2.5" }, /* @__PURE__ */ h("path", { d: "M21 12a9 9 0 1 1-6.219-8.56" })) : /* @__PURE__ */ h("svg", { class: "size-3", viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", "stroke-width": "2.5", "stroke-linecap": "round", "stroke-linejoin": "round" }, /* @__PURE__ */ h("path", { d: "m22 2-7 20-4-9-9-4Z" }), /* @__PURE__ */ h("path", { d: "M22 2 11 13" }))
912
- )))
913
- );
914
- }
915
-
916
- // src/components/AIChatContainer.tsx
4
+ import { ChatPanel } from "./ChatPanel.js";
917
5
  function AIChatContainer({ config, articleContext }) {
918
- const [open, setOpen] = useState2(false);
919
- const handleToggle = useCallback2(() => setOpen((prev) => !prev), []);
920
- const handleClose = useCallback2(() => setOpen(false), []);
921
- if (typeof window !== "undefined") {
6
+ const [open, setOpen] = useState(false);
7
+ const handleToggle = useCallback(() => setOpen((prev) => !prev), []);
8
+ const handleClose = useCallback(() => setOpen(false), []);
9
+ useEffect(() => {
922
10
  window.__aiChatToggle = handleToggle;
923
- }
11
+ }, [handleToggle]);
924
12
  return /* @__PURE__ */ h(
925
13
  ChatPanel,
926
14
  {