@lobehub/lobehub 2.0.0-next.97 → 2.0.0-next.98

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 (58) hide show
  1. package/.console-log-whitelist.json +14 -0
  2. package/.github/workflows/check-console-log.yml +117 -0
  3. package/.github/workflows/desktop-pr-build.yml +4 -4
  4. package/.github/workflows/release-desktop-beta.yml +4 -4
  5. package/.github/workflows/release.yml +1 -1
  6. package/.github/workflows/test.yml +5 -5
  7. package/CHANGELOG.md +33 -0
  8. package/changelog/v1.json +12 -0
  9. package/docs/development/database-schema.dbml +1 -0
  10. package/e2e/package.json +1 -1
  11. package/locales/ar/file.json +9 -11
  12. package/locales/bg-BG/file.json +8 -10
  13. package/locales/de-DE/file.json +9 -11
  14. package/locales/en-US/file.json +12 -14
  15. package/locales/es-ES/file.json +7 -9
  16. package/locales/fa-IR/file.json +9 -11
  17. package/locales/fr-FR/file.json +6 -8
  18. package/locales/it-IT/file.json +8 -10
  19. package/locales/ja-JP/file.json +10 -12
  20. package/locales/ko-KR/file.json +8 -10
  21. package/locales/nl-NL/file.json +8 -10
  22. package/locales/pl-PL/file.json +7 -9
  23. package/locales/pt-BR/file.json +7 -9
  24. package/locales/ru-RU/file.json +9 -11
  25. package/locales/tr-TR/file.json +8 -10
  26. package/locales/vi-VN/file.json +9 -11
  27. package/locales/zh-CN/file.json +10 -12
  28. package/locales/zh-TW/file.json +10 -12
  29. package/package.json +3 -2
  30. package/packages/database/migrations/0047_add_slug_document.sql +6 -0
  31. package/packages/database/migrations/meta/0047_snapshot.json +7891 -0
  32. package/packages/database/migrations/meta/_journal.json +7 -0
  33. package/packages/database/src/core/migrations.json +16 -7
  34. package/packages/database/src/models/document.ts +2 -2
  35. package/packages/database/src/schemas/file.ts +7 -1
  36. package/packages/model-bank/src/aiModels/qwen.ts +5 -3
  37. package/packages/model-runtime/src/core/openaiCompatibleFactory/index.ts +21 -21
  38. package/packages/obervability-otel/package.json +2 -2
  39. package/scripts/checkConsoleLog.mts +148 -0
  40. package/scripts/prebuild.mts +5 -5
  41. package/src/app/[variants]/(main)/changelog/index.tsx +1 -1
  42. package/src/app/[variants]/(main)/settings/_layout/Desktop/index.tsx +20 -16
  43. package/src/app/[variants]/(main)/settings/provider/(list)/ProviderGrid/Card.tsx +6 -3
  44. package/src/app/[variants]/(main)/settings/provider/(list)/index.tsx +3 -2
  45. package/src/app/[variants]/(main)/settings/provider/detail/index.tsx +14 -4
  46. package/src/app/[variants]/desktopRouter.config.tsx +23 -0
  47. package/src/app/[variants]/mobileRouter.config.tsx +23 -0
  48. package/src/features/KnowledgeManager/DocumentExplorer/NoteEditorModal.tsx +0 -20
  49. package/src/features/KnowledgeManager/DocumentExplorer/index.tsx +3 -3
  50. package/src/features/KnowledgeManager/FileExplorer/MasonryFileItem/index.tsx +0 -20
  51. package/src/features/KnowledgeManager/Header/AddButton.tsx +0 -1
  52. package/src/features/KnowledgeManager/Header/NewNoteButton.tsx +1 -1
  53. package/src/features/KnowledgeManager/Header/TogglePanelButton.tsx +2 -2
  54. package/src/features/KnowledgeManager/Home/UploadEntries.tsx +2 -2
  55. package/src/features/KnowledgeManager/Home/index.tsx +4 -4
  56. package/src/features/User/UserPanel/useMenu.tsx +7 -3
  57. package/src/locales/default/file.ts +11 -13
  58. package/src/store/chat/agents/__tests__/createAgentExecutors/helpers/testExecutor.ts +1 -4
@@ -329,6 +329,13 @@
329
329
  "when": 1763453175961,
330
330
  "tag": "0046_add_parent_id",
331
331
  "breakpoints": true
332
+ },
333
+ {
334
+ "idx": 47,
335
+ "version": "7",
336
+ "when": 1763535087148,
337
+ "tag": "0047_add_slug_document",
338
+ "breakpoints": true
332
339
  }
