@lobehub/lobehub 2.0.0-next.46 → 2.0.0-next.48
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 +11 -0
- package/CHANGELOG.md +42 -0
- package/apps/desktop/src/main/controllers/AuthCtr.ts +27 -2
- package/apps/desktop/src/main/core/infrastructure/ProtocolManager.ts +9 -4
- package/changelog/v1.json +14 -0
- package/docs/development/database-schema.dbml +2 -0
- package/docs/self-hosting/environment-variables/basic.mdx +49 -3
- package/docs/self-hosting/environment-variables/basic.zh-CN.mdx +49 -4
- package/locales/ar/discover.json +45 -0
- package/locales/ar/marketAuth.json +42 -0
- package/locales/ar/setting.json +94 -1
- package/locales/bg-BG/discover.json +45 -0
- package/locales/bg-BG/marketAuth.json +42 -0
- package/locales/bg-BG/setting.json +94 -1
- package/locales/de-DE/discover.json +45 -0
- package/locales/de-DE/marketAuth.json +42 -0
- package/locales/de-DE/setting.json +94 -1
- package/locales/en-US/discover.json +45 -0
- package/locales/en-US/marketAuth.json +42 -0
- package/locales/en-US/setting.json +94 -1
- package/locales/es-ES/discover.json +45 -0
- package/locales/es-ES/marketAuth.json +42 -0
- package/locales/es-ES/setting.json +94 -1
- package/locales/fa-IR/discover.json +45 -0
- package/locales/fa-IR/marketAuth.json +42 -0
- package/locales/fa-IR/setting.json +94 -1
- package/locales/fr-FR/discover.json +45 -0
- package/locales/fr-FR/marketAuth.json +42 -0
- package/locales/fr-FR/setting.json +94 -1
- package/locales/it-IT/discover.json +45 -0
- package/locales/it-IT/marketAuth.json +42 -0
- package/locales/it-IT/setting.json +94 -1
- package/locales/ja-JP/discover.json +45 -0
- package/locales/ja-JP/marketAuth.json +42 -0
- package/locales/ja-JP/setting.json +94 -1
- package/locales/ko-KR/discover.json +45 -0
- package/locales/ko-KR/marketAuth.json +42 -0
- package/locales/ko-KR/setting.json +94 -1
- package/locales/nl-NL/discover.json +45 -0
- package/locales/nl-NL/marketAuth.json +42 -0
- package/locales/nl-NL/setting.json +94 -1
- package/locales/pl-PL/discover.json +45 -0
- package/locales/pl-PL/marketAuth.json +42 -0
- package/locales/pl-PL/setting.json +94 -1
- package/locales/pt-BR/discover.json +45 -0
- package/locales/pt-BR/marketAuth.json +42 -0
- package/locales/pt-BR/setting.json +94 -1
- package/locales/ru-RU/discover.json +45 -0
- package/locales/ru-RU/marketAuth.json +42 -0
- package/locales/ru-RU/setting.json +94 -1
- package/locales/tr-TR/discover.json +45 -0
- package/locales/tr-TR/marketAuth.json +42 -0
- package/locales/tr-TR/setting.json +94 -1
- package/locales/vi-VN/discover.json +45 -0
- package/locales/vi-VN/marketAuth.json +42 -0
- package/locales/vi-VN/setting.json +94 -1
- package/locales/zh-CN/discover.json +45 -0
- package/locales/zh-CN/marketAuth.json +42 -0
- package/locales/zh-CN/setting.json +94 -1
- package/locales/zh-TW/discover.json +45 -0
- package/locales/zh-TW/marketAuth.json +42 -0
- package/locales/zh-TW/setting.json +94 -1
- package/package.json +27 -26
- package/packages/const/src/url.ts +1 -0
- package/packages/database/migrations/0044_add_tool_intervention.sql +1 -0
- package/packages/database/migrations/0044_high_toxin.sql +1 -0
- package/packages/database/migrations/0045_add_tool_intervention.sql +1 -0
- package/packages/database/migrations/meta/0039_snapshot.json +1 -1
- package/packages/database/migrations/meta/0044_snapshot.json +7813 -0
- package/packages/database/migrations/meta/0045_snapshot.json +8431 -0
- package/packages/database/migrations/meta/_journal.json +14 -0
- package/packages/database/src/core/migrations.json +36 -7
- package/packages/database/src/models/file.ts +15 -1
- package/packages/database/src/models/message.ts +1 -1
- package/packages/database/src/models/session.ts +42 -1
- package/packages/database/src/repositories/aiInfra/index.test.ts +1 -1
- package/packages/database/src/repositories/dataExporter/index.test.ts +1 -1
- package/packages/database/src/repositories/tableViewer/index.test.ts +1 -1
- package/packages/database/src/schemas/agent.ts +1 -0
- package/packages/database/src/schemas/message.ts +5 -8
- package/packages/electron-client-ipc/src/events/index.ts +6 -1
- package/packages/electron-client-ipc/src/events/remoteServer.ts +8 -0
- package/packages/fetch-sse/package.json +29 -0
- package/packages/{utils/src/fetch → fetch-sse/src}/__tests__/fetchSSE.test.ts +4 -4
- package/packages/{utils/src/fetch → fetch-sse/src}/__tests__/parseError.test.ts +7 -4
- package/packages/{utils/src/fetch → fetch-sse/src}/fetchSSE.ts +2 -2
- package/packages/{utils/src/fetch → fetch-sse/src}/parseError.ts +3 -3
- package/packages/model-bank/src/aiModels/mistral.ts +2 -1
- package/packages/model-runtime/src/core/contextBuilders/anthropic.test.ts +17 -11
- package/packages/model-runtime/src/core/contextBuilders/anthropic.ts +1 -1
- package/packages/model-runtime/src/core/contextBuilders/google.test.ts +1 -1
- package/packages/model-runtime/src/core/contextBuilders/google.ts +3 -6
- package/packages/model-runtime/src/core/contextBuilders/openai.test.ts +4 -2
- package/packages/model-runtime/src/core/contextBuilders/openai.ts +1 -1
- package/packages/model-runtime/src/core/openaiCompatibleFactory/createImage.test.ts +1 -1
- package/packages/model-runtime/src/core/openaiCompatibleFactory/createImage.ts +1 -1
- package/packages/model-runtime/src/core/openaiCompatibleFactory/index.test.ts +3 -6
- package/packages/model-runtime/src/core/streams/openai/responsesStream.test.ts +1 -1
- package/packages/model-runtime/src/helpers/mergeChatMethodOptions.ts +2 -1
- package/packages/model-runtime/src/providers/aihubmix/index.test.ts +1 -1
- package/packages/model-runtime/src/providers/anthropic/generateObject.test.ts +1 -1
- package/packages/model-runtime/src/providers/anthropic/index.test.ts +1 -1
- package/packages/model-runtime/src/providers/baichuan/index.test.ts +1 -1
- package/packages/model-runtime/src/providers/bedrock/index.test.ts +1 -1
- package/packages/model-runtime/src/providers/bfl/createImage.test.ts +4 -4
- package/packages/model-runtime/src/providers/bfl/createImage.ts +1 -1
- package/packages/model-runtime/src/providers/cloudflare/index.test.ts +1 -1
- package/packages/model-runtime/src/providers/cohere/index.test.ts +1 -1
- package/packages/model-runtime/src/providers/google/createImage.test.ts +2 -2
- package/packages/model-runtime/src/providers/google/createImage.ts +1 -1
- package/packages/model-runtime/src/providers/google/generateObject.test.ts +1 -1
- package/packages/model-runtime/src/providers/google/index.test.ts +1 -4
- package/packages/model-runtime/src/providers/groq/index.test.ts +1 -1
- package/packages/model-runtime/src/providers/hunyuan/index.test.ts +1 -1
- package/packages/model-runtime/src/providers/minimax/createImage.test.ts +1 -1
- package/packages/model-runtime/src/providers/mistral/index.test.ts +1 -1
- package/packages/model-runtime/src/providers/moonshot/index.test.ts +1 -1
- package/packages/model-runtime/src/providers/novita/index.test.ts +1 -1
- package/packages/model-runtime/src/providers/ollama/index.test.ts +43 -32
- package/packages/model-runtime/src/providers/ollama/index.ts +31 -7
- package/packages/model-runtime/src/providers/openrouter/index.test.ts +1 -1
- package/packages/model-runtime/src/providers/perplexity/index.test.ts +1 -1
- package/packages/model-runtime/src/providers/ppio/index.test.ts +1 -1
- package/packages/model-runtime/src/providers/qwen/createImage.test.ts +1 -1
- package/packages/model-runtime/src/providers/search1api/index.test.ts +1 -1
- package/packages/model-runtime/src/providers/siliconcloud/createImage.ts +1 -1
- package/packages/model-runtime/src/providers/taichu/index.test.ts +1 -1
- package/packages/model-runtime/src/providers/wenxin/index.test.ts +1 -1
- package/packages/model-runtime/src/providers/zhipu/index.test.ts +1 -1
- package/packages/model-runtime/src/utils/errorResponse.test.ts +1 -1
- package/packages/ssrf-safe-fetch/index.browser.ts +14 -0
- package/packages/ssrf-safe-fetch/package.json +8 -1
- package/packages/types/src/aiProvider.ts +1 -1
- package/packages/types/src/discover/assistants.ts +16 -0
- package/packages/types/src/document/index.ts +38 -38
- package/packages/types/src/exportConfig.ts +15 -15
- package/packages/types/src/generation/index.ts +5 -5
- package/packages/types/src/index.ts +1 -0
- package/packages/types/src/message/common/tools.ts +10 -0
- package/packages/types/src/message/db/item.ts +15 -1
- package/packages/types/src/message/ui/params.ts +15 -1
- package/packages/types/src/meta.ts +4 -0
- package/packages/types/src/openai/chat.ts +15 -15
- package/packages/types/src/plugins/mcp.ts +29 -29
- package/packages/types/src/plugins/protocol.ts +43 -43
- package/packages/types/src/search.ts +4 -4
- package/packages/types/src/session/agentSession.ts +2 -0
- package/packages/types/src/tool/plugin.ts +3 -3
- package/packages/utils/src/imageToBase64.ts +17 -10
- package/packages/utils/src/index.ts +1 -1
- package/src/app/(backend)/f/[id]/route.ts +55 -0
- package/src/app/(backend)/market/agent/[[...segments]]/route.ts +153 -0
- package/src/app/(backend)/market/oidc/[[...segments]]/route.ts +207 -0
- package/src/app/[variants]/(main)/(mobile)/me/settings/features/useCategory.tsx +1 -0
- package/src/app/[variants]/(main)/_layout/Desktop/SideBar/PinList/index.tsx +4 -2
- package/src/app/[variants]/(main)/chat/settings/features/AgentInfoDescription/index.tsx +349 -0
- package/src/app/[variants]/(main)/chat/settings/features/HeaderContent.tsx +2 -2
- package/src/app/[variants]/(main)/chat/settings/features/PublishResultModal/index.tsx +64 -0
- package/src/app/[variants]/(main)/chat/settings/features/SmartAgentActionButton/MarketPublishButton.tsx +196 -0
- package/src/app/[variants]/(main)/chat/settings/features/SmartAgentActionButton/MarketPublishModal.tsx +358 -0
- package/src/app/[variants]/(main)/chat/settings/features/SmartAgentActionButton/index.tsx +75 -0
- package/src/app/[variants]/(main)/discover/(detail)/assistant/AssistantDetailPage.tsx +11 -2
- package/src/app/[variants]/(main)/discover/(detail)/assistant/[...slugs]/Client.tsx +12 -1
- package/src/app/[variants]/(main)/discover/(detail)/assistant/[...slugs]/features/Details/Nav.tsx +19 -12
- package/src/app/[variants]/(main)/discover/(detail)/assistant/[...slugs]/features/Details/Overview/TagList.tsx +14 -5
- package/src/app/[variants]/(main)/discover/(detail)/assistant/[...slugs]/features/Details/Overview/index.tsx +2 -0
- package/src/app/[variants]/(main)/discover/(detail)/assistant/[...slugs]/features/Details/Related/index.tsx +14 -5
- package/src/app/[variants]/(main)/discover/(detail)/assistant/[...slugs]/features/Details/SystemRole/TagList.tsx +14 -5
- package/src/app/[variants]/(main)/discover/(detail)/assistant/[...slugs]/features/Details/SystemRole/index.tsx +43 -29
- package/src/app/[variants]/(main)/discover/(detail)/assistant/[...slugs]/features/Details/Versions/index.tsx +137 -0
- package/src/app/[variants]/(main)/discover/(detail)/assistant/[...slugs]/features/Details/index.tsx +2 -0
- package/src/app/[variants]/(main)/discover/(detail)/assistant/[...slugs]/features/Header.tsx +9 -10
- package/src/app/[variants]/(main)/discover/(detail)/assistant/[...slugs]/features/Sidebar/ActionButton/AddAgent.tsx +105 -14
- package/src/app/[variants]/(main)/discover/(detail)/assistant/[...slugs]/features/Sidebar/Related/index.tsx +20 -6
- package/src/app/[variants]/(main)/discover/(detail)/assistant/[...slugs]/features/StatusPage/index.tsx +113 -0
- package/src/app/[variants]/(main)/discover/(detail)/features/Breadcrumb.tsx +4 -3
- package/src/app/[variants]/(main)/discover/(list)/_layout/Desktop/Nav.tsx +3 -1
- package/src/app/[variants]/(main)/discover/(list)/assistant/AssistantPage.tsx +4 -1
- package/src/app/[variants]/(main)/discover/(list)/assistant/Client.tsx +6 -2
- package/src/app/[variants]/(main)/discover/(list)/assistant/features/Category/index.tsx +7 -3
- package/src/app/[variants]/(main)/discover/(list)/assistant/features/List/Item.tsx +13 -2
- package/src/app/[variants]/(main)/discover/(list)/assistant/features/MarketSourceSwitch.tsx +64 -0
- package/src/app/[variants]/(main)/discover/(list)/features/SortButton/index.tsx +26 -7
- package/src/app/[variants]/(main)/profile/_layout/Desktop/index.tsx +10 -10
- package/src/app/[variants]/(main)/settings/_layout/type.ts +1 -1
- package/src/app/[variants]/(main)/settings/agent/index.tsx +11 -10
- package/src/app/[variants]/(main)/settings/common/index.tsx +1 -1
- package/src/app/[variants]/(main)/settings/page.tsx +13 -10
- package/src/app/[variants]/(main)/settings/provider/ProviderMenu/Item.tsx +35 -36
- package/src/app/[variants]/(main)/settings/provider/ProviderMenu/SearchResult.tsx +5 -5
- package/src/app/[variants]/(main)/settings/provider/_layout/Desktop/Container.tsx +10 -4
- package/src/app/market-auth-callback/layout.tsx +15 -0
- package/src/app/market-auth-callback/page.tsx +196 -0
- package/src/envs/app.ts +4 -3
- package/src/features/AgentSetting/AgentPrompt/TokenTag.tsx +3 -2
- package/src/features/AgentSetting/AgentTTS/SelectWithTTSPreview.tsx +1 -1
- package/src/features/AgentSetting/store/action.ts +1 -1
- package/src/features/ChatInput/ActionBar/STT/browser.tsx +1 -1
- package/src/features/ChatInput/ActionBar/STT/openai.tsx +1 -1
- package/src/features/Conversation/components/Extras/TTS/InitPlayer.tsx +1 -1
- package/src/features/Conversation/components/VirtualizedList/index.tsx +2 -1
- package/src/features/PluginTag/PluginStatus.tsx +1 -1
- package/src/features/PluginsUI/Render/MCPType/index.tsx +26 -6
- package/src/hooks/useAgentOwnershipCheck.ts +143 -0
- package/src/instrumentation.node.ts +3 -2
- package/src/layout/AuthProvider/MarketAuth/MarketAuthProvider.tsx +364 -0
- package/src/layout/AuthProvider/MarketAuth/errors.ts +75 -0
- package/src/layout/AuthProvider/MarketAuth/index.ts +2 -0
- package/src/layout/AuthProvider/MarketAuth/oidc.ts +382 -0
- package/src/layout/AuthProvider/MarketAuth/types.ts +64 -0
- package/src/layout/AuthProvider/index.tsx +17 -4
- package/src/locales/default/discover.ts +46 -0
- package/src/locales/default/index.ts +2 -0
- package/src/locales/default/marketAuth.ts +42 -0
- package/src/locales/default/setting.ts +94 -1
- package/src/server/globalConfig/genServerAiProviderConfig.test.ts +5 -5
- package/src/server/globalConfig/genServerAiProviderConfig.ts +1 -1
- package/src/server/routers/desktop/mcp.ts +23 -8
- package/src/server/routers/lambda/market/index.ts +36 -14
- package/src/server/routers/lambda/message.ts +2 -2
- package/src/server/routers/tools/mcp.ts +24 -4
- package/src/server/services/discover/index.test.ts +153 -11
- package/src/server/services/discover/index.ts +339 -40
- package/src/server/services/file/impls/local.ts +4 -1
- package/src/server/services/file/index.ts +96 -1
- package/src/server/services/mcp/contentProcessor.ts +101 -0
- package/src/server/services/mcp/index.test.ts +52 -10
- package/src/server/services/mcp/index.ts +29 -26
- package/src/server/sitemap.ts +49 -35
- package/src/services/_url.ts +15 -1
- package/src/services/chat/chat.test.ts +5 -5
- package/src/services/chat/clientModelRuntime.test.ts +1 -1
- package/src/services/chat/index.ts +6 -6
- package/src/services/chat/types.ts +1 -2
- package/src/services/discover.ts +16 -5
- package/src/services/electron/remoteServer.ts +8 -1
- package/src/services/marketApi.ts +124 -0
- package/src/services/models.ts +2 -1
- package/src/services/session/index.ts +0 -14
- package/src/store/discover/slices/assistant/action.ts +20 -7
- package/{packages/utils/src → src/utils}/electron/desktopRemoteRPCFetch.ts +1 -1
- package/{packages/utils/src → src/utils/server}/parseModels.ts +1 -2
- package/src/utils/server/routeVariants.test.ts +340 -0
- package/vitest.config.mts +2 -0
- package/packages/model-runtime/src/utils/imageToBase64.test.ts +0 -91
- package/packages/model-runtime/src/utils/imageToBase64.ts +0 -62
- package/src/app/[variants]/(main)/chat/settings/features/SubmitAgentButton/SubmitAgentModal.tsx +0 -98
- package/src/app/[variants]/(main)/chat/settings/features/SubmitAgentButton/index.tsx +0 -35
- package/src/app/[variants]/(main)/chat/settings/features/SubmitAgentButton/style.ts +0 -47
- /package/packages/{utils/src/fetch → fetch-sse/src}/headers.ts +0 -0
- /package/packages/{utils/src/fetch → fetch-sse/src}/index.ts +0 -0
- /package/packages/{utils/src/fetch → fetch-sse/src}/request.ts +0 -0
- /package/{packages/utils/src → src/utils/server}/__snapshots__/parseModels.test.ts.snap +0 -0
- /package/{packages/utils/src → src/utils/server}/parseModels.test.ts +0 -0
|
@@ -7,6 +7,7 @@ import {
|
|
|
7
7
|
} from '@lobechat/const';
|
|
8
8
|
import {
|
|
9
9
|
AssistantListResponse,
|
|
10
|
+
AssistantMarketSource,
|
|
10
11
|
AssistantQueryParams,
|
|
11
12
|
AssistantSorts,
|
|
12
13
|
CacheRevalidate,
|
|
@@ -60,9 +61,12 @@ export class DiscoverService {
|
|
|
60
61
|
constructor({ accessToken }: { accessToken?: string } = {}) {
|
|
61
62
|
this.market = new MarketSDK({
|
|
62
63
|
accessToken,
|
|
63
|
-
baseURL: process.env.
|
|
64
|
+
baseURL: process.env.NEXT_PUBLIC_MARKET_BASE_URL,
|
|
64
65
|
});
|
|
65
|
-
log(
|
|
66
|
+
log(
|
|
67
|
+
'DiscoverService initialized with market baseURL: %s',
|
|
68
|
+
process.env.NEXT_PUBLIC_MARKET_BASE_URL,
|
|
69
|
+
);
|
|
66
70
|
}
|
|
67
71
|
|
|
68
72
|
async registerClient({ userAgent }: { userAgent?: string }) {
|
|
@@ -102,7 +106,7 @@ export class DiscoverService {
|
|
|
102
106
|
async fetchM2MToken(params: { clientId: string; clientSecret: string }) {
|
|
103
107
|
// 使用传入的客户端凭证创建新的 MarketSDK 实例
|
|
104
108
|
const tokenMarket = new MarketSDK({
|
|
105
|
-
baseURL: process.env.
|
|
109
|
+
baseURL: process.env.NEXT_PUBLIC_MARKET_BASE_URL,
|
|
106
110
|
clientId: params.clientId,
|
|
107
111
|
clientSecret: params.clientSecret,
|
|
108
112
|
});
|
|
@@ -208,25 +212,47 @@ export class DiscoverService {
|
|
|
208
212
|
return result;
|
|
209
213
|
};
|
|
210
214
|
|
|
211
|
-
|
|
215
|
+
private normalizeAuthorField = (author: unknown): string => {
|
|
216
|
+
if (!author) return '';
|
|
217
|
+
|
|
218
|
+
if (typeof author === 'string') return author;
|
|
219
|
+
|
|
220
|
+
if (typeof author === 'object') {
|
|
221
|
+
const { avatar, url, name } = author as {
|
|
222
|
+
avatar?: unknown;
|
|
223
|
+
name?: unknown;
|
|
224
|
+
url?: unknown;
|
|
225
|
+
};
|
|
226
|
+
|
|
227
|
+
if (typeof name === 'string' && name.length > 0) return name;
|
|
228
|
+
if (typeof avatar === 'string' && avatar.length > 0) return avatar;
|
|
229
|
+
if (typeof url === 'string' && url.length > 0) return url;
|
|
230
|
+
}
|
|
212
231
|
|
|
213
|
-
|
|
214
|
-
|
|
232
|
+
return '';
|
|
233
|
+
};
|
|
234
|
+
|
|
235
|
+
private isLegacySource = (source?: AssistantMarketSource) => source === 'legacy';
|
|
236
|
+
|
|
237
|
+
private legacyGetAssistantListRaw = async (locale?: string): Promise<DiscoverAssistantItem[]> => {
|
|
238
|
+
log('legacyGetAssistantListRaw: locale=%s', locale);
|
|
215
239
|
const normalizedLocale = normalizeLocale(locale);
|
|
216
240
|
const list = await this.assistantStore.getAgentIndex(normalizedLocale);
|
|
217
241
|
if (!list || !Array.isArray(list)) {
|
|
218
|
-
log('
|
|
242
|
+
log('legacyGetAssistantListRaw: no valid list found, returning empty array');
|
|
219
243
|
return [];
|
|
220
244
|
}
|
|
221
245
|
const result = list.map(({ meta, ...item }) => ({ ...item, ...meta }));
|
|
222
|
-
log('
|
|
246
|
+
log('legacyGetAssistantListRaw: returning %d items', result.length);
|
|
223
247
|
return result;
|
|
224
248
|
};
|
|
225
249
|
|
|
226
|
-
|
|
227
|
-
|
|
250
|
+
private legacyGetAssistantCategories = async (
|
|
251
|
+
params: CategoryListQuery = {},
|
|
252
|
+
): Promise<CategoryItem[]> => {
|
|
253
|
+
log('legacyGetAssistantCategories: params=%O', params);
|
|
228
254
|
const { q, locale } = params;
|
|
229
|
-
let list = await this.
|
|
255
|
+
let list = await this.legacyGetAssistantListRaw(locale);
|
|
230
256
|
if (q) {
|
|
231
257
|
const originalCount = list.length;
|
|
232
258
|
list = list.filter((item) => {
|
|
@@ -238,7 +264,7 @@ export class DiscoverService {
|
|
|
238
264
|
.includes(decodeURIComponent(q).toLowerCase());
|
|
239
265
|
});
|
|
240
266
|
log(
|
|
241
|
-
'
|
|
267
|
+
'legacyGetAssistantCategories: filtered by query "%s", %d -> %d items',
|
|
242
268
|
q,
|
|
243
269
|
originalCount,
|
|
244
270
|
list.length,
|
|
@@ -246,25 +272,26 @@ export class DiscoverService {
|
|
|
246
272
|
}
|
|
247
273
|
const categoryCounts = countBy(list, (item) => item.category);
|
|
248
274
|
const result = Object.entries(categoryCounts)
|
|
249
|
-
.filter(([category]) => Boolean(category))
|
|
275
|
+
.filter(([category]) => Boolean(category))
|
|
250
276
|
.map(([category, count]) => ({
|
|
251
277
|
category,
|
|
252
278
|
count,
|
|
253
279
|
}));
|
|
254
|
-
log('
|
|
280
|
+
log('legacyGetAssistantCategories: returning %d categories', result.length);
|
|
255
281
|
return result;
|
|
256
282
|
};
|
|
257
283
|
|
|
258
|
-
|
|
284
|
+
private legacyGetAssistantDetail = async (params: {
|
|
259
285
|
identifier: string;
|
|
260
286
|
locale?: string;
|
|
287
|
+
version?: string;
|
|
261
288
|
}): Promise<DiscoverAssistantDetail | undefined> => {
|
|
262
|
-
log('
|
|
289
|
+
log('legacyGetAssistantDetail: params=%O', params);
|
|
263
290
|
const { locale, identifier } = params;
|
|
264
291
|
const normalizedLocale = normalizeLocale(locale);
|
|
265
292
|
let data = await this.assistantStore.getAgent(identifier, normalizedLocale);
|
|
266
293
|
if (!data) {
|
|
267
|
-
log('
|
|
294
|
+
log('legacyGetAssistantDetail: assistant not found for identifier=%s', identifier);
|
|
268
295
|
return;
|
|
269
296
|
}
|
|
270
297
|
const { meta, ...item } = data;
|
|
@@ -274,30 +301,36 @@ export class DiscoverService {
|
|
|
274
301
|
locale,
|
|
275
302
|
page: 1,
|
|
276
303
|
pageSize: 7,
|
|
304
|
+
source: 'legacy',
|
|
277
305
|
});
|
|
278
306
|
const result = {
|
|
279
307
|
...assistant,
|
|
280
308
|
related: list.items.filter((item) => item.identifier !== assistant.identifier).slice(0, 6),
|
|
281
309
|
};
|
|
282
|
-
log(
|
|
310
|
+
log(
|
|
311
|
+
'legacyGetAssistantDetail: returning assistant with %d related items',
|
|
312
|
+
result.related.length,
|
|
313
|
+
);
|
|
283
314
|
return result;
|
|
284
315
|
};
|
|
285
316
|
|
|
286
|
-
|
|
287
|
-
log('
|
|
288
|
-
const list = await this.
|
|
317
|
+
private legacyGetAssistantIdentifiers = async (): Promise<IdentifiersResponse> => {
|
|
318
|
+
log('legacyGetAssistantIdentifiers: fetching identifiers');
|
|
319
|
+
const list = await this.legacyGetAssistantListRaw();
|
|
289
320
|
const result = list.map((item) => {
|
|
290
321
|
return {
|
|
291
322
|
identifier: item.identifier,
|
|
292
323
|
lastModified: item.createdAt,
|
|
293
324
|
};
|
|
294
325
|
});
|
|
295
|
-
log('
|
|
326
|
+
log('legacyGetAssistantIdentifiers: returning %d identifiers', result.length);
|
|
296
327
|
return result;
|
|
297
328
|
};
|
|
298
329
|
|
|
299
|
-
|
|
300
|
-
|
|
330
|
+
private legacyGetAssistantList = async (
|
|
331
|
+
params: AssistantQueryParams = {},
|
|
332
|
+
): Promise<AssistantListResponse> => {
|
|
333
|
+
log('legacyGetAssistantList: params=%O', params);
|
|
301
334
|
const {
|
|
302
335
|
locale,
|
|
303
336
|
category,
|
|
@@ -306,14 +339,29 @@ export class DiscoverService {
|
|
|
306
339
|
pageSize = 20,
|
|
307
340
|
q,
|
|
308
341
|
sort = AssistantSorts.CreatedAt,
|
|
342
|
+
ownerId,
|
|
309
343
|
} = params;
|
|
310
|
-
|
|
344
|
+
const currentPage = Number(page) || 1;
|
|
345
|
+
const currentPageSize = Number(pageSize) || 20;
|
|
346
|
+
|
|
347
|
+
if (ownerId) {
|
|
348
|
+
log('legacyGetAssistantList: ownerId filter not supported in legacy source');
|
|
349
|
+
return {
|
|
350
|
+
currentPage,
|
|
351
|
+
items: [],
|
|
352
|
+
pageSize: currentPageSize,
|
|
353
|
+
totalCount: 0,
|
|
354
|
+
totalPages: 0,
|
|
355
|
+
};
|
|
356
|
+
}
|
|
357
|
+
|
|
358
|
+
let list = await this.legacyGetAssistantListRaw(locale);
|
|
311
359
|
const originalCount = list.length;
|
|
312
360
|
|
|
313
361
|
if (category) {
|
|
314
362
|
list = list.filter((item) => item.category === category);
|
|
315
363
|
log(
|
|
316
|
-
'
|
|
364
|
+
'legacyGetAssistantList: filtered by category "%s", %d -> %d items',
|
|
317
365
|
category,
|
|
318
366
|
originalCount,
|
|
319
367
|
list.length,
|
|
@@ -330,11 +378,16 @@ export class DiscoverService {
|
|
|
330
378
|
.toLowerCase()
|
|
331
379
|
.includes(decodeURIComponent(q).toLowerCase());
|
|
332
380
|
});
|
|
333
|
-
log(
|
|
381
|
+
log(
|
|
382
|
+
'legacyGetAssistantList: filtered by query "%s", %d -> %d items',
|
|
383
|
+
q,
|
|
384
|
+
beforeFilter,
|
|
385
|
+
list.length,
|
|
386
|
+
);
|
|
334
387
|
}
|
|
335
388
|
|
|
336
389
|
if (sort) {
|
|
337
|
-
log('
|
|
390
|
+
log('legacyGetAssistantList: sorting by %s %s', sort, order);
|
|
338
391
|
switch (sort) {
|
|
339
392
|
case AssistantSorts.CreatedAt: {
|
|
340
393
|
list = list.sort((a, b) => {
|
|
@@ -349,9 +402,9 @@ export class DiscoverService {
|
|
|
349
402
|
case AssistantSorts.KnowledgeCount: {
|
|
350
403
|
list = list.sort((a, b) => {
|
|
351
404
|
if (order === 'asc') {
|
|
352
|
-
return a.knowledgeCount - b.knowledgeCount;
|
|
405
|
+
return (a.knowledgeCount || 0) - (b.knowledgeCount || 0);
|
|
353
406
|
} else {
|
|
354
|
-
return b.knowledgeCount - a.knowledgeCount;
|
|
407
|
+
return (b.knowledgeCount || 0) - (a.knowledgeCount || 0);
|
|
355
408
|
}
|
|
356
409
|
});
|
|
357
410
|
break;
|
|
@@ -359,9 +412,9 @@ export class DiscoverService {
|
|
|
359
412
|
case AssistantSorts.PluginCount: {
|
|
360
413
|
list = list.sort((a, b) => {
|
|
361
414
|
if (order === 'asc') {
|
|
362
|
-
return a.pluginCount - b.pluginCount;
|
|
415
|
+
return (a.pluginCount || 0) - (b.pluginCount || 0);
|
|
363
416
|
} else {
|
|
364
|
-
return b.pluginCount - a.pluginCount;
|
|
417
|
+
return (b.pluginCount || 0) - (a.pluginCount || 0);
|
|
365
418
|
}
|
|
366
419
|
});
|
|
367
420
|
break;
|
|
@@ -369,9 +422,9 @@ export class DiscoverService {
|
|
|
369
422
|
case AssistantSorts.TokenUsage: {
|
|
370
423
|
list = list.sort((a, b) => {
|
|
371
424
|
if (order === 'asc') {
|
|
372
|
-
return a.tokenUsage - b.tokenUsage;
|
|
425
|
+
return (a.tokenUsage || 0) - (b.tokenUsage || 0);
|
|
373
426
|
} else {
|
|
374
|
-
return b.tokenUsage - a.tokenUsage;
|
|
427
|
+
return (b.tokenUsage || 0) - (a.tokenUsage || 0);
|
|
375
428
|
}
|
|
376
429
|
});
|
|
377
430
|
break;
|
|
@@ -396,25 +449,271 @@ export class DiscoverService {
|
|
|
396
449
|
});
|
|
397
450
|
break;
|
|
398
451
|
}
|
|
452
|
+
default: {
|
|
453
|
+
break;
|
|
454
|
+
}
|
|
399
455
|
}
|
|
400
456
|
}
|
|
401
457
|
|
|
458
|
+
const start = (currentPage - 1) * currentPageSize;
|
|
459
|
+
const end = currentPage * currentPageSize;
|
|
402
460
|
const result = {
|
|
403
|
-
currentPage
|
|
404
|
-
items: list.slice(
|
|
405
|
-
pageSize,
|
|
461
|
+
currentPage,
|
|
462
|
+
items: list.slice(start, end),
|
|
463
|
+
pageSize: currentPageSize,
|
|
406
464
|
totalCount: list.length,
|
|
407
|
-
totalPages: Math.ceil(list.length /
|
|
465
|
+
totalPages: Math.ceil(list.length / currentPageSize),
|
|
408
466
|
};
|
|
409
467
|
log(
|
|
410
|
-
'
|
|
411
|
-
|
|
468
|
+
'legacyGetAssistantList: returning page %d/%d with %d items',
|
|
469
|
+
currentPage,
|
|
412
470
|
result.totalPages,
|
|
413
471
|
result.items.length,
|
|
414
472
|
);
|
|
415
473
|
return result;
|
|
416
474
|
};
|
|
417
475
|
|
|
476
|
+
// ============================== Assistant Market ==============================
|
|
477
|
+
|
|
478
|
+
getAssistantCategories = async (
|
|
479
|
+
params: CategoryListQuery & { source?: AssistantMarketSource } = {},
|
|
480
|
+
): Promise<CategoryItem[]> => {
|
|
481
|
+
log('getAssistantCategories: params=%O', params);
|
|
482
|
+
const { source, ...rest } = params;
|
|
483
|
+
if (this.isLegacySource(source)) {
|
|
484
|
+
return this.legacyGetAssistantCategories(rest);
|
|
485
|
+
}
|
|
486
|
+
|
|
487
|
+
const { q, locale } = rest;
|
|
488
|
+
const normalizedLocale = normalizeLocale(locale);
|
|
489
|
+
|
|
490
|
+
try {
|
|
491
|
+
// @ts-ignore
|
|
492
|
+
const categories = await this.market.agents.getCategories({
|
|
493
|
+
locale: normalizedLocale,
|
|
494
|
+
q,
|
|
495
|
+
});
|
|
496
|
+
log('getAssistantCategories: returning %d categories from market SDK', categories.length);
|
|
497
|
+
return categories;
|
|
498
|
+
} catch (error) {
|
|
499
|
+
log('getAssistantCategories: error fetching from market SDK: %O', error);
|
|
500
|
+
return [];
|
|
501
|
+
}
|
|
502
|
+
};
|
|
503
|
+
|
|
504
|
+
getAssistantDetail = async (params: {
|
|
505
|
+
identifier: string;
|
|
506
|
+
locale?: string;
|
|
507
|
+
source?: AssistantMarketSource;
|
|
508
|
+
version?: string;
|
|
509
|
+
}): Promise<DiscoverAssistantDetail | undefined> => {
|
|
510
|
+
log('getAssistantDetail: params=%O', params);
|
|
511
|
+
const { source, ...rest } = params;
|
|
512
|
+
if (this.isLegacySource(source)) {
|
|
513
|
+
return this.legacyGetAssistantDetail(rest);
|
|
514
|
+
}
|
|
515
|
+
|
|
516
|
+
const { locale, identifier, version } = rest;
|
|
517
|
+
const normalizedLocale = normalizeLocale(locale);
|
|
518
|
+
|
|
519
|
+
try {
|
|
520
|
+
// @ts-ignore
|
|
521
|
+
const data = await this.market.agents.getAgentDetail(identifier, {
|
|
522
|
+
locale: normalizedLocale,
|
|
523
|
+
version,
|
|
524
|
+
});
|
|
525
|
+
|
|
526
|
+
if (!data) {
|
|
527
|
+
log('getAssistantDetail: assistant not found for identifier=%s', identifier);
|
|
528
|
+
return;
|
|
529
|
+
}
|
|
530
|
+
|
|
531
|
+
const normalizedAuthor = this.normalizeAuthorField(data.author);
|
|
532
|
+
const assistant = {
|
|
533
|
+
author: normalizedAuthor || (data.ownerId !== null ? `User${data.ownerId}` : 'Unknown'),
|
|
534
|
+
avatar: data.avatar || normalizedAuthor || '',
|
|
535
|
+
category: (data as any).category || 'general',
|
|
536
|
+
config: data.config || {},
|
|
537
|
+
createdAt: (data as any).createdAt,
|
|
538
|
+
currentVersion: data.version,
|
|
539
|
+
description: (data as any).description || data.summary,
|
|
540
|
+
examples: Array.isArray((data as any).examples)
|
|
541
|
+
? (data as any).examples.map((example: any) => ({
|
|
542
|
+
content: typeof example === 'string' ? example : example.content || '',
|
|
543
|
+
role: example.role || 'user',
|
|
544
|
+
}))
|
|
545
|
+
: [],
|
|
546
|
+
homepage:
|
|
547
|
+
(data as any).homepage ||
|
|
548
|
+
`https://lobehub.com/discover/assistant/${(data as any).identifier}`,
|
|
549
|
+
identifier: (data as any).identifier,
|
|
550
|
+
knowledgeCount:
|
|
551
|
+
(data.config as any)?.knowledgeBases?.length || (data as any).knowledgeCount || 0,
|
|
552
|
+
pluginCount: (data.config as any)?.plugins?.length || (data as any).pluginCount || 0,
|
|
553
|
+
readme: data.documentationUrl || '',
|
|
554
|
+
schemaVersion: 1,
|
|
555
|
+
status: data.status,
|
|
556
|
+
summary: data.summary || '',
|
|
557
|
+
systemRole: (data.config as any)?.systemRole || '',
|
|
558
|
+
tags: data.tags || [],
|
|
559
|
+
title: (data as any).name || (data as any).identifier,
|
|
560
|
+
tokenUsage: data.tokenUsage || 0,
|
|
561
|
+
versions:
|
|
562
|
+
// @ts-ignore
|
|
563
|
+
data.versions?.map((item) => ({
|
|
564
|
+
createdAt: (item as any).createdAt || item.updatedAt,
|
|
565
|
+
isLatest: item.isLatest,
|
|
566
|
+
isValidated: item.isValidated,
|
|
567
|
+
status: item.status as any,
|
|
568
|
+
version: item.version,
|
|
569
|
+
})) || [],
|
|
570
|
+
};
|
|
571
|
+
|
|
572
|
+
// Get related assistants
|
|
573
|
+
const list = await this.getAssistantList({
|
|
574
|
+
category: assistant.category,
|
|
575
|
+
locale,
|
|
576
|
+
page: 1,
|
|
577
|
+
pageSize: 7,
|
|
578
|
+
source,
|
|
579
|
+
});
|
|
580
|
+
|
|
581
|
+
const result = {
|
|
582
|
+
...assistant,
|
|
583
|
+
related: list.items.filter((item) => item.identifier !== assistant.identifier).slice(0, 6),
|
|
584
|
+
};
|
|
585
|
+
|
|
586
|
+
log('getAssistantDetail: returning assistant with %d related items', result.related.length);
|
|
587
|
+
return result;
|
|
588
|
+
} catch (error) {
|
|
589
|
+
log('getAssistantDetail: error fetching from market SDK: %O', error);
|
|
590
|
+
return;
|
|
591
|
+
}
|
|
592
|
+
};
|
|
593
|
+
|
|
594
|
+
getAssistantIdentifiers = async (
|
|
595
|
+
params: { source?: AssistantMarketSource } = {},
|
|
596
|
+
): Promise<IdentifiersResponse> => {
|
|
597
|
+
log('getAssistantIdentifiers: fetching identifiers with params=%O', params);
|
|
598
|
+
if (this.isLegacySource(params.source)) {
|
|
599
|
+
return this.legacyGetAssistantIdentifiers();
|
|
600
|
+
}
|
|
601
|
+
|
|
602
|
+
try {
|
|
603
|
+
// @ts-ignore
|
|
604
|
+
const identifiers = await this.market.agents.getPublishedIdentifiers();
|
|
605
|
+
// @ts-ignore
|
|
606
|
+
const result = identifiers.map((item) => ({
|
|
607
|
+
identifier: item.id,
|
|
608
|
+
lastModified: item.lastModified,
|
|
609
|
+
}));
|
|
610
|
+
log('getAssistantIdentifiers: returning %d identifiers from market SDK', result.length);
|
|
611
|
+
return result;
|
|
612
|
+
} catch (error) {
|
|
613
|
+
log('getAssistantIdentifiers: error fetching from market SDK: %O', error);
|
|
614
|
+
return [];
|
|
615
|
+
}
|
|
616
|
+
};
|
|
617
|
+
|
|
618
|
+
getAssistantList = async (params: AssistantQueryParams = {}): Promise<AssistantListResponse> => {
|
|
619
|
+
log('getAssistantList: params=%O', params);
|
|
620
|
+
const { source, ...rest } = params;
|
|
621
|
+
if (this.isLegacySource(source)) {
|
|
622
|
+
return this.legacyGetAssistantList(rest);
|
|
623
|
+
}
|
|
624
|
+
|
|
625
|
+
const {
|
|
626
|
+
locale,
|
|
627
|
+
category,
|
|
628
|
+
order = 'desc',
|
|
629
|
+
page = 1,
|
|
630
|
+
pageSize = 20,
|
|
631
|
+
q,
|
|
632
|
+
sort = AssistantSorts.CreatedAt,
|
|
633
|
+
ownerId,
|
|
634
|
+
} = rest;
|
|
635
|
+
|
|
636
|
+
try {
|
|
637
|
+
const normalizedLocale = normalizeLocale(locale);
|
|
638
|
+
|
|
639
|
+
let apiSort: 'createdAt' | 'updatedAt' | 'name' = 'createdAt';
|
|
640
|
+
switch (sort) {
|
|
641
|
+
case AssistantSorts.Identifier:
|
|
642
|
+
case AssistantSorts.Title: {
|
|
643
|
+
apiSort = 'name';
|
|
644
|
+
break;
|
|
645
|
+
}
|
|
646
|
+
case AssistantSorts.CreatedAt:
|
|
647
|
+
case AssistantSorts.MyOwn: {
|
|
648
|
+
apiSort = 'createdAt';
|
|
649
|
+
break;
|
|
650
|
+
}
|
|
651
|
+
default: {
|
|
652
|
+
apiSort = 'createdAt';
|
|
653
|
+
}
|
|
654
|
+
}
|
|
655
|
+
|
|
656
|
+
// @ts-ignore
|
|
657
|
+
const data = await this.market.agents.getAgentList({
|
|
658
|
+
category,
|
|
659
|
+
locale: normalizedLocale,
|
|
660
|
+
order,
|
|
661
|
+
ownerId,
|
|
662
|
+
page,
|
|
663
|
+
pageSize,
|
|
664
|
+
q,
|
|
665
|
+
sort: apiSort,
|
|
666
|
+
status: 'published',
|
|
667
|
+
visibility: 'public',
|
|
668
|
+
});
|
|
669
|
+
|
|
670
|
+
const transformedItems: DiscoverAssistantItem[] = (data.items || []).map((item: any) => {
|
|
671
|
+
const normalizedAuthor = this.normalizeAuthorField(item.author);
|
|
672
|
+
return {
|
|
673
|
+
author: normalizedAuthor || (item.ownerId !== null ? `User${item.ownerId}` : 'Unknown'),
|
|
674
|
+
avatar: item.avatar || normalizedAuthor || '',
|
|
675
|
+
category: item.category || 'general',
|
|
676
|
+
config: item.config || {},
|
|
677
|
+
createdAt: item.createdAt || item.updatedAt || new Date().toISOString(),
|
|
678
|
+
description: item.description || item.summary || '',
|
|
679
|
+
homepage: item.homepage || `https://lobehub.com/discover/assistant/${item.identifier}`,
|
|
680
|
+
identifier: item.identifier,
|
|
681
|
+
knowledgeCount: item.knowledgeCount ?? item.config?.knowledgeBases?.length ?? 0,
|
|
682
|
+
pluginCount: item.pluginCount ?? item.config?.plugins?.length ?? 0,
|
|
683
|
+
schemaVersion: item.schemaVersion ?? 1,
|
|
684
|
+
tags: item.tags || [],
|
|
685
|
+
title: item.name || item.identifier,
|
|
686
|
+
tokenUsage: item.tokenUsage || 0,
|
|
687
|
+
};
|
|
688
|
+
});
|
|
689
|
+
|
|
690
|
+
const result: AssistantListResponse = {
|
|
691
|
+
currentPage: data.currentPage || page,
|
|
692
|
+
items: transformedItems,
|
|
693
|
+
pageSize: data.pageSize || pageSize,
|
|
694
|
+
totalCount: data.totalCount || 0,
|
|
695
|
+
totalPages: data.totalPages || 0,
|
|
696
|
+
};
|
|
697
|
+
|
|
698
|
+
log(
|
|
699
|
+
'getAssistantList: returning page %d/%d with %d items from market SDK',
|
|
700
|
+
result.currentPage,
|
|
701
|
+
result.totalPages,
|
|
702
|
+
result.items.length,
|
|
703
|
+
);
|
|
704
|
+
return result;
|
|
705
|
+
} catch (error) {
|
|
706
|
+
log('getAssistantList: error fetching from market SDK: %O', error);
|
|
707
|
+
return {
|
|
708
|
+
currentPage: page,
|
|
709
|
+
items: [],
|
|
710
|
+
pageSize,
|
|
711
|
+
totalCount: 0,
|
|
712
|
+
totalPages: 0,
|
|
713
|
+
};
|
|
714
|
+
}
|
|
715
|
+
};
|
|
716
|
+
|
|
418
717
|
// ============================== MCP Market ==============================
|
|
419
718
|
|
|
420
719
|
getMcpCategories = async (params: CategoryListQuery = {}): Promise<CategoryItem[]> => {
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import debug from 'debug';
|
|
1
2
|
import { sha256 } from 'js-sha256';
|
|
2
3
|
import { existsSync, readFileSync } from 'node:fs';
|
|
3
4
|
import path from 'node:path';
|
|
@@ -8,6 +9,8 @@ import { inferContentTypeFromImageUrl } from '@/utils/url';
|
|
|
8
9
|
import { FileServiceImpl } from './type';
|
|
9
10
|
import { extractKeyFromUrlOrReturnOriginal } from './utils';
|
|
10
11
|
|
|
12
|
+
const log = debug('lobe-file:desktop-local');
|
|
13
|
+
|
|
11
14
|
/**
|
|
12
15
|
* 桌面应用本地文件服务实现
|
|
13
16
|
*/
|
|
@@ -202,7 +205,7 @@ export class DesktopLocalFileImpl implements FileServiceImpl {
|
|
|
202
205
|
throw new Error('Failed to upload file via Electron IPC');
|
|
203
206
|
}
|
|
204
207
|
|
|
205
|
-
|
|
208
|
+
log('File uploaded successfully: %O', result.metadata);
|
|
206
209
|
return { key: result.metadata.path };
|
|
207
210
|
} catch (error) {
|
|
208
211
|
console.error('[DesktopLocalFileImpl] Failed to upload media file:', error);
|
|
@@ -1,11 +1,12 @@
|
|
|
1
1
|
import { LobeChatDatabase } from '@lobechat/database';
|
|
2
|
+
import { inferContentTypeFromImageUrl, nanoid, uuid } from '@lobechat/utils';
|
|
2
3
|
import { TRPCError } from '@trpc/server';
|
|
4
|
+
import { sha256 } from 'js-sha256';
|
|
3
5
|
|
|
4
6
|
import { serverDBEnv } from '@/config/db';
|
|
5
7
|
import { FileModel } from '@/database/models/file';
|
|
6
8
|
import { FileItem } from '@/database/schemas';
|
|
7
9
|
import { TempFileManager } from '@/server/utils/tempFileManager';
|
|
8
|
-
import { nanoid } from '@/utils/uuid';
|
|
9
10
|
|
|
10
11
|
import { FileServiceImpl, createFileServiceModule } from './impls';
|
|
11
12
|
|
|
@@ -94,6 +95,100 @@ export class FileService {
|
|
|
94
95
|
return this.impl.uploadMedia(key, buffer);
|
|
95
96
|
}
|
|
96
97
|
|
|
98
|
+
/**
|
|
99
|
+
* Create file record (common method)
|
|
100
|
+
* Automatically handles globalFiles deduplication logic
|
|
101
|
+
*
|
|
102
|
+
* @param params - File parameters
|
|
103
|
+
* @param params.id - Optional custom file ID (defaults to auto-generated)
|
|
104
|
+
* @returns File record and proxy URL
|
|
105
|
+
*/
|
|
106
|
+
public async createFileRecord(params: {
|
|
107
|
+
fileHash: string;
|
|
108
|
+
fileType: string;
|
|
109
|
+
id?: string;
|
|
110
|
+
name: string;
|
|
111
|
+
size: number;
|
|
112
|
+
url: string;
|
|
113
|
+
}): Promise<{ fileId: string; url: string }> {
|
|
114
|
+
// Check if hash already exists in globalFiles
|
|
115
|
+
const { isExist } = await this.fileModel.checkHash(params.fileHash);
|
|
116
|
+
|
|
117
|
+
// Create database record
|
|
118
|
+
// If hash doesn't exist, also create globalFiles record
|
|
119
|
+
const { id } = await this.fileModel.create(
|
|
120
|
+
{
|
|
121
|
+
fileHash: params.fileHash,
|
|
122
|
+
fileType: params.fileType,
|
|
123
|
+
id: params.id, // Use custom ID if provided
|
|
124
|
+
name: params.name,
|
|
125
|
+
size: params.size,
|
|
126
|
+
url: params.url,
|
|
127
|
+
},
|
|
128
|
+
!isExist, // insertToGlobalFiles
|
|
129
|
+
);
|
|
130
|
+
|
|
131
|
+
// Return unified proxy URL: /f/:id
|
|
132
|
+
return {
|
|
133
|
+
fileId: id,
|
|
134
|
+
url: `/f/${id}`,
|
|
135
|
+
};
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
/**
|
|
139
|
+
* Upload base64 data and create database record
|
|
140
|
+
* @param base64Data - Base64 data (supports data URI format or pure base64)
|
|
141
|
+
* @param pathname - File storage path (must include file extension)
|
|
142
|
+
* @returns Contains key (storage path), fileId (database record ID) and url (proxy access path)
|
|
143
|
+
*/
|
|
144
|
+
public async uploadBase64(
|
|
145
|
+
base64Data: string,
|
|
146
|
+
pathname: string,
|
|
147
|
+
): Promise<{ fileId: string; key: string; url: string }> {
|
|
148
|
+
let base64String: string;
|
|
149
|
+
|
|
150
|
+
// If data URI format (data:image/png;base64,xxx)
|
|
151
|
+
if (base64Data.startsWith('data:')) {
|
|
152
|
+
const commaIndex = base64Data.indexOf(',');
|
|
153
|
+
if (commaIndex === -1) {
|
|
154
|
+
throw new TRPCError({ code: 'BAD_REQUEST', message: 'Invalid base64 data format' });
|
|
155
|
+
}
|
|
156
|
+
base64String = base64Data.slice(commaIndex + 1);
|
|
157
|
+
} else {
|
|
158
|
+
// Pure base64 string
|
|
159
|
+
base64String = base64Data;
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
// Convert to Buffer
|
|
163
|
+
const buffer = Buffer.from(base64String, 'base64');
|
|
164
|
+
|
|
165
|
+
// Upload to storage (S3 or local)
|
|
166
|
+
const { key } = await this.uploadMedia(pathname, buffer);
|
|
167
|
+
|
|
168
|
+
// Extract filename from pathname
|
|
169
|
+
const name = pathname.split('/').pop() || 'unknown';
|
|
170
|
+
|
|
171
|
+
// Calculate file metadata
|
|
172
|
+
const size = buffer.length;
|
|
173
|
+
const fileType = inferContentTypeFromImageUrl(pathname) || 'application/octet-stream';
|
|
174
|
+
const hash = sha256(buffer);
|
|
175
|
+
|
|
176
|
+
// Generate UUID for cleaner URLs
|
|
177
|
+
const fileId = uuid();
|
|
178
|
+
|
|
179
|
+
// Use common method to create file record
|
|
180
|
+
const { fileId: createdId, url } = await this.createFileRecord({
|
|
181
|
+
fileHash: hash,
|
|
182
|
+
fileType,
|
|
183
|
+
id: fileId, // Use UUID instead of auto-generated ID
|
|
184
|
+
name,
|
|
185
|
+
size,
|
|
186
|
+
url: key, // Store original key (S3 key or desktop://)
|
|
187
|
+
});
|
|
188
|
+
|
|
189
|
+
return { fileId: createdId, key, url };
|
|
190
|
+
}
|
|
191
|
+
|
|
97
192
|
async downloadFileToLocal(
|
|
98
193
|
fileId: string,
|
|
99
194
|
): Promise<{ cleanup: () => void; file: FileItem; filePath: string }> {
|