@lobehub/chat 1.116.3 → 1.117.0

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 (124) hide show
  1. package/.github/PULL_REQUEST_TEMPLATE.md +1 -0
  2. package/.github/workflows/release.yml +2 -0
  3. package/.i18nrc.js +1 -1
  4. package/CHANGELOG.md +117 -0
  5. package/changelog/v1.json +21 -0
  6. package/locales/ar/components.json +12 -0
  7. package/locales/ar/models.json +3 -0
  8. package/locales/bg-BG/components.json +12 -0
  9. package/locales/bg-BG/models.json +3 -0
  10. package/locales/de-DE/components.json +12 -0
  11. package/locales/de-DE/models.json +3 -0
  12. package/locales/en-US/components.json +12 -0
  13. package/locales/en-US/models.json +3 -0
  14. package/locales/es-ES/components.json +12 -0
  15. package/locales/es-ES/models.json +3 -0
  16. package/locales/fa-IR/components.json +12 -0
  17. package/locales/fa-IR/models.json +3 -0
  18. package/locales/fr-FR/components.json +12 -0
  19. package/locales/fr-FR/models.json +3 -0
  20. package/locales/it-IT/components.json +12 -0
  21. package/locales/it-IT/models.json +3 -0
  22. package/locales/ja-JP/components.json +12 -0
  23. package/locales/ja-JP/models.json +3 -0
  24. package/locales/ko-KR/components.json +12 -0
  25. package/locales/ko-KR/models.json +3 -0
  26. package/locales/nl-NL/components.json +12 -0
  27. package/locales/nl-NL/models.json +3 -0
  28. package/locales/pl-PL/components.json +12 -0
  29. package/locales/pl-PL/models.json +3 -0
  30. package/locales/pt-BR/components.json +12 -0
  31. package/locales/pt-BR/models.json +3 -0
  32. package/locales/ru-RU/components.json +12 -0
  33. package/locales/ru-RU/models.json +3 -0
  34. package/locales/tr-TR/components.json +12 -0
  35. package/locales/tr-TR/models.json +3 -0
  36. package/locales/vi-VN/components.json +12 -0
  37. package/locales/vi-VN/models.json +3 -0
  38. package/locales/zh-CN/components.json +12 -0
  39. package/locales/zh-CN/models.json +3 -0
  40. package/locales/zh-TW/components.json +12 -0
  41. package/locales/zh-TW/models.json +3 -0
  42. package/package.json +5 -5
  43. package/packages/const/src/image.ts +9 -0
  44. package/packages/const/src/index.ts +2 -1
  45. package/packages/const/src/meta.ts +3 -2
  46. package/packages/const/src/settings/agent.ts +9 -4
  47. package/packages/const/src/settings/systemAgent.ts +0 -3
  48. package/packages/database/vitest.config.mts +1 -0
  49. package/packages/database/vitest.config.server.mts +1 -0
  50. package/packages/file-loaders/package.json +1 -1
  51. package/packages/file-loaders/vitest.config.mts +3 -7
  52. package/packages/model-runtime/src/RouterRuntime/createRuntime.ts +11 -9
  53. package/packages/model-runtime/src/google/createImage.test.ts +657 -0
  54. package/packages/model-runtime/src/google/createImage.ts +152 -0
  55. package/packages/model-runtime/src/google/index.test.ts +0 -328
  56. package/packages/model-runtime/src/google/index.ts +3 -40
  57. package/packages/model-runtime/src/utils/modelParse.ts +2 -1
  58. package/packages/model-runtime/src/utils/openaiCompatibleFactory/createImage.ts +239 -0
  59. package/packages/model-runtime/src/utils/openaiCompatibleFactory/index.test.ts +22 -22
  60. package/packages/model-runtime/src/utils/openaiCompatibleFactory/index.ts +9 -116
  61. package/packages/model-runtime/src/utils/postProcessModelList.ts +55 -0
  62. package/packages/model-runtime/src/utils/streams/google-ai.test.ts +7 -7
  63. package/packages/model-runtime/src/utils/streams/google-ai.ts +15 -2
  64. package/packages/model-runtime/src/utils/streams/openai/openai.test.ts +41 -0
  65. package/packages/model-runtime/src/utils/streams/openai/openai.ts +38 -2
  66. package/packages/model-runtime/src/utils/streams/protocol.test.ts +32 -0
  67. package/packages/model-runtime/src/utils/streams/protocol.ts +7 -3
  68. package/packages/model-runtime/src/utils/usageConverter.test.ts +58 -0
  69. package/packages/model-runtime/src/utils/usageConverter.ts +5 -1
  70. package/packages/model-runtime/vitest.config.mts +3 -0
  71. package/packages/prompts/package.json +0 -1
  72. package/packages/prompts/src/chains/__tests__/abstractChunk.test.ts +52 -0
  73. package/packages/prompts/src/chains/__tests__/answerWithContext.test.ts +100 -0
  74. package/packages/prompts/src/chains/__tests__/rewriteQuery.test.ts +88 -0
  75. package/packages/prompts/src/chains/__tests__/summaryGenerationTitle.test.ts +107 -0
  76. package/packages/prompts/src/chains/abstractChunk.ts +0 -2
  77. package/packages/prompts/src/chains/rewriteQuery.ts +3 -1
  78. package/packages/prompts/src/index.test.ts +41 -0
  79. package/packages/prompts/src/prompts/systemRole/index.test.ts +136 -0
  80. package/packages/prompts/vitest.config.mts +3 -0
  81. package/packages/types/src/index.ts +2 -0
  82. package/packages/utils/package.json +5 -1
  83. package/packages/utils/src/client/index.ts +2 -0
  84. package/packages/utils/src/server/index.ts +5 -0
  85. package/packages/utils/vitest.config.mts +4 -0
  86. package/src/app/(backend)/middleware/auth/index.test.ts +2 -2
  87. package/src/app/(backend)/middleware/auth/index.ts +1 -1
  88. package/src/app/(backend)/oidc/consent/route.ts +1 -2
  89. package/src/app/(backend)/webapi/chat/[provider]/route.test.ts +2 -2
  90. package/src/app/(backend)/webapi/plugin/gateway/route.ts +1 -1
  91. package/src/app/[variants]/(main)/files/[id]/page.tsx +1 -1
  92. package/src/app/[variants]/(main)/settings/sync/page.tsx +1 -1
  93. package/src/app/[variants]/(main)/settings/system-agent/index.tsx +2 -1
  94. package/src/components/HtmlPreview/HtmlPreviewAction.tsx +32 -0
  95. package/src/components/HtmlPreview/PreviewDrawer.tsx +133 -0
  96. package/src/components/HtmlPreview/index.ts +2 -0
  97. package/src/config/aiModels/google.ts +42 -22
  98. package/src/config/aiModels/openrouter.ts +33 -0
  99. package/src/config/aiModels/vertexai.ts +4 -4
  100. package/src/features/Conversation/Extras/Usage/UsageDetail/index.tsx +6 -0
  101. package/src/features/Conversation/Extras/Usage/UsageDetail/tokens.test.ts +38 -0
  102. package/src/features/Conversation/Extras/Usage/UsageDetail/tokens.ts +13 -1
  103. package/src/features/Conversation/components/ChatItem/ShareMessageModal/ShareText/index.tsx +1 -1
  104. package/src/features/Conversation/components/ChatItem/index.tsx +23 -0
  105. package/src/features/ShareModal/ShareJSON/index.tsx +2 -2
  106. package/src/features/ShareModal/ShareText/index.tsx +1 -1
  107. package/src/libs/oidc-provider/adapter.ts +1 -1
  108. package/src/libs/trpc/edge/middleware/jwtPayload.test.ts +1 -1
  109. package/src/libs/trpc/edge/middleware/jwtPayload.ts +1 -2
  110. package/src/libs/trpc/lambda/middleware/keyVaults.ts +1 -2
  111. package/src/locales/default/chat.ts +1 -0
  112. package/src/locales/default/components.ts +12 -0
  113. package/src/middleware.ts +3 -3
  114. package/src/server/routers/tools/search.test.ts +1 -1
  115. package/src/services/config.ts +2 -4
  116. package/src/utils/client/switchLang.ts +1 -1
  117. package/{packages/utils/src → src/utils}/server/pageProps.ts +2 -1
  118. package/tsconfig.json +1 -1
  119. package/vitest.config.mts +1 -0
  120. package/packages/model-runtime/src/UniformRuntime/index.ts +0 -117
  121. /package/{packages/const/src → src/const}/locale.ts +0 -0
  122. /package/{packages/utils/src → src/utils}/locale.test.ts +0 -0
  123. /package/{packages/utils/src → src/utils}/locale.ts +0 -0
  124. /package/{packages/utils/src → src/utils}/server/routeVariants.ts +0 -0
