@lobehub/lobehub 2.0.0-next.371 → 2.0.0-next.372
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 +25 -0
- package/changelog/v1.json +5 -0
- package/locales/zh-CN/memory.json +11 -0
- package/package.json +1 -1
- package/packages/builtin-tool-memory/src/client/Inspector/SearchUserMemory/index.tsx +3 -2
- package/packages/context-engine/src/engine/messages/index.ts +1 -0
- package/packages/context-engine/src/engine/messages/types.ts +11 -0
- package/packages/database/src/models/userMemory/activity.ts +163 -0
- package/packages/database/src/models/userMemory/index.ts +1 -0
- package/packages/database/src/models/userMemory/model.ts +175 -1
- package/packages/database/src/repositories/userMemory/index.ts +5 -0
- package/packages/prompts/src/prompts/userMemory/__snapshots__/formatSearchResults.test.ts.snap +12 -0
- package/packages/prompts/src/prompts/userMemory/formatSearchResults.test.ts +41 -0
- package/packages/prompts/src/prompts/userMemory/formatSearchResults.ts +56 -2
- package/packages/types/src/userMemory/activity.ts +19 -0
- package/packages/types/src/userMemory/index.ts +1 -0
- package/packages/types/src/userMemory/tools.ts +10 -1
- package/src/app/(backend)/api/dev/memory-user-memory/benchmark-locomo/route.ts +19 -0
- package/src/app/[variants]/(main)/memory/_layout/Sidebar/Header/Nav.tsx +8 -0
- package/src/app/[variants]/(main)/memory/activities/features/ActivityDropdown.tsx +68 -0
- package/src/app/[variants]/(main)/memory/activities/features/ActivityRightPanel.tsx +113 -0
- package/src/app/[variants]/(main)/memory/activities/features/List/GridView/ActivityCard.tsx +36 -0
- package/src/app/[variants]/(main)/memory/activities/features/List/GridView/index.tsx +32 -0
- package/src/app/[variants]/(main)/memory/activities/features/List/TimelineView/ActivityCard.tsx +34 -0
- package/src/app/[variants]/(main)/memory/activities/features/List/TimelineView/index.tsx +41 -0
- package/src/app/[variants]/(main)/memory/activities/features/List/index.tsx +43 -0
- package/src/app/[variants]/(main)/memory/activities/index.tsx +125 -0
- package/src/app/[variants]/(main)/memory/features/EditableModal/index.tsx +1 -0
- package/src/app/[variants]/router/desktopRouter.config.tsx +7 -0
- package/src/locales/default/memory.ts +12 -0
- package/src/server/routers/lambda/userMemories.test.ts +18 -4
- package/src/server/routers/lambda/userMemories.ts +131 -1
- package/src/server/routers/lambda/userMemory.ts +34 -1
- package/src/server/services/memory/userMemory/extract.ts +1 -0
- package/src/services/chat/mecha/contextEngineering.test.ts +1 -0
- package/src/services/chat/mecha/memoryManager.ts +2 -0
- package/src/services/userMemory/crud.ts +17 -0
- package/src/services/userMemory/index.ts +18 -1
- package/src/store/userMemory/initialState.ts +4 -1
- package/src/store/userMemory/selectors.ts +1 -0
- package/src/store/userMemory/slices/activity/action.ts +123 -0
- package/src/store/userMemory/slices/activity/index.ts +2 -0
- package/src/store/userMemory/slices/activity/initialState.ts +23 -0
- package/src/store/userMemory/slices/agent/action.ts +1 -0
- package/src/store/userMemory/slices/base/action.ts +27 -4
- package/src/store/userMemory/store.ts +3 -0
|
@@ -4,9 +4,16 @@ import type { SearchMemoryResult } from '@lobechat/types';
|
|
|
4
4
|
* Search result item interfaces matching the SearchMemoryResult type
|
|
5
5
|
*/
|
|
6
6
|
type ContextResult = SearchMemoryResult['contexts'][number];
|
|
7
|
+
type ActivityResult = SearchMemoryResult['activities'][number];
|
|
7
8
|
type ExperienceResult = SearchMemoryResult['experiences'][number];
|
|
8
9
|
type PreferenceResult = SearchMemoryResult['preferences'][number];
|
|
9
10
|
|
|
11
|
+
const formatDate = (value?: string | Date | null) => {
|
|
12
|
+
if (!value) return value;
|
|
13
|
+
|
|
14
|
+
return value instanceof Date ? value.toISOString() : value;
|
|
15
|
+
};
|
|
16
|
+
|
|
10
17
|
/**
|
|
11
18
|
* Format a single context memory item for search results
|
|
12
19
|
* Format: attributes for metadata, description as text content
|
|
@@ -64,6 +71,48 @@ const formatContextResult = (item: ContextResult): string => {
|
|
|
64
71
|
return ` <context ${attrs.join(' ')}>${content}</context>`;
|
|
65
72
|
};
|
|
66
73
|
|
|
74
|
+
/**
|
|
75
|
+
* Format a single activity memory item for search results
|
|
76
|
+
* Includes scheduling attributes and feedback
|
|
77
|
+
*/
|
|
78
|
+
const formatActivityResult = (item: ActivityResult): string => {
|
|
79
|
+
const attrs: string[] = [`id="${item.id}"`];
|
|
80
|
+
|
|
81
|
+
if (item.type) {
|
|
82
|
+
attrs.push(`type="${item.type}"`);
|
|
83
|
+
}
|
|
84
|
+
if (item.status) {
|
|
85
|
+
attrs.push(`status="${item.status}"`);
|
|
86
|
+
}
|
|
87
|
+
const startsAt = formatDate(item.startsAt);
|
|
88
|
+
if (startsAt) {
|
|
89
|
+
attrs.push(`startsAt="${startsAt}"`);
|
|
90
|
+
}
|
|
91
|
+
const endsAt = formatDate(item.endsAt);
|
|
92
|
+
if (endsAt) {
|
|
93
|
+
attrs.push(`endsAt="${endsAt}"`);
|
|
94
|
+
}
|
|
95
|
+
if (item.timezone) {
|
|
96
|
+
attrs.push(`timezone="${item.timezone}"`);
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
const children: string[] = [];
|
|
100
|
+
|
|
101
|
+
if (item.feedback) {
|
|
102
|
+
children.push(` <feedback>${item.feedback}</feedback>`);
|
|
103
|
+
}
|
|
104
|
+
if (item.narrative) {
|
|
105
|
+
children.push(` <narrative>${item.narrative}</narrative>`);
|
|
106
|
+
}
|
|
107
|
+
if (item.notes) {
|
|
108
|
+
children.push(` <notes>${item.notes}</notes>`);
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
const content = children.length > 0 ? `\n${children.join('\n')}\n ` : '';
|
|
112
|
+
|
|
113
|
+
return ` <activity ${attrs.join(' ')}>${content}</activity>`;
|
|
114
|
+
};
|
|
115
|
+
|
|
67
116
|
/**
|
|
68
117
|
* Format a single experience memory item for search results
|
|
69
118
|
* Format: attributes for metadata, situation and keyLearning as child elements
|
|
@@ -129,8 +178,8 @@ export const formatMemorySearchResults = ({
|
|
|
129
178
|
query,
|
|
130
179
|
results,
|
|
131
180
|
}: FormatSearchResultsOptions): string => {
|
|
132
|
-
const { contexts, experiences, preferences } = results;
|
|
133
|
-
const total = contexts.length + experiences.length + preferences.length;
|
|
181
|
+
const { activities, contexts, experiences, preferences } = results;
|
|
182
|
+
const total = activities.length + contexts.length + experiences.length + preferences.length;
|
|
134
183
|
|
|
135
184
|
if (total === 0) {
|
|
136
185
|
return `<memories query="${query}">
|
|
@@ -146,6 +195,11 @@ export const formatMemorySearchResults = ({
|
|
|
146
195
|
sections.push(`<contexts count="${contexts.length}">\n${contextsXml}\n</contexts>`);
|
|
147
196
|
}
|
|
148
197
|
|
|
198
|
+
if (activities.length > 0) {
|
|
199
|
+
const activitiesXml = activities.map(formatActivityResult).join('\n');
|
|
200
|
+
sections.push(`<activities count="${activities.length}">\n${activitiesXml}\n</activities>`);
|
|
201
|
+
}
|
|
202
|
+
|
|
149
203
|
// Add experiences section
|
|
150
204
|
if (experiences.length > 0) {
|
|
151
205
|
const experiencesXml = experiences.map(formatExperienceResult).join('\n');
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import type { BaseListItem, BaseListParams, BaseListResult } from './shared';
|
|
2
|
+
|
|
3
|
+
export type ActivityListSort = 'capturedAt' | 'startsAt';
|
|
4
|
+
|
|
5
|
+
export interface ActivityListParams extends BaseListParams {
|
|
6
|
+
sort?: ActivityListSort;
|
|
7
|
+
status?: string[];
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
export interface ActivityListItem extends BaseListItem {
|
|
11
|
+
endsAt: Date | null;
|
|
12
|
+
narrative: string | null;
|
|
13
|
+
notes: string | null;
|
|
14
|
+
startsAt: Date | null;
|
|
15
|
+
status: string | null;
|
|
16
|
+
timezone: string | null;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
export type ActivityListResult = BaseListResult<ActivityListItem>;
|
|
@@ -1,6 +1,11 @@
|
|
|
1
1
|
import { z } from 'zod';
|
|
2
2
|
|
|
3
|
-
import {
|
|
3
|
+
import {
|
|
4
|
+
UserMemoryActivity,
|
|
5
|
+
UserMemoryContext,
|
|
6
|
+
UserMemoryExperience,
|
|
7
|
+
UserMemoryPreference,
|
|
8
|
+
} from './layers';
|
|
4
9
|
|
|
5
10
|
export const searchMemorySchema = z.object({
|
|
6
11
|
// TODO: we need to dynamically fetch the available categories/types from the backend
|
|
@@ -8,6 +13,7 @@ export const searchMemorySchema = z.object({
|
|
|
8
13
|
// memoryType: z.string().optional(),
|
|
9
14
|
query: z.string(),
|
|
10
15
|
topK: z.object({
|
|
16
|
+
activities: z.coerce.number().int().min(0),
|
|
11
17
|
contexts: z.coerce.number().int().min(0),
|
|
12
18
|
experiences: z.coerce.number().int().min(0),
|
|
13
19
|
preferences: z.coerce.number().int().min(0),
|
|
@@ -17,6 +23,9 @@ export const searchMemorySchema = z.object({
|
|
|
17
23
|
export type SearchMemoryParams = z.infer<typeof searchMemorySchema>;
|
|
18
24
|
|
|
19
25
|
export interface SearchMemoryResult {
|
|
26
|
+
activities: Array<
|
|
27
|
+
Omit<UserMemoryActivity, 'userId' | 'narrativeVector' | 'feedbackVector'>
|
|
28
|
+
>;
|
|
20
29
|
contexts: Array<Omit<UserMemoryContext, 'userId' | 'titleVector' | 'descriptionVector'>>;
|
|
21
30
|
experiences: Array<
|
|
22
31
|
Omit<UserMemoryExperience, 'userId' | 'actionVector' | 'situationVector' | 'keyLearningVector'>
|
|
@@ -82,6 +82,7 @@ export const POST = async (req: Request) => {
|
|
|
82
82
|
const searchResult = await model.searchWithEmbedding({
|
|
83
83
|
embedding,
|
|
84
84
|
limits: {
|
|
85
|
+
activities: topK,
|
|
85
86
|
contexts: topK,
|
|
86
87
|
experiences: topK,
|
|
87
88
|
preferences: topK,
|
|
@@ -102,6 +103,9 @@ export const POST = async (req: Request) => {
|
|
|
102
103
|
...searchResult.preferences
|
|
103
104
|
.map((preference) => preference.userMemoryId)
|
|
104
105
|
.filter((id): id is string => !!id),
|
|
106
|
+
...searchResult.activities
|
|
107
|
+
.map((activity) => activity.userMemoryId)
|
|
108
|
+
.filter((id): id is string => !!id),
|
|
105
109
|
...identities
|
|
106
110
|
.map((identity) => identity.userMemoryId)
|
|
107
111
|
.filter((id): id is string => !!id),
|
|
@@ -179,10 +183,25 @@ export const POST = async (req: Request) => {
|
|
|
179
183
|
})
|
|
180
184
|
.filter(Boolean);
|
|
181
185
|
|
|
186
|
+
const activityItems = searchResult.activities
|
|
187
|
+
.map((activity) => {
|
|
188
|
+
const memory = activity.userMemoryId ? memoryMap.get(activity.userMemoryId) : undefined;
|
|
189
|
+
if (!memory) return undefined;
|
|
190
|
+
|
|
191
|
+
return {
|
|
192
|
+
activity,
|
|
193
|
+
id: activity.userMemoryId,
|
|
194
|
+
layer: LayersEnum.Activity,
|
|
195
|
+
memory,
|
|
196
|
+
};
|
|
197
|
+
})
|
|
198
|
+
.filter(Boolean);
|
|
199
|
+
|
|
182
200
|
const items = [
|
|
183
201
|
...contextItems.slice(0, topK),
|
|
184
202
|
...experienceItems.slice(0, topK),
|
|
185
203
|
...preferenceItems.slice(0, topK),
|
|
204
|
+
...activityItems.slice(0, topK),
|
|
186
205
|
...identityItems,
|
|
187
206
|
];
|
|
188
207
|
console.log('[locomo-dev-search] compiled items');
|
|
@@ -2,6 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
import { Flexbox } from '@lobehub/ui';
|
|
4
4
|
import {
|
|
5
|
+
CalendarClockIcon,
|
|
5
6
|
BrainCircuitIcon,
|
|
6
7
|
BubblesIcon,
|
|
7
8
|
HeartPulseIcon,
|
|
@@ -26,6 +27,7 @@ interface Item {
|
|
|
26
27
|
}
|
|
27
28
|
|
|
28
29
|
enum MemoryTabKey {
|
|
30
|
+
Activities = 'activities',
|
|
29
31
|
Contexts = 'contexts',
|
|
30
32
|
Experiences = 'experiences',
|
|
31
33
|
Home = 'home',
|
|
@@ -85,6 +87,12 @@ const Nav = memo(() => {
|
|
|
85
87
|
title: t('tab.experiences'),
|
|
86
88
|
url: '/memory/experiences',
|
|
87
89
|
},
|
|
90
|
+
{
|
|
91
|
+
icon: CalendarClockIcon,
|
|
92
|
+
key: MemoryTabKey.Activities,
|
|
93
|
+
title: t('tab.activities'),
|
|
94
|
+
url: '/memory/activities',
|
|
95
|
+
},
|
|
88
96
|
],
|
|
89
97
|
[t],
|
|
90
98
|
);
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
import { ActionIcon, type ActionIconProps, DropdownMenu } from '@lobehub/ui';
|
|
2
|
+
import { App } from 'antd';
|
|
3
|
+
import { MoreHorizontal, Pencil, Trash2 } from 'lucide-react';
|
|
4
|
+
import { type KeyboardEvent, type MouseEvent, memo } from 'react';
|
|
5
|
+
import { useTranslation } from 'react-i18next';
|
|
6
|
+
|
|
7
|
+
import { useUserMemoryStore } from '@/store/userMemory';
|
|
8
|
+
|
|
9
|
+
interface ActivityDropdownProps {
|
|
10
|
+
id: string;
|
|
11
|
+
size?: ActionIconProps['size'];
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
const ActivityDropdown = memo<ActivityDropdownProps>(({ id, size = 'small' }) => {
|
|
15
|
+
const { t } = useTranslation(['memory', 'common']);
|
|
16
|
+
const { modal } = App.useApp();
|
|
17
|
+
|
|
18
|
+
const activities = useUserMemoryStore((s) => s.activities);
|
|
19
|
+
const deleteActivity = useUserMemoryStore((s) => s.deleteActivity);
|
|
20
|
+
const setEditingMemory = useUserMemoryStore((s) => s.setEditingMemory);
|
|
21
|
+
|
|
22
|
+
const handleMenuClick = (info: { domEvent: MouseEvent | KeyboardEvent; key: string }) => {
|
|
23
|
+
info.domEvent.stopPropagation();
|
|
24
|
+
|
|
25
|
+
if (info.key === 'edit') {
|
|
26
|
+
const activity = activities.find((item) => item.id === id);
|
|
27
|
+
if (activity) {
|
|
28
|
+
setEditingMemory(id, activity.narrative || activity.notes || '', 'activity');
|
|
29
|
+
}
|
|
30
|
+
} else if (info.key === 'delete') {
|
|
31
|
+
modal.confirm({
|
|
32
|
+
cancelText: t('cancel', { ns: 'common' }),
|
|
33
|
+
content: t('activity.deleteConfirm'),
|
|
34
|
+
okButtonProps: { danger: true },
|
|
35
|
+
okText: t('confirm', { ns: 'common' }),
|
|
36
|
+
onOk: async () => {
|
|
37
|
+
await deleteActivity(id);
|
|
38
|
+
},
|
|
39
|
+
title: t('activity.deleteTitle'),
|
|
40
|
+
type: 'warning',
|
|
41
|
+
});
|
|
42
|
+
}
|
|
43
|
+
};
|
|
44
|
+
|
|
45
|
+
const menuItems = [
|
|
46
|
+
{
|
|
47
|
+
icon: <Pencil size={14} />,
|
|
48
|
+
key: 'edit',
|
|
49
|
+
label: t('activity.actions.edit'),
|
|
50
|
+
onClick: handleMenuClick,
|
|
51
|
+
},
|
|
52
|
+
{
|
|
53
|
+
danger: true,
|
|
54
|
+
icon: <Trash2 size={14} />,
|
|
55
|
+
key: 'delete',
|
|
56
|
+
label: t('activity.actions.delete'),
|
|
57
|
+
onClick: handleMenuClick,
|
|
58
|
+
},
|
|
59
|
+
];
|
|
60
|
+
|
|
61
|
+
return (
|
|
62
|
+
<DropdownMenu items={menuItems}>
|
|
63
|
+
<ActionIcon icon={MoreHorizontal} size={size} />
|
|
64
|
+
</DropdownMenu>
|
|
65
|
+
);
|
|
66
|
+
});
|
|
67
|
+
|
|
68
|
+
export default ActivityDropdown;
|
|
@@ -0,0 +1,113 @@
|
|
|
1
|
+
'use client';
|
|
2
|
+
|
|
3
|
+
import { Flexbox, Tag, Text } from '@lobehub/ui';
|
|
4
|
+
import dayjs from 'dayjs';
|
|
5
|
+
import { memo, useMemo } from 'react';
|
|
6
|
+
import { useTranslation } from 'react-i18next';
|
|
7
|
+
|
|
8
|
+
import CateTag from '@/app/[variants]/(main)/memory/features/CateTag';
|
|
9
|
+
import DetailLoading from '@/app/[variants]/(main)/memory/features/DetailLoading';
|
|
10
|
+
import DetailPanel from '@/app/[variants]/(main)/memory/features/DetailPanel';
|
|
11
|
+
import HashTags from '@/app/[variants]/(main)/memory/features/HashTags';
|
|
12
|
+
import HighlightedContent from '@/app/[variants]/(main)/memory/features/HighlightedContent';
|
|
13
|
+
import SourceLink from '@/app/[variants]/(main)/memory/features/SourceLink';
|
|
14
|
+
import Time from '@/app/[variants]/(main)/memory/features/Time';
|
|
15
|
+
import { DESKTOP_HEADER_ICON_SIZE } from '@/const/layoutTokens';
|
|
16
|
+
import { useQueryState } from '@/hooks/useQueryParam';
|
|
17
|
+
import { useUserMemoryStore } from '@/store/userMemory';
|
|
18
|
+
import { LayersEnum } from '@/types/userMemory';
|
|
19
|
+
|
|
20
|
+
import ActivityDropdown from './ActivityDropdown';
|
|
21
|
+
|
|
22
|
+
const formatTime = (value?: Date | string | null) => {
|
|
23
|
+
if (!value) return null;
|
|
24
|
+
const time = dayjs(value);
|
|
25
|
+
if (!time.isValid()) return null;
|
|
26
|
+
return time.format('YYYY-MM-DD HH:mm');
|
|
27
|
+
};
|
|
28
|
+
|
|
29
|
+
const ActivityRightPanel = memo(() => {
|
|
30
|
+
const { t } = useTranslation('memory');
|
|
31
|
+
const [activityId] = useQueryState('activityId', { clearOnDefault: true });
|
|
32
|
+
const useFetchMemoryDetail = useUserMemoryStore((s) => s.useFetchMemoryDetail);
|
|
33
|
+
|
|
34
|
+
const { data: activity, isLoading } = useFetchMemoryDetail(activityId, LayersEnum.Activity);
|
|
35
|
+
|
|
36
|
+
const schedule = useMemo(() => {
|
|
37
|
+
if (!activity) return null;
|
|
38
|
+
const start = formatTime(activity.startsAt);
|
|
39
|
+
const end = formatTime(activity.endsAt);
|
|
40
|
+
if (!start && !end) return null;
|
|
41
|
+
if (start && end) return `${start} → ${end}`;
|
|
42
|
+
return start || end;
|
|
43
|
+
}, [activity]);
|
|
44
|
+
|
|
45
|
+
if (!activityId) return null;
|
|
46
|
+
|
|
47
|
+
let content;
|
|
48
|
+
if (isLoading) content = <DetailLoading />;
|
|
49
|
+
if (activity) {
|
|
50
|
+
const capturedAt =
|
|
51
|
+
activity.startsAt ||
|
|
52
|
+
activity.capturedAt ||
|
|
53
|
+
activity.updatedAt ||
|
|
54
|
+
activity.createdAt ||
|
|
55
|
+
undefined;
|
|
56
|
+
|
|
57
|
+
content = (
|
|
58
|
+
<>
|
|
59
|
+
<CateTag cate={activity.type} />
|
|
60
|
+
<Text
|
|
61
|
+
as={'h1'}
|
|
62
|
+
fontSize={20}
|
|
63
|
+
style={{
|
|
64
|
+
lineHeight: 1.4,
|
|
65
|
+
marginBottom: 0,
|
|
66
|
+
}}
|
|
67
|
+
weight={'bold'}
|
|
68
|
+
>
|
|
69
|
+
{activity.title || t('activity.defaultType')}
|
|
70
|
+
</Text>
|
|
71
|
+
<Flexbox align="center" gap={16} horizontal justify="space-between">
|
|
72
|
+
{activity.status && <Tag>{activity.status}</Tag>}
|
|
73
|
+
<SourceLink source={activity.source} />
|
|
74
|
+
</Flexbox>
|
|
75
|
+
<Flexbox align="center" gap={16} horizontal justify="space-between">
|
|
76
|
+
<Time capturedAt={capturedAt} />
|
|
77
|
+
{activity.timezone && (
|
|
78
|
+
<Text fontSize={12} type="secondary">
|
|
79
|
+
{activity.timezone}
|
|
80
|
+
</Text>
|
|
81
|
+
)}
|
|
82
|
+
</Flexbox>
|
|
83
|
+
|
|
84
|
+
{schedule && <HighlightedContent>{schedule}</HighlightedContent>}
|
|
85
|
+
{activity.narrative && (
|
|
86
|
+
<HighlightedContent title={t('activity.narrative')}>{activity.narrative}</HighlightedContent>
|
|
87
|
+
)}
|
|
88
|
+
{activity.notes && (
|
|
89
|
+
<HighlightedContent title={t('activity.notes')}>{activity.notes}</HighlightedContent>
|
|
90
|
+
)}
|
|
91
|
+
{activity.feedback && (
|
|
92
|
+
<HighlightedContent title={t('activity.feedback')}>{activity.feedback}</HighlightedContent>
|
|
93
|
+
)}
|
|
94
|
+
|
|
95
|
+
<HashTags hashTags={activity.tags} />
|
|
96
|
+
</>
|
|
97
|
+
);
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
return (
|
|
101
|
+
<DetailPanel
|
|
102
|
+
header={{
|
|
103
|
+
right: activityId ? (
|
|
104
|
+
<ActivityDropdown id={activityId} size={DESKTOP_HEADER_ICON_SIZE} />
|
|
105
|
+
) : undefined,
|
|
106
|
+
}}
|
|
107
|
+
>
|
|
108
|
+
{content}
|
|
109
|
+
</DetailPanel>
|
|
110
|
+
);
|
|
111
|
+
});
|
|
112
|
+
|
|
113
|
+
export default ActivityRightPanel;
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
import type { ActivityListItem } from '@lobechat/types';
|
|
2
|
+
import { Tag } from '@lobehub/ui';
|
|
3
|
+
import { memo } from 'react';
|
|
4
|
+
import { useTranslation } from 'react-i18next';
|
|
5
|
+
|
|
6
|
+
import GridCard from '@/app/[variants]/(main)/memory/features/GridView/GridCard';
|
|
7
|
+
|
|
8
|
+
import ActivityDropdown from '../../ActivityDropdown';
|
|
9
|
+
|
|
10
|
+
interface ActivityCardProps {
|
|
11
|
+
activity: ActivityListItem;
|
|
12
|
+
onClick: (activity: ActivityListItem) => void;
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
const ActivityCard = memo<ActivityCardProps>(({ activity, onClick }) => {
|
|
16
|
+
const { t } = useTranslation('memory');
|
|
17
|
+
const capturedAt =
|
|
18
|
+
activity.startsAt || activity.capturedAt || activity.updatedAt || activity.createdAt;
|
|
19
|
+
|
|
20
|
+
return (
|
|
21
|
+
<GridCard
|
|
22
|
+
actions={<ActivityDropdown id={activity.id} />}
|
|
23
|
+
badges={activity.status ? <Tag>{activity.status}</Tag> : undefined}
|
|
24
|
+
capturedAt={capturedAt}
|
|
25
|
+
cate={activity.type}
|
|
26
|
+
hashTags={activity.tags}
|
|
27
|
+
onClick={() => onClick(activity)}
|
|
28
|
+
title={activity.title || t('activity.defaultType')}
|
|
29
|
+
titleAddon={activity.timezone}
|
|
30
|
+
>
|
|
31
|
+
{activity.narrative || activity.notes || t('activity.defaultType')}
|
|
32
|
+
</GridCard>
|
|
33
|
+
);
|
|
34
|
+
});
|
|
35
|
+
|
|
36
|
+
export default ActivityCard;
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
import type { ActivityListItem } from '@lobechat/types';
|
|
2
|
+
import { memo } from 'react';
|
|
3
|
+
|
|
4
|
+
import { useUserMemoryStore } from '@/store/userMemory';
|
|
5
|
+
|
|
6
|
+
import { GridView } from '../../../../features/GridView';
|
|
7
|
+
import ActivityCard from './ActivityCard';
|
|
8
|
+
|
|
9
|
+
interface GridViewProps {
|
|
10
|
+
activities: ActivityListItem[];
|
|
11
|
+
isLoading?: boolean;
|
|
12
|
+
onClick: (activity: ActivityListItem) => void;
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
const ActivitiesGridView = memo<GridViewProps>(({ activities, isLoading, onClick }) => {
|
|
16
|
+
const loadMoreActivities = useUserMemoryStore((s) => s.loadMoreActivities);
|
|
17
|
+
const activitiesHasMore = useUserMemoryStore((s) => s.activitiesHasMore);
|
|
18
|
+
|
|
19
|
+
return (
|
|
20
|
+
<GridView
|
|
21
|
+
hasMore={activitiesHasMore}
|
|
22
|
+
isLoading={isLoading}
|
|
23
|
+
items={activities}
|
|
24
|
+
onLoadMore={loadMoreActivities}
|
|
25
|
+
renderItem={(activity: ActivityListItem) => (
|
|
26
|
+
<ActivityCard activity={activity} onClick={() => onClick(activity)} />
|
|
27
|
+
)}
|
|
28
|
+
/>
|
|
29
|
+
);
|
|
30
|
+
});
|
|
31
|
+
|
|
32
|
+
export default ActivitiesGridView;
|
package/src/app/[variants]/(main)/memory/activities/features/List/TimelineView/ActivityCard.tsx
ADDED
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
import type { ActivityListItem } from '@lobechat/types';
|
|
2
|
+
import { memo } from 'react';
|
|
3
|
+
import { useTranslation } from 'react-i18next';
|
|
4
|
+
|
|
5
|
+
import TimeLineCard from '@/app/[variants]/(main)/memory/features/TimeLineView/TimeLineCard';
|
|
6
|
+
|
|
7
|
+
import ActivityDropdown from '../../ActivityDropdown';
|
|
8
|
+
|
|
9
|
+
interface ActivityCardProps {
|
|
10
|
+
activity: ActivityListItem;
|
|
11
|
+
onClick: (activity: ActivityListItem) => void;
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
const ActivityCard = memo<ActivityCardProps>(({ activity, onClick }) => {
|
|
15
|
+
const { t } = useTranslation('memory');
|
|
16
|
+
const capturedAt =
|
|
17
|
+
activity.startsAt || activity.capturedAt || activity.updatedAt || activity.createdAt;
|
|
18
|
+
|
|
19
|
+
return (
|
|
20
|
+
<TimeLineCard
|
|
21
|
+
actions={<ActivityDropdown id={activity.id} />}
|
|
22
|
+
capturedAt={capturedAt}
|
|
23
|
+
cate={activity.type}
|
|
24
|
+
hashTags={activity.tags}
|
|
25
|
+
onClick={() => onClick(activity)}
|
|
26
|
+
title={activity.title || t('activity.defaultType')}
|
|
27
|
+
titleAddon={activity.status}
|
|
28
|
+
>
|
|
29
|
+
{activity.narrative || activity.notes || activity.status || t('activity.defaultType')}
|
|
30
|
+
</TimeLineCard>
|
|
31
|
+
);
|
|
32
|
+
});
|
|
33
|
+
|
|
34
|
+
export default ActivityCard;
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
'use client';
|
|
2
|
+
|
|
3
|
+
import type { ActivityListItem } from '@lobechat/types';
|
|
4
|
+
import { memo } from 'react';
|
|
5
|
+
|
|
6
|
+
import { useUserMemoryStore } from '@/store/userMemory';
|
|
7
|
+
|
|
8
|
+
import { TimelineView as GenericTimelineView } from '../../../../features/TimeLineView';
|
|
9
|
+
import { PeriodHeader, TimelineItemWrapper } from '../../../../features/TimeLineView/PeriodGroup';
|
|
10
|
+
import ActivityCard from './ActivityCard';
|
|
11
|
+
|
|
12
|
+
interface ActivityTimelineViewProps {
|
|
13
|
+
activities: ActivityListItem[];
|
|
14
|
+
isLoading?: boolean;
|
|
15
|
+
onCardClick: (activity: ActivityListItem) => void;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
const ActivityTimelineView = memo<ActivityTimelineViewProps>(
|
|
19
|
+
({ activities, isLoading, onCardClick }) => {
|
|
20
|
+
const loadMoreActivities = useUserMemoryStore((s) => s.loadMoreActivities);
|
|
21
|
+
const activitiesHasMore = useUserMemoryStore((s) => s.activitiesHasMore);
|
|
22
|
+
|
|
23
|
+
return (
|
|
24
|
+
<GenericTimelineView
|
|
25
|
+
data={activities}
|
|
26
|
+
groupBy="day"
|
|
27
|
+
hasMore={activitiesHasMore}
|
|
28
|
+
isLoading={isLoading}
|
|
29
|
+
onLoadMore={loadMoreActivities}
|
|
30
|
+
renderHeader={(periodKey: string) => <PeriodHeader groupBy="day" periodKey={periodKey} />}
|
|
31
|
+
renderItem={(activity: ActivityListItem) => (
|
|
32
|
+
<TimelineItemWrapper>
|
|
33
|
+
<ActivityCard activity={activity} onClick={onCardClick} />
|
|
34
|
+
</TimelineItemWrapper>
|
|
35
|
+
)}
|
|
36
|
+
/>
|
|
37
|
+
);
|
|
38
|
+
},
|
|
39
|
+
);
|
|
40
|
+
|
|
41
|
+
export default ActivityTimelineView;
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
import { memo } from 'react';
|
|
2
|
+
import { useTranslation } from 'react-i18next';
|
|
3
|
+
|
|
4
|
+
import MemoryEmpty from '@/app/[variants]/(main)/memory/features/MemoryEmpty';
|
|
5
|
+
import { useQueryState } from '@/hooks/useQueryParam';
|
|
6
|
+
import { useGlobalStore } from '@/store/global';
|
|
7
|
+
import { useUserMemoryStore } from '@/store/userMemory';
|
|
8
|
+
|
|
9
|
+
import { type ViewMode } from '../../../features/ViewModeSwitcher';
|
|
10
|
+
import GridView from './GridView';
|
|
11
|
+
import TimelineView from './TimelineView';
|
|
12
|
+
|
|
13
|
+
interface ActivitiesListProps {
|
|
14
|
+
isLoading?: boolean;
|
|
15
|
+
searchValue?: string;
|
|
16
|
+
viewMode: ViewMode;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
const ActivitiesList = memo<ActivitiesListProps>(({ isLoading, searchValue, viewMode }) => {
|
|
20
|
+
const { t } = useTranslation(['memory', 'common']);
|
|
21
|
+
const [, setActivityId] = useQueryState('activityId', { clearOnDefault: true });
|
|
22
|
+
const toggleRightPanel = useGlobalStore((s) => s.toggleRightPanel);
|
|
23
|
+
const activities = useUserMemoryStore((s) => s.activities);
|
|
24
|
+
|
|
25
|
+
const handleCardClick = (activity: any) => {
|
|
26
|
+
setActivityId(activity.id);
|
|
27
|
+
toggleRightPanel(true);
|
|
28
|
+
};
|
|
29
|
+
|
|
30
|
+
const isEmpty = activities.length === 0;
|
|
31
|
+
|
|
32
|
+
if (isEmpty) {
|
|
33
|
+
return <MemoryEmpty search={Boolean(searchValue)} title={t('activity.empty')} />;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
return viewMode === 'timeline' ? (
|
|
37
|
+
<TimelineView activities={activities} isLoading={isLoading} onCardClick={handleCardClick} />
|
|
38
|
+
) : (
|
|
39
|
+
<GridView activities={activities} isLoading={isLoading} onClick={handleCardClick} />
|
|
40
|
+
);
|
|
41
|
+
});
|
|
42
|
+
|
|
43
|
+
export default ActivitiesList;
|