@lobehub/lobehub 2.0.0-next.150 → 2.0.0-next.152
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.
- package/CHANGELOG.md +50 -0
- package/changelog/v1.json +18 -0
- package/docs/development/database-schema.dbml +1 -0
- package/locales/ar/auth.json +1 -0
- package/locales/ar/models.json +0 -9
- package/locales/bg-BG/auth.json +1 -0
- package/locales/bg-BG/models.json +0 -9
- package/locales/de-DE/auth.json +1 -0
- package/locales/de-DE/models.json +0 -9
- package/locales/en-US/auth.json +1 -0
- package/locales/en-US/models.json +0 -9
- package/locales/es-ES/auth.json +1 -0
- package/locales/es-ES/models.json +0 -9
- package/locales/fa-IR/auth.json +1 -0
- package/locales/fa-IR/models.json +0 -9
- package/locales/fr-FR/auth.json +1 -0
- package/locales/fr-FR/models.json +0 -9
- package/locales/it-IT/auth.json +1 -0
- package/locales/it-IT/models.json +0 -9
- package/locales/ja-JP/auth.json +1 -0
- package/locales/ja-JP/models.json +0 -9
- package/locales/ko-KR/auth.json +1 -0
- package/locales/ko-KR/models.json +0 -9
- package/locales/nl-NL/auth.json +1 -0
- package/locales/nl-NL/models.json +0 -9
- package/locales/pl-PL/auth.json +1 -0
- package/locales/pl-PL/models.json +0 -9
- package/locales/pt-BR/auth.json +1 -0
- package/locales/pt-BR/models.json +0 -9
- package/locales/ru-RU/auth.json +1 -0
- package/locales/ru-RU/models.json +0 -9
- package/locales/tr-TR/auth.json +1 -0
- package/locales/tr-TR/models.json +0 -9
- package/locales/vi-VN/auth.json +1 -0
- package/locales/vi-VN/models.json +0 -9
- package/locales/zh-CN/auth.json +1 -0
- package/locales/zh-CN/models.json +0 -9
- package/locales/zh-TW/auth.json +1 -0
- package/locales/zh-TW/models.json +0 -9
- package/package.json +3 -1
- package/packages/database/migrations/0057_add_topic_user_memory_extract_status.sql +1 -0
- package/packages/database/migrations/meta/0057_snapshot.json +8426 -0
- package/packages/database/migrations/meta/_journal.json +7 -0
- package/packages/database/src/core/migrations.json +40 -11
- package/packages/database/src/schemas/topic.ts +5 -0
- package/packages/model-runtime/package.json +1 -0
- package/packages/model-runtime/src/utils/asyncifyPolling.ts +127 -104
- package/packages/types/src/topic/topic.ts +12 -0
- package/src/app/(backend)/api/auth/check-user/route.ts +8 -1
- package/src/app/[variants]/(auth)/signin/page.tsx +23 -10
- package/src/auth.ts +2 -1
- package/src/components/NextAuth/AuthIcons.tsx +2 -2
- package/src/server/services/mcp/index.test.ts +4 -20
- package/src/server/services/mcp/index.ts +39 -39
- package/src/store/serverConfig/action.ts +5 -1
- package/src/store/serverConfig/store.ts +2 -0
|
@@ -399,6 +399,13 @@
|
|
|
399
399
|
"when": 1764685643024,
|
|
400
400
|
"tag": "0056_update_agent_slug_index",
|
|
401
401
|
"breakpoints": true
|
|
402
|
+
},
|
|
403
|
+
{
|
|
404
|
+
"idx": 57,
|
|
405
|
+
"version": "7",
|
|
406
|
+
"when": 1764734167674,
|
|
407
|
+
"tag": "0057_add_topic_user_memory_extract_status",
|
|
408
|
+
"breakpoints": true
|
|
402
409
|
}
|
|
403
410
|
],
|
|
404
411
|
"version": "6"
|
|
@@ -223,7 +223,10 @@
|
|
|
223
223
|
"hash": "9646161fa041354714f823d726af27247bcd6e60fa3be5698c0d69f337a5700b"
|
|
224
224
|
},
|
|
225
225
|
{
|
|
226
|
-
"sql": [
|
|
226
|
+
"sql": [
|
|
227
|
+
"DROP TABLE \"user_budgets\";",
|
|
228
|
+
"\nDROP TABLE \"user_subscriptions\";"
|
|
229
|
+
],
|
|
227
230
|
"bps": true,
|
|
228
231
|
"folderMillis": 1729699958471,
|
|
229
232
|
"hash": "7dad43a2a25d1aec82124a4e53f8d82f8505c3073f23606c1dc5d2a4598eacf9"
|
|
@@ -295,7 +298,9 @@
|
|
|
295
298
|
"hash": "845a692ceabbfc3caf252a97d3e19a213bc0c433df2689900135f9cfded2cf49"
|
|
296
299
|
},
|
|
297
300
|
{
|
|
298
|
-
"sql": [
|
|
301
|
+
"sql": [
|
|
302
|
+
"ALTER TABLE \"messages\" ADD COLUMN \"reasoning\" jsonb;"
|
|
303
|
+
],
|
|
299
304
|
"bps": true,
|
|
300
305
|
"folderMillis": 1737609172353,
|
|
301
306
|
"hash": "2cb36ae4fcdd7b7064767e04bfbb36ae34518ff4bb1b39006f2dd394d1893868"
|
|
@@ -510,7 +515,9 @@
|
|
|
510
515
|
"hash": "a7ccf007fd185ff922823148d1eae6fafe652fc98d2fd2793f84a84f29e93cd1"
|
|
511
516
|
},
|
|
512
517
|
{
|
|
513
|
-
"sql": [
|
|
518
|
+
"sql": [
|
|
519
|
+
"ALTER TABLE \"ai_providers\" ADD COLUMN \"config\" jsonb;"
|
|
520
|
+
],
|
|
514
521
|
"bps": true,
|
|
515
522
|
"folderMillis": 1749309388370,
|
|
516
523
|
"hash": "39cea379f08ee4cb944875c0b67f7791387b508c2d47958bb4cd501ed1ef33eb"
|
|
@@ -628,7 +635,9 @@
|
|
|
628
635
|
"hash": "1ba9b1f74ea13348da98d6fcdad7867ab4316ed565bf75d84d160c526cdac14b"
|
|
629
636
|
},
|
|
630
637
|
{
|
|
631
|
-
"sql": [
|
|
638
|
+
"sql": [
|
|
639
|
+
"ALTER TABLE \"agents\" ADD COLUMN IF NOT EXISTS \"virtual\" boolean DEFAULT false;"
|
|
640
|
+
],
|
|
632
641
|
"bps": true,
|
|
633
642
|
"folderMillis": 1759116400580,
|
|
634
643
|
"hash": "433ddae88e785f2db734e49a4c115eee93e60afe389f7919d66e5ba9aa159a37"
|
|
@@ -678,13 +687,17 @@
|
|
|
678
687
|
"hash": "4bdc6505797d7a33b622498c138cfd47f637239f6905e1c484cd01d9d5f21d6b"
|
|
679
688
|
},
|
|
680
689
|
{
|
|
681
|
-
"sql": [
|
|
690
|
+
"sql": [
|
|
691
|
+
"ALTER TABLE \"user_settings\" ADD COLUMN IF NOT EXISTS \"image\" jsonb;"
|
|
692
|
+
],
|
|
682
693
|
"bps": true,
|
|
683
694
|
"folderMillis": 1760108430562,
|
|
684
695
|
"hash": "ce09b301abb80f6563abc2f526bdd20b4f69bae430f09ba2179b9e3bfec43067"
|
|
685
696
|
},
|
|
686
697
|
{
|
|
687
|
-
"sql": [
|
|
698
|
+
"sql": [
|
|
699
|
+
"ALTER TABLE \"documents\" ADD COLUMN IF NOT EXISTS \"editor_data\" jsonb;"
|
|
700
|
+
],
|
|
688
701
|
"bps": true,
|
|
689
702
|
"folderMillis": 1761554153406,
|
|
690
703
|
"hash": "bf2f21293e90e11cf60a784cf3ec219eafa95f7545d7d2f9d1449c0b0949599a"
|
|
@@ -764,13 +777,17 @@
|
|
|
764
777
|
"hash": "923ccbdf46c32be9a981dabd348e6923b4a365444241e9b8cc174bf5b914cbc5"
|
|
765
778
|
},
|
|
766
779
|
{
|
|
767
|
-
"sql": [
|
|
780
|
+
"sql": [
|
|
781
|
+
"ALTER TABLE \"agents\" ADD COLUMN IF NOT EXISTS \"market_identifier\" text;\n"
|
|
782
|
+
],
|
|
768
783
|
"bps": true,
|
|
769
784
|
"folderMillis": 1762870034882,
|
|
770
785
|
"hash": "4178aacb4b8892b7fd15d29209bbf9b1d1f9d7c406ba796f27542c0bcd919680"
|
|
771
786
|
},
|
|
772
787
|
{
|
|
773
|
-
"sql": [
|
|
788
|
+
"sql": [
|
|
789
|
+
"ALTER TABLE \"message_plugins\" ADD COLUMN IF NOT EXISTS \"intervention\" jsonb;\n"
|
|
790
|
+
],
|
|
774
791
|
"bps": true,
|
|
775
792
|
"folderMillis": 1762911968658,
|
|
776
793
|
"hash": "552a032cc0e595277232e70b5f9338658585bafe9481ae8346a5f322b673a68b"
|
|
@@ -799,7 +816,9 @@
|
|
|
799
816
|
"hash": "f823b521f4d25e5dc5ab238b372727d2d2d7f0aed27b5eabc8a9608ce4e50568"
|
|
800
817
|
},
|
|
801
818
|
{
|
|
802
|
-
"sql": [
|
|
819
|
+
"sql": [
|
|
820
|
+
"ALTER TABLE \"agents\" ADD COLUMN IF NOT EXISTS \"editor_data\" jsonb;"
|
|
821
|
+
],
|
|
803
822
|
"bps": true,
|
|
804
823
|
"folderMillis": 1764215503726,
|
|
805
824
|
"hash": "4188893a9083b3c7baebdbad0dd3f9d9400ede7584ca2394f5c64305dc9ec7b0"
|
|
@@ -840,7 +859,9 @@
|
|
|
840
859
|
"hash": "2c103eee82bdf329944fb622dd9c2b9f20df80eb54f23eb9254d2285de413099"
|
|
841
860
|
},
|
|
842
861
|
{
|
|
843
|
-
"sql": [
|
|
862
|
+
"sql": [
|
|
863
|
+
"ALTER TABLE \"user_settings\" ADD COLUMN IF NOT EXISTS \"market\" jsonb;"
|
|
864
|
+
],
|
|
844
865
|
"bps": true,
|
|
845
866
|
"folderMillis": 1764335703306,
|
|
846
867
|
"hash": "28c0d738c0b1fdf5fd871363be1a1477b4accbabdc140fe8dc6e9b339aae2c89"
|
|
@@ -910,5 +931,13 @@
|
|
|
910
931
|
"bps": true,
|
|
911
932
|
"folderMillis": 1764685643024,
|
|
912
933
|
"hash": "6e7ac7f964eb03efa3cb0d2fd35ded23e25c3abf955c4c2a51418f8daef54af9"
|
|
934
|
+
},
|
|
935
|
+
{
|
|
936
|
+
"sql": [
|
|
937
|
+
"CREATE INDEX IF NOT EXISTS \"topics_extract_status_gin_idx\" ON \"topics\" USING gin ((metadata->'userMemoryExtractStatus') jsonb_path_ops);\n"
|
|
938
|
+
],
|
|
939
|
+
"bps": true,
|
|
940
|
+
"folderMillis": 1764734167674,
|
|
941
|
+
"hash": "89c134be2948d3afc360d6bac11dea0c6fd5c902bf6093ed077033adb920fd02"
|
|
913
942
|
}
|
|
914
|
-
]
|
|
943
|
+
]
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
/* eslint-disable sort-keys-fix/sort-keys-fix */
|
|
2
2
|
import type { ChatTopicMetadata } from '@lobechat/types';
|
|
3
|
+
import { sql } from 'drizzle-orm';
|
|
3
4
|
import { boolean, index, jsonb, pgTable, primaryKey, text, uniqueIndex } from 'drizzle-orm/pg-core';
|
|
4
5
|
import { createInsertSchema } from 'drizzle-zod';
|
|
5
6
|
|
|
@@ -39,6 +40,10 @@ export const topics = pgTable(
|
|
|
39
40
|
index('topics_session_id_idx').on(t.sessionId),
|
|
40
41
|
index('topics_group_id_idx').on(t.groupId),
|
|
41
42
|
index('topics_agent_id_idx').on(t.agentId),
|
|
43
|
+
index('topics_extract_status_gin_idx').using(
|
|
44
|
+
'gin',
|
|
45
|
+
sql`(metadata->'userMemoryExtractStatus') jsonb_path_ops`,
|
|
46
|
+
),
|
|
42
47
|
],
|
|
43
48
|
);
|
|
44
49
|
|
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
import retry from 'async-retry';
|
|
2
|
+
|
|
1
3
|
export interface TaskResult<T> {
|
|
2
4
|
data?: T;
|
|
3
5
|
error?: any;
|
|
@@ -23,14 +25,14 @@ export interface AsyncifyPollingOptions<T, R> {
|
|
|
23
25
|
checkStatus: (result: T) => TaskResult<R>;
|
|
24
26
|
|
|
25
27
|
// Retry configuration
|
|
26
|
-
initialInterval?: number;
|
|
28
|
+
initialInterval?: number;
|
|
27
29
|
// Optional logger
|
|
28
30
|
logger?: {
|
|
29
31
|
debug?: (...args: any[]) => void;
|
|
30
32
|
error?: (...args: any[]) => void;
|
|
31
|
-
};
|
|
33
|
+
};
|
|
32
34
|
// Default 1.5
|
|
33
|
-
maxConsecutiveFailures?: number;
|
|
35
|
+
maxConsecutiveFailures?: number;
|
|
34
36
|
// Default 500ms
|
|
35
37
|
maxInterval?: number; // Default 3
|
|
36
38
|
maxRetries?: number; // Default Infinity
|
|
@@ -42,6 +44,24 @@ export interface AsyncifyPollingOptions<T, R> {
|
|
|
42
44
|
pollingQuery: () => Promise<T>;
|
|
43
45
|
}
|
|
44
46
|
|
|
47
|
+
// Internal error class to signal that polling should continue
|
|
48
|
+
class PendingError extends Error {
|
|
49
|
+
constructor() {
|
|
50
|
+
super('Task is pending, continue polling');
|
|
51
|
+
this.name = 'PendingError';
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
// Internal error class to signal that task has failed and should not retry
|
|
56
|
+
class TaskFailedError extends Error {
|
|
57
|
+
originalError: any;
|
|
58
|
+
constructor(error: any) {
|
|
59
|
+
super(error instanceof Error ? error.message : String(error));
|
|
60
|
+
this.name = 'TaskFailedError';
|
|
61
|
+
this.originalError = error;
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
|
|
45
65
|
/**
|
|
46
66
|
* Convert polling pattern to async/await pattern
|
|
47
67
|
*
|
|
@@ -62,114 +82,117 @@ export async function asyncifyPolling<T, R>(options: AsyncifyPollingOptions<T, R
|
|
|
62
82
|
logger,
|
|
63
83
|
} = options;
|
|
64
84
|
|
|
65
|
-
let retries = 0;
|
|
66
85
|
let consecutiveFailures = 0;
|
|
67
86
|
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
87
|
+
// async-retry uses Infinity for retries when maxRetries is Infinity
|
|
88
|
+
// but we need to handle this case properly
|
|
89
|
+
const retriesConfig = maxRetries === Infinity ? 1_000_000 : maxRetries - 1;
|
|
90
|
+
|
|
91
|
+
try {
|
|
92
|
+
return await retry(
|
|
93
|
+
async (bail, attemptNumber) => {
|
|
94
|
+
const retries = attemptNumber - 1;
|
|
95
|
+
|
|
96
|
+
try {
|
|
97
|
+
// Execute polling function
|
|
98
|
+
const pollingResult = await pollingQuery();
|
|
99
|
+
|
|
100
|
+
// Reset consecutive failures counter on successful execution
|
|
101
|
+
consecutiveFailures = 0;
|
|
102
|
+
|
|
103
|
+
// Check task status
|
|
104
|
+
const statusResult = checkStatus(pollingResult);
|
|
105
|
+
|
|
106
|
+
logger?.debug?.(`Task status: ${statusResult.status} (attempt ${attemptNumber})`);
|
|
107
|
+
|
|
108
|
+
switch (statusResult.status) {
|
|
109
|
+
case 'success': {
|
|
110
|
+
return statusResult.data as R;
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
case 'failed': {
|
|
114
|
+
// Task logic failed, throw error immediately (not counted as consecutive failure)
|
|
115
|
+
bail(new TaskFailedError(statusResult.error || new Error('Task failed')));
|
|
116
|
+
// This return is never reached due to bail, but needed for type safety
|
|
117
|
+
return undefined as R;
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
default: {
|
|
121
|
+
// 'pending' or unknown status - continue polling by throwing PendingError
|
|
122
|
+
throw new PendingError();
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
} catch (error) {
|
|
126
|
+
// Re-throw internal errors that should be handled by async-retry
|
|
127
|
+
if (error instanceof PendingError) {
|
|
128
|
+
throw error;
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
// Polling function execution failed (network error, etc.)
|
|
132
|
+
consecutiveFailures++;
|
|
133
|
+
|
|
134
|
+
logger?.error?.(
|
|
135
|
+
`Failed to execute polling function (attempt ${attemptNumber}/${maxRetries === Infinity ? '∞' : maxRetries}, consecutive failures: ${consecutiveFailures}/${maxConsecutiveFailures}):`,
|
|
136
|
+
error,
|
|
106
137
|
);
|
|
107
|
-
}
|
|
108
|
-
}
|
|
109
|
-
|
|
110
|
-
// Wait before retry and continue to next loop iteration
|
|
111
|
-
if (retries < maxRetries - 1) {
|
|
112
|
-
const currentInterval = Math.min(
|
|
113
|
-
initialInterval * Math.pow(backoffMultiplier, retries),
|
|
114
|
-
maxInterval,
|
|
115
|
-
);
|
|
116
|
-
|
|
117
|
-
logger?.debug?.(`Waiting ${currentInterval}ms before next retry`);
|
|
118
138
|
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
}
|
|
139
|
+
// Handle custom error processing if provided
|
|
140
|
+
if (onPollingError) {
|
|
141
|
+
const errorResult = onPollingError({
|
|
142
|
+
consecutiveFailures,
|
|
143
|
+
error,
|
|
144
|
+
retries,
|
|
145
|
+
});
|
|
146
|
+
|
|
147
|
+
if (!errorResult.isContinuePolling) {
|
|
148
|
+
// Custom error handler decided to stop polling
|
|
149
|
+
bail(errorResult.error || (error as Error));
|
|
150
|
+
return undefined as R;
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
// Custom error handler decided to continue polling
|
|
154
|
+
logger?.debug?.('Custom error handler decided to continue polling');
|
|
155
|
+
throw error; // Rethrow to trigger retry
|
|
156
|
+
} else {
|
|
157
|
+
// Default behavior: check if maximum consecutive failures reached
|
|
158
|
+
if (consecutiveFailures >= maxConsecutiveFailures) {
|
|
159
|
+
bail(
|
|
160
|
+
new Error(
|
|
161
|
+
`Failed to execute polling function after ${consecutiveFailures} consecutive attempts: ${error}`,
|
|
162
|
+
),
|
|
163
|
+
);
|
|
164
|
+
return undefined as R;
|
|
165
|
+
}
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
// Rethrow to trigger retry
|
|
169
|
+
throw error;
|
|
170
|
+
}
|
|
171
|
+
},
|
|
172
|
+
{
|
|
173
|
+
factor: backoffMultiplier,
|
|
174
|
+
maxTimeout: maxInterval,
|
|
175
|
+
minTimeout: initialInterval,
|
|
176
|
+
onRetry: (error, attempt) => {
|
|
177
|
+
if (!(error instanceof PendingError)) {
|
|
178
|
+
logger?.debug?.(`Retrying after error (attempt ${attempt})`);
|
|
179
|
+
}
|
|
180
|
+
},
|
|
181
|
+
randomize: false, // Disable jitter for predictable intervals
|
|
182
|
+
retries: retriesConfig,
|
|
183
|
+
},
|
|
184
|
+
);
|
|
185
|
+
} catch (error) {
|
|
186
|
+
// Handle TaskFailedError by throwing the original error
|
|
187
|
+
if (error instanceof TaskFailedError) {
|
|
188
|
+
throw error.originalError;
|
|
152
189
|
}
|
|
153
190
|
|
|
154
|
-
//
|
|
155
|
-
if (
|
|
156
|
-
|
|
157
|
-
const currentInterval = Math.min(
|
|
158
|
-
initialInterval * Math.pow(backoffMultiplier, retries),
|
|
159
|
-
maxInterval,
|
|
160
|
-
);
|
|
161
|
-
|
|
162
|
-
logger?.debug?.(`Waiting ${currentInterval}ms before next retry`);
|
|
163
|
-
|
|
164
|
-
// Wait for retry interval
|
|
165
|
-
await new Promise((resolve) => {
|
|
166
|
-
setTimeout(resolve, currentInterval);
|
|
167
|
-
});
|
|
191
|
+
// Handle max retries exceeded
|
|
192
|
+
if (error instanceof PendingError) {
|
|
193
|
+
throw new Error(`Task timeout after ${maxRetries} attempts`);
|
|
168
194
|
}
|
|
169
195
|
|
|
170
|
-
|
|
196
|
+
throw error;
|
|
171
197
|
}
|
|
172
|
-
|
|
173
|
-
// Maximum retries reached
|
|
174
|
-
throw new Error(`Task timeout after ${maxRetries} attempts`);
|
|
175
198
|
}
|
|
@@ -24,9 +24,21 @@ export interface GroupedTopic {
|
|
|
24
24
|
title?: string;
|
|
25
25
|
}
|
|
26
26
|
|
|
27
|
+
export interface TopicUserMemoryExtractRunState {
|
|
28
|
+
error?: string;
|
|
29
|
+
lastConversationDigest?: string;
|
|
30
|
+
lastMessageAt?: string;
|
|
31
|
+
lastRunAt?: string;
|
|
32
|
+
messageCount?: number;
|
|
33
|
+
processedMemoryCount?: number;
|
|
34
|
+
version?: string;
|
|
35
|
+
}
|
|
36
|
+
|
|
27
37
|
export interface ChatTopicMetadata {
|
|
28
38
|
model?: string;
|
|
29
39
|
provider?: string;
|
|
40
|
+
userMemoryExtractRunState?: TopicUserMemoryExtractRunState;
|
|
41
|
+
userMemoryExtractStatus?: 'pending' | 'completed' | 'failed';
|
|
30
42
|
}
|
|
31
43
|
|
|
32
44
|
export interface ChatTopicSummary {
|
|
@@ -5,6 +5,13 @@ import { account } from '@/database/schemas/betterAuth';
|
|
|
5
5
|
import { users } from '@/database/schemas/user';
|
|
6
6
|
import { serverDB } from '@/database/server';
|
|
7
7
|
|
|
8
|
+
export interface CheckUserResponseData {
|
|
9
|
+
emailVerified?: boolean;
|
|
10
|
+
exists: boolean;
|
|
11
|
+
hasPassword?: boolean;
|
|
12
|
+
providers?: string[];
|
|
13
|
+
}
|
|
14
|
+
|
|
8
15
|
/**
|
|
9
16
|
* Check if a user exists by email
|
|
10
17
|
* @param req - POST request with { email: string }
|
|
@@ -52,7 +59,7 @@ export async function POST(req: NextRequest) {
|
|
|
52
59
|
exists: true,
|
|
53
60
|
hasPassword,
|
|
54
61
|
providers,
|
|
55
|
-
});
|
|
62
|
+
} satisfies CheckUserResponseData);
|
|
56
63
|
} catch (error) {
|
|
57
64
|
console.error('Error checking user existence:', error);
|
|
58
65
|
return NextResponse.json({ error: 'Internal server error', exists: false }, { status: 500 });
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
import { ActionIcon, Button } from '@lobehub/ui';
|
|
4
4
|
import { LobeHub } from '@lobehub/ui/brand';
|
|
5
|
-
import { Form, Input, type InputRef } from 'antd';
|
|
5
|
+
import { Form, Input, type InputRef, Skeleton } from 'antd';
|
|
6
6
|
import { createStyles, useTheme } from 'antd-style';
|
|
7
7
|
import { ChevronLeft, ChevronRight, Lock, Mail } from 'lucide-react';
|
|
8
8
|
import { useRouter, useSearchParams } from 'next/navigation';
|
|
@@ -10,12 +10,13 @@ import { useEffect, useRef, useState } from 'react';
|
|
|
10
10
|
import { useTranslation } from 'react-i18next';
|
|
11
11
|
import { Flexbox } from 'react-layout-kit';
|
|
12
12
|
|
|
13
|
+
import type { CheckUserResponseData } from '@/app/(backend)/api/auth/check-user/route';
|
|
13
14
|
import { message } from '@/components/AntdStaticMethods';
|
|
14
15
|
import AuthIcons from '@/components/NextAuth/AuthIcons';
|
|
15
16
|
import { getAuthConfig } from '@/envs/auth';
|
|
16
17
|
import { requestPasswordReset, signIn } from '@/libs/better-auth/auth-client';
|
|
17
18
|
import { isBuiltinProvider, normalizeProviderId } from '@/libs/better-auth/utils/client';
|
|
18
|
-
import {
|
|
19
|
+
import { useServerConfigStore } from '@/store/serverConfig';
|
|
19
20
|
|
|
20
21
|
const useStyles = createStyles(({ css, token }) => ({
|
|
21
22
|
backButton: css`
|
|
@@ -97,7 +98,8 @@ export default function SignInPage() {
|
|
|
97
98
|
const [email, setEmail] = useState('');
|
|
98
99
|
const emailInputRef = useRef<InputRef>(null);
|
|
99
100
|
const passwordInputRef = useRef<InputRef>(null);
|
|
100
|
-
const
|
|
101
|
+
const serverConfigInit = useServerConfigStore((s) => s.serverConfigInit);
|
|
102
|
+
const oAuthSSOProviders = useServerConfigStore((s) => s.serverConfig.oAuthSSOProviders) || [];
|
|
101
103
|
|
|
102
104
|
// Auto-focus input when step changes
|
|
103
105
|
useEffect(() => {
|
|
@@ -162,7 +164,7 @@ export default function SignInPage() {
|
|
|
162
164
|
method: 'POST',
|
|
163
165
|
});
|
|
164
166
|
|
|
165
|
-
const data = await response.json();
|
|
167
|
+
const data: CheckUserResponseData = await response.json();
|
|
166
168
|
|
|
167
169
|
if (!data.exists) {
|
|
168
170
|
// User not found, redirect to signup page with email pre-filled
|
|
@@ -172,16 +174,15 @@ export default function SignInPage() {
|
|
|
172
174
|
);
|
|
173
175
|
return;
|
|
174
176
|
}
|
|
175
|
-
|
|
176
177
|
setEmail(values.email);
|
|
177
178
|
|
|
178
|
-
if (
|
|
179
|
-
|
|
179
|
+
if (data.hasPassword) {
|
|
180
|
+
setStep('password');
|
|
180
181
|
return;
|
|
181
182
|
}
|
|
182
183
|
|
|
183
|
-
if (
|
|
184
|
-
|
|
184
|
+
if (enableMagicLink) {
|
|
185
|
+
await handleSendMagicLink(values.email);
|
|
185
186
|
return;
|
|
186
187
|
}
|
|
187
188
|
|
|
@@ -303,8 +304,20 @@ export default function SignInPage() {
|
|
|
303
304
|
<>
|
|
304
305
|
<p className={styles.subtitle}>{t('betterAuth.signin.emailStep.subtitle')}</p>
|
|
305
306
|
|
|
307
|
+
{/* Social Login Section Skeleton */}
|
|
308
|
+
{!serverConfigInit && (
|
|
309
|
+
<Flexbox gap={12} style={{ marginTop: '2rem' }}>
|
|
310
|
+
<Skeleton.Button active block size="large" />
|
|
311
|
+
<Flexbox align="center" gap={12} horizontal>
|
|
312
|
+
<div className={styles.divider} />
|
|
313
|
+
<Skeleton.Input active size="small" style={{ minWidth: 80, width: 80 }} />
|
|
314
|
+
<div className={styles.divider} />
|
|
315
|
+
</Flexbox>
|
|
316
|
+
</Flexbox>
|
|
317
|
+
)}
|
|
318
|
+
|
|
306
319
|
{/* Social Login Section */}
|
|
307
|
-
{oAuthSSOProviders.length > 0 && (
|
|
320
|
+
{serverConfigInit && oAuthSSOProviders.length > 0 && (
|
|
308
321
|
<Flexbox gap={12} style={{ marginTop: '2rem' }}>
|
|
309
322
|
{oAuthSSOProviders.map((provider) => (
|
|
310
323
|
<Button
|
package/src/auth.ts
CHANGED
|
@@ -145,7 +145,8 @@ export const auth = betterAuth({
|
|
|
145
145
|
generateId: ({ model }) => {
|
|
146
146
|
// Better Auth passes the model name; handle both singular and plural for safety.
|
|
147
147
|
if (model === 'user' || model === 'users') {
|
|
148
|
-
|
|
148
|
+
// clerk id length is 32
|
|
149
|
+
return idGenerator('user', 32 - 'user_'.length);
|
|
149
150
|
}
|
|
150
151
|
|
|
151
152
|
// Other models: use shared nanoid generator (12 chars) to keep consistency.
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { SiApple } from '@icons-pack/react-simple-icons';
|
|
1
2
|
import { Aws, Google, Microsoft } from '@lobehub/icons';
|
|
2
3
|
import {
|
|
3
4
|
Auth0,
|
|
@@ -11,11 +12,10 @@ import {
|
|
|
11
12
|
NextAuth,
|
|
12
13
|
Zitadel,
|
|
13
14
|
} from '@lobehub/ui/icons';
|
|
14
|
-
import { Apple } from 'lucide-react';
|
|
15
15
|
import React from 'react';
|
|
16
16
|
|
|
17
17
|
const iconComponents: { [key: string]: React.ElementType } = {
|
|
18
|
-
'apple':
|
|
18
|
+
'apple': SiApple,
|
|
19
19
|
'auth0': Auth0,
|
|
20
20
|
'authelia': Authelia.Color,
|
|
21
21
|
'authentik': Authentik.Color,
|
|
@@ -324,12 +324,13 @@ describe('MCPService', () => {
|
|
|
324
324
|
expect(result).toHaveLength(1);
|
|
325
325
|
});
|
|
326
326
|
|
|
327
|
-
it('should throw
|
|
327
|
+
it('should throw original error when NoValidSessionId retry exceeds limit', async () => {
|
|
328
328
|
// Fail more than 3 times
|
|
329
329
|
mockClient.listTools.mockRejectedValue(new Error('NoValidSessionId'));
|
|
330
330
|
|
|
331
|
-
await expect(mcpService.listTools(mockParams)).rejects.toThrow(
|
|
332
|
-
|
|
331
|
+
await expect(mcpService.listTools(mockParams)).rejects.toThrow('NoValidSessionId');
|
|
332
|
+
// async-retry: 1 initial + 3 retries = 4 attempts
|
|
333
|
+
expect(mockClient.listTools).toHaveBeenCalledTimes(4);
|
|
333
334
|
});
|
|
334
335
|
|
|
335
336
|
it('should throw TRPCError on other errors without retry', async () => {
|
|
@@ -340,23 +341,6 @@ describe('MCPService', () => {
|
|
|
340
341
|
expect(mockClient.listTools).toHaveBeenCalledTimes(1);
|
|
341
342
|
});
|
|
342
343
|
|
|
343
|
-
it('should pass skipCache option to getClient', async () => {
|
|
344
|
-
const mockTools = [
|
|
345
|
-
{
|
|
346
|
-
name: 'tool1',
|
|
347
|
-
description: 'Test tool',
|
|
348
|
-
inputSchema: { type: 'object' },
|
|
349
|
-
},
|
|
350
|
-
];
|
|
351
|
-
|
|
352
|
-
mockClient.listTools.mockResolvedValue(mockTools);
|
|
353
|
-
|
|
354
|
-
await mcpService.listTools(mockParams, { skipCache: true });
|
|
355
|
-
|
|
356
|
-
// Verify getClient was called with skipCache
|
|
357
|
-
expect(mcpService.getClient).toHaveBeenCalledWith(mockParams, true);
|
|
358
|
-
});
|
|
359
|
-
|
|
360
344
|
it('should throw TRPCError with correct error message', async () => {
|
|
361
345
|
const error = new Error('Custom error message');
|
|
362
346
|
mockClient.listTools.mockRejectedValue(error);
|