@lobehub/chat 1.77.10 → 1.77.12

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 (97) hide show
  1. package/.github/scripts/pr-comment.js +111 -55
  2. package/CHANGELOG.md +51 -0
  3. package/changelog/v1.json +18 -0
  4. package/docs/self-hosting/environment-variables/basic.mdx +39 -0
  5. package/docs/self-hosting/environment-variables/basic.zh-CN.mdx +40 -1
  6. package/locales/ar/hotkey.json +4 -0
  7. package/locales/ar/models.json +6 -0
  8. package/locales/bg-BG/hotkey.json +4 -0
  9. package/locales/bg-BG/models.json +6 -0
  10. package/locales/de-DE/hotkey.json +4 -0
  11. package/locales/de-DE/models.json +6 -0
  12. package/locales/en-US/hotkey.json +4 -0
  13. package/locales/en-US/models.json +6 -0
  14. package/locales/es-ES/hotkey.json +4 -0
  15. package/locales/es-ES/models.json +6 -0
  16. package/locales/fa-IR/hotkey.json +4 -0
  17. package/locales/fa-IR/models.json +6 -0
  18. package/locales/fr-FR/hotkey.json +4 -0
  19. package/locales/fr-FR/models.json +6 -0
  20. package/locales/it-IT/hotkey.json +4 -0
  21. package/locales/it-IT/models.json +6 -0
  22. package/locales/ja-JP/hotkey.json +4 -0
  23. package/locales/ja-JP/models.json +6 -0
  24. package/locales/ko-KR/hotkey.json +4 -0
  25. package/locales/ko-KR/models.json +6 -0
  26. package/locales/nl-NL/hotkey.json +4 -0
  27. package/locales/nl-NL/models.json +6 -0
  28. package/locales/pl-PL/hotkey.json +4 -0
  29. package/locales/pl-PL/models.json +6 -0
  30. package/locales/pt-BR/hotkey.json +4 -0
  31. package/locales/pt-BR/models.json +6 -0
  32. package/locales/ru-RU/hotkey.json +4 -0
  33. package/locales/ru-RU/models.json +6 -0
  34. package/locales/tr-TR/hotkey.json +4 -0
  35. package/locales/tr-TR/models.json +6 -0
  36. package/locales/vi-VN/hotkey.json +4 -0
  37. package/locales/vi-VN/models.json +6 -0
  38. package/locales/zh-CN/hotkey.json +4 -0
  39. package/locales/zh-CN/models.json +7 -1
  40. package/locales/zh-TW/hotkey.json +4 -0
  41. package/locales/zh-TW/models.json +6 -0
  42. package/package.json +1 -1
  43. package/packages/electron-client-ipc/src/dispatch.ts +29 -0
  44. package/packages/electron-client-ipc/src/events/index.ts +9 -2
  45. package/packages/electron-client-ipc/src/events/menu.ts +5 -0
  46. package/packages/electron-client-ipc/src/events/search.ts +4 -0
  47. package/packages/electron-client-ipc/src/events/system.ts +3 -0
  48. package/packages/electron-client-ipc/src/events/windows.ts +8 -0
  49. package/packages/electron-client-ipc/src/index.ts +1 -0
  50. package/packages/web-crawler/src/crawler.ts +3 -1
  51. package/scripts/migrateServerDB/index.ts +3 -2
  52. package/src/app/(backend)/webapi/assistant/store/route.ts +7 -1
  53. package/src/app/(backend)/webapi/plugin/store/route.ts +6 -1
  54. package/src/app/[variants]/(main)/(mobile)/me/(home)/__tests__/UserBanner.test.tsx +1 -0
  55. package/src/app/[variants]/(main)/_layout/Desktop/SideBar/TopActions.tsx +3 -2
  56. package/src/app/[variants]/(main)/_layout/Desktop/SideBar/index.tsx +24 -5
  57. package/src/app/[variants]/(main)/_layout/Desktop/Titlebar.tsx +27 -0
  58. package/src/app/[variants]/(main)/_layout/Desktop/index.tsx +32 -3
  59. package/src/app/[variants]/(main)/chat/(workspace)/@conversation/features/ChatList/WelcomeChatItem/InboxWelcome/AgentsSuggest.tsx +3 -0
  60. package/src/app/[variants]/(main)/chat/(workspace)/_layout/Desktop/ChatHeader/index.tsx +12 -2
  61. package/src/app/[variants]/(main)/chat/(workspace)/page.tsx +2 -1
  62. package/src/app/[variants]/(main)/settings/hotkey/features/HotkeySetting.tsx +15 -6
  63. package/src/app/[variants]/(main)/settings/hotkey/page.tsx +10 -1
  64. package/src/app/[variants]/(main)/settings/provider/features/ProviderConfig/index.tsx +3 -1
  65. package/src/app/desktop/devtools/page.tsx +37 -38
  66. package/src/config/aiModels/google.ts +25 -1
  67. package/src/config/aiModels/novita.ts +1 -32
  68. package/src/config/aiModels/ollama.ts +12 -1
  69. package/src/config/aiModels/qwen.ts +1 -1
  70. package/src/const/hotkeys.ts +10 -0
  71. package/src/features/Conversation/Error/index.tsx +10 -0
  72. package/src/features/DevPanel/PostgresViewer/SchemaSidebar/index.tsx +1 -1
  73. package/src/features/User/UserPanel/index.tsx +1 -1
  74. package/src/features/User/__tests__/PanelContent.test.tsx +1 -0
  75. package/src/libs/agent-runtime/error.ts +1 -0
  76. package/src/libs/agent-runtime/ollama/index.ts +16 -12
  77. package/src/libs/trpc/index.ts +8 -1
  78. package/src/libs/trpc/middleware/userAuth.ts +2 -3
  79. package/src/locales/default/hotkey.ts +4 -0
  80. package/src/server/globalConfig/genServerAiProviderConfig.ts +12 -3
  81. package/src/server/globalConfig/index.ts +6 -1
  82. package/src/server/globalConfig/parseSystemAgent.test.ts +56 -0
  83. package/src/server/globalConfig/parseSystemAgent.ts +25 -0
  84. package/src/server/modules/AssistantStore/index.ts +8 -0
  85. package/src/server/services/changelog/index.test.ts +2 -0
  86. package/src/server/services/changelog/index.ts +6 -2
  87. package/src/services/__tests__/chat.test.ts +3 -0
  88. package/src/services/electron/devtools.ts +1 -1
  89. package/src/store/user/slices/modelList/selectors/modelConfig.ts +4 -0
  90. package/src/styles/global.ts +14 -0
  91. package/src/types/hotkey.ts +1 -0
  92. package/src/utils/errorResponse.ts +2 -0
  93. package/tsconfig.json +1 -6
  94. package/packages/electron-client-ipc/src/events/devtools.ts +0 -6
  95. package/src/app/[variants]/(main)/settings/hotkey/index.tsx +0 -9
  96. package/src/types/electron.ts +0 -11
  97. package/src/utils/electron/dispatch.ts +0 -10
