@lobehub/lobehub 2.0.0-next.251 → 2.0.0-next.253

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 (66) hide show
  1. package/CHANGELOG.md +58 -0
  2. package/apps/desktop/build/entitlements.mac.plist +9 -0
  3. package/apps/desktop/resources/locales/zh-CN/dialog.json +5 -1
  4. package/apps/desktop/resources/locales/zh-CN/menu.json +7 -0
  5. package/apps/desktop/src/main/controllers/SystemCtr.ts +186 -94
  6. package/apps/desktop/src/main/controllers/__tests__/SystemCtr.test.ts +200 -31
  7. package/apps/desktop/src/main/core/browser/Browser.ts +9 -0
  8. package/apps/desktop/src/main/locales/default/dialog.ts +7 -2
  9. package/apps/desktop/src/main/locales/default/menu.ts +7 -0
  10. package/apps/desktop/src/main/menus/impls/macOS.ts +44 -1
  11. package/apps/desktop/src/main/utils/fullDiskAccess.ts +121 -0
  12. package/changelog/v1.json +14 -0
  13. package/package.json +1 -1
  14. package/packages/builtin-tool-notebook/src/client/Render/CreateDocument/DocumentCard.tsx +0 -2
  15. package/packages/database/migrations/meta/_journal.json +1 -1
  16. package/packages/database/src/models/__tests__/topics/topic.create.test.ts +37 -8
  17. package/packages/database/src/models/topic.ts +71 -4
  18. package/packages/database/src/schemas/agentCronJob.ts +1 -2
  19. package/packages/electron-client-ipc/src/events/system.ts +1 -0
  20. package/packages/memory-user-memory/src/extractors/context.ts +1 -4
  21. package/packages/memory-user-memory/src/extractors/experience.ts +2 -8
  22. package/packages/memory-user-memory/src/extractors/preference.ts +2 -8
  23. package/packages/memory-user-memory/src/prompts/gatekeeper.ts +123 -123
  24. package/packages/memory-user-memory/src/prompts/layers/context.ts +152 -152
  25. package/packages/memory-user-memory/src/prompts/layers/experience.ts +159 -159
  26. package/packages/memory-user-memory/src/prompts/layers/identity.ts +213 -213
  27. package/packages/memory-user-memory/src/prompts/layers/preference.ts +160 -160
  28. package/packages/memory-user-memory/src/services/extractExecutor.ts +33 -30
  29. package/packages/memory-user-memory/src/types.ts +10 -8
  30. package/packages/types/src/topic/topic.ts +9 -0
  31. package/src/app/[variants]/(desktop)/desktop-onboarding/features/PermissionsStep.tsx +16 -30
  32. package/src/app/[variants]/(desktop)/desktop-onboarding/index.tsx +19 -9
  33. package/src/app/[variants]/(desktop)/desktop-onboarding/storage.ts +49 -0
  34. package/src/app/[variants]/(main)/chat/_layout/Sidebar/Body.tsx +4 -1
  35. package/src/app/[variants]/(main)/chat/_layout/Sidebar/Topic/CronTopicList/CronTopicGroup.tsx +74 -0
  36. package/src/app/[variants]/(main)/chat/_layout/Sidebar/Topic/CronTopicList/CronTopicItem.tsx +40 -0
  37. package/src/app/[variants]/(main)/chat/_layout/Sidebar/Topic/CronTopicList/index.tsx +140 -0
  38. package/src/app/[variants]/(main)/chat/_layout/Sidebar/Topic/List/index.tsx +1 -1
  39. package/src/app/[variants]/(main)/chat/_layout/Sidebar/Topic/TopicListContent/index.tsx +1 -1
  40. package/src/app/[variants]/(main)/chat/_layout/Sidebar/Topic/index.tsx +1 -1
  41. package/src/app/[variants]/(main)/chat/cron/[cronId]/index.tsx +664 -0
  42. package/src/app/[variants]/(main)/chat/profile/features/AgentCronJobs/CronJobCards.tsx +160 -0
  43. package/src/app/[variants]/(main)/chat/profile/features/AgentCronJobs/CronJobForm.tsx +202 -0
  44. package/src/app/[variants]/(main)/chat/profile/features/AgentCronJobs/CronJobList.tsx +137 -0
  45. package/src/app/[variants]/(main)/chat/profile/features/AgentCronJobs/hooks/useAgentCronJobs.ts +138 -0
  46. package/src/app/[variants]/(main)/chat/profile/features/AgentCronJobs/index.tsx +130 -0
  47. package/src/app/[variants]/(main)/chat/profile/features/ProfileEditor/index.tsx +33 -3
  48. package/src/app/[variants]/router/desktopRouter.config.tsx +7 -0
  49. package/src/features/ChatInput/ActionBar/Params/Controls.tsx +7 -6
  50. package/src/hooks/useFetchCronTopics.ts +29 -0
  51. package/src/hooks/useFetchCronTopicsWithJobInfo.ts +56 -0
  52. package/src/hooks/useFetchTopics.ts +4 -1
  53. package/src/locales/default/setting.ts +44 -1
  54. package/src/server/routers/lambda/agentCronJob.ts +367 -0
  55. package/src/server/routers/lambda/image/index.test.ts +2 -2
  56. package/src/server/routers/lambda/index.ts +2 -0
  57. package/src/server/routers/lambda/topic.ts +15 -3
  58. package/src/server/services/aiAgent/index.ts +18 -1
  59. package/src/server/services/memory/userMemory/extract.ts +14 -6
  60. package/src/services/agentCronJob.ts +95 -0
  61. package/src/services/topic/index.ts +1 -0
  62. package/src/store/chat/slices/topic/action.ts +53 -2
  63. package/src/store/chat/slices/topic/initialState.ts +1 -0
  64. package/src/store/chat/slices/topic/selectors.ts +14 -6
  65. package/src/tools/placeholders.ts +1 -4
  66. package/apps/desktop/src/main/controllers/scripts/full-disk-access.applescript +0 -85
