@lobehub/lobehub 2.0.0-next.327 → 2.0.0-next.329
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/.env.example +0 -3
- package/.env.example.development +0 -3
- package/CHANGELOG.md +58 -0
- package/Dockerfile +1 -2
- package/changelog/v1.json +18 -0
- package/docs/self-hosting/advanced/auth.mdx +5 -6
- package/docs/self-hosting/advanced/auth.zh-CN.mdx +5 -6
- package/docs/self-hosting/environment-variables/auth.mdx +0 -7
- package/docs/self-hosting/environment-variables/auth.zh-CN.mdx +0 -7
- package/locales/en-US/chat.json +6 -1
- package/locales/en-US/discover.json +1 -0
- package/locales/zh-CN/chat.json +5 -0
- package/locales/zh-CN/discover.json +1 -0
- package/package.json +1 -1
- package/packages/agent-runtime/src/agents/GeneralChatAgent.ts +24 -0
- package/packages/agent-runtime/src/agents/__tests__/GeneralChatAgent.test.ts +210 -0
- package/packages/agent-runtime/src/types/instruction.ts +46 -2
- package/packages/builtin-tool-gtd/src/const.ts +1 -0
- package/packages/builtin-tool-gtd/src/executor/index.ts +38 -21
- package/packages/builtin-tool-gtd/src/manifest.ts +15 -0
- package/packages/builtin-tool-gtd/src/systemRole.ts +33 -1
- package/packages/builtin-tool-gtd/src/types.ts +55 -33
- package/packages/builtin-tool-local-system/src/client/Inspector/ReadLocalFile/index.tsx +1 -0
- package/packages/builtin-tool-local-system/src/client/Inspector/RunCommand/index.tsx +1 -1
- package/packages/builtin-tool-local-system/src/client/Render/WriteFile/index.tsx +1 -1
- package/packages/builtin-tool-local-system/src/client/Streaming/WriteFile/index.tsx +5 -1
- package/packages/builtin-tool-notebook/src/systemRole.ts +27 -7
- package/packages/conversation-flow/src/transformation/FlatListBuilder.ts +13 -1
- package/packages/conversation-flow/src/transformation/__tests__/FlatListBuilder.test.ts +40 -0
- package/packages/database/src/models/__tests__/messages/message.thread-query.test.ts +134 -1
- package/packages/database/src/models/message.ts +8 -1
- package/packages/database/src/models/thread.ts +1 -1
- package/packages/types/src/message/ui/chat.ts +2 -0
- package/packages/types/src/topic/thread.ts +20 -0
- package/scripts/prebuild.mts +2 -2
- package/src/app/[variants]/(main)/community/(list)/agent/features/List/Item.tsx +1 -0
- package/src/components/StreamingMarkdown/index.tsx +10 -43
- package/src/envs/__tests__/app.test.ts +81 -0
- package/src/envs/app.ts +14 -2
- package/src/envs/auth.test.ts +0 -13
- package/src/envs/auth.ts +0 -41
- package/src/features/Conversation/Messages/AssistantGroup/components/MessageContent.tsx +0 -2
- package/src/features/Conversation/Messages/Task/ClientTaskDetail/CompletedState.tsx +108 -0
- package/src/features/Conversation/Messages/Task/ClientTaskDetail/InitializingState.tsx +66 -0
- package/src/features/Conversation/Messages/Task/ClientTaskDetail/InstructionAccordion.tsx +63 -0
- package/src/features/Conversation/Messages/Task/ClientTaskDetail/ProcessingState.tsx +123 -0
- package/src/features/Conversation/Messages/Task/ClientTaskDetail/index.tsx +106 -0
- package/src/features/Conversation/Messages/Task/TaskDetailPanel/index.tsx +1 -0
- package/src/features/Conversation/Messages/Task/index.tsx +11 -6
- package/src/features/Conversation/Messages/Tasks/TaskItem/TaskTitle.tsx +3 -2
- package/src/features/Conversation/Messages/Tasks/shared/InitializingState.tsx +0 -4
- package/src/features/Conversation/Messages/Tasks/shared/utils.ts +22 -1
- package/src/features/Conversation/Messages/components/ContentLoading.tsx +1 -1
- package/src/features/Conversation/components/Thinking/index.tsx +9 -30
- package/src/features/Conversation/store/slices/data/action.ts +2 -3
- package/src/features/NavPanel/components/BackButton.tsx +10 -13
- package/src/features/NavPanel/components/NavPanelDraggable.tsx +4 -0
- package/src/hooks/useAutoScroll.ts +117 -0
- package/src/libs/better-auth/auth-client.ts +0 -9
- package/src/libs/better-auth/define-config.ts +13 -12
- package/src/libs/better-auth/sso/index.ts +2 -1
- package/src/libs/better-auth/utils/config.ts +2 -2
- package/src/libs/next/proxy/define-config.ts +4 -6
- package/src/locales/default/chat.ts +6 -1
- package/src/locales/default/discover.ts +2 -0
- package/src/server/routers/lambda/__tests__/integration/topic.integration.test.ts +74 -0
- package/src/server/routers/lambda/aiAgent.ts +239 -1
- package/src/server/routers/lambda/thread.ts +2 -0
- package/src/server/routers/lambda/topic.ts +6 -0
- package/src/server/services/agentRuntime/AgentRuntimeService.test.ts +4 -1
- package/src/server/services/agentRuntime/AgentRuntimeService.ts +2 -1
- package/src/server/services/message/__tests__/index.test.ts +37 -0
- package/src/server/services/message/index.ts +6 -1
- package/src/services/aiAgent.ts +51 -0
- package/src/services/topic/index.ts +4 -0
- package/src/store/chat/agents/createAgentExecutors.ts +714 -12
- package/src/store/chat/slices/aiChat/actions/streamingExecutor.ts +6 -1
- package/src/store/chat/slices/message/actions/query.ts +33 -1
- package/src/store/chat/slices/message/selectors/displayMessage.test.ts +10 -0
- package/src/store/chat/slices/message/selectors/displayMessage.ts +1 -0
- package/src/store/chat/slices/operation/types.ts +4 -0
- package/src/store/chat/slices/topic/action.test.ts +2 -1
- package/src/store/chat/slices/topic/action.ts +1 -1
|
@@ -32,7 +32,7 @@ export class ThreadModel {
|
|
|
32
32
|
// @ts-ignore
|
|
33
33
|
const [result] = await this.db
|
|
34
34
|
.insert(threads)
|
|
35
|
-
.values({
|
|
35
|
+
.values({ status: ThreadStatus.Active, ...params, userId: this.userId })
|
|
36
36
|
.onConflictDoNothing()
|
|
37
37
|
.returning();
|
|
38
38
|
|
|
@@ -64,6 +64,8 @@ interface UIMessageBranch {
|
|
|
64
64
|
* Retrieved from the associated Thread via sourceMessageId
|
|
65
65
|
*/
|
|
66
66
|
export interface TaskDetail {
|
|
67
|
+
/** Whether this task runs in client mode (local execution) */
|
|
68
|
+
clientMode?: boolean;
|
|
67
69
|
/** Task completion time (ISO string) */
|
|
68
70
|
completedAt?: string;
|
|
69
71
|
/** Execution duration in milliseconds */
|
|
@@ -23,6 +23,8 @@ export enum ThreadStatus {
|
|
|
23
23
|
* Metadata for Thread, used for agent task execution
|
|
24
24
|
*/
|
|
25
25
|
export interface ThreadMetadata {
|
|
26
|
+
/** Whether this thread runs in client mode (local execution) */
|
|
27
|
+
clientMode?: boolean;
|
|
26
28
|
/** Task completion time */
|
|
27
29
|
completedAt?: string;
|
|
28
30
|
/** Execution duration in milliseconds */
|
|
@@ -68,16 +70,34 @@ export interface CreateThreadParams {
|
|
|
68
70
|
agentId?: string;
|
|
69
71
|
/** Group ID for group chat context */
|
|
70
72
|
groupId?: string;
|
|
73
|
+
/** Initial metadata for the thread */
|
|
74
|
+
metadata?: ThreadMetadata;
|
|
71
75
|
parentThreadId?: string;
|
|
72
76
|
sourceMessageId?: string;
|
|
77
|
+
/** Initial status (defaults to Active) */
|
|
78
|
+
status?: ThreadStatus;
|
|
73
79
|
title?: string;
|
|
74
80
|
topicId: string;
|
|
75
81
|
type: IThreadType;
|
|
76
82
|
}
|
|
77
83
|
|
|
84
|
+
export const threadMetadataSchema = z.object({
|
|
85
|
+
clientMode: z.boolean().optional(),
|
|
86
|
+
completedAt: z.string().optional(),
|
|
87
|
+
duration: z.number().optional(),
|
|
88
|
+
error: z.any().optional(),
|
|
89
|
+
operationId: z.string().optional(),
|
|
90
|
+
startedAt: z.string().optional(),
|
|
91
|
+
totalCost: z.number().optional(),
|
|
92
|
+
totalMessages: z.number().optional(),
|
|
93
|
+
totalTokens: z.number().optional(),
|
|
94
|
+
totalToolCalls: z.number().optional(),
|
|
95
|
+
});
|
|
96
|
+
|
|
78
97
|
export const createThreadSchema = z.object({
|
|
79
98
|
agentId: z.string().optional(),
|
|
80
99
|
groupId: z.string().optional(),
|
|
100
|
+
metadata: threadMetadataSchema.optional(),
|
|
81
101
|
parentThreadId: z.string().optional(),
|
|
82
102
|
sourceMessageId: z.string().optional(),
|
|
83
103
|
title: z.string().optional(),
|
package/scripts/prebuild.mts
CHANGED
|
@@ -51,10 +51,10 @@ const printEnvInfo = () => {
|
|
|
51
51
|
|
|
52
52
|
// Auth-related env vars
|
|
53
53
|
console.log('\n Auth Environment Variables:');
|
|
54
|
-
console.log(` NEXT_PUBLIC_AUTH_URL: ${process.env.NEXT_PUBLIC_AUTH_URL ?? '(not set)'}`);
|
|
55
|
-
console.log(` NEXTAUTH_URL: ${process.env.NEXTAUTH_URL ?? '(not set)'}`);
|
|
56
54
|
console.log(` APP_URL: ${process.env.APP_URL ?? '(not set)'}`);
|
|
57
55
|
console.log(` VERCEL_URL: ${process.env.VERCEL_URL ?? '(not set)'}`);
|
|
56
|
+
console.log(` VERCEL_BRANCH_URL: ${process.env.VERCEL_BRANCH_URL ?? '(not set)'}`);
|
|
57
|
+
console.log(` VERCEL_PROJECT_PRODUCTION_URL: ${process.env.VERCEL_PROJECT_PRODUCTION_URL ?? '(not set)'}`);
|
|
58
58
|
console.log(` AUTH_EMAIL_VERIFICATION: ${process.env.AUTH_EMAIL_VERIFICATION ?? '(not set)'}`);
|
|
59
59
|
console.log(` ENABLE_MAGIC_LINK: ${process.env.ENABLE_MAGIC_LINK ?? '(not set)'}`);
|
|
60
60
|
console.log(` AUTH_SECRET: ${process.env.AUTH_SECRET ? '✓ set' : '✗ not set'}`);
|
|
@@ -2,7 +2,9 @@
|
|
|
2
2
|
|
|
3
3
|
import { Markdown, ScrollShadow } from '@lobehub/ui';
|
|
4
4
|
import { createStaticStyles } from 'antd-style';
|
|
5
|
-
import {
|
|
5
|
+
import { type RefObject, memo, useEffect } from 'react';
|
|
6
|
+
|
|
7
|
+
import { useAutoScroll } from '@/hooks/useAutoScroll';
|
|
6
8
|
|
|
7
9
|
const styles = createStaticStyles(({ css }) => ({
|
|
8
10
|
container: css`
|
|
@@ -19,51 +21,16 @@ interface StreamingMarkdownProps {
|
|
|
19
21
|
}
|
|
20
22
|
|
|
21
23
|
const StreamingMarkdown = memo<StreamingMarkdownProps>(({ children, maxHeight = 400 }) => {
|
|
22
|
-
const
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
// Handle user scroll detection
|
|
27
|
-
const handleScroll = useCallback(() => {
|
|
28
|
-
// Ignore scroll events triggered by auto-scroll
|
|
29
|
-
if (isAutoScrollingRef.current) return;
|
|
30
|
-
|
|
31
|
-
const container = containerRef.current;
|
|
32
|
-
if (!container) return;
|
|
33
|
-
|
|
34
|
-
// Check if user scrolled away from bottom
|
|
35
|
-
const distanceToBottom = container.scrollHeight - container.scrollTop - container.clientHeight;
|
|
36
|
-
const isAtBottom = distanceToBottom < 20;
|
|
37
|
-
|
|
38
|
-
// If user scrolled up, stop auto-scrolling
|
|
39
|
-
if (!isAtBottom) {
|
|
40
|
-
setUserHasScrolled(true);
|
|
41
|
-
}
|
|
42
|
-
}, []);
|
|
43
|
-
|
|
44
|
-
// Auto scroll to bottom when content changes (unless user has scrolled)
|
|
45
|
-
useEffect(() => {
|
|
46
|
-
if (userHasScrolled) return;
|
|
47
|
-
|
|
48
|
-
const container = containerRef.current;
|
|
49
|
-
if (!container) return;
|
|
50
|
-
|
|
51
|
-
isAutoScrollingRef.current = true;
|
|
52
|
-
requestAnimationFrame(() => {
|
|
53
|
-
container.scrollTop = container.scrollHeight;
|
|
54
|
-
// Reset the flag after scroll completes
|
|
55
|
-
requestAnimationFrame(() => {
|
|
56
|
-
isAutoScrollingRef.current = false;
|
|
57
|
-
});
|
|
58
|
-
});
|
|
59
|
-
}, [children, userHasScrolled]);
|
|
24
|
+
const { ref, handleScroll, resetScrollLock } = useAutoScroll<HTMLDivElement>({
|
|
25
|
+
deps: [children],
|
|
26
|
+
});
|
|
60
27
|
|
|
61
|
-
// Reset
|
|
28
|
+
// Reset scroll lock when content is cleared (new stream starts)
|
|
62
29
|
useEffect(() => {
|
|
63
30
|
if (!children) {
|
|
64
|
-
|
|
31
|
+
resetScrollLock();
|
|
65
32
|
}
|
|
66
|
-
}, [children]);
|
|
33
|
+
}, [children, resetScrollLock]);
|
|
67
34
|
|
|
68
35
|
if (!children) return null;
|
|
69
36
|
|
|
@@ -72,7 +39,7 @@ const StreamingMarkdown = memo<StreamingMarkdownProps>(({ children, maxHeight =
|
|
|
72
39
|
className={styles.container}
|
|
73
40
|
offset={12}
|
|
74
41
|
onScroll={handleScroll}
|
|
75
|
-
ref={
|
|
42
|
+
ref={ref as RefObject<HTMLDivElement>}
|
|
76
43
|
size={12}
|
|
77
44
|
style={{ maxHeight }}
|
|
78
45
|
>
|
|
@@ -82,3 +82,84 @@ describe('getServerConfig', () => {
|
|
|
82
82
|
});
|
|
83
83
|
});
|
|
84
84
|
});
|
|
85
|
+
|
|
86
|
+
describe('APP_URL fallback', () => {
|
|
87
|
+
beforeEach(() => {
|
|
88
|
+
vi.resetModules();
|
|
89
|
+
// Clean up all related env vars
|
|
90
|
+
delete process.env.APP_URL;
|
|
91
|
+
delete process.env.VERCEL;
|
|
92
|
+
delete process.env.VERCEL_ENV;
|
|
93
|
+
delete process.env.VERCEL_URL;
|
|
94
|
+
delete process.env.VERCEL_BRANCH_URL;
|
|
95
|
+
delete process.env.VERCEL_PROJECT_PRODUCTION_URL;
|
|
96
|
+
});
|
|
97
|
+
|
|
98
|
+
it('should use APP_URL when explicitly set', async () => {
|
|
99
|
+
process.env.APP_URL = 'https://custom-app.com';
|
|
100
|
+
process.env.VERCEL = '1';
|
|
101
|
+
|
|
102
|
+
const { getAppConfig } = await import('../app');
|
|
103
|
+
const config = getAppConfig();
|
|
104
|
+
expect(config.APP_URL).toBe('https://custom-app.com');
|
|
105
|
+
});
|
|
106
|
+
|
|
107
|
+
describe('Vercel environment', () => {
|
|
108
|
+
it('should use VERCEL_PROJECT_PRODUCTION_URL in production', async () => {
|
|
109
|
+
process.env.VERCEL = '1';
|
|
110
|
+
process.env.VERCEL_ENV = 'production';
|
|
111
|
+
process.env.VERCEL_PROJECT_PRODUCTION_URL = 'lobechat.vercel.app';
|
|
112
|
+
process.env.VERCEL_BRANCH_URL = 'lobechat-git-main-org.vercel.app';
|
|
113
|
+
process.env.VERCEL_URL = 'lobechat-abc123.vercel.app';
|
|
114
|
+
|
|
115
|
+
const { getAppConfig } = await import('../app');
|
|
116
|
+
const config = getAppConfig();
|
|
117
|
+
expect(config.APP_URL).toBe('https://lobechat.vercel.app');
|
|
118
|
+
});
|
|
119
|
+
|
|
120
|
+
it('should use VERCEL_BRANCH_URL in preview environment', async () => {
|
|
121
|
+
process.env.VERCEL = '1';
|
|
122
|
+
process.env.VERCEL_ENV = 'preview';
|
|
123
|
+
process.env.VERCEL_BRANCH_URL = 'lobechat-git-feature-org.vercel.app';
|
|
124
|
+
process.env.VERCEL_URL = 'lobechat-abc123.vercel.app';
|
|
125
|
+
|
|
126
|
+
const { getAppConfig } = await import('../app');
|
|
127
|
+
const config = getAppConfig();
|
|
128
|
+
expect(config.APP_URL).toBe('https://lobechat-git-feature-org.vercel.app');
|
|
129
|
+
});
|
|
130
|
+
|
|
131
|
+
it('should fallback to VERCEL_URL when VERCEL_BRANCH_URL is not set', async () => {
|
|
132
|
+
process.env.VERCEL = '1';
|
|
133
|
+
process.env.VERCEL_ENV = 'preview';
|
|
134
|
+
process.env.VERCEL_URL = 'lobechat-abc123.vercel.app';
|
|
135
|
+
|
|
136
|
+
const { getAppConfig } = await import('../app');
|
|
137
|
+
const config = getAppConfig();
|
|
138
|
+
expect(config.APP_URL).toBe('https://lobechat-abc123.vercel.app');
|
|
139
|
+
});
|
|
140
|
+
});
|
|
141
|
+
|
|
142
|
+
describe('local environment', () => {
|
|
143
|
+
it('should use localhost:3010 in development', async () => {
|
|
144
|
+
|
|
145
|
+
vi.stubEnv('NODE_ENV', 'development');
|
|
146
|
+
|
|
147
|
+
const { getAppConfig } = await import('../app');
|
|
148
|
+
const config = getAppConfig();
|
|
149
|
+
expect(config.APP_URL).toBe('http://localhost:3010');
|
|
150
|
+
|
|
151
|
+
|
|
152
|
+
});
|
|
153
|
+
|
|
154
|
+
it('should use localhost:3210 in non-development', async () => {
|
|
155
|
+
|
|
156
|
+
vi.stubEnv('NODE_ENV', 'test');
|
|
157
|
+
|
|
158
|
+
const { getAppConfig } = await import('../app');
|
|
159
|
+
const config = getAppConfig();
|
|
160
|
+
expect(config.APP_URL).toBe('http://localhost:3210');
|
|
161
|
+
|
|
162
|
+
|
|
163
|
+
});
|
|
164
|
+
});
|
|
165
|
+
});
|
package/src/envs/app.ts
CHANGED
|
@@ -12,12 +12,24 @@ declare global {
|
|
|
12
12
|
}
|
|
13
13
|
const isInVercel = process.env.VERCEL === '1';
|
|
14
14
|
|
|
15
|
-
|
|
15
|
+
// Vercel URL fallback order (by stability):
|
|
16
|
+
// 1. VERCEL_PROJECT_PRODUCTION_URL - project level, most stable
|
|
17
|
+
// 2. VERCEL_BRANCH_URL - branch level, stable across deployments on same branch
|
|
18
|
+
// 3. VERCEL_URL - deployment level, changes every deployment
|
|
19
|
+
const getVercelUrl = () => {
|
|
20
|
+
if (process.env.VERCEL_ENV === 'production' && process.env.VERCEL_PROJECT_PRODUCTION_URL) {
|
|
21
|
+
return `https://${process.env.VERCEL_PROJECT_PRODUCTION_URL}`;
|
|
22
|
+
}
|
|
23
|
+
if (process.env.VERCEL_BRANCH_URL) {
|
|
24
|
+
return `https://${process.env.VERCEL_BRANCH_URL}`;
|
|
25
|
+
}
|
|
26
|
+
return `https://${process.env.VERCEL_URL}`;
|
|
27
|
+
};
|
|
16
28
|
|
|
17
29
|
const APP_URL = process.env.APP_URL
|
|
18
30
|
? process.env.APP_URL
|
|
19
31
|
: isInVercel
|
|
20
|
-
?
|
|
32
|
+
? getVercelUrl()
|
|
21
33
|
: process.env.NODE_ENV === 'development'
|
|
22
34
|
? 'http://localhost:3010'
|
|
23
35
|
: 'http://localhost:3210';
|
package/src/envs/auth.test.ts
CHANGED
|
@@ -44,17 +44,4 @@ describe('getAuthConfig fallbacks', () => {
|
|
|
44
44
|
|
|
45
45
|
expect(config.AUTH_SECRET).toBe('nextauth-secret');
|
|
46
46
|
});
|
|
47
|
-
|
|
48
|
-
it('should fall back to NEXTAUTH_URL origin when NEXT_PUBLIC_AUTH_URL is empty string', () => {
|
|
49
|
-
process.env.NEXT_PUBLIC_AUTH_URL = '';
|
|
50
|
-
process.env.NEXTAUTH_URL = 'https://example.com/api/auth';
|
|
51
|
-
|
|
52
|
-
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
|
53
|
-
// @ts-expect-error - allow overriding for test
|
|
54
|
-
globalThis.window = undefined;
|
|
55
|
-
|
|
56
|
-
const config = getAuthConfig();
|
|
57
|
-
|
|
58
|
-
expect(config.NEXT_PUBLIC_AUTH_URL).toBe('https://example.com');
|
|
59
|
-
});
|
|
60
47
|
});
|
package/src/envs/auth.ts
CHANGED
|
@@ -2,43 +2,6 @@
|
|
|
2
2
|
import { createEnv } from '@t3-oss/env-nextjs';
|
|
3
3
|
import { z } from 'zod';
|
|
4
4
|
|
|
5
|
-
/**
|
|
6
|
-
* Resolve public auth URL with compatibility fallbacks for NextAuth and Vercel deployments.
|
|
7
|
-
*/
|
|
8
|
-
const resolvePublicAuthUrl = () => {
|
|
9
|
-
if (process.env.NEXT_PUBLIC_AUTH_URL) return process.env.NEXT_PUBLIC_AUTH_URL;
|
|
10
|
-
|
|
11
|
-
if (process.env.NEXTAUTH_URL) {
|
|
12
|
-
try {
|
|
13
|
-
return new URL(process.env.NEXTAUTH_URL).origin;
|
|
14
|
-
} catch {
|
|
15
|
-
// ignore invalid NEXTAUTH_URL
|
|
16
|
-
}
|
|
17
|
-
}
|
|
18
|
-
|
|
19
|
-
if (process.env.APP_URL) {
|
|
20
|
-
try {
|
|
21
|
-
return new URL(process.env.APP_URL).origin;
|
|
22
|
-
} catch {
|
|
23
|
-
// ignore invalid APP_URL
|
|
24
|
-
}
|
|
25
|
-
}
|
|
26
|
-
|
|
27
|
-
if (process.env.VERCEL_URL) {
|
|
28
|
-
try {
|
|
29
|
-
const normalizedVercelUrl = process.env.VERCEL_URL.startsWith('http')
|
|
30
|
-
? process.env.VERCEL_URL
|
|
31
|
-
: `https://${process.env.VERCEL_URL}`;
|
|
32
|
-
|
|
33
|
-
return new URL(normalizedVercelUrl).origin;
|
|
34
|
-
} catch {
|
|
35
|
-
// ignore invalid Vercel URL
|
|
36
|
-
}
|
|
37
|
-
}
|
|
38
|
-
|
|
39
|
-
return undefined;
|
|
40
|
-
};
|
|
41
|
-
|
|
42
5
|
declare global {
|
|
43
6
|
// eslint-disable-next-line @typescript-eslint/no-namespace
|
|
44
7
|
namespace NodeJS {
|
|
@@ -50,7 +13,6 @@ declare global {
|
|
|
50
13
|
|
|
51
14
|
// ===== Auth (shared by Better Auth / Next Auth) ===== //
|
|
52
15
|
AUTH_SECRET?: string;
|
|
53
|
-
NEXT_PUBLIC_AUTH_URL?: string;
|
|
54
16
|
AUTH_EMAIL_VERIFICATION?: string;
|
|
55
17
|
ENABLE_MAGIC_LINK?: string;
|
|
56
18
|
AUTH_SSO_PROVIDERS?: string;
|
|
@@ -180,7 +142,6 @@ export const getAuthConfig = () => {
|
|
|
180
142
|
|
|
181
143
|
// ---------------------------------- better auth ----------------------------------
|
|
182
144
|
NEXT_PUBLIC_ENABLE_BETTER_AUTH: z.boolean().optional(),
|
|
183
|
-
NEXT_PUBLIC_AUTH_URL: z.string().optional(),
|
|
184
145
|
|
|
185
146
|
// ---------------------------------- next auth ----------------------------------
|
|
186
147
|
NEXT_PUBLIC_ENABLE_NEXT_AUTH: z.boolean().optional(),
|
|
@@ -310,8 +271,6 @@ export const getAuthConfig = () => {
|
|
|
310
271
|
|
|
311
272
|
// ---------------------------------- better auth ----------------------------------
|
|
312
273
|
NEXT_PUBLIC_ENABLE_BETTER_AUTH: process.env.NEXT_PUBLIC_ENABLE_BETTER_AUTH === '1',
|
|
313
|
-
// Fallback to NEXTAUTH_URL origin or Vercel deployment domain for seamless migration from next-auth
|
|
314
|
-
NEXT_PUBLIC_AUTH_URL: resolvePublicAuthUrl(),
|
|
315
274
|
// Fallback to NEXT_PUBLIC_* for seamless migration
|
|
316
275
|
AUTH_EMAIL_VERIFICATION:
|
|
317
276
|
process.env.AUTH_EMAIL_VERIFICATION === '1' ||
|
|
@@ -28,8 +28,6 @@ const MessageContent = memo<ContentBlockProps>(({ content, hasTools, id }) => {
|
|
|
28
28
|
if (!content && !hasTools) return <ContentLoading id={id} />;
|
|
29
29
|
|
|
30
30
|
if (content === LOADING_FLAT) {
|
|
31
|
-
if (hasTools) return null;
|
|
32
|
-
|
|
33
31
|
return <ContentLoading id={id} />;
|
|
34
32
|
}
|
|
35
33
|
|
|
@@ -0,0 +1,108 @@
|
|
|
1
|
+
'use client';
|
|
2
|
+
|
|
3
|
+
import { type AssistantContentBlock } from '@lobechat/types';
|
|
4
|
+
import { Accordion, AccordionItem, Block, Flexbox, Icon, Text } from '@lobehub/ui';
|
|
5
|
+
import { cssVar } from 'antd-style';
|
|
6
|
+
import { Workflow } from 'lucide-react';
|
|
7
|
+
import { memo, useMemo } from 'react';
|
|
8
|
+
import { useTranslation } from 'react-i18next';
|
|
9
|
+
|
|
10
|
+
import ContentBlock from '../../AssistantGroup/components/ContentBlock';
|
|
11
|
+
import { formatDuration } from '../../Tasks/shared/utils';
|
|
12
|
+
import Usage from '../../components/Extras/Usage';
|
|
13
|
+
|
|
14
|
+
interface CompletedStateProps {
|
|
15
|
+
assistantId: string;
|
|
16
|
+
blocks: AssistantContentBlock[];
|
|
17
|
+
duration?: number;
|
|
18
|
+
model?: string;
|
|
19
|
+
provider?: string;
|
|
20
|
+
totalCost?: number;
|
|
21
|
+
totalTokens?: number;
|
|
22
|
+
totalToolCalls?: number;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
const CompletedState = memo<CompletedStateProps>(
|
|
26
|
+
({ blocks, assistantId, duration, totalToolCalls, model, provider, totalTokens, totalCost }) => {
|
|
27
|
+
const { t } = useTranslation('chat');
|
|
28
|
+
|
|
29
|
+
// Split blocks: intermediate steps (all but last) and final result (last)
|
|
30
|
+
const { intermediateBlocks, finalBlock } = useMemo(() => {
|
|
31
|
+
if (blocks.length === 0) return { finalBlock: null, intermediateBlocks: [] };
|
|
32
|
+
if (blocks.length === 1) return { finalBlock: blocks[0], intermediateBlocks: [] };
|
|
33
|
+
|
|
34
|
+
return {
|
|
35
|
+
finalBlock: blocks.at(-1)!,
|
|
36
|
+
intermediateBlocks: blocks.slice(0, -1),
|
|
37
|
+
};
|
|
38
|
+
}, [blocks]);
|
|
39
|
+
|
|
40
|
+
if (!finalBlock) return null;
|
|
41
|
+
|
|
42
|
+
const title = (
|
|
43
|
+
<Flexbox align="center" gap={8} horizontal>
|
|
44
|
+
<Block
|
|
45
|
+
align="center"
|
|
46
|
+
flex="none"
|
|
47
|
+
gap={4}
|
|
48
|
+
height={24}
|
|
49
|
+
horizontal
|
|
50
|
+
justify="center"
|
|
51
|
+
style={{ fontSize: 12 }}
|
|
52
|
+
variant="outlined"
|
|
53
|
+
width={24}
|
|
54
|
+
>
|
|
55
|
+
<Icon color={cssVar.colorTextSecondary} icon={Workflow} />
|
|
56
|
+
</Block>
|
|
57
|
+
<Flexbox align="center" gap={4} horizontal>
|
|
58
|
+
<Text as="span" type="secondary" weight={500}>
|
|
59
|
+
{totalToolCalls}
|
|
60
|
+
</Text>
|
|
61
|
+
<Text as="span" type="secondary">
|
|
62
|
+
{t('task.metrics.toolCallsShort')}
|
|
63
|
+
</Text>
|
|
64
|
+
{/* Duration display */}
|
|
65
|
+
{duration && (
|
|
66
|
+
<Text as="span" type="secondary">
|
|
67
|
+
{t('task.metrics.duration', { duration: formatDuration(duration) })}
|
|
68
|
+
</Text>
|
|
69
|
+
)}
|
|
70
|
+
</Flexbox>
|
|
71
|
+
</Flexbox>
|
|
72
|
+
);
|
|
73
|
+
|
|
74
|
+
return (
|
|
75
|
+
<Flexbox gap={8}>
|
|
76
|
+
{/* Intermediate steps - collapsed by default */}
|
|
77
|
+
{intermediateBlocks.length > 0 && (
|
|
78
|
+
<Accordion defaultExpandedKeys={[]} gap={8}>
|
|
79
|
+
<AccordionItem itemKey="intermediate" paddingBlock={4} paddingInline={4} title={title}>
|
|
80
|
+
<Flexbox gap={8} paddingInline={4} style={{ marginTop: 8 }}>
|
|
81
|
+
{intermediateBlocks.map((block) => (
|
|
82
|
+
<ContentBlock
|
|
83
|
+
{...block}
|
|
84
|
+
assistantId={assistantId}
|
|
85
|
+
disableEditing
|
|
86
|
+
key={block.id}
|
|
87
|
+
/>
|
|
88
|
+
))}
|
|
89
|
+
</Flexbox>
|
|
90
|
+
</AccordionItem>
|
|
91
|
+
</Accordion>
|
|
92
|
+
)}
|
|
93
|
+
|
|
94
|
+
{/* Final result - always visible */}
|
|
95
|
+
<ContentBlock {...finalBlock} assistantId={assistantId} disableEditing />
|
|
96
|
+
|
|
97
|
+
{/* Usage display */}
|
|
98
|
+
{model && provider && (
|
|
99
|
+
<Usage model={model} provider={provider} usage={{ cost: totalCost, totalTokens }} />
|
|
100
|
+
)}
|
|
101
|
+
</Flexbox>
|
|
102
|
+
);
|
|
103
|
+
},
|
|
104
|
+
);
|
|
105
|
+
|
|
106
|
+
CompletedState.displayName = 'ClientCompletedState';
|
|
107
|
+
|
|
108
|
+
export default CompletedState;
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
'use client';
|
|
2
|
+
|
|
3
|
+
import { Flexbox, Text } from '@lobehub/ui';
|
|
4
|
+
import { createStaticStyles, keyframes } from 'antd-style';
|
|
5
|
+
import { memo } from 'react';
|
|
6
|
+
import { useTranslation } from 'react-i18next';
|
|
7
|
+
|
|
8
|
+
import NeuralNetworkLoading from '@/components/NeuralNetworkLoading';
|
|
9
|
+
import { shinyTextStyles } from '@/styles';
|
|
10
|
+
|
|
11
|
+
const shimmer = keyframes`
|
|
12
|
+
0% {
|
|
13
|
+
transform: translateX(-100%);
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
100% {
|
|
17
|
+
transform: translateX(100%);
|
|
18
|
+
}
|
|
19
|
+
`;
|
|
20
|
+
|
|
21
|
+
const styles = createStaticStyles(({ css, cssVar }) => ({
|
|
22
|
+
container: css`
|
|
23
|
+
padding-block: 12px;
|
|
24
|
+
`,
|
|
25
|
+
progress: css`
|
|
26
|
+
position: relative;
|
|
27
|
+
|
|
28
|
+
overflow: hidden;
|
|
29
|
+
|
|
30
|
+
height: 3px;
|
|
31
|
+
border-radius: 2px;
|
|
32
|
+
|
|
33
|
+
background: ${cssVar.colorFillSecondary};
|
|
34
|
+
`,
|
|
35
|
+
progressShimmer: css`
|
|
36
|
+
position: absolute;
|
|
37
|
+
inset-block-start: 0;
|
|
38
|
+
inset-inline-start: 0;
|
|
39
|
+
|
|
40
|
+
width: 100%;
|
|
41
|
+
height: 100%;
|
|
42
|
+
|
|
43
|
+
background: linear-gradient(90deg, transparent, ${cssVar.colorPrimaryBgHover}, transparent);
|
|
44
|
+
|
|
45
|
+
animation: ${shimmer} 2s infinite;
|
|
46
|
+
`,
|
|
47
|
+
}));
|
|
48
|
+
|
|
49
|
+
const InitializingState = memo(() => {
|
|
50
|
+
const { t } = useTranslation('chat');
|
|
51
|
+
|
|
52
|
+
return (
|
|
53
|
+
<Flexbox className={styles.container} gap={12}>
|
|
54
|
+
<Flexbox align="center" gap={8} horizontal>
|
|
55
|
+
<NeuralNetworkLoading size={14} />
|
|
56
|
+
<Text className={shinyTextStyles.shinyText} weight={500}>
|
|
57
|
+
{t('task.status.initializing')}
|
|
58
|
+
</Text>
|
|
59
|
+
</Flexbox>
|
|
60
|
+
</Flexbox>
|
|
61
|
+
);
|
|
62
|
+
});
|
|
63
|
+
|
|
64
|
+
InitializingState.displayName = 'InitializingState';
|
|
65
|
+
|
|
66
|
+
export default InitializingState;
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
import { Accordion, AccordionItem, Block, Flexbox, Icon, Markdown, Text } from '@lobehub/ui';
|
|
2
|
+
import { cssVar } from 'antd-style';
|
|
3
|
+
import { ScrollText } from 'lucide-react';
|
|
4
|
+
import { memo, useEffect, useState } from 'react';
|
|
5
|
+
import { useTranslation } from 'react-i18next';
|
|
6
|
+
|
|
7
|
+
interface InstructionAccordionProps {
|
|
8
|
+
childrenCount: number;
|
|
9
|
+
instruction: string;
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
const InstructionAccordion = memo<InstructionAccordionProps>(({ instruction, childrenCount }) => {
|
|
13
|
+
const { t } = useTranslation('chat');
|
|
14
|
+
|
|
15
|
+
// Auto-collapse instruction when children count exceeds threshold
|
|
16
|
+
const [expandedKeys, setExpandedKeys] = useState<string[]>(['instruction']);
|
|
17
|
+
|
|
18
|
+
useEffect(() => {
|
|
19
|
+
if (childrenCount > 1) {
|
|
20
|
+
setExpandedKeys([]);
|
|
21
|
+
}
|
|
22
|
+
}, [childrenCount > 1]);
|
|
23
|
+
|
|
24
|
+
return (
|
|
25
|
+
<Accordion
|
|
26
|
+
expandedKeys={expandedKeys}
|
|
27
|
+
gap={8}
|
|
28
|
+
onExpandedChange={(keys) => setExpandedKeys(keys as string[])}
|
|
29
|
+
>
|
|
30
|
+
<AccordionItem
|
|
31
|
+
itemKey="instruction"
|
|
32
|
+
paddingBlock={4}
|
|
33
|
+
paddingInline={4}
|
|
34
|
+
title={
|
|
35
|
+
<Flexbox align="center" gap={8} horizontal>
|
|
36
|
+
<Block
|
|
37
|
+
align="center"
|
|
38
|
+
flex="none"
|
|
39
|
+
gap={4}
|
|
40
|
+
height={24}
|
|
41
|
+
horizontal
|
|
42
|
+
justify="center"
|
|
43
|
+
style={{ fontSize: 12 }}
|
|
44
|
+
variant="outlined"
|
|
45
|
+
width={24}
|
|
46
|
+
>
|
|
47
|
+
<Icon color={cssVar.colorTextSecondary} icon={ScrollText} />
|
|
48
|
+
</Block>
|
|
49
|
+
<Text as="span" type="secondary">
|
|
50
|
+
{t('task.instruction')}
|
|
51
|
+
</Text>
|
|
52
|
+
</Flexbox>
|
|
53
|
+
}
|
|
54
|
+
>
|
|
55
|
+
<Block padding={12} style={{ marginBlock: 8, maxHeight: 300, overflow: 'auto' }} variant={'outlined'}>
|
|
56
|
+
<Markdown variant={'chat'}>{instruction}</Markdown>
|
|
57
|
+
</Block>
|
|
58
|
+
</AccordionItem>
|
|
59
|
+
</Accordion>
|
|
60
|
+
);
|
|
61
|
+
});
|
|
62
|
+
|
|
63
|
+
export default InstructionAccordion;
|