@@ -2,10 +2,12 @@
2
2
 
3
3
  import { useTheme } from 'antd-style';
4
4
  import dynamic from 'next/dynamic';
5
+ import { usePathname } from 'next/navigation';
5
6
  import { PropsWithChildren, Suspense, memo } from 'react';
6
7
  import { HotkeysProvider } from 'react-hotkeys-hook';
7
8
  import { Flexbox } from 'react-layout-kit';
8
9
 
10
+ import { isDesktop } from '@/const/version';
9
11
  import { BANNER_HEIGHT } from '@/features/AlertBanner/CloudBanner';
10
12
  import HotkeyHelperPanel from '@/features/HotkeyHelperPanel';
11
13
  import { usePlatform } from '@/hooks/usePlatform';
@@ -14,19 +16,31 @@ import { HotkeyScopeEnum } from '@/types/hotkey';
14
16
 
15
17
  import RegisterHotkeys from './RegisterHotkeys';
16
18
  import SideBar from './SideBar';
19
+ import TitleBar, { TITLE_BAR_HEIGHT } from './Titlebar';
17
20
 
18
21
  const CloudBanner = dynamic(() => import('@/features/AlertBanner/CloudBanner'));
19
22
 
20
23
  const Layout = memo<PropsWithChildren>(({ children }) => {
21
24
  const { isPWA } = usePlatform();
22
25
  const theme = useTheme();
26
+
27
+ const pathname = usePathname();
23
28
  const { showCloudPromotion } = useServerConfigStore(featureFlagsSelectors);
24
29
 
30
+ // setting page not show sidebar
31
+ const hideSideBar = isDesktop && pathname.startsWith('/settings');
25
32
  return (
26
33
  <HotkeysProvider initiallyActiveScopes={[HotkeyScopeEnum.Global]}>
34
+ {isDesktop && <TitleBar />}
27
35
  {showCloudPromotion && <CloudBanner />}
28
36
  <Flexbox
29
- height={showCloudPromotion ? `calc(100% - ${BANNER_HEIGHT}px)` : '100%'}
37
+ height={
38
+ isDesktop
39
+ ? `calc(100% - ${TITLE_BAR_HEIGHT}px)`
40
+ : showCloudPromotion
41
+ ? `calc(100% - ${BANNER_HEIGHT}px)`
42
+ : '100%'
43
+ }
30
44
  horizontal
31
45
  style={{
32
46
  borderTop: isPWA ? `1px solid ${theme.colorBorder}` : undefined,
@@ -34,8 +48,23 @@ const Layout = memo<PropsWithChildren>(({ children }) => {
34
48
  }}
35
49
  width={'100%'}
36
50
  >
37
- <SideBar />
38
- {children}
51
+ {!hideSideBar && <SideBar />}
52
+ {isDesktop ? (
53
+ <Flexbox
54
+ style={{
55
+ background: theme.colorBgLayout,
56
+ borderInlineStart: `1px solid ${theme.colorBorderSecondary}`,
57
+ borderStartStartRadius: !hideSideBar ? 12 : undefined,
58
+ borderTop: `1px solid ${theme.colorBorderSecondary}`,
59
+ overflow: 'hidden',
60
+ }}
61
+ width={'100%'}
62
+ >
63
+ {children}
64
+ </Flexbox>
65
+ ) : (
66
+ children
67
+ )}
39
68
  </Flexbox>
40
69
  <HotkeyHelperPanel />
41
70
  <Suspense>
@@ -88,6 +88,9 @@ const AgentsSuggest = memo<{ mobile?: boolean }>(({ mobile }) => {
88
88
  setSliceStart(Math.floor((Math.random() * assistantList.length) / 2));
89
89
  };
90
90
 
91
+ // if no assistant data, just hide the component
92
+ if (!isLoading && assistantList?.length === 0) return null;
93
+
91
94
  return (
92
95
  <Flexbox gap={8} width={'100%'}>
93
96
  <Flexbox align={'center'} horizontal justify={'space-between'}>
@@ -4,6 +4,7 @@ import { ChatHeader } from '@lobehub/ui/chat';
4
4
 
5
5
  import { useGlobalStore } from '@/store/global';
6
6
  import { systemStatusSelectors } from '@/store/global/selectors';
7
+ import { electronStylish } from '@/styles/electron';
7
8
 
8
9
  import HeaderAction from './HeaderAction';
9
10
  import Main from './Main';
@@ -14,8 +15,17 @@ const Header = () => {
14
15
  return (
15
16
  showHeader && (
16
17
  <ChatHeader
17
- left={<Main />}
18
- right={<HeaderAction />}
18
+ className={electronStylish.draggable}
19
+ left={
20
+ <div className={electronStylish.nodrag}>
21
+ <Main />
22
+ </div>
23
+ }
24
+ right={
25
+ <div className={electronStylish.nodrag}>
26
+ <HeaderAction />
27
+ </div>
28
+ }
19
29
  style={{ height: 48, minHeight: 48, paddingInline: 8, position: 'initial', zIndex: 11 }}
20
30
  />
21
31
  )
@@ -3,6 +3,7 @@ import { Suspense } from 'react';
3
3
  import StructuredData from '@/components/StructuredData';
4
4
  import { serverFeatureFlags } from '@/config/featureFlags';
5
5
  import { BRANDING_NAME } from '@/const/branding';
6
+ import { isDesktop } from '@/const/version';
6
7
  import { ldModule } from '@/server/ld';
7
8
  import { metadataModule } from '@/server/metadata';
8
9
  import { translation } from '@/server/translation';
@@ -38,7 +39,7 @@ const Page = async (props: DynamicLayoutProps) => {
38
39
  <StructuredData ld={ld} />
39
40
  <PageTitle />
40
41
  <TelemetryNotification mobile={isMobile} />
41
- {showChangelog && !hideDocs && !isMobile && (
42
+ {!isDesktop && showChangelog && !hideDocs && !isMobile && (
42
43
  <Suspense>
43
44
  <Changelog />
44
45
  </Suspense>
@@ -7,11 +7,19 @@ import { useTranslation } from 'react-i18next';
7
7
 
8
8
  import { HOTKEYS_REGISTRATION } from '@/const/hotkeys';
9
9
  import { FORM_STYLE } from '@/const/layoutTokens';
10
+ import { isDesktop } from '@/const/version';
10
11
  import hotkeyMeta from '@/locales/default/hotkey';
11
12
  import { useUserStore } from '@/store/user';
12
13
  import { settingsSelectors } from '@/store/user/selectors';
13
14
  import { HotkeyGroupEnum, HotkeyItem } from '@/types/hotkey';
14
15
 
16
+ const filterByDesktop = (item: HotkeyItem) => {
17
+ if (isDesktop) return true;
18
+
19
+ // is not desktop, filter out desktop only items
20
+ if (!isDesktop) return !item.isDesktop;
21
+ };
22
+
15
23
  type SettingItemGroup = ItemGroup;
16
24
 
17
25
  const HOTKEY_SETTING_KEY = 'hotkey';
@@ -30,6 +38,7 @@ const HotkeySetting = memo(() => {
30
38
  return value;
31
39
  })
32
40
  .filter(Boolean) as string[];
41
+
33
42
  return {
34
43
  children: (
35
44
  <HotkeyInput
@@ -51,16 +60,16 @@ const HotkeySetting = memo(() => {
51
60
  };
52
61
 
53
62
  const essential: SettingItemGroup = {
54
- children: HOTKEYS_REGISTRATION.filter((item) => item.group === HotkeyGroupEnum.Essential).map(
55
- (item) => mapHotkeyItem(item),
56
- ),
63
+ children: HOTKEYS_REGISTRATION.filter((item) => item.group === HotkeyGroupEnum.Essential)
64
+ .filter((item) => filterByDesktop(item))
65
+ .map((item) => mapHotkeyItem(item)),
57
66
  title: t('hotkey.group.essential'),
58
67
  };
59
68
 
60
69
  const conversation: SettingItemGroup = {
61
- children: HOTKEYS_REGISTRATION.filter(
62
- (item) => item.group === HotkeyGroupEnum.Conversation,
63
- ).map((item) => mapHotkeyItem(item)),
70
+ children: HOTKEYS_REGISTRATION.filter((item) => item.group === HotkeyGroupEnum.Conversation)
71
+ .filter((item) => filterByDesktop(item))
72
+ .map((item) => mapHotkeyItem(item)),
64
73
  title: t('hotkey.group.conversation'),
65
74
  };
66
75
 
@@ -3,6 +3,8 @@ import { translation } from '@/server/translation';
3
3
  import { DynamicLayoutProps } from '@/types/next';
4
4
  import { RouteVariants } from '@/utils/server/routeVariants';
5
5
 
6
+ import HotkeySetting from './features/HotkeySetting';
7
+
6
8
  export const generateMetadata = async (props: DynamicLayoutProps) => {
7
9
  const locale = await RouteVariants.getLocale(props);
8
10
  const { t } = await translation('setting', locale);
@@ -12,4 +14,11 @@ export const generateMetadata = async (props: DynamicLayoutProps) => {
12
14
  url: '/settings/hotkey',
13
15
  });
14
16
  };
15
- export { default } from './index';
17
+
18
+ const Page = () => {
19
+ return <HotkeySetting />;
20
+ };
21
+
22
+ Page.displayName = 'HotkeySetting';
23
+
24
+ export default Page;
@@ -16,7 +16,7 @@ import { z } from 'zod';
16
16
  import { FormInput, FormPassword } from '@/components/FormInput';
17
17
  import { FORM_STYLE } from '@/const/layoutTokens';
18
18
  import { AES_GCM_URL, BASE_PROVIDER_DOC_URL } from '@/const/url';
19
- import { isServerMode } from '@/const/version';
19
+ import { isDesktop, isServerMode } from '@/const/version';
20
20
  import { aiProviderSelectors, useAiInfraStore } from '@/store/aiInfra';
21
21
  import {
22
22
  AiProviderDetailItem,
@@ -244,12 +244,14 @@ const ProviderConfig = memo<ProviderConfigProps>(
244
244
 
245
245
  /*
246
246
  * Conditions to show Client Fetch Switch
247
+ * 0. is not desktop app
247
248
  * 1. provider is not disabled browser request
248
249
  * 2. provider show browser request by default
249
250
  * 3. Provider allow to edit endpoint and the value of endpoint is not empty
250
251
  * 4. There is an apikey provided by user
251
252
  */
252
253
  const showClientFetch =
254
+ !isDesktop &&
253
255
  !disableBrowserRequest &&
254
256
  (defaultShowBrowserRequest ||
255
257
  (showEndpoint && isProviderEndpointNotEmpty) ||
@@ -1,6 +1,6 @@
1
1
  'use client';
2
2
 
3
- import { ActionIcon, FluentEmoji, SideNav } from '@lobehub/ui';
3
+ import { ActionIcon, SideNav } from '@lobehub/ui';
4
4
  import { Cog, DatabaseIcon } from 'lucide-react';
5
5
  import { memo, useState } from 'react';
6
6
  import { Flexbox } from 'react-layout-kit';
@@ -30,44 +30,43 @@ const DevTools = memo(() => {
30
30
  const [tab, setTab] = useState<string>(items[0].key);
31
31
 
32
32
  return (
33
- <Flexbox
34
- height={'100%'}
35
- horizontal
36
- style={{ overflow: 'hidden', position: 'relative' }}
37
- width={'100%'}
38
- >
39
- <SideNav
40
- avatar={<FluentEmoji emoji={'🧰'} size={24} />}
41
- bottomActions={[]}
42
- style={{
43
- paddingBlock: 32,
44
- width: 48,
45
- }}
46
- topActions={items.map((item) => (
47
- <ActionIcon
48
- active={tab === item.key}
49
- key={item.key}
50
- onClick={() => setTab(item.key)}
51
- placement={'right'}
52
- title={item.key}
53
- >
54
- {item.icon}
55
- </ActionIcon>
56
- ))}
57
- />
58
- <Flexbox height={'100%'} style={{ overflow: 'hidden', position: 'relative' }} width={'100%'}>
59
- <Flexbox
60
- align={'center'}
61
- className={cx(`panel-drag-handle`, styles.header, electronStylish.draggable)}
62
- horizontal
63
- justify={'center'}
64
- >
65
- <Flexbox align={'baseline'} gap={6} horizontal>
66
- <b>{BRANDING_NAME} Dev Tools</b>
67
- <span style={{ color: theme.colorTextDescription }}>/</span>
68
- <span style={{ color: theme.colorTextDescription }}>{tab}</span>
69
- </Flexbox>
33
+ <Flexbox height={'100%'} style={{ overflow: 'hidden', position: 'relative' }} width={'100%'}>
34
+ <Flexbox
35
+ align={'center'}
36
+ className={cx(`panel-drag-handle`, styles.header, electronStylish.draggable)}
37
+ horizontal
38
+ justify={'center'}
39
+ >
40
+ <Flexbox align={'baseline'} gap={6} horizontal>
41
+ <b>{BRANDING_NAME} Dev Tools</b>
42
+ <span style={{ color: theme.colorTextDescription }}>/</span>
43
+ <span style={{ color: theme.colorTextDescription }}>{tab}</span>
70
44
  </Flexbox>
45
+ </Flexbox>
46
+ <Flexbox
47
+ height={'100%'}
48
+ horizontal
49
+ style={{ background: theme.colorBgLayout, overflow: 'hidden', position: 'relative' }}
50
+ width={'100%'}
51
+ >
52
+ <SideNav
53
+ bottomActions={[]}
54
+ style={{
55
+ background: 'transparent',
56
+ width: 48,
57
+ }}
58
+ topActions={items.map((item) => (
59
+ <ActionIcon
60
+ active={tab === item.key}
61
+ key={item.key}
62
+ onClick={() => setTab(item.key)}
63
+ placement={'right'}
64
+ title={item.key}
65
+ >
66
+ {item.icon}
67
+ </ActionIcon>
68
+ ))}
69
+ />
71
70
  {items.map((item) => (
72
71
  <Flexbox
73
72
  flex={1}
@@ -10,7 +10,7 @@ const googleChatModels: AIChatModelCard[] = [
10
10
  },
11
11
  contextWindowTokens: 1_048_576 + 65_536,
12
12
  description:
13
- 'Gemini 2.5 Pro Experimental 是 Google 最先进的思维模型,能够对代码、数学和STEM领域的复杂问题进行推理,还能利用长上下文来分析大型数据集、代码库和文档。',
13
+ 'Gemini 2.5 Pro Experimental 是 Google 最先进的思维模型,能够对代码、数学和STEM领域的复杂问题进行推理,以及使用长上下文分析大型数据集、代码库和文档。',
14
14
  displayName: 'Gemini 2.5 Pro Experimental 03-25',
15
15
  enabled: true,
16
16
  id: 'gemini-2.5-pro-exp-03-25',
@@ -27,6 +27,30 @@ const googleChatModels: AIChatModelCard[] = [
27
27
  },
28
28
  type: 'chat',
29
29
  },
30
+ {
31
+ abilities: {
32
+ functionCall: true,
33
+ reasoning: true,
34
+ search: true,
35
+ vision: true,
36
+ },
37
+ contextWindowTokens: 1_048_576 + 65_536,
38
+ description:
39
+ 'Gemini 2.5 Pro Preview 是 Google 最先进的思维模型,能够对代码、数学和STEM领域的复杂问题进行推理,以及使用长上下文分析大型数据集、代码库和文档。',
40
+ displayName: 'Gemini 2.5 Pro Preview 03-25 (Paid)',
41
+ id: 'gemini-2.5-pro-preview-03-25',
42
+ maxOutput: 65_536,
43
+ pricing: {
44
+ input: 1.25, // prompts <= 200k tokens
45
+ output: 10, // prompts <= 200k tokens
46
+ },
47
+ releasedAt: '2025-03-25',
48
+ settings: {
49
+ searchImpl: 'params',
50
+ searchProvider: 'google',
51
+ },
52
+ type: 'chat',
53
+ },
30
54
  {
31
55
  abilities: {
32
56
  reasoning: true,
@@ -9,7 +9,7 @@ const novitaChatModels: AIChatModelCard[] = [
9
9
  enabled: true,
10
10
  id: 'meta-llama/llama-3.3-70b-instruct',
11
11
  pricing: {
12
- input: 0.39,
12
+ input: 0.13,
13
13
  output: 0.39,
14
14
  },
15
15
  type: 'chat',
@@ -147,17 +147,6 @@ const novitaChatModels: AIChatModelCard[] = [
147
147
  },
148
148
  type: 'chat',
149
149
  },
150
- {
151
- contextWindowTokens: 4096,
152
- description: 'OpenChat 7B 是经过“C-RLFT(条件强化学习微调)”策略精调的开源语言模型库。',
153
- displayName: 'OpenChat 7B',
154
- id: 'openchat/openchat-7b',
155
- pricing: {
156
- input: 0.06,
157
- output: 0.06,
158
- },
159
- type: 'chat',
160
- },
161
150
  {
162
151
  contextWindowTokens: 64_000,
163
152
  displayName: 'Deepseek V3 Turbo',
@@ -308,26 +297,6 @@ const novitaChatModels: AIChatModelCard[] = [
308
297
  },
309
298
  type: 'chat',
310
299
  },
311
- {
312
- contextWindowTokens: 4096,
313
- displayName: 'Nous Hermes Llama2 13B',
314
- id: 'nousresearch/nous-hermes-llama2-13b',
315
- pricing: {
316
- input: 0.17,
317
- output: 0.17,
318
- },
319
- type: 'chat',
320
- },
321
- {
322
- contextWindowTokens: 4096,
323
- displayName: 'OpenHermes 2.5 Mistral 7B',
324
- id: 'teknium/openhermes-2.5-mistral-7b',
325
- pricing: {
326
- input: 0.17,
327
- output: 0.17,
328
- },
329
- type: 'chat',
330
- },
331
300
  {
332
301
  contextWindowTokens: 4096,
333
302
  displayName: 'Midnight Rose 70B',
@@ -13,6 +13,14 @@ const ollamaChatModels: AIChatModelCard[] = [
13
13
  id: 'deepseek-r1',
14
14
  type: 'chat',
15
15
  },
16
+ {
17
+ contextWindowTokens: 65_536,
18
+ description:
19
+ 'DeepSeek-V3 是一个强大的专家混合(MoE)语言模型,总参数量为 671B,每个 Token 激活 37B 参数。该模型采用多头潜在注意力(MLA)和 DeepSeekMoE 架构,实现了高效推理和经济训练,并在前代 DeepSeek-V3 的基础上显著提升了性能。',
20
+ displayName: 'DeepSeek V3 671B',
21
+ id: 'deepseek-v3',
22
+ type: 'chat',
23
+ },
16
24
  {
17
25
  abilities: {
18
26
  functionCall: true,
@@ -78,8 +86,10 @@ const ollamaChatModels: AIChatModelCard[] = [
78
86
  reasoning: true,
79
87
  },
80
88
  contextWindowTokens: 128_000,
81
- description: 'QwQ 是一个实验研究模型,专注于提高 AI 推理能力。',
89
+ description:
90
+ 'QwQ 是 Qwen 系列的推理模型。与传统的指令调优模型相比,QwQ 具备思考和推理的能力,能够在下游任务中,尤其是困难问题上,显著提升性能。QwQ-32B 是中型推理模型,能够在与最先进的推理模型(如 DeepSeek-R1、o1-mini)竞争时取得可观的表现。',
82
91
  displayName: 'QwQ 32B',
92
+ enabled: true,
83
93
  id: 'qwq',
84
94
  releasedAt: '2024-11-28',
85
95
  type: 'chat',
@@ -105,6 +115,7 @@ const ollamaChatModels: AIChatModelCard[] = [
105
115
  contextWindowTokens: 128_000,
106
116
  description: 'Qwen2.5 是阿里巴巴的新一代大规模语言模型,以优异的性能支持多元化的应用需求。',
107
117
  displayName: 'Qwen2.5 7B',
118
+ enabled: true,
108
119
  id: 'qwen2.5',
109
120
  type: 'chat',
110
121
  },
@@ -302,7 +302,7 @@ const qwenChatModels: AIChatModelCard[] = [
302
302
  description:
303
303
  '通义千问QVQ视觉推理模型,支持视觉输入及思维链输出,在数学、编程、视觉分析、创作以及通用任务上都表现了更强的能力。',
304
304
  displayName: 'QVQ Max',
305
- id: 'qvq-max-latest',
305
+ id: 'qvq-max', // Unsupported model `qvq-max-latest` for OpenAI compatibility mode
306
306
  maxOutput: 8192,
307
307
  organization: 'Qwen',
308
308
  pricing: {
@@ -10,6 +10,7 @@ import {
10
10
 
11
11
  // mod 在 Mac 上是 command 键,alt 在 Win 上是 ctrl 键
12
12
  export const HOTKEYS_REGISTRATION: HotkeyRegistration = [
13
+ // basic
13
14
  {
14
15
  group: HotkeyGroupEnum.Essential,
15
16
  id: HotkeyEnum.Search,
@@ -47,6 +48,15 @@ export const HOTKEYS_REGISTRATION: HotkeyRegistration = [
47
48
  keys: combineKeys([KeyEnum.Ctrl, KeyEnum.Shift, KeyEnum.QuestionMark]),
48
49
  scopes: [HotkeyScopeEnum.Global],
49
50
  },
51
+ {
52
+ group: HotkeyGroupEnum.Essential,
53
+ id: HotkeyEnum.OpenSettings,
54
+ isDesktop: true,
55
+ keys: combineKeys([KeyEnum.Mod, KeyEnum.Comma]),
56
+ nonEditable: true,
57
+ scopes: [HotkeyScopeEnum.Global],
58
+ },
59
+ // Chat
50
60
  {
51
61
  group: HotkeyGroupEnum.Conversation,
52
62
  id: HotkeyEnum.OpenChatSettings,
@@ -19,6 +19,10 @@ import { ErrorActionContainer } from './style';
19
19
  const loading = () => <Skeleton active />;
20
20
 
21
21
  const OllamaBizError = dynamic(() => import('./OllamaBizError'), { loading, ssr: false });
22
+ const OllamaSetupGuide = dynamic(() => import('./OllamaBizError/SetupGuide'), {
23
+ loading,
24
+ ssr: false,
25
+ });
22
26
 
23
27
  // Config for the errorMessage display
24
28
  const getErrorAlertConfig = (
@@ -49,6 +53,7 @@ const getErrorAlertConfig = (
49
53
  };
50
54
  }
51
55
 
56
+ case AgentRuntimeErrorType.OllamaServiceUnavailable:
52
57
  case AgentRuntimeErrorType.NoOpenAIAPIKey: {
53
58
  return {
54
59
  extraDefaultExpand: true,
@@ -85,6 +90,11 @@ const ErrorMessageExtra = memo<{ data: ChatMessage }>(({ data }) => {
85
90
  if (!error?.type) return;
86
91
 
87
92
  switch (error.type) {
93
+ // TODO: 优化 Ollama setup 的流程,isDesktop 模式下可以直接做到端到端检测
94
+ case AgentRuntimeErrorType.OllamaServiceUnavailable: {
95
+ return <OllamaSetupGuide />;
96
+ }
97
+
88
98
  case AgentRuntimeErrorType.OllamaBizError: {
89
99
  return <OllamaBizError {...data} />;
90
100
  }
@@ -129,7 +129,7 @@ const SchemaPanel = ({ onTableSelect, selectedTable }: SchemaPanelProps) => {
129
129
  };
130
130
 
131
131
  return (
132
- <DraggablePanel placement={'left'}>
132
+ <DraggablePanel minWidth={264} placement={'left'}>
133
133
  <Flexbox height={'100%'} style={{ overflow: 'hidden', position: 'relative' }}>
134
134
  <Flexbox
135
135
  align={'center'}
@@ -13,7 +13,7 @@ import { useNewVersion } from './useNewVersion';
13
13
  const useStyles = createStyles(({ css }) => {
14
14
  return {
15
15
  popover: css`
16
- inset-block-start: ${isDesktop ? 24 : 8}px !important;
16
+ inset-block-start: ${isDesktop ? 32 : 8}px !important;
17
17
  inset-inline-start: 8px !important;
18
18
  `,
19
19
  };
@@ -63,6 +63,7 @@ vi.mock('../DataStatistics', () => ({
63
63
 
64
64
  vi.mock('@/const/version', () => ({
65
65
  isDeprecatedEdition: false,
66
+ isDesktop: false,
66
67
  }));
67
68
 
68
69
  // 定义一个变量来存储 enableAuth 的值
@@ -17,6 +17,7 @@ export const AgentRuntimeErrorType = {
17
17
 
18
18
  InvalidOllamaArgs: 'InvalidOllamaArgs',
19
19
  OllamaBizError: 'OllamaBizError',
20
+ OllamaServiceUnavailable: 'OllamaServiceUnavailable',
20
21
 
21
22
  InvalidBedrockCredentials: 'InvalidBedrockCredentials',
22
23
  InvalidVertexCredentials: 'InvalidVertexCredentials',
@@ -2,6 +2,7 @@ import { Ollama, Tool } from 'ollama/browser';
2
2
  import { ClientOptions } from 'openai';
3
3
 
4
4
  import { OpenAIChatMessage } from '@/libs/agent-runtime';
5
+ import { ChatModelCard } from '@/types/llm';
5
6
 
6
7
  import { LobeRuntimeAI } from '../BaseAI';
7
8
  import { AgentRuntimeErrorType } from '../error';
@@ -19,8 +20,6 @@ import { OllamaStream, convertIterableToStream } from '../utils/streams';
19
20
  import { parseDataUri } from '../utils/uriParser';
20
21
  import { OllamaMessage } from './type';
21
22
 
22
- import { ChatModelCard } from '@/types/llm';
23
-
24
23
  export interface OllamaModelCard {
25
24
  name: string;
26
25
  }
@@ -81,6 +80,15 @@ export class LobeOllamaAI implements LobeRuntimeAI {
81
80
  name: string;
82
81
  status_code: number;
83
82
  };
83
+ if (e.message === 'fetch failed') {
84
+ throw AgentRuntimeError.chat({
85
+ error: {
86
+ message: 'please check whether your ollama service is available',
87
+ },
88
+ errorType: AgentRuntimeErrorType.OllamaServiceUnavailable,
89
+ provider: ModelProvider.Ollama,
90
+ });
91
+ }
84
92
 
85
93
  throw AgentRuntimeError.chat({
86
94
  error: {
@@ -116,22 +124,18 @@ export class LobeOllamaAI implements LobeRuntimeAI {
116
124
 
117
125
  return modelList
118
126
  .map((model) => {
119
- const knownModel = LOBE_DEFAULT_MODEL_LIST.find((m) => model.name.toLowerCase() === m.id.toLowerCase());
127
+ const knownModel = LOBE_DEFAULT_MODEL_LIST.find(
128
+ (m) => model.name.toLowerCase() === m.id.toLowerCase(),
129
+ );
120
130
 
121
131
  return {
122
132
  contextWindowTokens: knownModel?.contextWindowTokens ?? undefined,
123
133
  displayName: knownModel?.displayName ?? undefined,
124
134
  enabled: knownModel?.enabled || false,
125
- functionCall:
126
- knownModel?.abilities?.functionCall
127
- || false,
135
+ functionCall: knownModel?.abilities?.functionCall || false,
128
136
  id: model.name,
129
- reasoning:
130
- knownModel?.abilities?.functionCall
131
- || false,
132
- vision:
133
- knownModel?.abilities?.functionCall
134
- || false,
137
+ reasoning: knownModel?.abilities?.functionCall || false,
138
+ vision: knownModel?.abilities?.functionCall || false,
135
139
  };
136
140
  })
137
141
  .filter(Boolean) as ChatModelCard[];
@@ -7,6 +7,9 @@
7
7
  * @link https://trpc.io/docs/v11/router
8
8
  * @link https://trpc.io/docs/v11/procedures
9
9
  */
10
+ import { DESKTOP_USER_ID } from '@/const/desktop';
11
+ import { isDesktop } from '@/const/version';
12
+
10
13
  import { trpc } from './init';
11
14
  import { jwtPayloadChecker } from './middleware/jwtPayload';
12
15
  import { userAuth } from './middleware/userAuth';
@@ -21,7 +24,11 @@ export const router = trpc.router;
21
24
  * Create an unprotected procedure
22
25
  * @link https://trpc.io/docs/v11/procedures
23
26
  **/
24
- export const publicProcedure = trpc.procedure;
27
+ export const publicProcedure = trpc.procedure.use(({ next }) => {
28
+ return next({
29
+ ctx: { userId: isDesktop ? DESKTOP_USER_ID : null },
30
+ });
31
+ });
25
32
 
26
33
  // procedure that asserts that the user is logged in
27
34
  export const authedProcedure = trpc.procedure.use(userAuth);
@@ -9,11 +9,10 @@ import { trpc } from '../init';
9
9
  export const userAuth = trpc.middleware(async (opts) => {
10
10
  const { ctx } = opts;
11
11
 
12
+ // 桌面端模式下,跳过默认鉴权逻辑
12
13
  if (isDesktop) {
13
14
  return opts.next({
14
- ctx: {
15
- userId: DESKTOP_USER_ID,
16
- },
15
+ ctx: { userId: DESKTOP_USER_ID },
17
16
  });
18
17
  }
19
18
  // `ctx.user` is nullable
@@ -17,6 +17,10 @@ const hotkey: HotkeyI18nTranslations = {
17
17
  desc: '查看所有快捷键的使用说明',
18
18
  title: '打开快捷键帮助',
19
19
  },
20
+ openSettings: {
21
+ desc: '打开应用设置页面',
22
+ title: '应用设置',
23
+ },
20
24
  regenerateMessage: {
21
25
  desc: '重新生成最后一条消息',
22
26
  title: '重新生成消息',