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