@lobehub/lobehub 2.0.0-next.224 → 2.0.0-next.226

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (34) hide show
  1. package/.github/workflows/test.yml +18 -14
  2. package/CHANGELOG.md +50 -0
  3. package/changelog/v1.json +14 -0
  4. package/locales/en-US/common.json +3 -2
  5. package/locales/en-US/setting.json +4 -0
  6. package/locales/zh-CN/setting.json +4 -0
  7. package/package.json +2 -2
  8. package/packages/database/src/models/user.ts +33 -0
  9. package/packages/database/src/repositories/knowledge/index.ts +1 -1
  10. package/src/app/[variants]/(main)/chat/profile/features/Header/AgentPublishButton/ForkConfirmModal.tsx +67 -0
  11. package/src/app/[variants]/(main)/chat/profile/features/Header/AgentPublishButton/PublishButton.tsx +92 -105
  12. package/src/app/[variants]/(main)/chat/profile/features/Header/AgentPublishButton/index.tsx +13 -48
  13. package/src/app/[variants]/(main)/chat/profile/features/Header/AgentPublishButton/useMarketPublish.ts +69 -93
  14. package/src/business/client/hooks/useRenderBusinessChatErrorMessageExtra.tsx +10 -0
  15. package/src/features/CommandMenu/ContextCommands.tsx +97 -37
  16. package/src/features/CommandMenu/SearchResults.tsx +100 -276
  17. package/src/features/CommandMenu/components/CommandItem.tsx +1 -1
  18. package/src/features/CommandMenu/utils/contextCommands.ts +56 -1
  19. package/src/features/Conversation/Error/index.tsx +7 -1
  20. package/src/layout/AuthProvider/MarketAuth/MarketAuthProvider.tsx +11 -28
  21. package/src/layout/AuthProvider/MarketAuth/ProfileSetupModal.tsx +30 -25
  22. package/src/layout/AuthProvider/MarketAuth/useMarketUserProfile.ts +4 -9
  23. package/src/libs/redis/manager.ts +51 -15
  24. package/src/libs/trpc/lambda/middleware/index.ts +1 -0
  25. package/src/libs/trpc/lambda/middleware/marketSDK.ts +68 -0
  26. package/src/locales/default/common.ts +2 -2
  27. package/src/locales/default/setting.ts +5 -0
  28. package/src/server/routers/lambda/market/agent.ts +504 -0
  29. package/src/server/routers/lambda/market/index.ts +17 -0
  30. package/src/server/routers/lambda/market/oidc.ts +169 -0
  31. package/src/server/routers/lambda/market/social.ts +532 -0
  32. package/src/server/routers/lambda/market/user.ts +123 -0
  33. package/src/services/marketApi.ts +24 -84
  34. package/src/services/social.ts +70 -166
@@ -12,7 +12,8 @@ concurrency:
12
12
 
13
13
  jobs:
14
14
  # Check for duplicate runs
15
- pre_job:
15
+ check-duplicate-run:
16
+ name: Check Duplicate Run
16
17
  runs-on: ubuntu-latest
17
18
  outputs:
18
19
  should_skip: ${{ steps.skip_check.outputs.should_skip }}
@@ -26,8 +27,8 @@ jobs:
26
27
 
27
28
  # Package tests - all packages in single job to save runner resources
28
29
  test-packages:
29
- needs: pre_job
30
- if: needs.pre_job.outputs.should_skip != 'true'
30
+ needs: check-duplicate-run
31
+ if: needs.check-duplicate-run.outputs.should_skip != 'true'
31
32
  runs-on: ubuntu-latest
32
33
  name: Test Packages
33
34
  env:
@@ -61,21 +62,24 @@ jobs:
61
62
  - name: Upload coverage to Codecov
62
63
  if: always()
63
64
  run: |
65
+ curl -Os https://cli.codecov.io/latest/linux/codecov
66
+ chmod +x codecov
64
67
  for package in $PACKAGES; do
65
- # Extract directory name: @lobechat/file-loaders -> file-loaders, model-bank -> model-bank
66
68
  dir="${package#@lobechat/}"
67
69
  if [ -f "./packages/$dir/coverage/lcov.info" ]; then
