@lobehub/chat 1.48.2 → 1.48.4

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 (49) hide show
  1. package/CHANGELOG.md +50 -0
  2. package/changelog/v1.json +18 -0
  3. package/docs/self-hosting/server-database/docker-compose.zh-CN.mdx +197 -194
  4. package/locales/ar/chat.json +0 -4
  5. package/locales/ar/components.json +5 -0
  6. package/locales/bg-BG/chat.json +0 -4
  7. package/locales/bg-BG/components.json +5 -0
  8. package/locales/de-DE/chat.json +0 -4
  9. package/locales/de-DE/components.json +5 -0
  10. package/locales/en-US/chat.json +0 -4
  11. package/locales/en-US/components.json +5 -0
  12. package/locales/es-ES/chat.json +0 -4
  13. package/locales/es-ES/components.json +5 -0
  14. package/locales/fa-IR/chat.json +0 -4
  15. package/locales/fa-IR/components.json +5 -0
  16. package/locales/fr-FR/chat.json +0 -4
  17. package/locales/fr-FR/components.json +5 -0
  18. package/locales/it-IT/chat.json +0 -4
  19. package/locales/it-IT/components.json +5 -0
  20. package/locales/ja-JP/chat.json +0 -4
  21. package/locales/ja-JP/components.json +5 -0
  22. package/locales/ko-KR/chat.json +0 -4
  23. package/locales/ko-KR/components.json +5 -0
  24. package/locales/nl-NL/chat.json +0 -4
  25. package/locales/nl-NL/components.json +5 -0
  26. package/locales/pl-PL/chat.json +0 -4
  27. package/locales/pl-PL/components.json +5 -0
  28. package/locales/pt-BR/chat.json +0 -4
  29. package/locales/pt-BR/components.json +5 -0
  30. package/locales/ru-RU/chat.json +0 -4
  31. package/locales/ru-RU/components.json +5 -0
  32. package/locales/tr-TR/chat.json +0 -4
  33. package/locales/tr-TR/components.json +5 -0
  34. package/locales/vi-VN/chat.json +0 -4
  35. package/locales/vi-VN/components.json +5 -0
  36. package/locales/zh-CN/chat.json +0 -4
  37. package/locales/zh-CN/components.json +5 -0
  38. package/locales/zh-TW/chat.json +0 -4
  39. package/locales/zh-TW/components.json +5 -0
  40. package/package.json +1 -1
  41. package/src/app/(main)/settings/provider/features/ModelList/ModelItem.tsx +31 -36
  42. package/src/components/Thinking/index.tsx +137 -0
  43. package/src/const/currency.ts +2 -0
  44. package/src/const/discover.ts +0 -2
  45. package/src/features/Conversation/Messages/Assistant/Reasoning/index.tsx +6 -110
  46. package/src/locales/default/chat.ts +0 -4
  47. package/src/locales/default/components.ts +5 -0
  48. package/src/utils/format.test.ts +2 -9
  49. package/src/utils/format.ts +2 -2
@@ -85,5 +85,10 @@
85
85
  "ModelSwitchPanel": {
86
86
  "emptyModel": "No enabled model. Please go to settings to enable.",
87
87
  "provider": "Provider"
88
+ },
89
+ "Thinking": {
90
+ "thinking": "Deep in thought...",
91
+ "thought": "Thought deeply",
92
+ "thoughtWithDuration": "Thought deeply (took {{duration}} seconds)"
88
93
  }
89
94
  }
@@ -82,10 +82,6 @@
82
82
  }
83
83
  }
84
84
  },
85
- "reasoning": {
86
- "thinking": "Pensando profundamente",
87
- "thought": "He reflexionado profundamente (durante {{duration}} segundos)"
88
- },
89
85
  "regenerate": "Regenerar",
90
86
  "roleAndArchive": "Rol y archivo",
91
87
  "searchAgentPlaceholder": "Asistente de búsqueda...",