333
340
  ],
334
341
  "version": "6"
@@ -778,15 +778,24 @@
778
778
  {
779
779
  "sql": [
780
780
  "ALTER TABLE \"documents\" ALTER COLUMN \"id\" SET DATA TYPE varchar(255);",
781
- "\nALTER TABLE \"documents\" ADD COLUMN \"parent_id\" varchar(255);",
782
- "\nALTER TABLE \"files\" ADD COLUMN \"parent_id\" varchar(255);",
783
- "\nALTER TABLE \"documents\" ADD CONSTRAINT \"documents_parent_id_documents_id_fk\" FOREIGN KEY (\"parent_id\") REFERENCES \"public\".\"documents\"(\"id\") ON DELETE set null ON UPDATE no action;",
784
- "\nALTER TABLE \"files\" ADD CONSTRAINT \"files_parent_id_documents_id_fk\" FOREIGN KEY (\"parent_id\") REFERENCES \"public\".\"documents\"(\"id\") ON DELETE set null ON UPDATE no action;",
785
- "\nCREATE INDEX \"documents_parent_id_idx\" ON \"documents\" USING btree (\"parent_id\");",
786
- "\nCREATE INDEX \"files_parent_id_idx\" ON \"files\" USING btree (\"parent_id\");"
781
+ "\nALTER TABLE \"documents\" ADD COLUMN IF NOT EXISTS \"parent_id\" varchar(255);",
782
+ "\nALTER TABLE \"files\" ADD COLUMN IF NOT EXISTS \"parent_id\" varchar(255);",
783
+ "\nDO $$ BEGIN\n ALTER TABLE \"documents\" ADD CONSTRAINT \"documents_parent_id_documents_id_fk\" FOREIGN KEY (\"parent_id\") REFERENCES \"public\".\"documents\"(\"id\") ON DELETE set null ON UPDATE no action;\nEXCEPTION\n WHEN duplicate_object THEN null;\nEND $$;",
784
+ "\nDO $$ BEGIN\n ALTER TABLE \"files\" ADD CONSTRAINT \"files_parent_id_documents_id_fk\" FOREIGN KEY (\"parent_id\") REFERENCES \"public\".\"documents\"(\"id\") ON DELETE set null ON UPDATE no action;\nEXCEPTION\n WHEN duplicate_object THEN null;\nEND $$;",
785
+ "\nCREATE INDEX IF NOT EXISTS \"documents_parent_id_idx\" ON \"documents\" USING btree (\"parent_id\");",
786
+ "\nCREATE INDEX IF NOT EXISTS \"files_parent_id_idx\" ON \"files\" USING btree (\"parent_id\");"
787
787
  ],
788
788
  "bps": true,
789
789
  "folderMillis": 1763453175961,
790
- "hash": "6cfc00744de6a8f4d60b793673911bb740f9a50661663e28b843e5adae08f94a"
790
+ "hash": "6c2081c8ac22772a8276052c9ae0852a4c03463cbe998df26f85683ab356b914"
791
+ },
792
+ {
793
+ "sql": [
794
+ "ALTER TABLE \"documents\" ADD COLUMN IF NOT EXISTS \"slug\" varchar(255);",
795
+ "\nDO $$ BEGIN\n CREATE UNIQUE INDEX IF NOT EXISTS \"documents_slug_user_id_unique\" ON \"documents\" (\"slug\",\"user_id\") WHERE \"slug\" IS NOT NULL;\nEXCEPTION\n WHEN duplicate_object THEN null;\nEND $$;"
796
+ ],
797
+ "bps": true,
798
+ "folderMillis": 1763535087148,
799
+ "hash": "57a82ce14d96ffa9f140ff63f00af994e91a74703f4d2378286e36be259f117b"
791
800
  }
792
801
  ]
@@ -31,14 +31,14 @@ export class DocumentModel {
31
31
  return this.db.delete(documents).where(eq(documents.userId, this.userId));
32
32
  };
33
33
 
