@lobehub/lobehub 2.0.0-next.217 → 2.0.0-next.218
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 +33 -0
- package/changelog/v1.json +12 -0
- package/package.json +1 -1
- package/packages/builtin-tool-cloud-sandbox/src/ExecutionRuntime/index.ts +18 -31
- package/packages/builtin-tool-cloud-sandbox/src/types.ts +3 -3
- package/src/app/[variants]/(main)/chat/profile/features/EditorCanvas/TypoBar.tsx +1 -11
- package/src/app/[variants]/(main)/group/profile/features/EditorCanvas/TypoBar.tsx +1 -11
- package/src/app/[variants]/(main)/memory/features/EditableModal/index.tsx +8 -101
- package/src/features/ChatInput/InputEditor/index.tsx +1 -0
- package/src/features/ChatInput/TypoBar/index.tsx +0 -11
- package/src/features/Conversation/ChatItem/components/MessageContent/index.tsx +11 -12
- package/src/features/EditorModal/EditorCanvas.tsx +81 -0
- package/src/features/EditorModal/TextareCanvas.tsx +28 -0
- package/src/{app/[variants]/(main)/memory/features/EditableModal → features/EditorModal}/Typobar.tsx +0 -11
- package/src/features/EditorModal/index.tsx +51 -0
- package/src/features/PageEditor/EditorCanvas/InlineToolbar.tsx +1 -17
- package/src/server/routers/tools/market.ts +118 -102
- package/src/server/services/discover/index.ts +10 -5
- package/src/services/codeInterpreter.ts +12 -20
- package/src/store/chat/slices/plugin/actions/pluginTypes.ts +13 -86
- package/src/features/Conversation/ChatItem/components/MessageContent/EditableModal.tsx +0 -119
- package/src/features/Conversation/ChatItem/components/MessageContent/Typobar.tsx +0 -150
|
@@ -1,14 +1,13 @@
|
|
|
1
1
|
import { type CodeInterpreterToolName, MarketSDK } from '@lobehub/market-sdk';
|
|
2
2
|
import { TRPCError } from '@trpc/server';
|
|
3
3
|
import debug from 'debug';
|
|
4
|
+
import { sha256 } from 'js-sha256';
|
|
4
5
|
import { z } from 'zod';
|
|
5
6
|
|
|
6
|
-
import { DocumentModel } from '@/database/models/document';
|
|
7
|
-
import { FileModel } from '@/database/models/file';
|
|
8
7
|
import { type ToolCallContent } from '@/libs/mcp';
|
|
9
8
|
import { authedProcedure, router } from '@/libs/trpc/lambda';
|
|
10
9
|
import { marketUserInfo, serverDatabase, telemetry } from '@/libs/trpc/lambda/middleware';
|
|
11
|
-
import { generateTrustedClientToken } from '@/libs/trusted-client';
|
|
10
|
+
import { generateTrustedClientToken, isTrustedClientEnabled } from '@/libs/trusted-client';
|
|
12
11
|
import { FileS3 } from '@/server/modules/S3';
|
|
13
12
|
import { DiscoverService } from '@/server/services/discover';
|
|
14
13
|
import { FileService } from '@/server/services/file';
|
|
@@ -69,21 +68,13 @@ const callCodeInterpreterToolSchema = z.object({
|
|
|
69
68
|
userId: z.string(),
|
|
70
69
|
});
|
|
71
70
|
|
|
72
|
-
// Schema for
|
|
73
|
-
const
|
|
71
|
+
// Schema for export and upload file (combined operation)
|
|
72
|
+
const exportAndUploadFileSchema = z.object({
|
|
74
73
|
filename: z.string(),
|
|
74
|
+
path: z.string(),
|
|
75
75
|
topicId: z.string(),
|
|
76
76
|
});
|
|
77
77
|
|
|
78
|
-
// Schema for saving exported file content to document
|
|
79
|
-
const saveExportedFileContentSchema = z.object({
|
|
80
|
-
content: z.string(),
|
|
81
|
-
fileId: z.string(),
|
|
82
|
-
fileType: z.string(),
|
|
83
|
-
filename: z.string(),
|
|
84
|
-
url: z.string(),
|
|
85
|
-
});
|
|
86
|
-
|
|
87
78
|
// Schema for cloud MCP endpoint call
|
|
88
79
|
const callCloudMcpEndpointSchema = z.object({
|
|
89
80
|
apiParams: z.record(z.any()),
|
|
@@ -94,8 +85,7 @@ const callCloudMcpEndpointSchema = z.object({
|
|
|
94
85
|
|
|
95
86
|
// ============================== Type Exports ==============================
|
|
96
87
|
export type CallCodeInterpreterToolInput = z.infer<typeof callCodeInterpreterToolSchema>;
|
|
97
|
-
export type
|
|
98
|
-
export type SaveExportedFileContentInput = z.infer<typeof saveExportedFileContentSchema>;
|
|
88
|
+
export type ExportAndUploadFileInput = z.infer<typeof exportAndUploadFileSchema>;
|
|
99
89
|
|
|
100
90
|
export interface CallToolResult {
|
|
101
91
|
error?: {
|
|
@@ -107,22 +97,16 @@ export interface CallToolResult {
|
|
|
107
97
|
success: boolean;
|
|
108
98
|
}
|
|
109
99
|
|
|
110
|
-
export interface
|
|
111
|
-
downloadUrl: string;
|
|
112
|
-
error?: {
|
|
113
|
-
message: string;
|
|
114
|
-
};
|
|
115
|
-
key: string;
|
|
116
|
-
success: boolean;
|
|
117
|
-
uploadUrl: string;
|
|
118
|
-
}
|
|
119
|
-
|
|
120
|
-
export interface SaveExportedFileContentResult {
|
|
121
|
-
documentId?: string;
|
|
100
|
+
export interface ExportAndUploadFileResult {
|
|
122
101
|
error?: {
|
|
123
102
|
message: string;
|
|
124
103
|
};
|
|
104
|
+
fileId?: string;
|
|
105
|
+
filename: string;
|
|
106
|
+
mimeType?: string;
|
|
107
|
+
size?: number;
|
|
125
108
|
success: boolean;
|
|
109
|
+
url?: string;
|
|
126
110
|
}
|
|
127
111
|
|
|
128
112
|
// ============================== Router ==============================
|
|
@@ -140,17 +124,26 @@ export const marketRouter = router({
|
|
|
140
124
|
let result: { content: string; state: any; success: boolean } | undefined;
|
|
141
125
|
|
|
142
126
|
try {
|
|
143
|
-
//
|
|
144
|
-
const
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
127
|
+
// Check if trusted client is enabled - if so, we don't need user's accessToken
|
|
128
|
+
const trustedClientEnabled = isTrustedClientEnabled();
|
|
129
|
+
|
|
130
|
+
let userAccessToken: string | undefined;
|
|
131
|
+
|
|
132
|
+
if (!trustedClientEnabled) {
|
|
133
|
+
// Query user_settings to get market.accessToken only if trusted client is not enabled
|
|
134
|
+
const userState = await ctx.userModel.getUserState(async () => ({}));
|
|
135
|
+
userAccessToken = userState.settings?.market?.accessToken;
|
|
136
|
+
|
|
137
|
+
log('callCloudMcpEndpoint: userAccessToken exists=%s', !!userAccessToken);
|
|
138
|
+
|
|
139
|
+
if (!userAccessToken) {
|
|
140
|
+
throw new TRPCError({
|
|
141
|
+
code: 'UNAUTHORIZED',
|
|
142
|
+
message: 'User access token not found. Please sign in to Market first.',
|
|
143
|
+
});
|
|
144
|
+
}
|
|
145
|
+
} else {
|
|
146
|
+
log('callCloudMcpEndpoint: using trusted client authentication');
|
|
154
147
|
}
|
|
155
148
|
|
|
156
149
|
const cloudResult = await ctx.discoverService.callCloudMcpEndpoint({
|
|
@@ -277,99 +270,122 @@ export const marketRouter = router({
|
|
|
277
270
|
}),
|
|
278
271
|
|
|
279
272
|
/**
|
|
280
|
-
*
|
|
273
|
+
* Export a file from sandbox and upload to S3, then create a persistent file record
|
|
274
|
+
* This combines the previous getExportFileUploadUrl + callCodeInterpreterTool + createFileRecord flow
|
|
275
|
+
* Returns a permanent /f/:id URL instead of a temporary pre-signed URL
|
|
281
276
|
*/
|
|
282
|
-
|
|
283
|
-
.input(
|
|
284
|
-
.mutation(async ({ input }) => {
|
|
285
|
-
const { filename, topicId } = input;
|
|
277
|
+
exportAndUploadFile: marketToolProcedure
|
|
278
|
+
.input(exportAndUploadFileSchema)
|
|
279
|
+
.mutation(async ({ input, ctx }) => {
|
|
280
|
+
const { path, filename, topicId } = input;
|
|
286
281
|
|
|
287
|
-
log('
|
|
282
|
+
log('Exporting and uploading file: %s from path: %s in topic: %s', filename, path, topicId);
|
|
288
283
|
|
|
289
284
|
try {
|
|
290
285
|
const s3 = new FileS3();
|
|
291
286
|
|
|
287
|
+
// Use date-based sharding for privacy compliance (GDPR, CCPA)
|
|
288
|
+
const today = new Date().toISOString().split('T')[0];
|
|
289
|
+
|
|
292
290
|
// Generate a unique key for the exported file
|
|
293
|
-
const key = `code-interpreter-exports/${topicId}/${filename}`;
|
|
291
|
+
const key = `code-interpreter-exports/${today}/${topicId}/${filename}`;
|
|
294
292
|
|
|
295
|
-
// Generate pre-signed upload URL
|
|
293
|
+
// Step 1: Generate pre-signed upload URL
|
|
296
294
|
const uploadUrl = await s3.createPreSignedUrl(key);
|
|
297
|
-
|
|
298
|
-
// Generate download URL (pre-signed for preview)
|
|
299
|
-
const downloadUrl = await s3.createPreSignedUrlForPreview(key);
|
|
300
|
-
|
|
301
295
|
log('Generated upload URL for key: %s', key);
|
|
302
296
|
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
297
|
+
// Step 2: Generate trusted client token if user info is available
|
|
298
|
+
const trustedClientToken = ctx.marketUserInfo
|
|
299
|
+
? generateTrustedClientToken(ctx.marketUserInfo)
|
|
300
|
+
: undefined;
|
|
301
|
+
|
|
302
|
+
// Only require user accessToken if trusted client is not available
|
|
303
|
+
let userAccessToken: string | undefined;
|
|
304
|
+
if (!trustedClientToken) {
|
|
305
|
+
const userState = await ctx.userModel.getUserState(async () => ({}));
|
|
306
|
+
userAccessToken = userState.settings?.market?.accessToken;
|
|
307
|
+
|
|
308
|
+
if (!userAccessToken) {
|
|
309
|
+
return {
|
|
310
|
+
error: { message: 'User access token not found. Please sign in to Market first.' },
|
|
311
|
+
filename,
|
|
312
|
+
success: false,
|
|
313
|
+
} as ExportAndUploadFileResult;
|
|
314
|
+
}
|
|
315
|
+
} else {
|
|
316
|
+
log('Using trusted client authentication for exportAndUploadFile');
|
|
317
|
+
}
|
|
311
318
|
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
success: false,
|
|
319
|
-
uploadUrl: '',
|
|
320
|
-
} as GetExportFileUploadUrlResult;
|
|
321
|
-
}
|
|
322
|
-
}),
|
|
319
|
+
// Initialize MarketSDK
|
|
320
|
+
const market = new MarketSDK({
|
|
321
|
+
accessToken: userAccessToken,
|
|
322
|
+
baseURL: process.env.NEXT_PUBLIC_MARKET_BASE_URL,
|
|
323
|
+
trustedClientToken,
|
|
324
|
+
});
|
|
323
325
|
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
const { content, fileId, fileType, filename, url } = input;
|
|
326
|
+
// Step 3: Call sandbox's exportFile tool with the upload URL
|
|
327
|
+
const response = await market.plugins.runBuildInTool(
|
|
328
|
+
'exportFile',
|
|
329
|
+
{ path, uploadUrl },
|
|
330
|
+
{ topicId, userId: ctx.userId },
|
|
331
|
+
);
|
|
331
332
|
|
|
332
|
-
|
|
333
|
+
log('Sandbox exportFile response: %O', response);
|
|
333
334
|
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
335
|
+
if (!response.success) {
|
|
336
|
+
return {
|
|
337
|
+
error: { message: response.error?.message || 'Failed to export file from sandbox' },
|
|
338
|
+
filename,
|
|
339
|
+
success: false,
|
|
340
|
+
} as ExportAndUploadFileResult;
|
|
341
|
+
}
|
|
342
|
+
|
|
343
|
+
const result = response.data?.result;
|
|
344
|
+
const uploadSuccess = result?.success !== false;
|
|
337
345
|
|
|
338
|
-
|
|
339
|
-
const file = await fileModel.findById(fileId);
|
|
340
|
-
if (!file) {
|
|
346
|
+
if (!uploadSuccess) {
|
|
341
347
|
return {
|
|
342
|
-
error: { message: '
|
|
348
|
+
error: { message: result?.error || 'Failed to upload file from sandbox' },
|
|
349
|
+
filename,
|
|
343
350
|
success: false,
|
|
344
|
-
} as
|
|
351
|
+
} as ExportAndUploadFileResult;
|
|
345
352
|
}
|
|
346
353
|
|
|
347
|
-
//
|
|
348
|
-
const
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
354
|
+
// Step 4: Get file metadata from S3 to verify upload and get actual size
|
|
355
|
+
const metadata = await s3.getFileMetadata(key);
|
|
356
|
+
const fileSize = metadata.contentLength;
|
|
357
|
+
const mimeType = metadata.contentType || result?.mimeType || 'application/octet-stream';
|
|
358
|
+
|
|
359
|
+
// Step 5: Create persistent file record using FileService
|
|
360
|
+
// Generate a simple hash from the key (since we don't have the actual file content)
|
|
361
|
+
const fileHash = sha256(key + Date.now().toString());
|
|
362
|
+
|
|
363
|
+
const { fileId, url } = await ctx.fileService.createFileRecord({
|
|
364
|
+
fileHash,
|
|
365
|
+
fileType: mimeType,
|
|
366
|
+
name: filename,
|
|
367
|
+
size: fileSize,
|
|
368
|
+
url: key, // Store S3 key
|
|
358
369
|
});
|
|
359
370
|
|
|
360
|
-
log('Created
|
|
371
|
+
log('Created file record: fileId=%s, url=%s', fileId, url);
|
|
361
372
|
|
|
362
373
|
return {
|
|
363
|
-
|
|
374
|
+
fileId,
|
|
375
|
+
filename,
|
|
376
|
+
mimeType,
|
|
377
|
+
size: fileSize,
|
|
364
378
|
success: true,
|
|
365
|
-
|
|
379
|
+
url, // This is the permanent /f/:id URL
|
|
380
|
+
} as ExportAndUploadFileResult;
|
|
366
381
|
} catch (error) {
|
|
367
|
-
log('Error
|
|
382
|
+
log('Error in exportAndUploadFile: %O', error);
|
|
368
383
|
|
|
369
384
|
return {
|
|
370
385
|
error: { message: (error as Error).message },
|
|
386
|
+
filename,
|
|
371
387
|
success: false,
|
|
372
|
-
} as
|
|
388
|
+
} as ExportAndUploadFileResult;
|
|
373
389
|
}
|
|
374
390
|
}),
|
|
375
391
|
});
|
|
@@ -149,7 +149,7 @@ export class DiscoverService {
|
|
|
149
149
|
apiParams: Record<string, any>;
|
|
150
150
|
identifier: string;
|
|
151
151
|
toolName: string;
|
|
152
|
-
userAccessToken
|
|
152
|
+
userAccessToken?: string;
|
|
153
153
|
}) {
|
|
154
154
|
log('callCloudMcpEndpoint: params=%O', {
|
|
155
155
|
apiParams: params.apiParams,
|
|
@@ -159,7 +159,14 @@ export class DiscoverService {
|
|
|
159
159
|
});
|
|
160
160
|
|
|
161
161
|
try {
|
|
162
|
-
//
|
|
162
|
+
// Build headers - only include Authorization if userAccessToken is provided
|
|
163
|
+
// When userAccessToken is not provided, MarketSDK will use trustedClientToken for authentication
|
|
164
|
+
const headers: Record<string, string> = {};
|
|
165
|
+
if (params.userAccessToken) {
|
|
166
|
+
headers.Authorization = `Bearer ${params.userAccessToken}`;
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
// Call cloud gateway with optional user access token in Authorization header
|
|
163
170
|
const result = await this.market.plugins.callCloudGateway(
|
|
164
171
|
{
|
|
165
172
|
apiParams: params.apiParams,
|
|
@@ -167,9 +174,7 @@ export class DiscoverService {
|
|
|
167
174
|
toolName: params.toolName,
|
|
168
175
|
},
|
|
169
176
|
{
|
|
170
|
-
headers
|
|
171
|
-
Authorization: `Bearer ${params.userAccessToken}`,
|
|
172
|
-
},
|
|
177
|
+
headers,
|
|
173
178
|
},
|
|
174
179
|
);
|
|
175
180
|
|
|
@@ -2,10 +2,8 @@ import { toolsClient } from '@/libs/trpc/client';
|
|
|
2
2
|
import type {
|
|
3
3
|
CallCodeInterpreterToolInput,
|
|
4
4
|
CallToolResult,
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
SaveExportedFileContentInput,
|
|
8
|
-
SaveExportedFileContentResult,
|
|
5
|
+
ExportAndUploadFileInput,
|
|
6
|
+
ExportAndUploadFileResult,
|
|
9
7
|
} from '@/server/routers/tools/market';
|
|
10
8
|
import { useUserStore } from '@/store/user';
|
|
11
9
|
import { settingsSelectors } from '@/store/user/slices/settings/selectors/settings';
|
|
@@ -44,31 +42,25 @@ class CodeInterpreterService {
|
|
|
44
42
|
}
|
|
45
43
|
|
|
46
44
|
/**
|
|
47
|
-
*
|
|
45
|
+
* Export a file from sandbox and upload to S3, then create a persistent file record
|
|
46
|
+
* This is a single call that combines: getUploadUrl + callTool(exportFile) + createFileRecord
|
|
47
|
+
* Returns a permanent /f/:id URL instead of a temporary pre-signed URL
|
|
48
|
+
* @param path - The file path in the sandbox
|
|
48
49
|
* @param filename - The name of the file to export
|
|
49
50
|
* @param topicId - The topic ID for organizing files
|
|
50
51
|
*/
|
|
51
|
-
async
|
|
52
|
+
async exportAndUploadFile(
|
|
53
|
+
path: string,
|
|
52
54
|
filename: string,
|
|
53
55
|
topicId: string,
|
|
54
|
-
): Promise<
|
|
55
|
-
const input:
|
|
56
|
+
): Promise<ExportAndUploadFileResult> {
|
|
57
|
+
const input: ExportAndUploadFileInput = {
|
|
56
58
|
filename,
|
|
59
|
+
path,
|
|
57
60
|
topicId,
|
|
58
61
|
};
|
|
59
62
|
|
|
60
|
-
return toolsClient.market.
|
|
61
|
-
}
|
|
62
|
-
|
|
63
|
-
/**
|
|
64
|
-
* Save exported file content to documents table
|
|
65
|
-
* This creates a document record linked to the file for content retrieval
|
|
66
|
-
* @param params - File content and metadata
|
|
67
|
-
*/
|
|
68
|
-
async saveExportedFileContent(
|
|
69
|
-
params: SaveExportedFileContentInput,
|
|
70
|
-
): Promise<SaveExportedFileContentResult> {
|
|
71
|
-
return toolsClient.market.saveExportedFileContent.mutate(params);
|
|
63
|
+
return toolsClient.market.exportAndUploadFile.mutate(input);
|
|
72
64
|
}
|
|
73
65
|
}
|
|
74
66
|
|
|
@@ -9,63 +9,20 @@ import { type StateCreator } from 'zustand/vanilla';
|
|
|
9
9
|
|
|
10
10
|
import { type MCPToolCallResult } from '@/libs/mcp';
|
|
11
11
|
import { chatService } from '@/services/chat';
|
|
12
|
-
import { codeInterpreterService } from '@/services/codeInterpreter';
|
|
13
|
-
import { fileService } from '@/services/file';
|
|
14
12
|
import { mcpService } from '@/services/mcp';
|
|
15
13
|
import { messageService } from '@/services/message';
|
|
16
14
|
import { AI_RUNTIME_OPERATION_TYPES } from '@/store/chat/slices/operation';
|
|
17
15
|
import { type ChatStore } from '@/store/chat/store';
|
|
18
16
|
import { useToolStore } from '@/store/tool';
|
|
19
17
|
import { hasExecutor } from '@/store/tool/slices/builtin/executors';
|
|
18
|
+
import { useUserStore } from '@/store/user';
|
|
19
|
+
import { userProfileSelectors } from '@/store/user/slices/auth/selectors';
|
|
20
20
|
import { safeParseJSON } from '@/utils/safeParseJSON';
|
|
21
21
|
|
|
22
22
|
import { dbMessageSelectors } from '../../message/selectors';
|
|
23
23
|
|
|
24
24
|
const log = debug('lobe-store:plugin-types');
|
|
25
25
|
|
|
26
|
-
/**
|
|
27
|
-
* Get MIME type from filename extension
|
|
28
|
-
*/
|
|
29
|
-
const getMimeTypeFromFilename = (filename: string): string => {
|
|
30
|
-
const ext = filename.split('.').pop()?.toLowerCase() || '';
|
|
31
|
-
const mimeTypes: Record<string, string> = {
|
|
32
|
-
// Images
|
|
33
|
-
bmp: 'image/bmp',
|
|
34
|
-
gif: 'image/gif',
|
|
35
|
-
jpeg: 'image/jpeg',
|
|
36
|
-
jpg: 'image/jpeg',
|
|
37
|
-
png: 'image/png',
|
|
38
|
-
svg: 'image/svg+xml',
|
|
39
|
-
webp: 'image/webp',
|
|
40
|
-
// Videos
|
|
41
|
-
mp4: 'video/mp4',
|
|
42
|
-
webm: 'video/webm',
|
|
43
|
-
mov: 'video/quicktime',
|
|
44
|
-
avi: 'video/x-msvideo',
|
|
45
|
-
// Documents
|
|
46
|
-
csv: 'text/csv',
|
|
47
|
-
doc: 'application/msword',
|
|
48
|
-
docx: 'application/vnd.openxmlformats-officedocument.wordprocessingml.document',
|
|
49
|
-
html: 'text/html',
|
|
50
|
-
json: 'application/json',
|
|
51
|
-
md: 'text/markdown',
|
|
52
|
-
pdf: 'application/pdf',
|
|
53
|
-
ppt: 'application/vnd.ms-powerpoint',
|
|
54
|
-
pptx: 'application/vnd.openxmlformats-officedocument.presentationml.presentation',
|
|
55
|
-
rtf: 'application/rtf',
|
|
56
|
-
txt: 'text/plain',
|
|
57
|
-
xls: 'application/vnd.ms-excel',
|
|
58
|
-
xlsx: 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet',
|
|
59
|
-
xml: 'application/xml',
|
|
60
|
-
// Code
|
|
61
|
-
css: 'text/css',
|
|
62
|
-
js: 'text/javascript',
|
|
63
|
-
py: 'text/x-python',
|
|
64
|
-
ts: 'text/typescript',
|
|
65
|
-
};
|
|
66
|
-
return mimeTypes[ext] || 'application/octet-stream';
|
|
67
|
-
};
|
|
68
|
-
|
|
69
26
|
/**
|
|
70
27
|
* Plugin type-specific implementations
|
|
71
28
|
* Each method handles a specific type of plugin invocation
|
|
@@ -284,10 +241,13 @@ export const pluginTypes: StateCreator<
|
|
|
284
241
|
const { CloudSandboxExecutionRuntime } =
|
|
285
242
|
await import('@lobechat/builtin-tool-cloud-sandbox/executionRuntime');
|
|
286
243
|
|
|
244
|
+
// Get userId from user store
|
|
245
|
+
const userId = userProfileSelectors.userId(useUserStore.getState()) || 'anonymous';
|
|
246
|
+
|
|
287
247
|
// Create runtime with context
|
|
288
248
|
const runtime = new CloudSandboxExecutionRuntime({
|
|
289
249
|
topicId: message?.topicId || 'default',
|
|
290
|
-
userId
|
|
250
|
+
userId,
|
|
291
251
|
});
|
|
292
252
|
|
|
293
253
|
// Parse arguments
|
|
@@ -341,58 +301,25 @@ export const pluginTypes: StateCreator<
|
|
|
341
301
|
context,
|
|
342
302
|
);
|
|
343
303
|
|
|
344
|
-
// Handle exportFile:
|
|
304
|
+
// Handle exportFile: associate the file (already created by server) with assistant message (parent)
|
|
345
305
|
if (payload.apiName === 'exportFile' && data.success && data.state) {
|
|
346
306
|
const exportState = data.state as ExportFileState;
|
|
347
|
-
|
|
307
|
+
// Server now creates the file record and returns fileId in the response
|
|
308
|
+
if (exportState.fileId && exportState.filename) {
|
|
348
309
|
try {
|
|
349
|
-
//
|
|
350
|
-
// Extract the path before query params: .../code-interpreter-exports/tpc_xxx/filename.ext
|
|
351
|
-
const urlPath = exportState.downloadUrl.split('?')[0];
|
|
352
|
-
const hash = `ci-export-${btoa(urlPath).slice(0, 32)}`;
|
|
353
|
-
|
|
354
|
-
// Use mimeType from state if available, otherwise infer from filename
|
|
355
|
-
const mimeType = exportState.mimeType || getMimeTypeFromFilename(exportState.filename);
|
|
356
|
-
|
|
357
|
-
// 1. Create file record in database
|
|
358
|
-
const fileResult = await fileService.createFile({
|
|
359
|
-
fileType: mimeType,
|
|
360
|
-
hash,
|
|
361
|
-
name: exportState.filename,
|
|
362
|
-
size: exportState.size || 0,
|
|
363
|
-
source: 'code-interpreter',
|
|
364
|
-
url: exportState.downloadUrl,
|
|
365
|
-
});
|
|
366
|
-
|
|
367
|
-
// 2. If there's text content, save it to documents table for retrieval
|
|
368
|
-
if (exportState.content) {
|
|
369
|
-
await codeInterpreterService.saveExportedFileContent({
|
|
370
|
-
content: exportState.content,
|
|
371
|
-
fileId: fileResult.id,
|
|
372
|
-
fileType: mimeType,
|
|
373
|
-
filename: exportState.filename,
|
|
374
|
-
url: exportState.downloadUrl,
|
|
375
|
-
});
|
|
376
|
-
|
|
377
|
-
log(
|
|
378
|
-
'[invokeCloudCodeInterpreterTool] Saved file content to document: fileId=%s',
|
|
379
|
-
fileResult.id,
|
|
380
|
-
);
|
|
381
|
-
}
|
|
382
|
-
|
|
383
|
-
// 3. Associate file with the assistant message (parent of tool message)
|
|
310
|
+
// Associate file with the assistant message (parent of tool message)
|
|
384
311
|
// The current message (id) is the tool message, we need to attach to its parent
|
|
385
312
|
const targetMessageId = message?.parentId || id;
|
|
386
313
|
|
|
387
|
-
await messageService.addFilesToMessage(targetMessageId, [
|
|
314
|
+
await messageService.addFilesToMessage(targetMessageId, [exportState.fileId], {
|
|
388
315
|
agentId: message?.agentId,
|
|
389
316
|
topicId: message?.topicId,
|
|
390
317
|
});
|
|
391
318
|
|
|
392
319
|
log(
|
|
393
|
-
'[invokeCloudCodeInterpreterTool]
|
|
320
|
+
'[invokeCloudCodeInterpreterTool] Associated exported file with message: targetMessageId=%s, fileId=%s, filename=%s',
|
|
394
321
|
targetMessageId,
|
|
395
|
-
|
|
322
|
+
exportState.fileId,
|
|
396
323
|
exportState.filename,
|
|
397
324
|
);
|
|
398
325
|
} catch (error) {
|
|
@@ -1,119 +0,0 @@
|
|
|
1
|
-
import {
|
|
2
|
-
ReactCodePlugin,
|
|
3
|
-
ReactCodemirrorPlugin,
|
|
4
|
-
ReactHRPlugin,
|
|
5
|
-
ReactLinkHighlightPlugin,
|
|
6
|
-
ReactListPlugin,
|
|
7
|
-
ReactMathPlugin,
|
|
8
|
-
ReactTablePlugin,
|
|
9
|
-
} from '@lobehub/editor';
|
|
10
|
-
import { Editor, useEditor } from '@lobehub/editor/react';
|
|
11
|
-
import { Flexbox, Modal } from '@lobehub/ui';
|
|
12
|
-
import { memo, useMemo } from 'react';
|
|
13
|
-
import { useTranslation } from 'react-i18next';
|
|
14
|
-
|
|
15
|
-
import { useUserStore } from '@/store/user';
|
|
16
|
-
import { labPreferSelectors } from '@/store/user/slices/preference/selectors';
|
|
17
|
-
|
|
18
|
-
import TypoBar from './Typobar';
|
|
19
|
-
|
|
20
|
-
interface EditableMessageProps {
|
|
21
|
-
editing?: boolean;
|
|
22
|
-
onChange?: (value: string) => void;
|
|
23
|
-
onEditingChange?: (editing: boolean) => void;
|
|
24
|
-
value?: string;
|
|
25
|
-
}
|
|
26
|
-
|
|
27
|
-
const EditableMessage = memo<EditableMessageProps>(
|
|
28
|
-
({ editing, onEditingChange, onChange, value }) => {
|
|
29
|
-
const { t } = useTranslation('common');
|
|
30
|
-
const editor = useEditor();
|
|
31
|
-
|
|
32
|
-
const enableRichRender = useUserStore(labPreferSelectors.enableInputMarkdown);
|
|
33
|
-
|
|
34
|
-
const richRenderProps = useMemo(
|
|
35
|
-
() =>
|
|
36
|
-
!enableRichRender
|
|
37
|
-
? {
|
|
38
|
-
enablePasteMarkdown: false,
|
|
39
|
-
markdownOption: {
|
|
40
|
-
bold: false,
|
|
41
|
-
code: false,
|
|
42
|
-
header: false,
|
|
43
|
-
italic: false,
|
|
44
|
-
quote: false,
|
|
45
|
-
strikethrough: false,
|
|
46
|
-
underline: false,
|
|
47
|
-
underlineStrikethrough: false,
|
|
48
|
-
},
|
|
49
|
-
}
|
|
50
|
-
: {
|
|
51
|
-
plugins: [
|
|
52
|
-
ReactListPlugin,
|
|
53
|
-
ReactCodePlugin,
|
|
54
|
-
ReactCodemirrorPlugin,
|
|
55
|
-
ReactHRPlugin,
|
|
56
|
-
ReactLinkHighlightPlugin,
|
|
57
|
-
ReactTablePlugin,
|
|
58
|
-
ReactMathPlugin,
|
|
59
|
-
],
|
|
60
|
-
},
|
|
61
|
-
[enableRichRender],
|
|
62
|
-
);
|
|
63
|
-
|
|
64
|
-
return (
|
|
65
|
-
<Modal
|
|
66
|
-
cancelText={t('cancel')}
|
|
67
|
-
closable={false}
|
|
68
|
-
destroyOnHidden
|
|
69
|
-
okText={t('ok')}
|
|
70
|
-
onCancel={() => onEditingChange?.(false)}
|
|
71
|
-
onOk={() => {
|
|
72
|
-
if (!editor) return;
|
|
73
|
-
const newValue = editor.getDocument('markdown') as unknown as string;
|
|
74
|
-
onChange?.(newValue);
|
|
75
|
-
onEditingChange?.(false);
|
|
76
|
-
}}
|
|
77
|
-
open={editing}
|
|
78
|
-
styles={{
|
|
79
|
-
body: {
|
|
80
|
-
overflow: 'hidden',
|
|
81
|
-
padding: 0,
|
|
82
|
-
},
|
|
83
|
-
}}
|
|
84
|
-
title={null}
|
|
85
|
-
width={'min(90vw, 960px)'}
|
|
86
|
-
>
|
|
87
|
-
<TypoBar editor={editor} />
|
|
88
|
-
<Flexbox
|
|
89
|
-
onClick={() => {
|
|
90
|
-
editor.focus();
|
|
91
|
-
}}
|
|
92
|
-
paddingBlock={16}
|
|
93
|
-
paddingInline={48}
|
|
94
|
-
style={{ cursor: 'text', maxHeight: '80vh', minHeight: '50vh', overflowY: 'auto' }}
|
|
95
|
-
>
|
|
96
|
-
<Editor
|
|
97
|
-
autoFocus
|
|
98
|
-
content={''}
|
|
99
|
-
editor={editor}
|
|
100
|
-
onInit={(editor) => {
|
|
101
|
-
if (!editor) return;
|
|
102
|
-
try {
|
|
103
|
-
editor?.setDocument('markdown', value);
|
|
104
|
-
} catch {}
|
|
105
|
-
}}
|
|
106
|
-
style={{
|
|
107
|
-
paddingBottom: 120,
|
|
108
|
-
}}
|
|
109
|
-
type={'text'}
|
|
110
|
-
variant={'chat'}
|
|
111
|
-
{...richRenderProps}
|
|
112
|
-
/>
|
|
113
|
-
</Flexbox>
|
|
114
|
-
</Modal>
|
|
115
|
-
);
|
|
116
|
-
},
|
|
117
|
-
);
|
|
118
|
-
|
|
119
|
-
export default EditableMessage;
|