@@ -85,5 +85,10 @@
85
85
  "ModelSwitchPanel": {
86
86
  "emptyModel": "No hay modelos habilitados. Vaya a la configuración para habilitarlos.",
87
87
  "provider": "Proveedor"
88
+ },
89
+ "Thinking": {
90
+ "thinking": "Pensando profundamente...",
91
+ "thought": "He pensado profundamente",
92
+ "thoughtWithDuration": "He pensado profundamente (durante {{duration}} segundos)"
88
93
  }
89
94
  }
@@ -82,10 +82,6 @@
82
82
  }
83
83
  }
84
84
  },
85
- "reasoning": {
86
- "thinking": "در حال تفکر عمیق",
87
- "thought": "به طور عمیق فکر شده است (زمان صرف شده: {{duration}} ثانیه)"
88
- },
89
85
  "regenerate": "بازتولید",
90
86
  "roleAndArchive": "نقش‌ها و بایگانی",
91
87
  "searchAgentPlaceholder": "جستجوی دستیار...",
@@ -85,5 +85,10 @@
85
85
  "ModelSwitchPanel": {
86
86
  "emptyModel": "هیچ مدلی فعال نیست، لطفاً به تنظیمات بروید و آن را فعال کنید",
87
87
  "provider": "ارائه‌دهنده"
88
+ },
89
+ "Thinking": {
90
+ "thinking": "در حال تفکر عمیق...",
91
+ "thought": "تفکر عمیق انجام شد",
92
+ "thoughtWithDuration": "تفکر عمیق انجام شد (مدت زمان {{duration}} ثانیه)"
88
93
  }
89
94
  }
@@ -82,10 +82,6 @@
82
82
  }
83
83
  }
84
84
  },
85
- "reasoning": {
86
- "thinking": "En pleine réflexion profonde",
87
- "thought": "J'ai réfléchi en profondeur (durée : {{duration}} secondes)"
88
- },
89
85
  "regenerate": "Regénérer",
90
86
  "roleAndArchive": "Rôle et archivage",
91
87
  "searchAgentPlaceholder": "Assistant de recherche...",
@@ -85,5 +85,10 @@
85
85
  "ModelSwitchPanel": {
86
86
  "emptyModel": "Aucun modèle activé. Veuillez vous rendre dans les paramètres pour l'activer.",
87
87
  "provider": "Fournisseur"
88
+ },
89
+ "Thinking": {
90
+ "thinking": "En pleine réflexion...",
91
+ "thought": "Réflexion terminée",
92
+ "thoughtWithDuration": "Réflexion terminée (durée : {{duration}} secondes)"
88
93
  }
89
94
  }
@@ -82,10 +82,6 @@
82
82
  }
83
83
  }
84
84
  },
85
- "reasoning": {
86
- "thinking": "In profonda riflessione",
87
- "thought": "Pensato in profondità (tempo impiegato {{duration}} secondi)"
88
- },
89
85
  "regenerate": "Rigenera",
90
86
  "roleAndArchive": "Ruolo e archivio",
91
87
  "searchAgentPlaceholder": "Assistente di ricerca...",
@@ -85,5 +85,10 @@
85
85
  "ModelSwitchPanel": {
86
86
  "emptyModel": "Nessun modello attivo. Vai alle impostazioni per attivarne uno.",
87
87
  "provider": "Provider"
88
+ },
89
+ "Thinking": {
90
+ "thinking": "Pensando profondamente...",
91
+ "thought": "Pensato profondamente",
92
+ "thoughtWithDuration": "Pensato profondamente (tempo impiegato {{duration}} secondi)"
88
93
  }
89
94
  }
@@ -82,10 +82,6 @@
82
82
  }
83
83
  }
84
84
  },
85
- "reasoning": {
86
- "thinking": "深く考えています",
87
- "thought": "深く考えました(所要時間 {{duration}} 秒)"
88
- },
89
85
  "regenerate": "再生成",
90
86
  "roleAndArchive": "役割とアーカイブ",
91
87
  "searchAgentPlaceholder": "検索アシスタント...",
@@ -85,5 +85,10 @@
85
85
  "ModelSwitchPanel": {
86
86
  "emptyModel": "有効なモデルがありません。設定に移動して有効にしてください。",
87
87
  "provider": "プロバイダー"
88
+ },
89
+ "Thinking": {
90
+ "thinking": "深く考えています...",
91
+ "thought": "深く考えました",
92
+ "thoughtWithDuration": "深く考えました(所要時間 {{duration}} 秒)"
88
93
  }
