@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,926 +1,14 @@
|
|
|
1
|
-
|
|
2
|
-
import {
|
|
1
|
+
import { useState, useCallback, useEffect } from "preact/hooks";
|
|
2
|
+
import { h } from 'preact';
|
|
3
3
|
|
|
4
|
-
import {
|
|
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] =
|
|
919
|
-
const handleToggle =
|
|
920
|
-
const handleClose =
|
|
921
|
-
|
|
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
|
{
|