@lobehub/chat 1.124.0 → 1.124.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (145) hide show
  1. package/.env.example +5 -0
  2. package/.github/scripts/pr-comment.js +11 -2
  3. package/.github/workflows/desktop-pr-build.yml +86 -12
  4. package/.github/workflows/release-desktop-beta.yml +91 -20
  5. package/CHANGELOG.md +58 -0
  6. package/Dockerfile +2 -0
  7. package/Dockerfile.database +2 -0
  8. package/Dockerfile.pglite +2 -0
  9. package/apps/desktop/electron-builder.js +8 -4
  10. package/changelog/v1.json +21 -0
  11. package/docs/self-hosting/environment-variables/model-provider.mdx +18 -0
  12. package/docs/self-hosting/environment-variables/model-provider.zh-CN.mdx +20 -0
  13. package/locales/ar/chat.json +2 -0
  14. package/locales/bg-BG/chat.json +2 -0
  15. package/locales/de-DE/chat.json +2 -0
  16. package/locales/en-US/chat.json +2 -0
  17. package/locales/es-ES/chat.json +2 -0
  18. package/locales/fa-IR/chat.json +2 -0
  19. package/locales/fr-FR/chat.json +2 -0
  20. package/locales/it-IT/chat.json +2 -0
  21. package/locales/ja-JP/chat.json +2 -0
  22. package/locales/ko-KR/chat.json +2 -0
  23. package/locales/nl-NL/chat.json +2 -0
  24. package/locales/pl-PL/chat.json +2 -0
  25. package/locales/pt-BR/chat.json +2 -0
  26. package/locales/ru-RU/chat.json +2 -0
  27. package/locales/tr-TR/chat.json +2 -0
  28. package/locales/vi-VN/chat.json +2 -0
  29. package/locales/zh-CN/chat.json +2 -0
  30. package/locales/zh-CN/modelProvider.json +1 -1
  31. package/locales/zh-TW/chat.json +2 -0
  32. package/package.json +1 -1
  33. package/packages/const/src/hotkeys.ts +1 -1
  34. package/packages/const/src/index.ts +1 -0
  35. package/packages/const/src/settings/hotkey.ts +3 -2
  36. package/packages/const/src/trace.ts +1 -1
  37. package/packages/const/src/user.ts +1 -2
  38. package/packages/database/src/client/db.test.ts +19 -13
  39. package/packages/electron-server-ipc/src/ipcClient.test.ts +783 -1
  40. package/packages/file-loaders/src/loadFile.test.ts +61 -0
  41. package/packages/file-loaders/src/utils/isTextReadableFile.test.ts +43 -0
  42. package/packages/file-loaders/src/utils/parser-utils.test.ts +155 -0
  43. package/packages/model-bank/src/aiModels/aihubmix.ts +38 -4
  44. package/packages/model-bank/src/aiModels/groq.ts +26 -8
  45. package/packages/model-bank/src/aiModels/hunyuan.ts +3 -3
  46. package/packages/model-bank/src/aiModels/modelscope.ts +13 -2
  47. package/packages/model-bank/src/aiModels/moonshot.ts +25 -5
  48. package/packages/model-bank/src/aiModels/novita.ts +40 -9
  49. package/packages/model-bank/src/aiModels/openrouter.ts +0 -13
  50. package/packages/model-bank/src/aiModels/qwen.ts +62 -1
  51. package/packages/model-bank/src/aiModels/siliconcloud.ts +20 -0
  52. package/packages/model-bank/src/aiModels/volcengine.ts +141 -15
  53. package/packages/model-runtime/package.json +2 -1
  54. package/packages/model-runtime/src/ai21/index.test.ts +2 -2
  55. package/packages/model-runtime/src/ai360/index.test.ts +2 -2
  56. package/packages/model-runtime/src/akashchat/index.test.ts +19 -0
  57. package/packages/model-runtime/src/anthropic/index.test.ts +1 -2
  58. package/packages/model-runtime/src/baichuan/index.test.ts +1 -2
  59. package/packages/model-runtime/src/bedrock/index.test.ts +1 -2
  60. package/packages/model-runtime/src/bfl/createImage.test.ts +1 -2
  61. package/packages/model-runtime/src/bfl/index.test.ts +1 -2
  62. package/packages/model-runtime/src/cloudflare/index.test.ts +1 -2
  63. package/packages/model-runtime/src/cohere/index.test.ts +19 -0
  64. package/packages/model-runtime/src/deepseek/index.test.ts +2 -2
  65. package/packages/model-runtime/src/fireworksai/index.test.ts +2 -2
  66. package/packages/model-runtime/src/giteeai/index.test.ts +2 -2
  67. package/packages/model-runtime/src/github/index.test.ts +2 -2
  68. package/packages/model-runtime/src/google/createImage.test.ts +1 -2
  69. package/packages/model-runtime/src/google/index.test.ts +1 -1
  70. package/packages/model-runtime/src/groq/index.test.ts +2 -3
  71. package/packages/model-runtime/src/huggingface/index.test.ts +40 -0
  72. package/packages/model-runtime/src/hunyuan/index.test.ts +2 -3
  73. package/packages/model-runtime/src/internlm/index.test.ts +2 -2
  74. package/packages/model-runtime/src/jina/index.test.ts +19 -0
  75. package/packages/model-runtime/src/lmstudio/index.test.ts +2 -2
  76. package/packages/model-runtime/src/minimax/index.test.ts +19 -0
  77. package/packages/model-runtime/src/mistral/index.test.ts +2 -3
  78. package/packages/model-runtime/src/modelscope/index.test.ts +19 -0
  79. package/packages/model-runtime/src/moonshot/index.test.ts +1 -2
  80. package/packages/model-runtime/src/nebius/index.test.ts +19 -0
  81. package/packages/model-runtime/src/newapi/index.test.ts +49 -42
  82. package/packages/model-runtime/src/newapi/index.ts +124 -143
  83. package/packages/model-runtime/src/novita/index.test.ts +3 -4
  84. package/packages/model-runtime/src/nvidia/index.test.ts +19 -0
  85. package/packages/model-runtime/src/openrouter/index.test.ts +2 -3
  86. package/packages/model-runtime/src/perplexity/index.test.ts +2 -3
  87. package/packages/model-runtime/src/ppio/index.test.ts +3 -4
  88. package/packages/model-runtime/src/qwen/index.test.ts +2 -2
  89. package/packages/model-runtime/src/sambanova/index.test.ts +19 -0
  90. package/packages/model-runtime/src/search1api/index.test.ts +19 -0
  91. package/packages/model-runtime/src/sensenova/index.test.ts +2 -2
  92. package/packages/model-runtime/src/spark/index.test.ts +2 -2
  93. package/packages/model-runtime/src/stepfun/index.test.ts +2 -2
  94. package/packages/model-runtime/src/taichu/index.test.ts +4 -5
  95. package/packages/model-runtime/src/tencentcloud/index.test.ts +1 -1
  96. package/packages/model-runtime/src/togetherai/index.test.ts +1 -2
  97. package/packages/model-runtime/src/upstage/index.test.ts +1 -2
  98. package/packages/model-runtime/src/utils/openaiCompatibleFactory/index.test.ts +9 -7
  99. package/packages/model-runtime/src/utils/streams/anthropic.ts +2 -2
  100. package/packages/model-runtime/src/utils/streams/openai/openai.ts +20 -13
  101. package/packages/model-runtime/src/utils/streams/openai/responsesStream.test.ts +1 -2
  102. package/packages/model-runtime/src/utils/streams/openai/responsesStream.ts +2 -2
  103. package/packages/model-runtime/src/utils/streams/protocol.ts +2 -2
  104. package/packages/model-runtime/src/wenxin/index.test.ts +2 -3
  105. package/packages/model-runtime/src/xai/index.test.ts +2 -2
  106. package/packages/model-runtime/src/zeroone/index.test.ts +1 -2
  107. package/packages/model-runtime/src/zhipu/index.test.ts +2 -3
  108. package/packages/model-runtime/vitest.config.mts +0 -7
  109. package/packages/types/src/index.ts +2 -0
  110. package/packages/types/src/message/base.ts +1 -1
  111. package/packages/types/src/openai/chat.ts +2 -3
  112. package/packages/utils/package.json +2 -1
  113. package/packages/utils/src/_deprecated/parseModels.test.ts +1 -1
  114. package/packages/utils/src/_deprecated/parseModels.ts +1 -1
  115. package/packages/utils/src/client/topic.test.ts +1 -2
  116. package/packages/utils/src/client/topic.ts +1 -2
  117. package/packages/utils/src/electron/desktopRemoteRPCFetch.ts +1 -1
  118. package/packages/utils/src/fetch/fetchSSE.ts +7 -8
  119. package/packages/utils/src/fetch/parseError.ts +1 -3
  120. package/packages/utils/src/format.test.ts +1 -2
  121. package/packages/utils/src/index.ts +1 -0
  122. package/packages/utils/src/toolManifest.ts +1 -2
  123. package/packages/utils/src/trace.ts +1 -1
  124. package/packages/utils/vitest.config.mts +1 -1
  125. package/packages/web-crawler/src/__tests__/urlRules.test.ts +275 -0
  126. package/packages/web-crawler/src/crawImpl/__tests__/exa.test.ts +269 -0
  127. package/packages/web-crawler/src/crawImpl/__tests__/firecrawl.test.ts +284 -0
  128. package/packages/web-crawler/src/crawImpl/__tests__/naive.test.ts +234 -0
  129. package/packages/web-crawler/src/crawImpl/__tests__/tavily.test.ts +359 -0
  130. package/packages/web-crawler/src/utils/__tests__/errorType.test.ts +217 -0
  131. package/packages/web-crawler/vitest.config.mts +3 -0
  132. package/scripts/electronWorkflow/mergeMacReleaseFiles.ts +207 -0
  133. package/src/app/[variants]/(main)/settings/provider/(detail)/newapi/page.tsx +1 -1
  134. package/src/components/Thinking/index.tsx +2 -3
  135. package/src/config/llm.ts +8 -0
  136. package/src/features/ChatInput/Desktop/index.tsx +16 -4
  137. package/src/features/ChatInput/StoreUpdater.tsx +2 -0
  138. package/src/libs/traces/index.ts +1 -1
  139. package/src/locales/default/chat.ts +1 -0
  140. package/src/locales/default/modelProvider.ts +1 -1
  141. package/src/server/modules/ModelRuntime/trace.ts +1 -2
  142. package/src/store/chat/slices/aiChat/actions/__tests__/cancel-functionality.test.ts +107 -0
  143. package/src/store/chat/slices/aiChat/actions/__tests__/generateAIChatV2.test.ts +352 -7
  144. package/src/store/chat/slices/aiChat/actions/generateAIChatV2.ts +2 -1
  145. package/packages/model-runtime/src/openrouter/__snapshots__/index.test.ts.snap +0 -113