@@ -2,27 +2,37 @@
2
2
 
3
3
  import { Flexbox, Skeleton } from '@lobehub/ui';
4
4
  import { Suspense, memo, useCallback, useEffect, useState } from 'react';
5
- import { useNavigate, useSearchParams } from 'react-router-dom';
5
+ import { useSearchParams } from 'react-router-dom';
6
6
 
7
7
  import Loading from '@/components/Loading/BrandTextLoading';
8
8
  import { electronSystemService } from '@/services/electron/system';
9
- import { isDev } from '@/utils/env';
10
9
 
11
10
  import OnboardingContainer from './_layout';
12
11
  import DataModeStep from './features/DataModeStep';
13
12
  import LoginStep from './features/LoginStep';
14
13
  import PermissionsStep from './features/PermissionsStep';
15
14
  import WelcomeStep from './features/WelcomeStep';
16
- import { getDesktopOnboardingCompleted, setDesktopOnboardingCompleted } from './storage';
15
+ import {
16
+ clearDesktopOnboardingStep,
17
+ getDesktopOnboardingStep,
18
+ setDesktopOnboardingCompleted,
19
+ setDesktopOnboardingStep,
20
+ } from './storage';
17
21
 
18
22
  const DesktopOnboardingPage = memo(() => {
19
- const navigate = useNavigate();
20
23
  const [searchParams, setSearchParams] = useSearchParams();
21
24
  const [isMac, setIsMac] = useState(true);
22
25
  const [isLoading, setIsLoading] = useState(true);
23
26
 
24
- // 从 URL query 参数获取初始步骤,默认为 1
27
+ // 从 localStorage 或 URL query 参数获取初始步骤
28
+ // 优先使用 localStorage 以支持重启后恢复
25
29
  const getInitialStep = useCallback(() => {
30
+ // First try localStorage (for app restart scenario)
31
+ const savedStep = getDesktopOnboardingStep();
32
+ if (savedStep !== null) {
33
+ return savedStep;
34
+ }
35
+ // Then try URL params
26
36
  const stepParam = searchParams.get('step');
27
37
  if (stepParam) {
28
38
  const step = parseInt(stepParam, 10);
@@ -33,11 +43,10 @@ const DesktopOnboardingPage = memo(() => {
33
43
 
34
44
  const [currentStep, setCurrentStep] = useState(getInitialStep);
35
45
 
36
- // 检查是否已完成 onboarding
46
+ // 持久化当前步骤到 localStorage
37
47
  useEffect(() => {
38
- if (isDev) return;
39
- if (getDesktopOnboardingCompleted()) navigate('/', { replace: true });
40
- }, [navigate]);
48
+ setDesktopOnboardingStep(currentStep);
49
+ }, [currentStep]);
41
50
 
42
51
  // 设置窗口大小和可调整性
43
52
  useEffect(() => {
@@ -117,6 +126,7 @@ const DesktopOnboardingPage = memo(() => {
117
126
  case 4: {
118
127
  // 如果是第4步(LoginStep),完成 onboarding
119
128
  setDesktopOnboardingCompleted();
129
+ clearDesktopOnboardingStep(); // Clear persisted step since onboarding is complete
120
130
  // Restore window resizable before hard reload (cleanup won't run due to hard navigation)
121
131
  electronSystemService
122
132
  .setWindowResizable({ resizable: true })
@@ -1,4 +1,5 @@
1
1
  export const DESKTOP_ONBOARDING_STORAGE_KEY = 'lobechat:desktop:onboarding:completed:v1';
2
+ export const DESKTOP_ONBOARDING_STEP_KEY = 'lobechat:desktop:onboarding:step:v1';
2
3
 
3
4
  export const getDesktopOnboardingCompleted = () => {
4
5
  if (typeof window === 'undefined') return true;
@@ -32,3 +33,51 @@ export const clearDesktopOnboardingCompleted = () => {
32
33
  return false;
33
34
  }
34
35
  };
36
+
37
+ /**
38
+ * Get the persisted onboarding step (for restoring after app restart)
39
+ */
40
+ export const getDesktopOnboardingStep = (): number | null => {
41
+ if (typeof window === 'undefined') return null;
42
+
43
+ try {
44
+ const step = window.localStorage.getItem(DESKTOP_ONBOARDING_STEP_KEY);
45
+ if (step) {
46
+ const parsedStep = Number.parseInt(step, 10);
47
+ if (parsedStep >= 1 && parsedStep <= 4) {
48
+ return parsedStep;
49
+ }
50
+ }
51
+ return null;
52
+ } catch {
53
+ return null;
54
+ }
55
+ };
56
+
57
+ /**
58
+ * Persist the current onboarding step
59
+ */
60
+ export const setDesktopOnboardingStep = (step: number) => {
61
+ if (typeof window === 'undefined') return false;
62
+
63
+ try {
64
+ window.localStorage.setItem(DESKTOP_ONBOARDING_STEP_KEY, step.toString());
65
+ return true;
66
+ } catch {
67
+ return false;
68
+ }
69
+ };
70
+
71
+ /**
72
+ * Clear the persisted onboarding step (called when onboarding completes)
73
+ */
74
+ export const clearDesktopOnboardingStep = () => {
75
+ if (typeof window === 'undefined') return false;
76
+
77
+ try {
78
+ window.localStorage.removeItem(DESKTOP_ONBOARDING_STEP_KEY);
79
+ return true;
80
+ } catch {
81
+ return false;
82
+ }
83
+ };
@@ -2,15 +2,18 @@ import { Accordion, Flexbox } from '@lobehub/ui';
2
2
  import React, { memo } from 'react';
3
3
 
4
4
  import Topic from './Topic';
5
+ import CronTopicList from './Topic/CronTopicList';
5
6
 
6
7
  export enum ChatSidebarKey {
7
- Topic = 'topic',
8
+ CronTopics = 'cronTopics',
9
+ Topic = 'topic'
8
10
  }
9
11
 
10
12
  const Body = memo(() => {
11
13
  return (
12
14
  <Flexbox paddingInline={4}>
13
15
  <Accordion defaultExpandedKeys={[ChatSidebarKey.Topic]} gap={8}>
16
+ <CronTopicList itemKey={ChatSidebarKey.CronTopics} />
14
17
  <Topic itemKey={ChatSidebarKey.Topic} />
15
18
  </Accordion>
16
19
  </Flexbox>
@@ -0,0 +1,74 @@
1
+ 'use client';
2
+
3
+ import { Flexbox, Icon, Text } from '@lobehub/ui';
4
+ import { cssVar } from 'antd-style';
5
+ import { Clock } from 'lucide-react';
6
+ import { type MouseEvent, memo, useCallback } from 'react';
7
+ import { useParams } from 'react-router-dom';
8
+
9
+ import { useRouter } from '@/app/[variants]/(main)/hooks/useRouter';
10
+ import type { AgentCronJob } from '@/database/schemas/agentCronJob';
11
+
12
+ import CronTopicItem from './CronTopicItem';
13
+
14
+ interface CronTopicGroupProps {
15
+ cronJob: AgentCronJob | null;
16
+ cronJobId: string;
17
+ topics: Array<{
18
+ createdAt: Date | string;
19
+ favorite?: boolean | null;
20
+ historySummary?: string | null;
21
+ id: string;
22
+ metadata?: any;
23
+ title?: string | null;
24
+ trigger?: string | null;
25
+ updatedAt: Date | string;
26
+ }>;
27
+ }
28
+
29
+ const CronTopicGroup = memo<CronTopicGroupProps>(({ cronJob, cronJobId, topics }) => {
30
+ const { aid } = useParams<{ aid?: string }>();
31
+ const router = useRouter();
32
+ const handleOpenCronJob = useCallback(
33
+ (event: MouseEvent) => {
34
+ event.stopPropagation();
35
+ if (!aid) return;
36
+ router.push(`/agent/${aid}/cron/${cronJobId}`);
37
+ },
38
+ [aid, cronJobId, router],
39
+ );
40
+
41
+ const cronJobName = cronJob?.name || `Cron Job ${cronJobId.slice(-8)}`;
42
+ const isEnabled = cronJob?.enabled ?? false;
43
+
44
+ return (
45
+ <Flexbox gap={1}>
46
+ <Flexbox
47
+ align="center"
48
+ gap={6}
49
+ height={24}
50
+ horizontal
51
+ onClick={handleOpenCronJob}
52
+ paddingInline={8}
53
+ style={{ cursor: 'pointer', opacity: isEnabled ? 1 : 0.6, overflow: 'hidden' }}
54
+ >
55
+ <Icon icon={Clock} style={{ color: cssVar.colorTextDescription, opacity: 0.7 }} />
56
+ <Text ellipsis fontSize={12} style={{ flex: 1 }} type={'secondary'} weight={500}>
57
+ {cronJobName}
58
+ </Text>
59
+ {topics.length > 0 && (
60
+ <Text style={{ color: cssVar.colorTextDescription, fontSize: 11 }}>{topics.length}</Text>
61
+ )}
62
+ </Flexbox>
63
+ {topics.length > 0 && (
64
+ <Flexbox gap={1} paddingBlock={1}>
65
+ {topics.map((topic) => (
66
+ <CronTopicItem key={topic.id} topic={topic} />
67
+ ))}
68
+ </Flexbox>
69
+ )}
70
+ </Flexbox>
71
+ );
72
+ });
73
+
74
+ export default CronTopicGroup;
@@ -0,0 +1,40 @@
1
+ 'use client';
2
+
3
+ import { memo } from 'react';
4
+ import { useTranslation } from 'react-i18next';
5
+
6
+ import { useChatStore } from '@/store/chat';
7
+
8
+ import TopicItem from '../List/Item';
9
+
10
+ interface CronTopicItemProps {
11
+ topic: {
12
+ createdAt: Date | string;
13
+ favorite?: boolean | null;
14
+ historySummary?: string | null;
15
+ id: string;
16
+ metadata?: any;
17
+ title?: string | null;
18
+ trigger?: string | null;
19
+ updatedAt: Date | string;
20
+ };
21
+ }
22
+
23
+ const CronTopicItem = memo<CronTopicItemProps>(({ topic }) => {
24
+ const { t } = useTranslation('topic');
25
+ const [activeTopicId, activeThreadId] = useChatStore((s) => [s.activeTopicId, s.activeThreadId]);
26
+
27
+ const displayTitle = topic.title || topic.historySummary || t('defaultTitle');
28
+
29
+ return (
30
+ <TopicItem
31
+ active={activeTopicId === topic.id}
32
+ fav={!!topic.favorite}
33
+ id={topic.id}
34
+ threadId={activeThreadId}
35
+ title={displayTitle}
36
+ />
37
+ );
38
+ });
39
+
40
+ export default CronTopicItem;
@@ -0,0 +1,140 @@
1
+ 'use client';
2
+
3
+ import { ENABLE_BUSINESS_FEATURES } from '@lobechat/business-const';
4
+ import { AccordionItem, ActionIcon, Flexbox, Icon, Text } from '@lobehub/ui';
5
+ import { message } from 'antd';
6
+ import { Calendar, Plus } from 'lucide-react';
7
+ import { memo, useCallback } from 'react';
8
+ import { useTranslation } from 'react-i18next';
9
+ import urlJoin from 'url-join';
10
+
11
+ import NeuralNetworkLoading from '@/components/NeuralNetworkLoading';
12
+ import EmptyNavItem from '@/features/NavPanel/components/EmptyNavItem';
13
+ import SkeletonList from '@/features/NavPanel/components/SkeletonList';
14
+ import { useFetchCronTopicsWithJobInfo } from '@/hooks/useFetchCronTopicsWithJobInfo';
15
+ import { useQueryRoute } from '@/hooks/useQueryRoute';
16
+ import { agentCronJobService } from '@/services/agentCronJob';
17
+ import { useAgentStore } from '@/store/agent';
18
+
19
+ import CronTopicGroup from './CronTopicGroup';
20
+
21
+ interface CronTopicListProps {
22
+ itemKey: string;
23
+ }
24
+
25
+ const CronTopicList = memo<CronTopicListProps>(({ itemKey }) => {
26
+ const { t } = useTranslation('setting');
27
+ const router = useQueryRoute();
28
+ const agentId = useAgentStore((s) => s.activeAgentId);
29
+ const { cronTopicsGroupsWithJobInfo, isLoading, mutate } = useFetchCronTopicsWithJobInfo();
30
+ const totalTopics = cronTopicsGroupsWithJobInfo.reduce(
31
+ (acc, group) => acc + group.topics.length,
32
+ 0,
33
+ );
34
+
35
+ const handleCreateCronJob = useCallback(async () => {
36
+ if (!agentId) return;
37
+ try {
38
+ const result = await agentCronJobService.create({
39
+ agentId,
40
+ content: t('agentCronJobs.form.content.placeholder') || 'This is a cron job',
41
+ cronPattern: '0 */30 * * *',
42
+ enabled: true,
43
+ name: t('agentCronJobs.addJob') || 'Cron Job Task',
44
+ });
45
+
46
+ if (result.success) {
47
+ await mutate();
48
+ router.push(urlJoin('/agent', agentId, 'cron', result.data.id));
49
+ }
50
+ } catch (error) {
51
+ console.error('Failed to create cron job:', error);
52
+ message.error('Failed to create scheduled task');
53
+ }
54
+ }, [agentId, mutate, router, t]);
55
+
56
+ if (!ENABLE_BUSINESS_FEATURES) return null;
57
+
58
+ const addAction = (
59
+ <ActionIcon
60
+ disabled={!agentId}
61
+ icon={Plus}
62
+ onClick={handleCreateCronJob}
63
+ size={'small'}
64
+ title={t('agentCronJobs.addJob')}
65
+ />
66
+ );
67
+
68
+ if (isLoading) {
69
+ return (
70
+ <AccordionItem
71
+ action={addAction}
72
+ itemKey={itemKey}
73
+ paddingBlock={4}
74
+ paddingInline={'8px 4px'}
75
+ title={
76
+ <Flexbox align="center" gap={4} horizontal>
77
+ <Icon icon={Calendar} size={12} />
78
+ <Text ellipsis fontSize={12} type={'secondary'} weight={500}>
79
+ {t('agentCronJobs.title')}
80
+ </Text>
81
+ <NeuralNetworkLoading size={14} />
82
+ </Flexbox>
83
+ }
84
+ >
85
+ <SkeletonList />
86
+ </AccordionItem>
87
+ );
88
+ }
89
+
90
+ if (cronTopicsGroupsWithJobInfo.length === 0) {
91
+ return (
92
+ <AccordionItem
93
+ action={addAction}
94
+ itemKey={itemKey}
95
+ paddingBlock={4}
96
+ paddingInline={'8px 4px'}
97
+ title={
98
+ <Flexbox align="center" gap={4} horizontal>
99
+ <Icon icon={Calendar} size={12} />
100
+ <Text ellipsis fontSize={12} type={'secondary'} weight={500}>
101
+ {t('agentCronJobs.title')}
102
+ </Text>
103
+ </Flexbox>
104
+ }
105
+ >
106
+ <EmptyNavItem onClick={handleCreateCronJob} title={t('agentCronJobs.addJob')} />
107
+ </AccordionItem>
108
+ );
109
+ }
110
+
111
+ return (
112
+ <AccordionItem
113
+ action={addAction}
114
+ itemKey={itemKey}
115
+ paddingBlock={4}
116
+ paddingInline={'8px 4px'}
117
+ title={
118
+ <Flexbox align="center" gap={4} horizontal>
119
+ <Icon icon={Calendar} size={12} />
120
+ <Text ellipsis fontSize={12} type={'secondary'} weight={500}>
121
+ {`${t('agentCronJobs.title')} ${totalTopics > 0 ? totalTopics : ''}`}
122
+ </Text>
123
+ </Flexbox>
124
+ }
125
+ >
126
+ <Flexbox gap={2} paddingBlock={2}>
127
+ {cronTopicsGroupsWithJobInfo.map((group) => (
128
+ <CronTopicGroup
129
+ cronJob={group.cronJob}
130
+ cronJobId={group.cronJobId}
131
+ key={group.cronJobId}
132
+ topics={group.topics}
133
+ />
134
+ ))}
135
+ </Flexbox>
136
+ </AccordionItem>
137
+ );
138
+ });
139
+
140
+ export default CronTopicList;
@@ -32,7 +32,7 @@ const TopicList = memo(() => {
32
32
 
33
33
  const [topicDisplayMode] = useUserStore((s) => [preferenceSelectors.topicDisplayMode(s)]);
34
34
 
35
- useFetchTopics();
35
+ useFetchTopics({ excludeTriggers: ['cron'] });
36
36
 
37
37
  // Show skeleton when current session's topic data is not yet loaded
38
38
  if (isUndefinedTopics) return <SkeletonList />;
@@ -30,7 +30,7 @@ const TopicListContent = memo(() => {
30
30
 
31
31
  const [topicDisplayMode] = useUserStore((s) => [preferenceSelectors.topicDisplayMode(s)]);
32
32
 
33
- useFetchTopics();
33
+ useFetchTopics({ excludeTriggers: ['cron'] });
34
34
 
35
35
  if (isInSearchMode) return <SearchResult />;
36
36
 
@@ -22,7 +22,7 @@ const Topic = memo<TopicProps>(({ itemKey }) => {
22
22
  const { t } = useTranslation(['topic', 'common']);
23
23
  const [topicCount] = useChatStore((s) => [topicSelectors.currentTopicCount(s)]);
24
24
  const dropdownMenu = useTopicActionsDropdownMenu();
25
- const { isRevalidating } = useFetchTopics();
25
+ const { isRevalidating } = useFetchTopics({ excludeTriggers: ['cron'] });
26
26
 
27
27
  return (
28
28
  <AccordionItem