@@ -0,0 +1,41 @@
1
+ import { describe, expect, it, vi } from 'vitest';
2
+
3
+ import * as chains from './chains';
4
+ import * as mainExports from './index';
5
+ import * as prompts from './prompts';
6
+
7
+ // Mock the problematic dependency
8
+ vi.mock('@/locales/resources', () => ({
9
+ supportLocales: ['en-US', 'zh-CN'],
10
+ }));
11
+
12
+ describe('Main Index Export', () => {
13
+ it('should export all chains', () => {
14
+ expect(mainExports).toEqual(expect.objectContaining(chains));
15
+ });
16
+
17
+ it('should export all prompts', () => {
18
+ expect(mainExports).toEqual(expect.objectContaining(prompts));
19
+ });
20
+
21
+ it('should have all expected chain exports', () => {
22
+ const chainExports = [
23
+ 'chainAbstractChunkText',
24
+ 'chainAnswerWithContext',
25
+ 'chainLangDetect',
26
+ 'chainPickEmoji',
27
+ 'chainRewriteQuery',
28
+ 'chainSummaryAgentName',
29
+ 'chainSummaryDescription',
30
+ 'chainSummaryGenerationTitle',
31
+ 'chainSummaryHistory',
32
+ 'chainSummaryTags',
33
+ 'chainSummaryTitle',
34
+ 'chainTranslate',
35
+ ];
36
+
37
+ chainExports.forEach((exportName) => {
38
+ expect(mainExports).toHaveProperty(exportName);
39
+ });
40
+ });
41
+ });
@@ -0,0 +1,136 @@
1
+ import { describe, expect, it } from 'vitest';
2
+
3
+ import { BuiltinSystemRolePrompts } from './index';
4
+
5
+ describe('BuiltinSystemRolePrompts', () => {
6
+ it('should return welcome message only when only welcome is provided', () => {
7
+ const result = BuiltinSystemRolePrompts({
8
+ welcome: 'Welcome to the assistant!',
9
+ });
10
+
11
+ expect(result).toBe('Welcome to the assistant!');
12
+ });
13
+
14
+ it('should return plugins message only when only plugins is provided', () => {
15
+ const result = BuiltinSystemRolePrompts({
16
+ plugins: 'Available plugins: calculator, weather',
17
+ });
18
+
19
+ expect(result).toBe('Available plugins: calculator, weather');
20
+ });
21
+
22
+ it('should return history summary when only history is provided', () => {
23
+ const result = BuiltinSystemRolePrompts({
24
+ historySummary: 'User discussed AI topics previously',
25
+ });
26
+
27
+ expect(result).toContain('<chat_history_summary>');
28
+ expect(result).toContain(
29
+ '<docstring>Users may have lots of chat messages, here is the summary of the history:</docstring>',
30
+ );
31
+ expect(result).toContain('<summary>User discussed AI topics previously</summary>');
32
+ expect(result).toContain('</chat_history_summary>');
33
+ expect(result.trim()).toMatch(/^<chat_history_summary>[\s\S]*<\/chat_history_summary>$/);
34
+ });
35
+
36
+ it('should combine all three parts when all are provided', () => {
37
+ const result = BuiltinSystemRolePrompts({
38
+ welcome: 'Welcome!',
39
+ plugins: 'Plugins available',
40
+ historySummary: 'Previous conversation summary',
41
+ });
42
+
43
+ expect(result).toContain('Welcome!');
44
+ expect(result).toContain('Plugins available');
45
+ expect(result).toContain('<chat_history_summary>');
46
+ expect(result).toContain('Previous conversation summary');
47
+ expect(result).toContain('</chat_history_summary>');
48
+
49
+ // Should be joined with double newlines
50
+ const parts = result.split('\n\n');
51
+ expect(parts).toHaveLength(3);
52
+ });
53
+
54
+ it('should combine welcome and plugins when no history is provided', () => {
55
+ const result = BuiltinSystemRolePrompts({
56
+ welcome: 'Hello user!',
57
+ plugins: 'Available tools',
58
+ });
59
+
60
+ expect(result).toBe('Hello user!\n\nAvailable tools');
61
+ });
62
+
63
+ it('should combine welcome and history when no plugins provided', () => {
64
+ const result = BuiltinSystemRolePrompts({
65
+ welcome: 'Greetings!',
66
+ historySummary: 'Chat history here',
67
+ });
68
+
69
+ expect(result).toContain('Greetings!');
70
+ expect(result).toContain('<chat_history_summary>');
71
+ expect(result).toContain('Chat history here');
72
+ });
73
+
74
+ it('should combine plugins and history when no welcome provided', () => {
75
+ const result = BuiltinSystemRolePrompts({
76
+ plugins: 'Tool list',
77
+ historySummary: 'Summary of previous chats',
78
+ });
79
+
80
+ expect(result).toContain('Tool list');
81
+ expect(result).toContain('<chat_history_summary>');
82
+ expect(result).toContain('Summary of previous chats');
83
+ });
84
+
85
+ it('should return empty string when no parameters provided', () => {
86
+ const result = BuiltinSystemRolePrompts({});
87
+
88
+ expect(result).toBe('');
89
+ });
90
+
91
+ it('should filter out falsy values', () => {
92
+ const result = BuiltinSystemRolePrompts({
93
+ welcome: '',
94
+ plugins: 'Valid plugins',
95
+ historySummary: undefined,
96
+ });
97
+
98
+ expect(result).toBe('Valid plugins');
99
+ });
100
+
101
+ it('should handle null and undefined values gracefully', () => {
102
+ const result = BuiltinSystemRolePrompts({
103
+ welcome: undefined,
104
+ plugins: null as any,
105
+ historySummary: 'Valid history',
106
+ });
107
+
108
+ expect(result).toContain('<chat_history_summary>');
109
+ expect(result).toContain('<summary>Valid history</summary>');
110
+ expect(result).toContain('</chat_history_summary>');
111
+ });
112
+
113
+ it('should preserve whitespace in individual components', () => {
114
+ const result = BuiltinSystemRolePrompts({
115
+ welcome: 'Welcome\nwith newlines',
116
+ plugins: 'Plugins\twith tabs',
117
+ });
118
+
119
+ expect(result).toContain('Welcome\nwith newlines');
120
+ expect(result).toContain('Plugins\twith tabs');
121
+ });
122
+
123
+ it('should format history summary with proper XML structure', () => {
124
+ const historySummary = 'User asked about weather and traffic';
125
+ const result = BuiltinSystemRolePrompts({
126
+ historySummary,
127
+ });
128
+
129
+ expect(result).toContain('<chat_history_summary>');
130
+ expect(result).toContain(
131
+ '<docstring>Users may have lots of chat messages, here is the summary of the history:</docstring>',
132
+ );
133
+ expect(result).toContain('<summary>User asked about weather and traffic</summary>');
134
+ expect(result).toContain('</chat_history_summary>');
135
+ });
136
+ });
@@ -2,6 +2,9 @@ import { defineConfig } from 'vitest/config';
2
2
 