89
94
  }
@@ -82,10 +82,6 @@
82
82
  }
83
83
  }
84
84
  },
85
- "reasoning": {
86
- "thinking": "심층 사고 중",
87
- "thought": "심층적으로 생각했습니다 (소요 시간: {{duration}} 초)"
88
- },
89
85
  "regenerate": "재생성",
90
86
  "roleAndArchive": "역할 및 아카이브",
91
87
  "searchAgentPlaceholder": "검색 도우미...",
@@ -85,5 +85,10 @@
85
85
  "ModelSwitchPanel": {
86
86
  "emptyModel": "활성화된 모델이 없습니다. 설정으로 이동하여 활성화하세요",
87
87
  "provider": "제공자"
88
+ },
89
+ "Thinking": {
90
+ "thinking": "심층적으로 생각 중...",
91
+ "thought": "심층적으로 생각했습니다",
92
+ "thoughtWithDuration": "심층적으로 생각했습니다 (소요 시간: {{duration}} 초)"
88
93
  }
89
94
  }
@@ -82,10 +82,6 @@
82
82
  }
83
83
  }
84
84
  },
85
- "reasoning": {
86
- "thinking": "Diep aan het denken",
87
- "thought": "Diep nagedacht (tijd: {{duration}} seconden)"
88
- },
89
85
  "regenerate": "Opnieuw genereren",
90
86
  "roleAndArchive": "Rol en archief",
91
87
  "searchAgentPlaceholder": "Zoekassistent...",
@@ -85,5 +85,10 @@
85
85
  "ModelSwitchPanel": {
86
86
  "emptyModel": "No enabled model, please go to settings to enable.",
87
87
  "provider": "Provider"
88
+ },
89
+ "Thinking": {
90
+ "thinking": "Diep in gedachten...",
91
+ "thought": "Diep nagedacht",
92
+ "thoughtWithDuration": "Diep nagedacht (geduurd {{duration}} seconden)"
88
93
  }
89
94
  }
@@ -82,10 +82,6 @@
82
82
  }
83
83
  }
84
84
  },
85
- "reasoning": {
86
- "thinking": "Głęboko myślę",
87
- "thought": "Głęboko myślałem (czas: {{duration}} sekund)"
88
- },
89
85
  "regenerate": "Wygeneruj ponownie",
90
86
  "roleAndArchive": "Rola i archiwum",
91
87
  "searchAgentPlaceholder": "Wyszukaj pomocnika...",
@@ -85,5 +85,10 @@
85
85
  "ModelSwitchPanel": {
86
86
  "emptyModel": "Brak włączonych modeli, przejdź do ustawień i włącz je",
87
87
  "provider": "Dostawca"
88
+ },
89
+ "Thinking": {
90
+ "thinking": "Głęboko myślę...",
91
+ "thought": "Już głęboko myślałem",
92
+ "thoughtWithDuration": "Już głęboko myślałem (czas: {{duration}} sekund)"
88
93
  }
89
94
  }
@@ -82,10 +82,6 @@
82
82
  }
83
83
  }
84
84
  },
85
- "reasoning": {
86
- "thinking": "Pensando profundamente",
87
- "thought": "Pensou profundamente (tempo gasto: {{duration}} segundos)"
88
- },
89
85
  "regenerate": "Regenerar",
90
86
  "roleAndArchive": "Função e Arquivo",
91
87
  "searchAgentPlaceholder": "Assistente de busca...",
@@ -85,5 +85,10 @@
85
85
  "ModelSwitchPanel": {
86
86
  "emptyModel": "Nenhum modelo habilitado. Por favor, vá para as configurações e habilite um.",
87
87
  "provider": "Fornecedor"
88
+ },
89
+ "Thinking": {
90
+ "thinking": "Pensando profundamente...",
91
+ "thought": "Pensamento profundo concluído",
92
+ "thoughtWithDuration": "Pensamento profundo concluído (durante {{duration}} segundos)"
88
93
  }
89
94
  }
@@ -82,10 +82,6 @@
82
82
  }