34
- query = async () => {
34
+ query = async (): Promise<DocumentItem[]> => {
35
35
  return this.db.query.documents.findMany({
36
36
  orderBy: [desc(documents.updatedAt)],
37
37
  where: eq(documents.userId, this.userId),
38
38
  });
39
39
  };
40
40
 
41
- findById = async (id: string) => {
41
+ findById = async (id: string): Promise<DocumentItem | undefined> => {
42
42
  return this.db.query.documents.findFirst({
43
43
  where: and(eq(documents.id, id), eq(documents.userId, this.userId)),
44
44
  });
@@ -1,4 +1,5 @@
1
1
  /* eslint-disable sort-keys-fix/sort-keys-fix */
2
+ import { isNotNull } from 'drizzle-orm';
2
3
  import {
3
4
  boolean,
4
5
  index,
@@ -91,6 +92,8 @@ export const documents = pgTable(
91
92
 
92
93
  editorData: jsonb('editor_data').$type<Record<string, any>>(),
93
94
 
95
+ slug: varchar('slug', { length: 255 }),
96
+
94
97
  // Timestamps
95
98
  ...timestamps,
96
99
  },
@@ -100,6 +103,9 @@ export const documents = pgTable(
100
103
  index('documents_file_id_idx').on(table.fileId),
101
104
  index('documents_parent_id_idx').on(table.parentId),
102
105
  uniqueIndex('documents_client_id_user_id_unique').on(table.clientId, table.userId),
106
+ uniqueIndex('documents_slug_user_id_unique')
107
+ .on(table.slug, table.userId)
108
+ .where(isNotNull(table.slug)),
103
109
  ],
104
110
  );
105
111
 
@@ -133,7 +139,7 @@ export const files = pgTable(
133
139
  url: text('url').notNull(),
134
140
  source: text('source').$type<FileSource>(),
135
141
 
136
- // Parent document (for folder hierarchy structure)
142
+ // Parent Folder or Document
137
143
  // @ts-ignore
138
144
  parentId: varchar('parent_id', { length: 255 }).references(() => documents.id, {
139
145
  onDelete: 'set null',
@@ -1499,11 +1499,12 @@ const qwenChatModels: AIChatModelCard[] = [
1499
1499
  },
1500
1500
  {
1501
1501
  abilities: {
1502
- vision: true,
1503
1502
  reasoning: true,
1503
+ vision: true,
1504
1504
  },
1505
1505
  contextWindowTokens: 131_072,
1506
- description: 'Qwen3 VL 32B 思考模式(开源版),针对高难度强推理与长视频理解场景,提供顶尖的视觉+文本推理能力。',
1506
+ description:
1507
+ 'Qwen3 VL 32B 思考模式(开源版),针对高难度强推理与长视频理解场景,提供顶尖的视觉+文本推理能力。',
1507
1508
  displayName: 'Qwen3 VL 32B Thinking',
1508
1509
  id: 'qwen3-vl-32b-thinking',
1509
1510
  maxOutput: 32_768,
@@ -1525,7 +1526,8 @@ const qwenChatModels: AIChatModelCard[] = [
1525
1526
  vision: true,
1526
1527
  },
1527
1528
  contextWindowTokens: 131_072,
1528
- description: 'Qwen3 VL 32B 非思考模式(Instruct),适用于非思考指令场景,保持强大的视觉理解能力。',
1529
+ description:
1530
+ 'Qwen3 VL 32B 非思考模式(Instruct),适用于非思考指令场景,保持强大的视觉理解能力。',
1529
1531
  displayName: 'Qwen3 VL 32B Instruct',
1530
1532
  id: 'qwen3-vl-32b-instruct',
1531
1533
  maxOutput: 32_768,
@@ -138,10 +138,10 @@ export interface OpenAICompatibleFactoryOptions<T extends Record<string, any> =
138
138
  useToolsCalling?: boolean;
139
139
  };
140
140
  models?:
141
- | ((params: { client: OpenAI }) => Promise<ChatModelCard[]>)
142
- | {
143
- transformModel?: (model: OpenAI.Model) => ChatModelCard;
144
- };
141
+ | ((params: { client: OpenAI }) => Promise<ChatModelCard[]>)
142
+ | {
143
+ transformModel?: (model: OpenAI.Model) => ChatModelCard;
144
+ };
145
145
  provider: string;
146
146
  responses?: {
147
147
  handlePayload?: (
@@ -317,9 +317,9 @@ export const createOpenAICompatibleRuntime = <T extends Record<string, any> = an
317
317
  const postPayload = chatCompletion?.handlePayload
318
318
  ? chatCompletion.handlePayload(processedPayload, this._options)
319
319
  : ({
320
- ...processedPayload,
321
- stream: processedPayload.stream ?? true,
322
- } as OpenAI.ChatCompletionCreateParamsStreaming);
320
+ ...processedPayload,
321
+ stream: processedPayload.stream ?? true,
322
+ } as OpenAI.ChatCompletionCreateParamsStreaming);
323
323
 
324
324
  if ((postPayload as any).apiMode === 'responses') {
325
325
  return this.handleResponseAPIMode(processedPayload, options);
@@ -385,13 +385,13 @@ export const createOpenAICompatibleRuntime = <T extends Record<string, any> = an
385
385
  return StreamingResponse(
386
386
  chatCompletion?.handleStream
387
387
  ? chatCompletion.handleStream(prod, {
388
- callbacks: streamOptions.callbacks,
389
- inputStartAt,
390
- })
388
+ callbacks: streamOptions.callbacks,
389
+ inputStartAt,
390
+ })
391
391
  : OpenAIStream(prod, {
392
- ...streamOptions,
393
- inputStartAt,
394
- }),
392
+ ...streamOptions,
393
+ inputStartAt,
394
+ }),
395
395
  {
396
396
  headers: options?.headers,
397
397
  },
@@ -415,9 +415,9 @@ export const createOpenAICompatibleRuntime = <T extends Record<string, any> = an
415
415
  return StreamingResponse(
416
416
  chatCompletion?.handleStream
417
417
  ? chatCompletion.handleStream(stream, {
418
- callbacks: streamOptions.callbacks,
419
- inputStartAt,
420
- })
418
+ callbacks: streamOptions.callbacks,
419
+ inputStartAt,
420
+ })
421
421
  : OpenAIStream(stream, { ...streamOptions, enableStreaming: false, inputStartAt }),
422
422
  {
423
423
  headers: options?.headers,
@@ -839,11 +839,11 @@ export const createOpenAICompatibleRuntime = <T extends Record<string, any> = an
839
839
  ...res,
840
840
  ...(reasoning || reasoning_effort
841
841
  ? {
842
- reasoning: {
843
- ...reasoning,
844
- ...(reasoning_effort && { effort: reasoning_effort }),
845
- },
846
- }
842
+ reasoning: {
843
+ ...reasoning,
844
+ ...(reasoning_effort && { effort: reasoning_effort }),
845
+ },
846
+ }
847
847
  : {}),
848
848
  input,
849
849
  ...(max_tokens && { max_output_tokens: max_tokens }),
@@ -9,12 +9,12 @@
9
9
  },
10
10
  "dependencies": {
11
11
  "@opentelemetry/api": "^1.9.0",
12
- "@opentelemetry/auto-instrumentations-node": "^0.66.0",
12
+ "@opentelemetry/auto-instrumentations-node": "^0.67.0",
13
13
  "@opentelemetry/exporter-metrics-otlp-http": "^0.208.0",
14
14
  "@opentelemetry/exporter-trace-otlp-http": "^0.208.0",
15
15
  "@opentelemetry/instrumentation": "^0.208.0",
16
16
  "@opentelemetry/instrumentation-http": "^0.208.0",
17
- "@opentelemetry/instrumentation-pg": "^0.60.0",
17
+ "@opentelemetry/instrumentation-pg": "^0.61.0",
18
18
  "@opentelemetry/resources": "^2.2.0",
19
19
  "@opentelemetry/sdk-metrics": "^2.2.0",
20
20
  "@opentelemetry/sdk-node": "^0.208.0",
@@ -0,0 +1,148 @@
1
+ #!/usr/bin/env tsx
2
+
3
+ import { execSync } from 'node:child_process';
4
+ import { readFileSync } from 'node:fs';
5
+
6
+ interface WhitelistConfig {
7
+ files?: string[];
8
+ patterns?: string[];
9
+ }
10
+
11
+ const WHITELIST_PATH = '.console-log-whitelist.json';
12
+
13
+ /**
14
+ * Load whitelist configuration
15
+ */
16
+ const loadWhitelist = (): WhitelistConfig => {
17
+ try {
18
+ const content = readFileSync(WHITELIST_PATH, 'utf8');
19
+ return JSON.parse(content);
20
+ } catch {
21
+ return { files: [], patterns: [] };
22
+ }
23
+ };
24
+
25
+ /**
26
+ * Check if a file is whitelisted
27
+ */
28
+ const isWhitelisted = (filePath: string, whitelist: WhitelistConfig): boolean => {
29
+ const normalizedPath = filePath.replaceAll('\\', '/');
30
+
31
+ // Check exact file matches
32
+ if (whitelist.files?.some((f) => normalizedPath.includes(f.replaceAll('\\', '/')))) {
33
+ return true;
34
+ }
35
+
36
+ // Check pattern matches (simple glob-like patterns)
37
+ if (whitelist.patterns) {
38
+ for (const pattern of whitelist.patterns) {
39
+ // Escape dots and replace glob patterns
40
+ // Use a placeholder for ** to avoid conflicts with single *
41
+ let regexPattern = pattern
42
+ .replaceAll('.', '\\.')
43
+ .replaceAll('**', '\u0000DOUBLESTAR\u0000')
44
+ .replaceAll('*', '[^/]*')
45
+ .replaceAll('\u0000DOUBLESTAR\u0000', '.*');
46
+
47
+ // If pattern ends with /**, match everything under that directory
48
+ // If pattern ends with **, just match everything from that point
49
+ const regex = new RegExp(`^${regexPattern}`);
50
+ if (regex.test(normalizedPath)) {
51
+ return true;
52
+ }
53
+ }
54
+ }
55
+
56
+ return false;
57
+ };
58
+
59
+ /**
60
+ * Main check function
61
+ */
62
+ const checkConsoleLogs = () => {
63
+ const whitelist = loadWhitelist();
64
+
65
+ console.log('🔍 Checking for console.log statements...\n');
66
+
67
+ try {
68
+ // Search for console.log in TypeScript and JavaScript files
69
+ const output = execSync(
70
+ `git grep -n "console\\.log" -- "*.ts" "*.tsx" "*.js" "*.jsx" "*.mts" "*.cts" || true`,
71
+ { encoding: 'utf8' },
72
+ );
73
+
74
+ if (!output.trim()) {
75
+ console.log('✅ No console.log statements found!');
76
+ return;
77
+ }
78
+
79
+ const lines = output.trim().split('\n');
80
+ const violations: Array<{ content: string, file: string; line: string; }> = [];
81
+
82
+ for (const line of lines) {
83
+ // Parse git grep output: filename:lineNumber:content
84
+ const match = line.match(/^([^:]+):(\d+):(.+)$/);
85
+ if (!match) continue;
86
+
87
+ const [, filePath, lineNumber, content] = match;
88
+
89
+ // Skip if whitelisted
90
+ if (isWhitelisted(filePath, whitelist)) {
91
+ continue;
92
+ }
93
+
94
+ // Skip comments
95
+ const trimmedContent = content.trim();
96
+ if (trimmedContent.startsWith('//') || trimmedContent.startsWith('*')) {
97
+ continue;
98
+ }
99
+
100
+ violations.push({
101
+ content: content.trim(),
102
+ file: filePath,
103
+ line: lineNumber,
104
+ });
105
+ }
106
+
107
+ if (violations.length === 0) {
108
+ console.log('✅ No console.log violations found (all matches are whitelisted or in comments)!');
109
+ return;
110
+ }
111
+
112
+ // Report violations as warnings
113
+ console.log('⚠️ Found console.log statements in the following files:\n');
114
+
115
+ // Use GitHub Actions annotation format for better visibility in CI
116
+ const isCI = process.env.CI === 'true' || process.env.GITHUB_ACTIONS === 'true';
117
+
118
+ for (const violation of violations) {
119
+ if (isCI) {
120
+ // GitHub Actions warning annotation format
121
+ console.log(`::warning file=${violation.file},line=${violation.line}::console.log found: ${violation.content}`);
122
+ } else {
123
+ console.log(` ${violation.file}:${violation.line}`);
124
+ console.log(` ${violation.content}\n`);
125
+ }
126
+ }
127
+
128
+ console.log(`\n💡 Total violations: ${violations.length}`);
129
+ console.log(
130
+ `\n📝 To whitelist files, add them to ${WHITELIST_PATH} in the following format:`,
131
+ );
132
+ console.log(`{
133
+ "files": ["path/to/file.ts"],
134
+ "patterns": ["scripts/**/*.mts", "**/*.test.ts"]
135
+ }\n`);
136
+
137
+ // Exit with 0 to not block CI, but warnings will still be visible
138
+ process.exit(0);
139
+ } catch (error: unknown) {
140
+ if (error instanceof Error && 'status' in error && error.status !== 0) {
141
+ console.error('❌ Error running git grep:', error.message);
142
+ process.exit(1);
143
+ }
144
+ throw error;
145
+ }
146
+ };
147
+
148
+ checkConsoleLogs();
@@ -10,11 +10,11 @@ dotenv.config();
10
10
  /* eslint-disable sort-keys-fix/sort-keys-fix */
11
11
  const partialBuildPages = [
12
12
  // no need for desktop
13
- {
14
- name: 'changelog',
15
- disabled: isDesktop,
16
- paths: ['src/app/[variants]/(main)/changelog'],
17
- },
13
+ // {
14
+ // name: 'changelog',
15
+ // disabled: isDesktop,
16
+ // paths: ['src/app/[variants]/(main)/changelog'],
17
+ // },
18
18
  {
19
19
  name: 'auth',
20
20
  disabled: isDesktop,
@@ -18,7 +18,7 @@ const Page = (props: { isMobile: boolean }) => {
18
18
  const { isMobile } = props;
19
19
  const { hideDocs } = useServerConfigStore(featureFlagsSelectors);
20
20
 
21
- const { data } = useSWR('changelog-index', async () => {
21
+ const { data = [] } = useSWR('changelog-index', async () => {
22
22
  const changelogService = new ChangelogService();
23
23
  return await changelogService.getChangelogIndex();
24
24
  });
@@ -1,6 +1,7 @@
1
1
  'use client';
2
2
 
3
3
  import { useResponsive, useTheme } from 'antd-style';
4
+ import useMergedState from 'rc-util/lib/hooks/useMergedState';
4
5
  import { memo, useEffect, useRef } from 'react';
5
6
  import { Flexbox } from 'react-layout-kit';
6
7
  import { useSearchParams } from 'react-router-dom';
@@ -13,7 +14,6 @@ import SettingsContent from '../SettingsContent';
13
14
  import { LayoutProps } from '../type';
14
15
  import Header from './Header';
15
16
  import SideBar from './SideBar';
16
- import useMergedState from 'rc-util/lib/hooks/useMergedState';
17
17
 
18
18
  const Layout = memo<LayoutProps>(() => {
19
19
  const ref = useRef<HTMLDivElement | null>(null);
@@ -22,21 +22,23 @@ const Layout = memo<LayoutProps>(() => {
22
22
 
23
23
  const [searchParams, setSearchParams] = useSearchParams();
24
24
 
25
- const [activeTabState, setActiveTabState] = useMergedState({
26
- active: searchParams.get('active') as SettingsTabs ? searchParams.get('active') as SettingsTabs : SettingsTabs.Common,
27
- }, {
28
- onChange: (obj: {
29
- active: SettingsTabs;
30
- provider?: string;
31
- }) => {
32
- if (obj.provider) {
33
- setSearchParams({ active: obj.active, provider: obj.provider });
34
- } else {
35
- searchParams.delete('provider');
36
- setSearchParams({ active: obj.active });
37
- }
25
+ const [activeTabState, setActiveTabState] = useMergedState(
26
+ {
27
+ active: (searchParams.get('active') as SettingsTabs)
28
+ ? (searchParams.get('active') as SettingsTabs)
29
+ : SettingsTabs.Common,
30
+ },
31
+ {
32
+ onChange: (obj: { active: SettingsTabs; provider?: string }) => {
33
+ if (obj.provider) {
34
+ setSearchParams({ active: obj.active, provider: obj.provider });
35
+ } else {
36
+ searchParams.delete('provider');
37
+ setSearchParams({ active: obj.active });
38
+ }
39
+ },
38
40
  },
39
- });
41
+ );
40
42
 
41
43
  const setActiveTab = (tab: SettingsTabs) => {
42
44
  if (tab === SettingsTabs.Provider) {
@@ -57,7 +59,9 @@ const Layout = memo<LayoutProps>(() => {
57
59
  };
58
60
  }, []);
59
61
 
60
- const category = <CategoryContent activeTab={activeTabState.active} onMenuSelect={setActiveTab} />;
62
+ const category = (
63
+ <CategoryContent activeTab={activeTabState.active} onMenuSelect={setActiveTab} />
64
+ );
61
65
 
62
66
  return (
63
67
  <Flexbox
@@ -33,9 +33,12 @@ const ProviderCard = memo<ProviderCardProps>(
33
33
  return (
34
34
  <Flexbox className={cx(styles.container)} gap={24}>
35
35
  <Flexbox gap={12} padding={16} width={'100%'}>
36
- <div onClick={() => {
37
- onProviderSelect(id);
38
- }} style={{ cursor: 'pointer' }}>
36
+ <div
37
+ onClick={() => {
38
+ onProviderSelect(id);
39
+ }}
40
+ style={{ cursor: 'pointer' }}
41
+ >
39
42
  <Flexbox gap={12} width={'100%'}>
40
43
  <Flexbox align={'center'} horizontal justify={'space-between'}>
41
44
  {source === 'builtin' ? (
@@ -1,13 +1,14 @@
1
1
  'use client';
2
2
 
3
+ import { useMemo, useState } from 'react';
4
+ import { useSearchParams } from 'react-router-dom';
5
+
3
6
  import { isCustomBranding } from '@/const/version';
4
7
 
5
8
  import DesktopLayout from '../_layout/Desktop';
6
9
  import MobileLayout from '../_layout/Mobile';
7
10
  import ProviderDetailPage from '../detail';
8
11
  import Footer from './Footer';
9
- import { useMemo, useState } from 'react';
10
- import { useSearchParams } from 'react-router-dom';
11
12
 
12
13
  const Page = (props: { mobile?: boolean }) => {
13
14
  const [SearchParams, setSearchParams] = useSearchParams();
@@ -1,4 +1,5 @@
1
1
  import dynamic from 'next/dynamic';
2
+
2
3
  import Loading from '@/components/Loading/BrandTextLoading';
3
4
 
4
5
  const NewAPI = dynamic(() => import('./newapi'), { loading: () => <Loading />, ssr: false });
@@ -7,17 +8,26 @@ const VertexAI = dynamic(() => import('./vertexai'), { loading: () => <Loading /
7
8
  const GitHub = dynamic(() => import('./github'), { loading: () => <Loading />, ssr: false });
8
9
  const Ollama = dynamic(() => import('./ollama'), { loading: () => <Loading />, ssr: false });
9
10
  const ComfyUI = dynamic(() => import('./comfyui'), { loading: () => <Loading />, ssr: false });
10
- const Cloudflare = dynamic(() => import('./cloudflare'), { loading: () => <Loading />, ssr: false });
11
+ const Cloudflare = dynamic(() => import('./cloudflare'), {
12
+ loading: () => <Loading />,
13
+ ssr: false,
14
+ });
11
15
  const Bedrock = dynamic(() => import('./bedrock'), { loading: () => <Loading />, ssr: false });
12
16
  const AzureAI = dynamic(() => import('./azureai'), { loading: () => <Loading />, ssr: false });
13
17
  const Azure = dynamic(() => import('./azure'), { loading: () => <Loading />, ssr: false });
14
- const ProviderGrid = dynamic(() => import('../(list)/ProviderGrid'), { loading: () => <Loading />, ssr: false });
15
- const DefaultPage = dynamic(() => import('./default/ProviderDetialPage'), { loading: () => <Loading />, ssr: false });
18
+ const ProviderGrid = dynamic(() => import('../(list)/ProviderGrid'), {
19
+ loading: () => <Loading />,
20
+ ssr: false,
21
+ });
22
+ const DefaultPage = dynamic(() => import('./default/ProviderDetialPage'), {
23
+ loading: () => <Loading />,
24
+ ssr: false,
25
+ });
16
26
 
17
27
  type ProviderDetailPageProps = {
18
28
  id?: string | null;
19
29
  onProviderSelect: (provider: string) => void;
20
- }
30
+ };
21
31
 
22
32
  const ProviderDetailPage = (props: ProviderDetailPageProps) => {
23
33
  const { id, onProviderSelect } = props;
@@ -40,6 +40,16 @@ const ChatLayout = dynamic(() => import('./(main)/chat/_layout/Desktop'), {
40
40
  ssr: false,
41
41
  });
42
42
 
43
+ // Changelog components
44
+ const ChangelogPage = dynamic(() => import('./(main)/changelog/index').then((m) => m.DesktopPage), {
45
+ loading: () => <Loading />,
46
+ ssr: false,
47
+ });
48
+ const ChangelogLayout = dynamic(() => import('./(main)/changelog/_layout/Desktop'), {
49
+ loading: () => <Loading />,
50
+ ssr: false,
51
+ });
52
+
43
53
  // Discover List components
44
54
  const DesktopHomePage = dynamic(
45
55
  () => import('./(main)/discover/(list)/(home)/index').then((m) => m.DesktopHomePage),
@@ -256,6 +266,7 @@ const KnowledgeErrorBoundary = createErrorBoundary('/knowledge');
256
266
  const SettingsErrorBoundary = createErrorBoundary('/settings');
257
267
  const ImageErrorBoundary = createErrorBoundary('/image');
258
268
  const ProfileErrorBoundary = createErrorBoundary('/profile');
269
+ const ChangelogErrorBoundary = createErrorBoundary('/changelog');
259
270
  const RootErrorBoundary = createErrorBoundary('/chat'); // Root level falls back to chat
260
271
 
261
272
  // Root layout wrapper component
@@ -455,6 +466,18 @@ export const createDesktopRouter = (locale: Locales) =>
455
466
  path: 'profile',
456
467
  },
457
468
 
469
+ // changelog routes
470
+ {
471
+ children: [
472
+ {
473
+ element: <ChangelogPage />,
474
+ index: true,
475
+ },
476
+ ],
477
+ element: <ChangelogLayout locale={locale} />,
478
+ errorElement: <ChangelogErrorBoundary />,
479
+ path: 'changelog',
480
+ },
458
481
  // Default route - redirect to chat
459
482
  {
460
483
  index: true,
@@ -41,6 +41,15 @@ const ChatLayout = dynamic(() => import('./(main)/chat/_layout/Mobile'), {
41
41
  ssr: false,
42
42
  });
43
43
 
44
+ // Changelog components
45
+ const ChangelogPage = dynamic(() => import('./(main)/changelog/index').then((m) => m.MobilePage), {
46
+ loading: () => <Loading />,
47
+ ssr: false,
48
+ });
49
+ const ChangelogLayout = dynamic(() => import('./(main)/changelog/_layout/Mobile'), {
50
+ loading: () => <Loading />,
51
+ ssr: false,
52
+ });
44
53
  // Discover List components
45
54
  const MobileHomePage = dynamic(
46
55
  () => import('./(main)/discover/(list)/(home)/index').then((m) => m.MobileHomePage),
@@ -272,6 +281,7 @@ const createErrorBoundary = (resetPath: string) => {
272
281
  // Create error boundaries for each route
273
282
  const ChatErrorBoundary = createErrorBoundary('/chat');
274
283
  const DiscoverErrorBoundary = createErrorBoundary('/discover');
284
+ const ChangelogErrorBoundary = createErrorBoundary('/changelog');
275
285
  const KnowledgeErrorBoundary = createErrorBoundary('/knowledge');
276
286
  const SettingsErrorBoundary = createErrorBoundary('/settings');
277
287
  const ImageErrorBoundary = createErrorBoundary('/image');
@@ -499,6 +509,19 @@ export const createMobileRouter = (locale: Locales) =>
499
509
  path: 'me',
500
510
  },
501
511
 
512
+ // changelog routes
513
+ {
514
+ children: [
515
+ {
516
+ element: <ChangelogPage />,
517
+ index: true,
518
+ },
519
+ ],
520
+ element: <ChangelogLayout locale={locale} />,
521
+ errorElement: <ChangelogErrorBoundary />,
522
+ path: 'changelog',
523
+ },
524
+
502
525
  // Default route - redirect to chat
503
526
  {
504
527
  index: true,
@@ -143,40 +143,20 @@ const NoteEditorModal = memo<NoteEditorModalProps>(
143
143
  const handleOpenChange = (isOpen: boolean) => {
144
144
  // When modal opens, load document content if in edit mode
145
145
  if (isOpen && documentId && editor) {
146
- console.log('[NoteEditorModal] Loading content:', {
147
- cachedEditorDataPreview: cachedEditorData
148
- ? JSON.stringify(cachedEditorData).slice(0, 100)
149
- : null,
150
- cachedEditorDataType: typeof cachedEditorData,
151
- documentId,
152
- documentTitle,
153
- hasCachedEditorData: !!cachedEditorData,
154
- });
155
-
156
146
  // If editorData is already cached (from list), use it directly
157
147
  if (cachedEditorData) {
158
- console.log('[NoteEditorModal] Using cached editorData', cachedEditorData);
159
148
  setNoteTitle(documentTitle || '');
160
149
  editor.setDocument('json', JSON.stringify(cachedEditorData));
161
150
  return;
162
151
  }
163
152
 
164
153
  // Otherwise, fetch full content from API
165
- console.log('[NoteEditorModal] Fetching from API');
166
154
  documentService
167
155
  .getDocumentById(documentId)
168
156
  .then((doc) => {
169
157
  if (doc && doc.content) {
170
158
  setNoteTitle(doc.title || doc.filename || '');
171
159
 
172
- console.log('[NoteEditorModal] Fetched doc.editorData:', {
173
- editorDataPreview: doc.editorData
174
- ? JSON.stringify(doc.editorData).slice(0, 100)
175
- : null,
176
- editorDataType: typeof doc.editorData,
177
- hasEditorData: !!doc.editorData,
178
- });
179
-
180
160
  editor.setDocument('json', doc.editorData);
181
161
  }
182
162
  })