3
3
  export default defineConfig({
4
4
  test: {
5
+ coverage: {
6
+ reporter: ['text', 'json', 'lcov', 'text-summary'],
7
+ },
5
8
  environment: 'happy-dom',
6
9
  },
7
10
  });
@@ -1,3 +1,4 @@
1
+ export * from './agent';
1
2
  export * from './artifact';
2
3
  export * from './chunk';
3
4
  export * from './clientDB';
@@ -6,6 +7,7 @@ export * from './fetch';
6
7
  export * from './knowledgeBase';
7
8
  export * from './llm';
8
9
  export * from './message';
10
+ export * from './meta';
9
11
  export * from './user';
10
12
  export * from './user/settings';
11
13
  // FIXME: I think we need a refactor for the "openai" types
@@ -2,7 +2,11 @@
2
2
  "name": "@lobechat/utils",
3
3
  "version": "1.0.0",
4
4
  "private": true,
5
- "main": "./src/index.ts",
5
+ "exports": {
6
+ ".": "./src/index.ts",
7
+ "./server": "./src/server/index.ts",
8
+ "./client": "./src/client/index.ts"
9
+ },
6
10
  "scripts": {
7
11
  "test": "vitest",
8
12
  "test:coverage": "vitest --coverage"
@@ -0,0 +1,2 @@
1
+ export * from './downloadFile';
2
+ export * from './exportFile';
@@ -0,0 +1,5 @@
1
+ export * from './auth';
2
+ export * from './correctOIDCUrl';
3
+ export * from './geo';
4
+ export * from './responsive';
5
+ export * from './xor';
@@ -7,9 +7,13 @@ export default defineConfig({
7
7
  /* eslint-disable sort-keys-fix/sort-keys-fix */
8
8
  '@/types': resolve(__dirname, '../types/src'),
9
9
  '@/const': resolve(__dirname, '../const/src'),
10
+ '@/libs/model-runtime': resolve(__dirname, '../model-runtime/src'),
10
11
  '@': resolve(__dirname, '../../src'),
11
12
  /* eslint-enable */
12
13
  },
14
+ coverage: {
15
+ reporter: ['text', 'json', 'lcov', 'text-summary'],
16
+ },
13
17
  environment: 'happy-dom',
14
18
  setupFiles: join(__dirname, './tests/setup.ts'),
15
19
  },
@@ -1,9 +1,9 @@
1
1
  import { AgentRuntimeError } from '@lobechat/model-runtime';
2
2
  import { ChatErrorType } from '@lobechat/types';
3
+ import { getXorPayload } from '@lobechat/utils/server';
3
4
  import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest';
4
5
 
5
6
  import { createErrorResponse } from '@/utils/errorResponse';
6
- import { getXorPayload } from '@/utils/server/xor';
7
7
 
8
8
  import { RequestHandler, checkAuth } from './index';
9
9
  import { checkAuthMethod } from './utils';
@@ -20,7 +20,7 @@ vi.mock('./utils', () => ({
20
20
  checkAuthMethod: vi.fn(),
21
21
  }));
22
22
 
23
- vi.mock('@/utils/server/xor', () => ({
23
+ vi.mock('@lobechat/utils/server', () => ({
24
24
  getXorPayload: vi.fn(),
25
25
  }));
26
26
 
@@ -5,6 +5,7 @@ import {
5
5
  ModelRuntime,
6
6
  } from '@lobechat/model-runtime';
7
7
  import { ChatErrorType } from '@lobechat/types';
8
+ import { getXorPayload } from '@lobechat/utils/server';
8
9
  import { NextRequest } from 'next/server';
9
10
 
10
11
  import {
@@ -17,7 +18,6 @@ import {
17
18
  import { ClerkAuth } from '@/libs/clerk-auth';
18
19
  import { validateOIDCJWT } from '@/libs/oidc-provider/jwt';
19
20
  import { createErrorResponse } from '@/utils/errorResponse';
20
- import { getXorPayload } from '@/utils/server/xor';
21
21
 
22
22
  import { checkAuthMethod } from './utils';
23
23
 
@@ -1,9 +1,8 @@
1
+ import { correctOIDCUrl, getUserAuth } from '@lobechat/utils/server';
1
2
  import debug from 'debug';
2
3
  import { NextRequest, NextResponse } from 'next/server';
3
4
 
4
5
  import { OIDCService } from '@/server/services/oidc';
5
- import { getUserAuth } from '@/utils/server/auth';
6
- import { correctOIDCUrl } from '@/utils/server/correctOIDCUrl';
7
6
 
8
7
  const log = debug('lobe-oidc:consent');
9
8
 
@@ -2,11 +2,11 @@
2
2
  import { getAuth } from '@clerk/nextjs/server';
3
3
  import { LobeRuntimeAI, ModelRuntime } from '@lobechat/model-runtime';
4
4
  import { ChatErrorType } from '@lobechat/types';
5
+ import { getXorPayload } from '@lobechat/utils/server';
5
6
  import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest';
6
7
 
7
8
  import { checkAuthMethod } from '@/app/(backend)/middleware/auth/utils';
8
9
  import { LOBE_CHAT_AUTH_HEADER, OAUTH_AUTHORIZED } from '@/const/auth';
9
- import { getXorPayload } from '@/utils/server/xor';
10
10
 
11
11
  import { POST } from './route';
12
12
 
@@ -18,7 +18,7 @@ vi.mock('@/app/(backend)/middleware/auth/utils', () => ({
18
18
  checkAuthMethod: vi.fn(),
19
19
  }));
20
20
 
21
- vi.mock('@/utils/server/xor', () => ({
21
+ vi.mock('@lobechat/utils/server', () => ({
22
22
  getXorPayload: vi.fn(),
23
23
  }));
24
24
 
@@ -1,5 +1,6 @@
1
1
  import { AgentRuntimeError } from '@lobechat/model-runtime';
2
2
  import { ChatErrorType, ErrorType, TraceNameMap } from '@lobechat/types';
3
+ import { getXorPayload } from '@lobechat/utils/server';
3
4
  import { PluginRequestPayload } from '@lobehub/chat-plugin-sdk';
4
5
  import { createGatewayOnEdgeRuntime } from '@lobehub/chat-plugins-gateway';
5
6
 
@@ -8,7 +9,6 @@ import { LOBE_CHAT_TRACE_ID } from '@/const/trace';
8
9
  import { getAppConfig } from '@/envs/app';
9
10
  import { TraceClient } from '@/libs/traces';
10
11
  import { createErrorResponse } from '@/utils/errorResponse';
11
- import { getXorPayload } from '@/utils/server/xor';
12
12
  import { getTracePayload } from '@/utils/trace';
13
13
 
14
14
  import { parserPluginSettings } from './settings';
@@ -1,3 +1,4 @@
1
+ import { getUserAuth } from '@lobechat/utils/server';
1
2
  import { notFound } from 'next/navigation';
2
3
  import { Flexbox } from 'react-layout-kit';
3
4
 
@@ -5,7 +6,6 @@ import FileViewer from '@/features/FileViewer';
5
6
  import { createCallerFactory } from '@/libs/trpc/lambda';
6
7
  import { lambdaRouter } from '@/server/routers/lambda';
7
8
  import { PagePropsWithId } from '@/types/next';
8
- import { getUserAuth } from '@/utils/server/auth';
9
9
 
10
10
  import FileDetail from '../features/FileDetail';
11
11
  import Header from './Header';
@@ -1,10 +1,10 @@
1
+ import { gerServerDeviceInfo } from '@lobechat/utils/server';
1
2
  import { notFound } from 'next/navigation';
2
3
 
3
4
  import { serverFeatureFlags } from '@/config/featureFlags';
4
5
  import { metadataModule } from '@/server/metadata';
5
6
  import { translation } from '@/server/translation';
6
7
  import { DynamicLayoutProps } from '@/types/next';
7
- import { gerServerDeviceInfo } from '@/utils/server/responsive';
8
8
  import { RouteVariants } from '@/utils/server/routeVariants';
9
9
 
10
10
  import Page from './index';
@@ -1,6 +1,7 @@
1
1
  'use client';
2
2
 
3
- import { DEFAULT_REWRITE_QUERY } from '@/const/settings';
3
+ import { DEFAULT_REWRITE_QUERY } from '@lobechat/prompts';
4
+
4
5
  import { isServerMode } from '@/const/version';
5
6
  import { featureFlagsSelectors, useServerConfigStore } from '@/store/serverConfig';
6
7
 
@@ -0,0 +1,32 @@
1
+ import { ActionIcon } from '@lobehub/ui';
2
+ import { Eye } from 'lucide-react';
3
+ import { memo, useState } from 'react';
4
+ import { useTranslation } from 'react-i18next';
5
+
6
+ import HtmlPreviewDrawer from './PreviewDrawer';
7
+
8
+ interface HtmlPreviewActionProps {
9
+ content: string;
10
+ size?: number;
11
+ }
12
+
13
+ const HtmlPreviewAction = memo<HtmlPreviewActionProps>(({ content, size }) => {
14
+ const { t } = useTranslation('components');
15
+ const [open, setOpen] = useState(false);
16
+
17
+ return (
18
+ <>
19
+ <ActionIcon
20
+ icon={Eye}
21
+ onClick={() => setOpen(true)}
22
+ size={size}
23
+ title={t('HtmlPreview.actions.preview')}
24
+ />
25
+ <HtmlPreviewDrawer content={content} onClose={() => setOpen(false)} open={open} />
26
+ </>
27
+ );
28
+ });
29
+
30
+ HtmlPreviewAction.displayName = 'HtmlPreviewAction';
31
+
32
+ export default HtmlPreviewAction;
@@ -0,0 +1,133 @@
1
+ import { exportFile } from '@lobechat/utils/client';
2
+ import { Block, Button, Highlighter, Segmented } from '@lobehub/ui';
3
+ import { Drawer } from 'antd';
4
+ import { createStyles } from 'antd-style';
5
+ import { Code2, Download, Eye } from 'lucide-react';
6
+ import { memo, useCallback, useState } from 'react';
7
+ import { useTranslation } from 'react-i18next';
8
+ import { Flexbox } from 'react-layout-kit';
9
+
10
+ import { isDesktop } from '@/const/version';
11
+ import { TITLE_BAR_HEIGHT } from '@/features/ElectronTitlebar';
12
+
13
+ const useStyles = createStyles(({ css }) => ({
14
+ container: css`
15
+ height: 100%;
16
+ `,
17
+ iframe: css`
18
+ width: 100%;
19
+ height: 100%;
20
+ border: none;
21
+ `,
22
+ }));
23
+
24
+ interface HtmlPreviewDrawerProps {
25
+ content: string;
26
+ onClose: () => void;
27
+ open: boolean;
28
+ }
29
+
30
+ const HtmlPreviewDrawer = memo<HtmlPreviewDrawerProps>(({ content, open, onClose }) => {
31
+ const { styles } = useStyles();
32
+ const { t } = useTranslation('components');
33
+ const [mode, setMode] = useState<'preview' | 'code'>('preview');
34
+
35
+ const htmlContent = content;
36
+
37
+ const extractTitle = useCallback(() => {
38
+ const m = htmlContent.match(/<title>([\S\s]*?)<\/title>/i);
39
+ return m ? m[1].trim() : undefined;
40
+ }, [htmlContent]);
41
+
42
+ const sanitizeFileName = useCallback((name: string) => {
43
+ return name
44
+ .replaceAll(/["*/:<>?\\|]/g, '-')
45
+ .replaceAll(/\s+/g, ' ')
46
+ .trim()
47
+ .slice(0, 100);
48
+ }, []);
49
+
50
+ const onDownload = useCallback(() => {
51
+ const title = extractTitle();
52
+ const base = title ? sanitizeFileName(title) : `chat-html-preview-${Date.now()}`;
53
+ exportFile(content, `${base}.html`);
54
+ }, [content, extractTitle, sanitizeFileName]);
55
+
56
+ const Title = (
57
+ <Flexbox align={'center'} horizontal justify={'space-between'} style={{ width: '100%' }}>
58
+ {t('HtmlPreview.title')}
59
+ <Segmented
60
+ onChange={(v) => setMode(v as 'preview' | 'code')}
61
+ options={[
62
+ {
63
+ label: (
64
+ <Flexbox align={'center'} gap={6} horizontal>
65
+ <Eye size={16} />
66
+ {t('HtmlPreview.mode.preview')}
67
+ </Flexbox>
68
+ ),
69
+ value: 'preview',
70
+ },
71
+ {
72
+ label: (
73
+ <Flexbox align={'center'} gap={6} horizontal>
74
+ <Code2 size={16} />
75
+ {t('HtmlPreview.mode.code')}
76
+ </Flexbox>
77
+ ),
78
+ value: 'code',
79
+ },
80
+ ]}
81
+ value={mode}
82
+ />
83
+ <Button
84
+ color={'default'}
85
+ icon={<Download size={16} />}
86
+ onClick={onDownload}
87
+ variant={'filled'}
88
+ >
89
+ {t('HtmlPreview.actions.download')}
90
+ </Button>
91
+ </Flexbox>
92
+ );
93
+
94
+ return (
95
+ <Drawer
96
+ destroyOnHidden
97
+ height={isDesktop ? `calc(100vh - ${TITLE_BAR_HEIGHT}px)` : '100vh'}
98
+ onClose={onClose}
99
+ open={open}
100
+ placement="bottom"
101
+ styles={{
102
+ body: { height: '100%', padding: 0 },
103
+ header: { paddingBlock: 8, paddingInline: 12 },
104
+ }}
105
+ title={Title}
106
+ >
107
+ {mode === 'preview' ? (
108
+ <Block className={styles.container}>
109
+ <iframe
110
+ className={styles.iframe}
111
+ sandbox="allow-scripts allow-same-origin"
112
+ srcDoc={content}
113
+ title={t('HtmlPreview.iframeTitle')}
114
+ />
115
+ </Block>
116
+ ) : (
117
+ <Block className={styles.container}>
118
+ <Highlighter
119
+ language={'html'}
120
+ showLanguage={false}
121
+ style={{ height: '100%', overflow: 'auto' }}
122
+ >
123
+ {htmlContent}
124
+ </Highlighter>
125
+ </Block>
126
+ )}
127
+ </Drawer>
128
+ );
129
+ });
130
+
131
+ HtmlPreviewDrawer.displayName = 'HtmlPreviewDrawer';
132
+
133
+ export default HtmlPreviewDrawer;
@@ -0,0 +1,2 @@
1
+ export { default as HtmlPreviewAction } from './HtmlPreviewAction';
2
+ export { default as HtmlPreviewDrawer } from './PreviewDrawer';
@@ -1,3 +1,4 @@
1
+ import { CHAT_MODEL_IMAGE_GENERATION_PARAMS } from '@/const/image';
1
2
  import { ModelParamsSchema } from '@/libs/standard-parameters';
2
3
  import { AIChatModelCard, AIImageModelCard } from '@/types/aiModel';
3
4
 
@@ -194,21 +195,21 @@ const googleChatModels: AIChatModelCard[] = [
194
195
  imageOutput: true,
195
196
  vision: true,
196
197
  },
197
- contextWindowTokens: 32_768 + 32_768,
198
+ contextWindowTokens: 32_768 + 8192,
198
199
  description:
199
200
  'Gemini 2.5 Flash Image Preview 是 Google 最新、最快、最高效的原生多模态模型,它允许您通过对话生成和编辑图像。',
200
201
  displayName: 'Gemini 2.5 Flash Image Preview',
201
202
  enabled: true,
202
203
  id: 'gemini-2.5-flash-image-preview',
203
- maxOutput: 32_768,
204
+ maxOutput: 8192,
204
205
  pricing: {
205
206
  units: [
206
207
  { name: 'textInput', rate: 0.3, strategy: 'fixed', unit: 'millionTokens' },
207
208
  { name: 'textOutput', rate: 2.5, strategy: 'fixed', unit: 'millionTokens' },
208
- { name: 'imageOutput', rate: 3, strategy: 'fixed', unit: 'millionTokens' },
209
+ { name: 'imageOutput', rate: 30, strategy: 'fixed', unit: 'millionTokens' },
209
210
  ],
210
211
  },
211
- releasedAt: '2025-08-27',
212
+ releasedAt: '2025-08-26',
212
213
  type: 'chat',
213
214
  },
214
215
  {
@@ -605,71 +606,90 @@ const imagenBaseParameters: ModelParamsSchema = {
605
606
  prompt: { default: '' },
606
607
  };
607
608
 
609
+ /* eslint-disable sort-keys-fix/sort-keys-fix */
608
610
  const googleImageModels: AIImageModelCard[] = [
609
611
  {
610
- description: 'Imagen 4th generation text-to-image model series',
611
- displayName: 'Imagen 4',
612
+ displayName: 'Gemini 2.5 Flash Image Preview',
613
+ id: 'gemini-2.5-flash-image-preview:image',
612
614
  enabled: true,
615
+ type: 'image',
616
+ description:
617
+ 'Gemini 2.5 Flash Image Preview 是 Google 最新、最快、最高效的原生多模态模型,它允许您通过对话生成和编辑图像。',
618
+ releasedAt: '2025-08-26',
619
+ parameters: CHAT_MODEL_IMAGE_GENERATION_PARAMS,
620
+ pricing: {
621
+ units: [
622
+ { name: 'textInput', rate: 0.3, strategy: 'fixed', unit: 'millionTokens' },
623
+ { name: 'textOutput', rate: 2.5, strategy: 'fixed', unit: 'millionTokens' },
624
+ { name: 'imageOutput', rate: 3, strategy: 'fixed', unit: 'millionTokens' },
625
+ ],
626
+ },
627
+ },
628
+ {
629
+ displayName: 'Imagen 4',
613
630
  id: 'imagen-4.0-generate-001',
631
+ enabled: true,
632
+ type: 'image',
633
+ description: 'Imagen 4th generation text-to-image model series',
614
634
  organization: 'Deepmind',
635
+ releasedAt: '2025-08-15',
615
636
  parameters: imagenBaseParameters,
616
637
  pricing: {
617
638
  units: [{ name: 'imageGeneration', rate: 0.04, strategy: 'fixed', unit: 'image' }],
618
639
  },
619
- releasedAt: '2024-08-14',
620
- type: 'image',
621
640
  },
622
641
  {
623
- description: 'Imagen 4th generation text-to-image model series Ultra version',
624
642
  displayName: 'Imagen 4 Ultra',
625
- enabled: true,
626
643
  id: 'imagen-4.0-ultra-generate-001',
644
+ enabled: true,
645
+ type: 'image',
646
+ description: 'Imagen 4th generation text-to-image model series Ultra version',
627
647
  organization: 'Deepmind',
648
+ releasedAt: '2025-08-15',
628
649
  parameters: imagenBaseParameters,
629
650
  pricing: {
630
651
  units: [{ name: 'imageGeneration', rate: 0.06, strategy: 'fixed', unit: 'image' }],
631
652
  },
632
- releasedAt: '2024-08-14',
633
- type: 'image',
634
653
  },
635
654
  {
636
- description: 'Imagen 4th generation text-to-image model series Fast version',
637
655
  displayName: 'Imagen 4 Fast',
638
- enabled: true,
639
656
  id: 'imagen-4.0-fast-generate-001',
657
+ enabled: true,
658
+ type: 'image',
659
+ description: 'Imagen 4th generation text-to-image model series Fast version',
640
660
  organization: 'Deepmind',
661
+ releasedAt: '2025-08-15',
641
662
  parameters: imagenBaseParameters,
642
663
  pricing: {
643
664
  units: [{ name: 'imageGeneration', rate: 0.02, strategy: 'fixed', unit: 'image' }],
644
665
  },
645
- releasedAt: '2024-08-14',
646
- type: 'image',
647
666
  },
648
667
  {
649
- description: 'Imagen 4th generation text-to-image model series',
650
668
  displayName: 'Imagen 4 Preview 06-06',
651
669
  id: 'imagen-4.0-generate-preview-06-06',
670
+ type: 'image',
671
+ description: 'Imagen 4th generation text-to-image model series',
652
672
  organization: 'Deepmind',
673
+ releasedAt: '2024-06-06',
653
674
  parameters: imagenBaseParameters,
654
675
  pricing: {
655
676
  units: [{ name: 'imageGeneration', rate: 0.04, strategy: 'fixed', unit: 'image' }],
656
677
  },
657
- releasedAt: '2024-06-06',
658
- type: 'image',
659
678
  },
660
679
  {
661
- description: 'Imagen 4th generation text-to-image model series Ultra version',
662
680
  displayName: 'Imagen 4 Ultra Preview 06-06',
663
681
  id: 'imagen-4.0-ultra-generate-preview-06-06',
682
+ type: 'image',
683
+ description: 'Imagen 4th generation text-to-image model series Ultra version',
664
684
  organization: 'Deepmind',
685
+ releasedAt: '2025-06-11',
665
686
  parameters: imagenBaseParameters,
666
687
  pricing: {
667
688
  units: [{ name: 'imageGeneration', rate: 0.06, strategy: 'fixed', unit: 'image' }],
668
689
  },
669
- releasedAt: '2024-06-06',
670
- type: 'image',
671
690
  },
672
691
  ];
692
+ /* eslint-enable sort-keys-fix/sort-keys-fix */
673
693
 
674
694
  export const allModels = [...googleChatModels, ...googleImageModels];
675
695