68
- echo "Uploading coverage for $package..."
69
- npx codecov --token=${{ secrets.CODECOV_TOKEN }} \
70
- --file=./packages/$dir/coverage/lcov.info \
71
- --flags=packages/$dir
70
+ echo "Uploading coverage for $dir..."
71
+ ./codecov upload-process \
72
+ -t ${{ secrets.CODECOV_TOKEN }} \
73
+ -f ./packages/$dir/coverage/lcov.info \
74
+ -F packages/$dir \
75
+ --disable-search
72
76
  fi
73
77
  done
74
78
 
75
79
  # App tests
76
80
  test-website:
77
- needs: pre_job
78
- if: needs.pre_job.outputs.should_skip != 'true'
81
+ needs: check-duplicate-run
82
+ if: needs.check-duplicate-run.outputs.should_skip != 'true'
79
83
  name: Test Website
80
84
 
81
85
  runs-on: ubuntu-latest
@@ -108,8 +112,8 @@ jobs:
108
112
  flags: app
109
113
 
110
114
  test-desktop:
111
- needs: pre_job
112
- if: needs.pre_job.outputs.should_skip != 'true'
115
+ needs: check-duplicate-run
116
+ if: needs.check-duplicate-run.outputs.should_skip != 'true'
113
117
  name: Test Desktop App
114
118
 
115
119
  runs-on: ubuntu-latest
@@ -150,8 +154,8 @@ jobs:
150
154
  flags: desktop
151
155
 
152
156
  test-databsae:
153
- needs: pre_job
154
- if: needs.pre_job.outputs.should_skip != 'true'
157
+ needs: check-duplicate-run
158
+ if: needs.check-duplicate-run.outputs.should_skip != 'true'
155
159
  name: Test Database
156
160
 
157
161
  runs-on: ubuntu-latest
package/CHANGELOG.md CHANGED
@@ -2,6 +2,56 @@
2
2
 
3
3
  # Changelog
4
4
 