83
83
  }
84
84
  },
85
- "reasoning": {
86
- "thinking": "Глубокое размышление",
87
- "thought": "Глубоко размышлял (время: {{duration}} секунд)"
88
- },
89
85
  "regenerate": "Сгенерировать заново",
90
86
  "roleAndArchive": "Роль и архив",
91
87
  "searchAgentPlaceholder": "Поиск помощника...",
@@ -85,5 +85,10 @@
85
85
  "ModelSwitchPanel": {
86
86
  "emptyModel": "Нет активированных моделей. Пожалуйста, перейдите в настройки и включите модель",
87
87
  "provider": "Поставщик"
88
+ },
89
+ "Thinking": {
90
+ "thinking": "Глубокое размышление...",
91
+ "thought": "Глубоко размышлено",
92
+ "thoughtWithDuration": "Глубоко размышлено (время: {{duration}} секунд)"
88
93
  }
89
94
  }
@@ -82,10 +82,6 @@
82
82
  }
83
83
  }
84
84
  },
85
- "reasoning": {
86
- "thinking": "Derin düşünme aşamasında",
87
- "thought": "Derinlemesine düşündü (geçen süre {{duration}} saniye)"
88
- },
89
85
  "regenerate": "Tekrarla",
90
86
  "roleAndArchive": "Rol ve Arşiv",
91
87
  "searchAgentPlaceholder": "Arama Asistanı...",
@@ -85,5 +85,10 @@
85
85
  "ModelSwitchPanel": {
86
86
  "emptyModel": "Etkinleştirilmiş model bulunmamaktadır, lütfen ayarlara giderek açın",
87
87
  "provider": "Sağlayıcı"
88
+ },
89
+ "Thinking": {
90
+ "thinking": "Derin düşünme aşamasında...",
91
+ "thought": "Derin düşünme tamamlandı",
92
+ "thoughtWithDuration": "Derin düşünme tamamlandı (geçen süre {{duration}} saniye)"
88
93
  }
89
94
  }
@@ -82,10 +82,6 @@
82
82
  }
83
83
  }
84
84
  },
85
- "reasoning": {
86
- "thinking": "Đang suy nghĩ sâu sắc",
87
- "thought": "Đã suy nghĩ sâu sắc (thời gian: {{duration}} giây)"
88
- },
89
85
  "regenerate": "Tạo lại",
90
86
  "roleAndArchive": "Vai trò và lưu trữ",
91
87
  "searchAgentPlaceholder": "Trợ lý tìm kiếm...",
@@ -85,5 +85,10 @@
85
85
  "ModelSwitchPanel": {
86
86
  "emptyModel": "Không có mô hình nào được kích hoạt, vui lòng điều chỉnh trong cài đặt",
87
87
  "provider": "Nhà cung cấp"
88
+ },
89
+ "Thinking": {
90
+ "thinking": "Đang suy nghĩ sâu sắc...",
91
+ "thought": "Đã suy nghĩ sâu sắc",
92
+ "thoughtWithDuration": "Đã suy nghĩ sâu sắc (thời gian: {{duration}} giây)"
88
93
  }
89
94
  }
@@ -82,10 +82,6 @@
82
82
  }
83
83
  }
84
84
  },
85
- "reasoning": {
86
- "thinking": "深度思考中",
87
- "thought": "已深度思考(用时 {{duration}} 秒)"
88
- },
89
85
  "regenerate": "重新生成",
90
86
  "roleAndArchive": "角色与记录",
91
87
  "searchAgentPlaceholder": "搜索助手...",
@@ -85,5 +85,10 @@
85
85
  "ModelSwitchPanel": {
86
86
  "emptyModel": "没有启用的模型,请前往设置开启",
87
87
  "provider": "提供商"
88
+ },
89
+ "Thinking": {
90
+ "thinking": "深度思考中...",
91
+ "thought": "已深度思考",
92
+ "thoughtWithDuration": "已深度思考(用时 {{duration}} 秒)"
88
93
  }
89
94
  }
@@ -82,10 +82,6 @@
82
82
  }
83
83
  }
84
84
  },