@@ -0,0 +1,207 @@
1
+ /* eslint-disable unicorn/no-process-exit, unicorn/prefer-top-level-await */
2
+ import fs from 'fs-extra';
3
+ import path from 'node:path';
4
+ import { parse, stringify } from 'yaml';
5
+
6
+ interface LatestMacYml {
7
+ files: Array<{
8
+ sha512: string;
9
+ size: number;
10
+ url: string;
11
+ }>;
12
+ path: string;
13
+ releaseDate: string;
14
+ sha512: string;
15
+ version: string;
16
+ }
17
+
18
+ // 配置
19
+ const RELEASE_TAG = process.env.RELEASE_TAG || process.argv[2];
20
+ const FILE_NAME = 'latest-mac.yml';
21
+ const RELEASE_DIR = path.resolve('release');
22
+
23
+ // 验证环境变量和输入
24
+ if (!RELEASE_TAG) {
25
+ console.error('❌ RELEASE_TAG environment variable or argument is required');
26
+ process.exit(1);
27
+ }
28
+
29
+ // 验证 release tag 格式
30
+ if (!/^v?\d+\.\d+\.\d+/.test(RELEASE_TAG)) {
31
+ console.error(`❌ Invalid RELEASE_TAG format: ${RELEASE_TAG}. Expected format: v1.2.3`);
32
+ process.exit(1);
33
+ }
34
+
35
+ /**
36
+ * 检测 latest-mac.yml 文件的平台类型
37
+ */
38
+ function detectPlatform(yamlContent: LatestMacYml): 'intel' | 'arm' | 'both' | 'none' {
39
+ const hasIntel = yamlContent.files.some((file) => file.url.includes('-x64.dmg'));
40
+ const hasArm = yamlContent.files.some((file) => file.url.includes('-arm64.dmg'));
41
+
42
+ if (hasIntel && hasArm) return 'both';
43
+ if (hasIntel && !hasArm) return 'intel';
44
+ if (!hasIntel && hasArm) return 'arm';
45
+ return 'none';
46
+ }
47
+
48
+ /**
49
+ * 合并两个 latest-mac.yml 文件
50
+ * @param intelContent Intel 平台的 YAML 内容
51
+ * @param armContent ARM 平台的 YAML 内容
52
+ */
53
+ function mergeYamlFiles(intelContent: LatestMacYml, armContent: LatestMacYml): string {
54
+ // 以 Intel 为基础(保持兼容性)
55
+ const merged: LatestMacYml = {
56
+ ...intelContent,
57
+ files: [...intelContent.files, ...armContent.files],
58
+ };
59
+
60
+ // 使用 yaml 库生成,保持 sha512 在同一行
61
+ return stringify(merged, {
62
+ lineWidth: 0, // 不换行
63
+ });
64
+ }
65
+
66
+ // GitHub API functions removed since we're working with local files only
67
+
68
+ /**
69
+ * 读取本地文件
70
+ */
71
+ function readLocalFile(filePath: string): string | null {
72
+ try {
73
+ if (fs.existsSync(filePath)) {
74
+ const content = fs.readFileSync(filePath, 'utf8');
75
+ console.log(`✅ Read local file: ${filePath} (${content.length} chars)`);
76
+ return content;
77
+ }
78
+ console.log(`⚠️ Local file not found: ${filePath}`);
79
+ return null;
80
+ } catch (error) {
81
+ console.error(`❌ Error reading local file ${filePath}:`, error);
82
+ return null;
83
+ }
84
+ }
85
+
86
+ /**
87
+ * 写入本地文件
88
+ */
89
+ function writeLocalFile(filePath: string, content: string): void {
90
+ try {
91
+ fs.writeFileSync(filePath, content, 'utf8');
92
+ console.log(`✅ Written local file: ${filePath} (${content.length} chars)`);
93
+ } catch (error) {
94
+ console.error(`❌ Error writing local file ${filePath}:`, error);
95
+ throw error;
96
+ }
97
+ }
98
+
99
+ /**
100
+ * 主函数
101
+ */
102
+ async function main(): Promise<void> {
103
+ try {
104
+ console.log(`🚀 Starting macOS Release file merge for ${RELEASE_TAG}`);
105
+ console.log(`📁 Working directory: ${RELEASE_DIR}`);
106
+
107
+ // 1. 检查 release 目录下的所有文件
108
+ const releaseFiles = fs.readdirSync(RELEASE_DIR);
109
+ console.log(`📂 Files in release directory: ${releaseFiles.join(', ')}`);
110
+
111
+ // 2. 查找所有 latest-mac*.yml 文件
112
+ const macYmlFiles = releaseFiles.filter(
113
+ (f) => f.startsWith('latest-mac') && f.endsWith('.yml'),
114
+ );
115
+ console.log(`🔍 Found macOS YAML files: ${macYmlFiles.join(', ')}`);
116
+
117
+ if (macYmlFiles.length === 0) {
118
+ console.log('⚠️ No macOS YAML files found, skipping merge');
119
+ return;
120
+ }
121
+
122
+ // 3. 处理找到的文件,识别平台
123
+ const macFiles: Array<{
124
+ content: string;
125
+ filename: string;
126
+ platform: 'intel' | 'arm';
127
+ yaml: LatestMacYml;
128
+ }> = [];
129
+
130
+ for (const fileName of macYmlFiles) {
131
+ const filePath = path.join(RELEASE_DIR, fileName);
132
+ const content = readLocalFile(filePath);
133
+
134
+ if (!content) continue;
135
+
136
+ try {
137
+ const yamlContent = parse(content) as LatestMacYml;
138
+ const platform = detectPlatform(yamlContent);
139
+
140
+ if (platform === 'intel' || platform === 'arm') {
141
+ macFiles.push({ content, filename: fileName, platform, yaml: yamlContent });
142
+ console.log(`🔍 Detected ${platform} platform in ${fileName}`);
143
+ } else if (platform === 'both') {
144
+ console.log(`✅ Found already merged file: ${fileName}`);
145
+ // 如果已经是合并后的文件,直接复制为最终文件
146
+ writeLocalFile(path.join(RELEASE_DIR, FILE_NAME), content);
147
+ return;
148
+ } else {
149
+ console.log(`⚠️ Unknown platform type: ${platform} in ${fileName}`);
150
+ }
151
+ } catch (error) {
152
+ console.warn(`⚠️ Failed to parse ${fileName}:`, error);
153
+ }
154
+ }
155
+
156
+ // 4. 检查是否有两个不同平台的文件
157
+ const intelFiles = macFiles.filter((f) => f.platform === 'intel');
158
+ const armFiles = macFiles.filter((f) => f.platform === 'arm');
159
+
160
+ if (intelFiles.length === 0 && armFiles.length === 0) {
161
+ console.log('⚠️ No valid platform files found');
162
+ return;
163
+ }
164
+
165
+ if (intelFiles.length === 0) {
166
+ console.log('⚠️ No Intel files found, using ARM only');
167
+ writeLocalFile(path.join(RELEASE_DIR, FILE_NAME), armFiles[0].content);
168
+ return;
169
+ }
170
+
171
+ if (armFiles.length === 0) {
172
+ console.log('⚠️ No ARM files found, using Intel only');
173
+ writeLocalFile(path.join(RELEASE_DIR, FILE_NAME), intelFiles[0].content);
174
+ return;
175
+ }
176
+
177
+ // 5. 合并 Intel 和 ARM 文件
178
+ const intelFile = intelFiles[0];
179
+ const armFile = armFiles[0];
180
+
181
+ console.log(`🔄 Merging ${intelFile.filename} (Intel) and ${armFile.filename} (ARM)...`);
182
+ const mergedContent = mergeYamlFiles(intelFile.yaml, armFile.yaml);
183
+
184
+ // 6. 保存合并后的文件
185
+ const mergedFilePath = path.join(RELEASE_DIR, FILE_NAME);
186
+ writeLocalFile(mergedFilePath, mergedContent);
187
+
188
+ // 7. 验证合并结果
189
+ const mergedYaml = parse(mergedContent) as LatestMacYml;
190
+ const finalPlatform = detectPlatform(mergedYaml);
191
+
192
+ if (finalPlatform === 'both') {
193
+ console.log('✅ Successfully merged both Intel and ARM platforms');
194
+ console.log(`📊 Final file contains ${mergedYaml.files.length} files`);
195
+ } else {
196
+ console.warn(`⚠️ Merge result unexpected: ${finalPlatform}`);
197
+ }
198
+
199
+ console.log('🎉 Merge complete!');
200
+ } catch (error) {
201
+ console.error('❌ Error during merge:', error);
202
+ process.exit(1);
203
+ }
204
+ }
205
+
206
+ // 运行主函数
207
+ void main();
@@ -16,7 +16,7 @@ const Page = () => {
16
16
  ...NewAPIProviderCard.settings,
17
17
  proxyUrl: {
18
18
  desc: t('newapi.apiUrl.desc'),
19
- placeholder: 'https://any-newapi-provider.com/v1',
19
+ placeholder: 'https://any-newapi-provider.com/',
20
20
  title: t('newapi.apiUrl.title'),
21
21
  },
22
22
  }}