5
+ ## [Version 2.0.0-next.226](https://github.com/lobehub/lobe-chat/compare/v2.0.0-next.225...v2.0.0-next.226)
6
+
7
+ <sup>Released on **2026-01-06**</sup>
8
+
9
+ #### ♻ Code Refactoring
10
+
11
+ - **misc**: Change all market routes & api call into lambda trpc client call.
12
+
13
+ <br/>
14
+
15
+ <details>
16
+ <summary><kbd>Improvements and Fixes</kbd></summary>
17
+
18
+ #### Code refactoring
19
+
20
+ - **misc**: Change all market routes & api call into lambda trpc client call, closes [#11256](https://github.com/lobehub/lobe-chat/issues/11256) ([8f7e378](https://github.com/lobehub/lobe-chat/commit/8f7e378))
21
+
22
+ </details>
23
+
24
+ <div align="right">
25
+
26
+ [![](https://img.shields.io/badge/-BACK_TO_TOP-151515?style=flat-square)](#readme-top)
27
+
28
+ </div>
29
+
30
+ ## [Version 2.0.0-next.225](https://github.com/lobehub/lobe-chat/compare/v2.0.0-next.224...v2.0.0-next.225)
31
+
32
+ <sup>Released on **2026-01-06**</sup>
33
+
34
+ #### ✨ Features
35
+
36
+ - **ModelSwitchPanel**: Add provider preference storage in By Model view.
37
+
38
+ <br/>
39
+
40
+ <details>
41
+ <summary><kbd>Improvements and Fixes</kbd></summary>
42
+
43
+ #### What's improved
44
+
45
+ - **ModelSwitchPanel**: Add provider preference storage in By Model view, closes [#11246](https://github.com/lobehub/lobe-chat/issues/11246) ([d778093](https://github.com/lobehub/lobe-chat/commit/d778093))
46
+
47
+ </details>
48
+
49
+ <div align="right">
50
+
51
+ [![](https://img.shields.io/badge/-BACK_TO_TOP-151515?style=flat-square)](#readme-top)
52
+
53
+ </div>
54
+
5
55
  ## [Version 2.0.0-next.224](https://github.com/lobehub/lobe-chat/compare/v2.0.0-next.223...v2.0.0-next.224)
6
56
 
7
57
  <sup>Released on **2026-01-06**</sup>
package/changelog/v1.json CHANGED
@@ -1,4 +1,18 @@
1
1
  [
2
+ {
3
+ "children": {
4
+ "improvements": [
5
+ "Change all market routes & api call into lambda trpc client call."
6
+ ]
7
+ },
8
+ "date": "2026-01-06",
9
+ "version": "2.0.0-next.226"
10
+ },
11
+ {
12
+ "children": {},
13
+ "date": "2026-01-06",
14
+ "version": "2.0.0-next.225"
15
+ },
2
16
  {
3
17
  "children": {},
4
18
  "date": "2026-01-06",
@@ -119,8 +119,8 @@
119
119
  "cmdk.navigate": "Navigate",
120
120
  "cmdk.newAgent": "Create New Agent",
121
121
  "cmdk.newAgentTeam": "Create New Group",
122
- "cmdk.newLibrary": "New Library",
123
- "cmdk.newPage": "New Page",
122
+ "cmdk.newLibrary": "Create New Library",
123
+ "cmdk.newPage": "Create New Page",
124
124
  "cmdk.newTopic": "New topic in current Agent",
125
125
  "cmdk.noResults": "No results found",
126
126
  "cmdk.openSettings": "Open Settings",
@@ -158,6 +158,7 @@
158
158
  "cmdk.themeLight": "Light",
159
159
  "cmdk.toOpen": "Open",
160
160
  "cmdk.toSelect": "Select",
161
+ "cmdk.upgradePlan": "Upgrade Plan",
161
162
  "confirm": "Confirm",
162
163
  "contact": "Contact Us",
163
164
  "copy": "Copy",
@@ -127,6 +127,10 @@
127
127
  "llm.proxyUrl.title": "API proxy URL",
128
128
  "llm.waitingForMore": "More models are <1>planned to be added</1>, stay tuned",
129
129
  "llm.waitingForMoreLinkAriaLabel": "Open the Provider request form",
130
+ "marketPublish.forkConfirm.by": "by {{author}}",
131
+ "marketPublish.forkConfirm.confirm": "Confirm Publish",
132
+ "marketPublish.forkConfirm.description": "You are about to publish a derivative version based on an existing agent from the community. Your new agent will be created as a separate entry in the marketplace.",
133
+ "marketPublish.forkConfirm.title": "Publish Derivative Agent",
130
134
  "marketPublish.modal.changelog.extra": "Describe the key changes and improvements in this version",
131
135
  "marketPublish.modal.changelog.label": "Changelog",
132
136
  "marketPublish.modal.changelog.maxLengthError": "Changelog must not exceed 500 characters",
@@ -127,6 +127,10 @@
127
127
  "llm.proxyUrl.title": "API 代理地址",
128
128
  "llm.waitingForMore": "更多模型正在 <1>计划接入</1> 中,敬请期待",
129
129
  "llm.waitingForMoreLinkAriaLabel": "打开模型服务商接入需求表单",
130
+ "marketPublish.forkConfirm.by": "作者:{{author}}",
131
+ "marketPublish.forkConfirm.confirm": "确认发布",
132
+ "marketPublish.forkConfirm.description": "你即将基于社区中已存在的助理发布一个二次创作版本。你的新助理将作为独立条目发布到市场。",
133
+ "marketPublish.forkConfirm.title": "发布二次创作助理",
130
134
  "marketPublish.modal.changelog.extra": "描述此版本的主要变更和改进",
131
135
  "marketPublish.modal.changelog.label": "变更日志",
132
136
  "marketPublish.modal.changelog.maxLengthError": "变更日志不能超过500个字符",
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@lobehub/lobehub",
3
- "version": "2.0.0-next.224",
3
+ "version": "2.0.0-next.226",
4
4
  "description": "LobeHub - an open-source,comprehensive AI Agent framework that supports speech synthesis, multimodal, and extensible Function Call plugin system. Supports one-click free deployment of your private ChatGPT/LLM web application.",
5
5
  "keywords": [
6
6
  "framework",
@@ -204,7 +204,7 @@
204
204
  "@lobehub/desktop-ipc-typings": "workspace:*",
205
205
  "@lobehub/editor": "^3.4.1",
206
206
  "@lobehub/icons": "^4.0.2",
207
- "@lobehub/market-sdk": "^0.25.1",
207
+ "@lobehub/market-sdk": "^0.27.1",
208
208
  "@lobehub/tts": "^4.0.2",
209
209
  "@lobehub/ui": "^4.9.3",
210
210
  "@modelcontextprotocol/sdk": "^1.25.1",
@@ -1,5 +1,6 @@
1
1
  import {
2
2
  SSOProvider,
3
+ UserGeneralConfig,
3
4
  UserGuide,
4
5
  UserKeyVaults,
5
6
  UserPreference,
@@ -45,6 +46,11 @@ export type ListUsersForMemoryExtractorOptions = {
45
46
  whitelist?: string[];
46
47
  };
47
48
 
49
+ export interface UserInfoForAIGeneration {
50
+ responseLanguage: string;
51
+ userName: string;
52
+ }
53
+
48
54
  export class UserModel {
49
55
  private userId: string;
50
56
  private db: LobeChatDatabase;
@@ -339,4 +345,31 @@ export class UserModel {
339
345
  where,
340
346
  });
341
347
  };
348
+
349
+ /**
350
+ * Get user info for AI generation (name and language preference)
351
+ */
352
+ static getInfoForAIGeneration = async (
353
+ db: LobeChatDatabase,
354
+ userId: string,
355
+ ): Promise<UserInfoForAIGeneration> => {
356
+ const result = await db
357
+ .select({
358
+ firstName: users.firstName,
359
+ fullName: users.fullName,
360
+ general: userSettings.general,
361
+ })
362
+ .from(users)
363
+ .leftJoin(userSettings, eq(users.id, userSettings.id))
364
+ .where(eq(users.id, userId))
365
+ .limit(1);
366
+
367
+ const user = result[0];
368
+ const general = user?.general as UserGeneralConfig | undefined;
369
+
370
+ return {
371
+ responseLanguage: general?.responseLanguage || 'en-US',
372
+ userName: user?.fullName || user?.firstName || 'User',
373
+ };
374
+ };
342
375
  }
@@ -29,7 +29,7 @@ export interface KnowledgeItem {
29
29
  }
30
30
 
31
31
  /**
32
- * Knowledge Repository - combines files and documents into a unified interface
32
+ * Resources Repository - combines files and documents into a unified interface
33
33
  */
34
34
  export class KnowledgeRepo {
35
35
  private userId: string;
@@ -0,0 +1,67 @@
1
+ 'use client';
2
+
3
+ import { Avatar, Flexbox, Modal } from '@lobehub/ui';
4
+ import { memo } from 'react';
5
+ import { useTranslation } from 'react-i18next';
6
+
7
+ interface OriginalAgentInfo {
8
+ author?: {
9
+ avatar?: string;
10
+ name?: string;
11
+ userName?: string;
12
+ };
13
+ avatar?: string;
14
+ identifier: string;
15
+ name: string;
16
+ }
17
+
18
+ interface ForkConfirmModalProps {
19
+ loading?: boolean;
20
+ onCancel: () => void;
21
+ onConfirm: () => void;
22
+ open: boolean;
23
+ originalAgent: OriginalAgentInfo | null;
24
+ }
25
+
26
+ const ForkConfirmModal = memo<ForkConfirmModalProps>(
27
+ ({ open, onCancel, onConfirm, originalAgent, loading }) => {
28
+ const { t } = useTranslation('setting');
29
+
30
+ if (!originalAgent) return null;
31
+
32
+ const authorName = originalAgent.author?.name || originalAgent.author?.userName || 'Unknown';
33
+
34
+ return (
35
+ <Modal
36
+ cancelText={t('cancel', { ns: 'common' })}
37
+ centered
38
+ closable
39
+ confirmLoading={loading}
40
+ okText={t('marketPublish.forkConfirm.confirm')}
41
+ onCancel={onCancel}
42
+ onOk={onConfirm}
43
+ open={open}
44
+ title={t('marketPublish.forkConfirm.title')}
45
+ width={480}
46
+ >
47
+ <Flexbox gap={16} style={{ marginTop: 16 }}>
48
+ <Flexbox align="center" gap={12} horizontal>
49
+ <Avatar avatar={originalAgent.avatar} size={48} style={{ flex: 'none' }} />
50
+ <Flexbox gap={4}>
51
+ <div style={{ fontWeight: 500 }}>{originalAgent.name}</div>
52
+ <div style={{ fontSize: 12, opacity: 0.6 }}>
53
+ {t('marketPublish.forkConfirm.by', { author: authorName })}
54
+ </div>
55
+ </Flexbox>
56
+ </Flexbox>
57
+
58
+ <p style={{ lineHeight: 1.6, margin: 0 }}>{t('marketPublish.forkConfirm.description')}</p>
59
+ </Flexbox>
60
+ </Modal>
61
+ );
62
+ },
63
+ );
64
+
65
+ ForkConfirmModal.displayName = 'ForkConfirmModal';
66
+
67
+ export default ForkConfirmModal;
@@ -1,127 +1,107 @@
1
1
  import { ActionIcon } from '@lobehub/ui';
2
2
  import { ShapesUploadIcon } from '@lobehub/ui/icons';
3
- import { memo, useCallback, useMemo } from 'react';
3
+ import { memo, useCallback, useMemo, useState } from 'react';
4
4
  import { useTranslation } from 'react-i18next';
5
5
 
6
6
  import { message } from '@/components/AntdStaticMethods';
7
7
  import { HEADER_ICON_SIZE } from '@/const/layoutTokens';
8
- import { checkOwnership } from '@/hooks/useAgentOwnershipCheck';
9
8
  import { useMarketAuth } from '@/layout/AuthProvider/MarketAuth';
10
9
  import { resolveMarketAuthError } from '@/layout/AuthProvider/MarketAuth/errors';
11
10
  import { useServerConfigStore } from '@/store/serverConfig';
12
11
 
12
+ import ForkConfirmModal from './ForkConfirmModal';
13
13
  import type { MarketPublishAction } from './types';
14
- import { useMarketPublish } from './useMarketPublish';
14
+ import { type OriginalAgentInfo, useMarketPublish } from './useMarketPublish';
15
15
 
16
16
  interface MarketPublishButtonProps {
17
17
  action: MarketPublishAction;
18
- marketIdentifier?: string;
19
18
  onPublishSuccess?: (identifier: string) => void;
20
19
  }
21
20
 
22
- const PublishButton = memo<MarketPublishButtonProps>(
23
- ({ action, marketIdentifier, onPublishSuccess }) => {
24
- const { t } = useTranslation(['setting', 'marketAuth']);
25
-
26
- const mobile = useServerConfigStore((s) => s.isMobile);
27
-
28
- const { isAuthenticated, isLoading, session, signIn } = useMarketAuth();
29
- const { isPublishing, publish } = useMarketPublish({
30
- action,
31
- onSuccess: onPublishSuccess,
32
- });
33
-
34
- const buttonConfig = useMemo(() => {
35
- if (action === 'upload') {
36
- return {
37
- authSuccessMessage: t('messages.success.upload', { ns: 'marketAuth' }),
38
- authenticated: t('marketPublish.upload.tooltip'),
39
- unauthenticated: t('marketPublish.upload.tooltip'),
40
- } as const;
41
- }
21
+ const PublishButton = memo<MarketPublishButtonProps>(({ action, onPublishSuccess }) => {
22
+ const { t } = useTranslation(['setting', 'marketAuth']);
23
+
24
+ const mobile = useServerConfigStore((s) => s.isMobile);
25
+
26
+ const { isAuthenticated, isLoading, signIn } = useMarketAuth();
27
+ const { checkOwnership, isCheckingOwnership, isPublishing, publish } = useMarketPublish({
28
+ action,
29
+ onSuccess: onPublishSuccess,
30
+ });
42
31
 
43
- const submitText = t('submitAgentModal.tooltips');
32
+ // Fork confirmation modal state
33
+ const [showForkModal, setShowForkModal] = useState(false);
34
+ const [originalAgentInfo, setOriginalAgentInfo] = useState<OriginalAgentInfo | null>(null);
44
35
 
36
+ const buttonConfig = useMemo(() => {
37
+ if (action === 'upload') {
45
38
  return {
46
- authSuccessMessage: t('messages.success.submit', { ns: 'marketAuth' }),
47
- authenticated: submitText,
48
- unauthenticated: t('marketPublish.submit.tooltip'),
39
+ authenticated: t('marketPublish.upload.tooltip'),
40
+ unauthenticated: t('marketPublish.upload.tooltip'),
49
41
  } as const;
50
- }, [action, t]);
51
-
52
- const handleButtonClick = useCallback(async () => {
53
- if (!isAuthenticated) {
54
- try {
55
- const accountId = await signIn();
56
- // Check ownership after authentication if marketIdentifier exists
57
- if (marketIdentifier && accountId !== null) {
58
- let accessToken = session?.accessToken;
59
-
60
- if (!accessToken && typeof window !== 'undefined') {
61
- const storedSession = sessionStorage.getItem('market_auth_session');
62
- if (storedSession) {
63
- try {
64
- const parsed = JSON.parse(storedSession) as { accessToken?: string };
65
- accessToken = parsed.accessToken;
66
- } catch (parseError) {
67
- console.error(
68
- '[MarketPublishButton] Failed to parse stored session:',
69
- parseError,
70
- );
71
- }
72
- }
73
- }
74
-
75
- if (accessToken) {
76
- try {
77
- const isOwner = await checkOwnership({
78
- accessToken,
79
- accountId,
80
- marketIdentifier,
81
- skipCache: true,
82
- });
83
-
84
- // If user is not the owner and trying to upload, just return
85
- // The parent component should handle the action switch
86
- if (!isOwner && action === 'upload') {
87
- return;
88
- }
89
- } catch (ownershipError) {
90
- console.error('[MarketPublishButton] Failed to confirm ownership:', ownershipError);
91
- }
92
- }
93
- }
94
-
95
- // After authentication, proceed with publish
96
- await publish();
97
- } catch (error) {
98
- console.error(`[MarketPublishButton][${action}] Authorization failed:`, error);
99
- const normalizedError = resolveMarketAuthError(error);
100
- message.error({
101
- content: t(`errors.${normalizedError.code}`, { ns: 'marketAuth' }),
102
- key: 'market-auth',
103
- });
104
- }
105
- return;
42
+ }
43
+
44
+ const submitText = t('submitAgentModal.tooltips');
45
+
46
+ return {
47
+ authenticated: submitText,
48
+ unauthenticated: t('marketPublish.submit.tooltip'),
49
+ } as const;
50
+ }, [action, t]);
51
+
52
+ const doPublish = useCallback(async () => {
53
+ // Check ownership before publishing
54
+ const { needsForkConfirm, originalAgent } = await checkOwnership();
55
+
56
+ if (needsForkConfirm && originalAgent) {
57
+ // Show fork confirmation modal
58
+ setOriginalAgentInfo(originalAgent);
59
+ setShowForkModal(true);
60
+ return;
61
+ }
62
+
63
+ // No confirmation needed, proceed with publish
64
+ await publish();
65
+ }, [checkOwnership, publish]);
66
+
67
+ const handleButtonClick = useCallback(async () => {
68
+ if (!isAuthenticated) {
69
+ try {
70
+ await signIn();
71
+ // After authentication, proceed with ownership check and publish
72
+ await doPublish();
73
+ } catch (error) {
74
+ console.error(`[MarketPublishButton][${action}] Authorization failed:`, error);
75
+ const normalizedError = resolveMarketAuthError(error);
76
+ message.error({
77
+ content: t(`errors.${normalizedError.code}`, { ns: 'marketAuth' }),
78
+ key: 'market-auth',
79
+ });
106
80
  }
107
-
108
- // User is authenticated, directly publish
109
- await publish();
110
- }, [
111
- action,
112
- buttonConfig.authSuccessMessage,
113
- isAuthenticated,
114
- marketIdentifier,
115
- publish,
116
- session?.accessToken,
117
- signIn,
118
- t,
119
- ]);
120
-
121
- const buttonTitle = isAuthenticated ? buttonConfig.authenticated : buttonConfig.unauthenticated;
122
- const loading = isLoading || isPublishing;
123
-
124
- return (
81
+ return;
82
+ }
83
+
84
+ // User is authenticated, check ownership and publish
85
+ await doPublish();
86
+ }, [action, doPublish, isAuthenticated, signIn, t]);
87
+
88
+ const handleForkConfirm = useCallback(async () => {
89
+ setShowForkModal(false);
90
+ setOriginalAgentInfo(null);
91
+ // User confirmed, proceed with publish
92
+ await publish();
93
+ }, [publish]);
94
+
95
+ const handleForkCancel = useCallback(() => {
96
+ setShowForkModal(false);
97
+ setOriginalAgentInfo(null);
98
+ }, []);
99
+
100
+ const buttonTitle = isAuthenticated ? buttonConfig.authenticated : buttonConfig.unauthenticated;
101
+ const loading = isLoading || isCheckingOwnership || isPublishing;
102
+
103
+ return (
104
+ <>
125
105
  <ActionIcon
126
106
  icon={ShapesUploadIcon}
127
107
  loading={loading}
@@ -129,9 +109,16 @@ const PublishButton = memo<MarketPublishButtonProps>(
129
109
  size={HEADER_ICON_SIZE(mobile)}
130
110
  title={buttonTitle}
131
111
  />
132
- );
133
- },
134
- );
112
+ <ForkConfirmModal
113
+ loading={isPublishing}
114
+ onCancel={handleForkCancel}
115
+ onConfirm={handleForkConfirm}
116
+ open={showForkModal}
117
+ originalAgent={originalAgentInfo}
118
+ />
119
+ </>
120
+ );
121
+ });
135
122
 
136
123
  PublishButton.displayName = 'MarketPublishButton';
137
124
 
@@ -1,20 +1,22 @@
1
- import { ActionIcon } from '@lobehub/ui';
2
1
  import isEqual from 'fast-deep-equal';
3
- import { Loader2 } from 'lucide-react';
4
- import { memo, useCallback, useMemo, useState } from 'react';
5
- import { useTranslation } from 'react-i18next';
2
+ import { memo, useCallback, useState } from 'react';
6
3
 
7
- import { useAgentOwnershipCheck } from '@/hooks/useAgentOwnershipCheck';
8
4
  import { useAgentStore } from '@/store/agent';
9
5
  import { agentSelectors } from '@/store/agent/selectors';
10
6
 
11
7
  import PublishButton from './PublishButton';
12
8
  import PublishResultModal from './PublishResultModal';
13
9
 
10
+ /**
11
+ * Agent Publish Button Component
12
+ *
13
+ * Simplified version - backend now handles ownership check automatically.
14
+ * The action type (submit vs upload) is determined by backend based on:
15
+ * 1. Whether the identifier exists
16
+ * 2. Whether the current user is the owner
17
+ */
14
18
  const AgentPublishButton = memo(() => {
15
- const { t } = useTranslation('setting');
16
19
  const meta = useAgentStore(agentSelectors.currentAgentMeta, isEqual);
17
- const { isOwnAgent } = useAgentOwnershipCheck(meta?.marketIdentifier);
18
20
 
19
21
  const [showResultModal, setShowResultModal] = useState(false);
20
22
  const [publishedIdentifier, setPublishedIdentifier] = useState<string>();
@@ -24,50 +26,13 @@ const AgentPublishButton = memo(() => {
24
26
  setShowResultModal(true);
25
27
  }, []);
26
28
 
27
- const buttonType = useMemo(() => {
28
- if (!meta?.marketIdentifier) {
29
- return 'submit';
30
- }
31
- if (isOwnAgent === null) {
32
- return 'loading';
33
- }
34
- if (isOwnAgent === true) {
35
- return 'upload';
36
- }
37
- return 'submit';
38
- }, [meta?.marketIdentifier, isOwnAgent]);
39
-
40
- const content = useMemo(() => {
41
- switch (buttonType) {
42
- case 'upload': {
43
- return (
44
- <PublishButton
45
- action="upload"
46
- marketIdentifier={meta?.marketIdentifier}
47
- onPublishSuccess={handlePublishSuccess}
48
- />
49
- );
50
- }
51
-
52
- case 'submit': {
53
- return (
54
- <PublishButton
55
- action="submit"
56
- marketIdentifier={meta?.marketIdentifier}
57
- onPublishSuccess={handlePublishSuccess}
58
- />
59
- );
60
- }
61
-
62
- default: {
63
- return <ActionIcon disabled icon={Loader2} loading title={t('checkingPermissions')} />;
64
- }
65
- }
66
- }, [buttonType, meta?.marketIdentifier]);
29
+ // Determine action based on whether we have an existing marketIdentifier
30
+ // Backend will verify ownership and decide to create new or update
31
+ const action = meta?.marketIdentifier ? 'upload' : 'submit';
67
32
 
68
33
  return (
69
34
  <>
70
- {content}
35
+ <PublishButton action={action} onPublishSuccess={handlePublishSuccess} />
71
36
  <PublishResultModal
72
37
  identifier={publishedIdentifier}
73
38
  onCancel={() => setShowResultModal(false)}