85
- "reasoning": {
86
- "thinking": "深入思考中",
87
- "thought": "已深度思考(用時 {{duration}} 秒)"
88
- },
89
85
  "regenerate": "重新生成",
90
86
  "roleAndArchive": "角色與記錄",
91
87
  "searchAgentPlaceholder": "搜尋助手...",
@@ -85,5 +85,10 @@
85
85
  "ModelSwitchPanel": {
86
86
  "emptyModel": "沒有啟用的模型,請前往設定開啟",
87
87
  "provider": "提供商"
88
+ },
89
+ "Thinking": {
90
+ "thinking": "深度思考中...",
91
+ "thought": "已深度思考",
92
+ "thoughtWithDuration": "已深度思考(用時 {{duration}} 秒)"
88
93
  }
89
94
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@lobehub/chat",
3
- "version": "1.48.2",
3
+ "version": "1.48.4",
4
4
  "description": "Lobe Chat - an open-source, high-performance chatbot framework that supports speech synthesis, multimodal, and extensible Function Call plugin system. Supports one-click free deployment of your private ChatGPT/LLM web application.",
5
5
  "keywords": [
6
6
  "framework",
@@ -11,12 +11,10 @@ import { ModelInfoTags } from '@/components/ModelSelect';
11
11
  import { useIsMobile } from '@/hooks/useIsMobile';
12
12
  import { aiModelSelectors, useAiInfraStore } from '@/store/aiInfra';
13
13
  import { AiModelSourceEnum, AiProviderModelListItem, ChatModelPricing } from '@/types/aiModel';
14
+ import { formatPriceByCurrency } from '@/utils/format';
14
15
 
15
16
  import ModelConfigModal from './ModelConfigModal';
16
17
 
17
- const f = (number: number | undefined, text: string) =>
18
- typeof number !== 'undefined' ? text : undefined;
19
-
20
18
  export const useStyles = createStyles(({ css, token, cx }) => {
21
19
  const config = css`
22
20
  opacity: 0;
@@ -74,7 +72,7 @@ const ModelItem = memo<ModelItemProps>(
74
72
  type,
75
73
  }) => {
76
74
  const { styles } = useStyles();
77
- const { t } = useTranslation(['modelProvider', 'components', 'models']);
75
+ const { t } = useTranslation(['modelProvider', 'components', 'models', 'common']);
78
76
  const theme = useTheme();
79
77
 
80
78
  const [activeAiProvider, isModelLoading, toggleModelEnabled, removeAiModel] = useAiInfraStore(
@@ -90,41 +88,43 @@ const ModelItem = memo<ModelItemProps>(
90
88
  const [showConfig, setShowConfig] = useState(false);
91
89
 
92
90
  const formatPricing = (): string[] => {
91
+ if (!pricing) return [];
92
+
93
93
  switch (type) {
94
94
  case 'chat': {
95
95
  return [
96
- f(
97
- pricing?.input,
98
- t('providerModels.item.pricing.inputTokens', { amount: pricing?.input }),
99
- ),
100
- f(
101
- pricing?.output,
102
- t('providerModels.item.pricing.outputTokens', { amount: pricing?.output }),
103
- ),
96
+ typeof pricing.input === 'number' &&
97
+ t('providerModels.item.pricing.inputTokens', {
98
+ amount: formatPriceByCurrency(pricing.input, pricing?.currency),
99
+ }),
100
+ typeof pricing.output === 'number' &&
101
+ t('providerModels.item.pricing.outputTokens', {
102
+ amount: formatPriceByCurrency(pricing.output, pricing?.currency),
103
+ }),
104
104
  ].filter(Boolean) as string[];
105
105
  }
106
106
  case 'embedding': {
107
107
  return [
108
- f(
109
- pricing?.input,
110
- t('providerModels.item.pricing.inputTokens', { amount: pricing?.input }),
111
- ),
108
+ typeof pricing.input === 'number' &&
109
+ t('providerModels.item.pricing.inputTokens', {
110
+ amount: formatPriceByCurrency(pricing.input, pricing?.currency),
111
+ }),
112
112
  ].filter(Boolean) as string[];
113
113
  }
114
114
  case 'tts': {
115
115
  return [
116
- f(
117
- pricing?.input,
118
- t('providerModels.item.pricing.inputCharts', { amount: pricing?.input }),
119
- ),
116
+ typeof pricing.input === 'number' &&
117
+ t('providerModels.item.pricing.inputCharts', {
118
+ amount: formatPriceByCurrency(pricing.input, pricing?.currency),
119
+ }),
120
120
  ].filter(Boolean) as string[];
121
121
  }
122
122
  case 'stt': {
123
123
  return [
124
- f(
125
- pricing?.input,
126
- t('providerModels.item.pricing.inputMinutes', { amount: pricing?.input }),
127
- ),
124
+ typeof pricing.input === 'number' &&
125
+ t('providerModels.item.pricing.inputMinutes', {
126
+ amount: formatPriceByCurrency(pricing.input, pricing?.currency),
127
+ }),
128
128
  ].filter(Boolean) as string[];
129
129
  }
130
130
 
@@ -142,7 +142,12 @@ const ModelItem = memo<ModelItemProps>(
142
142
  releasedAt && t('providerModels.item.releasedAt', { releasedAt }),
143
143
  ...formatPricing(),
144
144
  ].filter(Boolean) as string[];
145
+
145
146
  const { message, modal } = App.useApp();
147
+ const copyModelId = async () => {
148
+ await copyToClipboard(id);
149
+ message.success({ content: t('copySuccess', { ns: 'common' }) });
150
+ };
146
151
 
147
152
  const isMobile = useIsMobile();
148
153
 
@@ -179,12 +184,7 @@ const ModelItem = memo<ModelItemProps>(
179
184
  </Flexbox>
180
185
  </Flexbox>
181
186
  <div>
182
- <Tag
183
- onClick={() => {
184
- copyToClipboard(id);
185
- }}
186
- style={{ cursor: 'pointer', marginRight: 0 }}
187
- >
187
+ <Tag onClick={copyModelId} style={{ cursor: 'pointer', marginRight: 0 }}>
188
188
  {id}
189
189
  </Tag>
190
190
  </div>
@@ -251,12 +251,7 @@ const ModelItem = memo<ModelItemProps>(
251
251
  <Flexbox flex={1} gap={2} style={{ minWidth: 0 }}>
252
252
  <Flexbox align={'center'} gap={8} horizontal>
253
253
  {displayName || id}
254
- <Tag
255
- onClick={() => {
256
- copyToClipboard(id);
257
- }}
258
- style={{ cursor: 'pointer', marginRight: 0 }}
259
- >
254
+ <Tag onClick={copyModelId} style={{ cursor: 'pointer', marginRight: 0 }}>
260
255
  {id}
261
256
  </Tag>
262
257
  <Flexbox className={styles.config} horizontal>
@@ -0,0 +1,137 @@
1
+ import { Icon, Markdown } from '@lobehub/ui';
2
+ import { createStyles } from 'antd-style';
3
+ import { AnimatePresence, motion } from 'framer-motion';
4
+ import { AtomIcon, ChevronDown, ChevronRight } from 'lucide-react';
5
+ import { rgba } from 'polished';
6
+ import { memo, useEffect, useState } from 'react';
7
+ import { useTranslation } from 'react-i18next';
8
+ import { Flexbox } from 'react-layout-kit';
9
+
10
+ const useStyles = createStyles(({ css, token, isDarkMode }) => ({
11
+ container: css`
12
+ cursor: pointer;
13
+
14
+ width: fit-content;
15
+ padding-block: 4px;
16
+ padding-inline: 8px;
17
+ border-radius: 6px;
18
+
19
+ color: ${token.colorTextTertiary};
20
+
21
+ &:hover {
22
+ background: ${isDarkMode ? token.colorFillQuaternary : token.colorFillTertiary};
23
+ }
24
+ `,
25
+ expand: css`
26
+ background: ${isDarkMode ? token.colorFillQuaternary : token.colorFillTertiary} !important;
27
+ `,
28
+ shinyText: css`
29
+ color: ${rgba(token.colorText, 0.45)};
30
+
31
+ background: linear-gradient(
32
+ 120deg,
33
+ ${rgba(token.colorTextBase, 0)} 40%,
34
+ ${token.colorTextSecondary} 50%,
35
+ ${rgba(token.colorTextBase, 0)} 60%
36
+ );
37
+ background-clip: text;
38
+ background-size: 200% 100%;
39
+
40
+ animation: shine 1.5s linear infinite;
41
+
42
+ @keyframes shine {
43
+ 0% {
44
+ background-position: 100%;
45
+ }
46
+
47
+ 100% {
48
+ background-position: -100%;
49
+ }
50
+ }
51
+ `,
52
+ title: css`
53
+ overflow: hidden;
54
+ display: -webkit-box;
55
+ -webkit-box-orient: vertical;
56
+ -webkit-line-clamp: 1;
57
+
58
+ font-size: 12px;
59
+ text-overflow: ellipsis;
60
+ `,
61
+ }));
62
+
63
+ interface ThinkingProps {
64
+ content?: string;
65
+ duration?: number;
66
+ thinking?: boolean;
67
+ }
68
+
69
+ const Thinking = memo<ThinkingProps>(({ content = '', duration, thinking }) => {
70
+ const { t } = useTranslation('components');
71
+ const { styles, cx } = useStyles();
72
+
73
+ const [showDetail, setShowDetail] = useState(false);
74
+
75
+ useEffect(() => {
76
+ if (thinking && !content) {
77
+ setShowDetail(true);
78
+ }
79
+
80
+ if (!thinking) {
81
+ setShowDetail(false);
82
+ }
83
+ }, [thinking, content]);
84
+
85
+ return (
86
+ <Flexbox
87
+ className={cx(styles.container, showDetail && styles.expand)}
88
+ gap={16}
89
+ onClick={() => {
90
+ setShowDetail(!showDetail);
91
+ }}
92
+ >
93
+ <Flexbox distribution={'space-between'} flex={1} horizontal>
94
+ {thinking ? (
95
+ <Flexbox gap={8} horizontal>
96
+ <Icon icon={AtomIcon} />
97
+ <Flexbox className={styles.shinyText} horizontal>
98
+ {t('Thinking.thinking')}
99
+ </Flexbox>
100
+ </Flexbox>
101
+ ) : (
102
+ <Flexbox gap={8} horizontal>
103
+ <Icon icon={AtomIcon} />
104
+ {typeof duration === 'undefined'
105
+ ? t('Thinking.thoughtWithDuration')
106
+ : t('Thinking.thought', { duration: ((duration || 0) / 1000).toFixed(1) })}
107
+ </Flexbox>
108
+ )}
109
+ <Icon icon={showDetail ? ChevronDown : ChevronRight} />
110
+ </Flexbox>
111
+
112
+ <AnimatePresence initial={false}>
113
+ {showDetail && (
114
+ <motion.div
115
+ animate="open"
116
+ exit="collapsed"
117
+ initial="collapsed"
118
+ style={{ overflow: 'hidden' }}
119
+ transition={{
120
+ duration: 0.2,
121
+ ease: [0.4, 0, 0.2, 1], // 使用 ease-out 缓动函数
122
+ }}
123
+ variants={{
124
+ collapsed: { height: 0, opacity: 0, width: 'auto' },
125
+ open: { height: 'auto', opacity: 1, width: 'auto' },
126
+ }}
127
+ >
128
+ <Markdown variant={'chat'}>{content}</Markdown>
129
+ </motion.div>
130
+ )}
131
+ {/*<Flexbox></Flexbox>*/}
132
+ </AnimatePresence>
133
+ </Flexbox>
134
+ );
135
+ });
136
+
137
+ export default Thinking;
@@ -0,0 +1,2 @@
1
+ // in 2025.01.26
2
+ export const USD_TO_CNY = 7.24;
@@ -6,8 +6,6 @@ import {
6
6
  DiscoverProviderItem,
7
7
  } from '@/types/discover';
8
8
 
9
- export const CNY_TO_USD = 7.14;
10
-
11
9
  const DEFAULT_CREATED_AT = new Date().toISOString();
12
10
 
13
11
  export const DEFAULT_DISCOVER_ASSISTANT_ITEM: Partial<DiscoverAssistantItem> = {