@@ -1,3 +1,4 @@
1
+ import { ChatCitationItem } from '@lobechat/types';
1
2
  import { ActionIcon, CopyButton, Icon, Markdown, ScrollShadow } from '@lobehub/ui';
2
3
  import { createStyles } from 'antd-style';
3
4
  import { AnimatePresence, motion } from 'framer-motion';
@@ -7,8 +8,6 @@ import { CSSProperties, RefObject, memo, useEffect, useRef, useState } from 'rea
7
8
  import { useTranslation } from 'react-i18next';
8
9
  import { Flexbox } from 'react-layout-kit';
9
10
 
10
- import { CitationItem } from '@/types/message';
11
-
12
11
  const useStyles = createStyles(({ css, token }) => ({
13
12
  container: css`
14
13
  overflow: hidden;
@@ -74,7 +73,7 @@ const useStyles = createStyles(({ css, token }) => ({
74
73
  }));
75
74
 
76
75
  interface ThinkingProps {
77
- citations?: CitationItem[];
76
+ citations?: ChatCitationItem[];
78
77
  content?: string;
79
78
  duration?: number;
80
79
  style?: CSSProperties;
package/src/config/llm.ts CHANGED
@@ -186,6 +186,10 @@ export const getLLMConfig = () => {
186
186
 
187
187
  ENABLED_AIHUBMIX: z.boolean(),
188
188
  AIHUBMIX_API_KEY: z.string().optional(),
189
+
190
+ ENABLED_NEWAPI: z.boolean(),
191
+ NEWAPI_API_KEY: z.string().optional(),
192
+ NEWAPI_PROXY_URL: z.string().optional(),
189
193
  },
190
194
  runtimeEnv: {
191
195
  API_KEY_SELECT_MODE: process.env.API_KEY_SELECT_MODE,
@@ -368,6 +372,10 @@ export const getLLMConfig = () => {
368
372
  ENABLED_AIHUBMIX: !!process.env.AIHUBMIX_API_KEY,
369
373
  AIHUBMIX_API_KEY: process.env.AIHUBMIX_API_KEY,
370
374
 
375
+ ENABLED_NEWAPI: !!process.env.NEWAPI_API_KEY,
376
+ NEWAPI_API_KEY: process.env.NEWAPI_API_KEY,
377
+ NEWAPI_PROXY_URL: process.env.NEWAPI_PROXY_URL,
378
+
371
379
  ENABLED_NEBIUS: !!process.env.NEBIUS_API_KEY,
372
380
  NEBIUS_API_KEY: process.env.NEBIUS_API_KEY,
373
381
  },
@@ -1,9 +1,11 @@
1
1
  'use client';
2
2
 
3
3
  import { ChatInput, ChatInputActionBar } from '@lobehub/editor/react';
4
+ import { Text } from '@lobehub/ui';
4
5
  import { createStyles } from 'antd-style';
5
6
  import { memo, useEffect } from 'react';
6
- import { Flexbox } from 'react-layout-kit';
7
+ import { useTranslation } from 'react-i18next';
8
+ import { Center, Flexbox } from 'react-layout-kit';
7
9
 
8
10
  import { useChatInputStore } from '@/features/ChatInput/store';
9
11
  import { useChatStore } from '@/store/chat';
@@ -12,7 +14,6 @@ import { chatSelectors } from '@/store/chat/selectors';
12
14
  import ActionBar from '../ActionBar';
13
15
  import InputEditor from '../InputEditor';
14
16
  import SendArea from '../SendArea';
15
- import ShortcutHint from '../SendArea/ShortcutHint';
16
17
  import TypoBar from '../TypoBar';
17
18
  import FilePreview from './FilePreview';
18
19
 
@@ -28,6 +29,9 @@ const useStyles = createStyles(({ css, token }) => ({
28
29
  }
29
30
  }
30
31
  `,
32
+ footnote: css`
33
+ font-size: 10px;
34
+ `,
31
35
  fullscreen: css`
32
36
  position: absolute;
33
37
  z-index: 100;
@@ -42,6 +46,7 @@ const useStyles = createStyles(({ css, token }) => ({
42
46
  }));
43
47
 
44
48
  const DesktopChatInput = memo<{ showFootnote?: boolean }>(({ showFootnote }) => {
49
+ const { t } = useTranslation('chat');
45
50
  const [slashMenuRef, expand, showTypoBar, editor, leftActions] = useChatInputStore((s) => [
46
51
  s.slashMenuRef,
47
52
  s.expand,
@@ -65,7 +70,8 @@ const DesktopChatInput = memo<{ showFootnote?: boolean }>(({ showFootnote }) =>
65
70
  {!expand && fileNode}
66
71
  <Flexbox
67
72
  className={cx(styles.container, expand && styles.fullscreen)}
68
- paddingBlock={showFootnote ? 0 : '0 12px'}
73
+ gap={8}
74
+ paddingBlock={showFootnote ? '0 8px' : '0 12px'}
69
75
  paddingInline={12}
70
76
  >
71
77
  <ChatInput
@@ -85,7 +91,13 @@ const DesktopChatInput = memo<{ showFootnote?: boolean }>(({ showFootnote }) =>
85
91
  {expand && fileNode}
86
92
  <InputEditor />
87
93
  </ChatInput>
88
- {showFootnote && !expand && <ShortcutHint />}
94
+ {showFootnote && !expand && (
95
+ <Center style={{ pointerEvents: 'none', zIndex: 100 }}>
96
+ <Text className={styles.footnote} type={'secondary'}>
97
+ {t('input.disclaimer')}
98
+ </Text>
99
+ </Center>
100
+ )}
89
101
  </Flexbox>
90
102
  </>
91
103
  );
@@ -19,12 +19,14 @@ const StoreUpdater = memo<StoreUpdaterProps>(
19
19
  rightActions,
20
20
  onSend,
21
21
  onMarkdownContentChange,
22
+ sendMenu,
22
23
  }) => {
23
24
  const storeApi = useStoreApi();
24
25
  const useStoreUpdater = createStoreUpdater(storeApi);
25
26
  const editor = useChatInputEditor();
26
27
 
27
28
  useStoreUpdater('mobile', mobile);
29
+ useStoreUpdater('sendMenu', sendMenu);
28
30
  useStoreUpdater('leftActions', leftActions);
29
31
  useStoreUpdater('rightActions', rightActions);
30
32
 
@@ -1,7 +1,7 @@
1
+ import { CURRENT_VERSION } from '@lobechat/const';
1
2
  import { Langfuse } from 'langfuse';
2
3
  import { CreateLangfuseTraceBody } from 'langfuse-core';
3
4
 
4
- import { CURRENT_VERSION } from '@/const/version';
5
5
  import { getLangfuseConfig } from '@/envs/langfuse';
6
6
  import { TraceEventClient } from '@/libs/traces/event';
7
7
 
@@ -71,6 +71,7 @@ export default {
71
71
  input: {
72
72
  addAi: '添加一条 AI 消息',
73
73
  addUser: '添加一条用户消息',
74
+ disclaimer: 'AI 也可能会犯错,请检查重要信息',
74
75
  errorMsg: '消息发送失败,请检查网络后重试: {{errorMsg}}',
75
76
  more: '更多',
76
77
  send: '发送',
@@ -164,7 +164,7 @@ export default {
164
164
  title: 'API 密钥',
165
165
  },
166
166
  apiUrl: {
167
- desc: 'New API 服务的 API 地址,大部分时候需要带 /v1',
167
+ desc: 'New API 服务的 API 地址,大部分时候不要带 /v1',
168
168
  title: 'API 地址',
169
169
  },
170
170
  enabled: {
@@ -1,9 +1,8 @@
1
+ import { INBOX_SESSION_ID, LOBE_CHAT_OBSERVATION_ID, LOBE_CHAT_TRACE_ID } from '@lobechat/const';
1
2
  import { ChatStreamCallbacks, ChatStreamPayload } from '@lobechat/model-runtime';
2
3
  import { TracePayload, TraceTagMap } from '@lobechat/types';
3
4
  import { after } from 'next/server';
4
5
 
5
- import { INBOX_SESSION_ID } from '@/const/session';
6
- import { LOBE_CHAT_OBSERVATION_ID, LOBE_CHAT_TRACE_ID } from '@/const/trace';
7
6
  import { TraceClient } from '@/libs/traces';
8
7
 
9
8
  export interface AgentChatOptions {
@@ -0,0 +1,107 @@
1
+ import { act, renderHook } from '@testing-library/react';
2
+ import { describe, expect, it, vi } from 'vitest';
3
+
4
+ import { useChatStore } from '../../../../store';
5
+
6
+ describe('Cancel send message functionality tests', () => {
7
+ describe('cancelSendMessageInServer', () => {
8
+ it('should be able to call cancel method normally', () => {
9
+ const { result } = renderHook(() => useChatStore());
10
+
11
+ // Initial state setup
12
+ act(() => {
13
+ useChatStore.setState({
14
+ activeId: 'session-1',
15
+ activeTopicId: 'topic-1',
16
+ mainSendMessageOperations: {},
17
+ });
18
+ });
19
+
20
+ // Test method exists
21
+ expect(typeof result.current.cancelSendMessageInServer).toBe('function');
22
+
23
+ // Test method can be called safely
24
+ expect(() => {
25
+ act(() => {
26
+ result.current.cancelSendMessageInServer();
27
+ });
28
+ }).not.toThrow();
29
+ });
30
+
31
+ it('should be able to call with specified topic ID', () => {
32
+ const { result } = renderHook(() => useChatStore());
33
+
34
+ act(() => {
35
+ useChatStore.setState({
36
+ activeId: 'session-1',
37
+ mainSendMessageOperations: {},
38
+ });
39
+ });
40
+
41
+ expect(() => {
42
+ act(() => {
43
+ result.current.cancelSendMessageInServer('topic-2');
44
+ });
45
+ }).not.toThrow();
46
+ });
47
+ });
48
+
49
+ describe('clearSendMessageError', () => {
50
+ it('should be able to call clear error method normally', () => {
51
+ const { result } = renderHook(() => useChatStore());
52
+
53
+ act(() => {
54
+ useChatStore.setState({
55
+ activeId: 'session-1',
56
+ activeTopicId: 'topic-1',
57
+ mainSendMessageOperations: {},
58
+ });
59
+ });
60
+
61
+ expect(typeof result.current.clearSendMessageError).toBe('function');
62
+
63
+ expect(() => {
64
+ act(() => {
65
+ result.current.clearSendMessageError();
66
+ });
67
+ }).not.toThrow();
68
+ });
69
+ });
70
+
71
+ describe('Internal methods', () => {
72
+ it('should have internal state management methods', () => {
73
+ const { result } = renderHook(() => useChatStore());
74
+
75
+ expect(typeof result.current.internal_toggleSendMessageOperation).toBe('function');
76
+ expect(typeof result.current.internal_updateSendMessageOperation).toBe('function');
77
+ });
78
+
79
+ it('internal_toggleSendMessageOperation should work normally', () => {
80
+ const { result } = renderHook(() => useChatStore());
81
+
82
+ act(() => {
83
+ useChatStore.setState({ mainSendMessageOperations: {} });
84
+ });
85
+
86
+ expect(() => {
87
+ act(() => {
88
+ const abortController = result.current.internal_toggleSendMessageOperation(
89
+ 'test-key',
90
+ true,
91
+ );
92
+ expect(abortController).toBeInstanceOf(AbortController);
93
+ });
94
+ }).not.toThrow();
95
+ });
96
+ });
97
+
98
+ describe('State structure', () => {
99
+ it('should have mainSendMessageOperations state', () => {
100
+ const { result } = renderHook(() => useChatStore());
101
+
102
+ // Ensure state exists
103
+ expect(result.current.mainSendMessageOperations).toBeDefined();
104
+ expect(typeof result.current.mainSendMessageOperations).toBe('object');
105
+ });
106
+ });
107
+ });