mirror of
https://github.com/lobehub/lobehub
synced 2026-04-21 09:37:28 +00:00
👷 build(database): add topic status and tasks automation mode (#13994)
This commit is contained in:
parent
93603ae83b
commit
3bcd581e7c
8 changed files with 14884 additions and 16 deletions
|
|
@ -516,8 +516,9 @@ table document_histories {
|
||||||
saved_at "timestamp with time zone" [not null]
|
saved_at "timestamp with time zone" [not null]
|
||||||
|
|
||||||
indexes {
|
indexes {
|
||||||
(document_id, saved_at) [name: 'document_histories_document_id_saved_at_idx']
|
document_id [name: 'document_histories_document_id_idx']
|
||||||
(user_id, saved_at) [name: 'document_histories_user_id_saved_at_idx']
|
user_id [name: 'document_histories_user_id_idx']
|
||||||
|
saved_at [name: 'document_histories_saved_at_idx']
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -1523,7 +1524,8 @@ table tasks {
|
||||||
status text [not null, default: 'backlog']
|
status text [not null, default: 'backlog']
|
||||||
priority integer [default: 0]
|
priority integer [default: 0]
|
||||||
sort_order integer [default: 0]
|
sort_order integer [default: 0]
|
||||||
heartbeat_interval integer [default: 300]
|
automation_mode text
|
||||||
|
heartbeat_interval integer
|
||||||
heartbeat_timeout integer
|
heartbeat_timeout integer
|
||||||
last_heartbeat_at "timestamp with time zone"
|
last_heartbeat_at "timestamp with time zone"
|
||||||
schedule_pattern text
|
schedule_pattern text
|
||||||
|
|
@ -1550,6 +1552,7 @@ table tasks {
|
||||||
parent_task_id [name: 'tasks_parent_task_id_idx']
|
parent_task_id [name: 'tasks_parent_task_id_idx']
|
||||||
status [name: 'tasks_status_idx']
|
status [name: 'tasks_status_idx']
|
||||||
priority [name: 'tasks_priority_idx']
|
priority [name: 'tasks_priority_idx']
|
||||||
|
automation_mode [name: 'tasks_automation_mode_idx']
|
||||||
(status, last_heartbeat_at) [name: 'tasks_heartbeat_idx']
|
(status, last_heartbeat_at) [name: 'tasks_heartbeat_idx']
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -1631,6 +1634,8 @@ table topics {
|
||||||
metadata jsonb
|
metadata jsonb
|
||||||
trigger text
|
trigger text
|
||||||
mode text
|
mode text
|
||||||
|
status text
|
||||||
|
completed_at "timestamp with time zone"
|
||||||
accessed_at "timestamp with time zone" [not null, default: `now()`]
|
accessed_at "timestamp with time zone" [not null, default: `now()`]
|
||||||
created_at "timestamp with time zone" [not null, default: `now()`]
|
created_at "timestamp with time zone" [not null, default: `now()`]
|
||||||
updated_at "timestamp with time zone" [not null, default: `now()`]
|
updated_at "timestamp with time zone" [not null, default: `now()`]
|
||||||
|
|
@ -1643,6 +1648,8 @@ table topics {
|
||||||
group_id [name: 'topics_group_id_idx']
|
group_id [name: 'topics_group_id_idx']
|
||||||
agent_id [name: 'topics_agent_id_idx']
|
agent_id [name: 'topics_agent_id_idx']
|
||||||
trigger [name: 'topics_trigger_idx']
|
trigger [name: 'topics_trigger_idx']
|
||||||
|
status [name: 'topics_status_idx']
|
||||||
|
(user_id, completed_at) [name: 'topics_user_id_completed_at_idx']
|
||||||
() [name: 'topics_extract_status_gin_idx']
|
() [name: 'topics_extract_status_gin_idx']
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,7 @@
|
||||||
|
ALTER TABLE "tasks" ALTER COLUMN "heartbeat_interval" DROP DEFAULT;--> statement-breakpoint
|
||||||
|
ALTER TABLE "tasks" ADD COLUMN IF NOT EXISTS "automation_mode" text;--> statement-breakpoint
|
||||||
|
ALTER TABLE "topics" ADD COLUMN IF NOT EXISTS "status" text;--> statement-breakpoint
|
||||||
|
ALTER TABLE "topics" ADD COLUMN IF NOT EXISTS "completed_at" timestamp with time zone;--> statement-breakpoint
|
||||||
|
CREATE INDEX IF NOT EXISTS "tasks_automation_mode_idx" ON "tasks" USING btree ("automation_mode");--> statement-breakpoint
|
||||||
|
CREATE INDEX IF NOT EXISTS "topics_status_idx" ON "topics" USING btree ("status");--> statement-breakpoint
|
||||||
|
CREATE INDEX IF NOT EXISTS "topics_user_id_completed_at_idx" ON "topics" USING btree ("user_id","completed_at");
|
||||||
14822
packages/database/migrations/meta/0099_snapshot.json
Normal file
14822
packages/database/migrations/meta/0099_snapshot.json
Normal file
File diff suppressed because it is too large
Load diff
|
|
@ -693,6 +693,13 @@
|
||||||
"when": 1776234919716,
|
"when": 1776234919716,
|
||||||
"tag": "0098_add_document_history",
|
"tag": "0098_add_document_history",
|
||||||
"breakpoints": true
|
"breakpoints": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"idx": 99,
|
||||||
|
"version": "7",
|
||||||
|
"when": 1776674965365,
|
||||||
|
"tag": "0099_topic_status_tasks_automation_mode",
|
||||||
|
"breakpoints": true
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"version": "6"
|
"version": "6"
|
||||||
|
|
|
||||||
|
|
@ -61,6 +61,8 @@ describe('TopicModel - Create', () => {
|
||||||
editorData: null,
|
editorData: null,
|
||||||
trigger: null,
|
trigger: null,
|
||||||
mode: null,
|
mode: null,
|
||||||
|
status: null,
|
||||||
|
completedAt: null,
|
||||||
createdAt: expect.any(Date),
|
createdAt: expect.any(Date),
|
||||||
updatedAt: expect.any(Date),
|
updatedAt: expect.any(Date),
|
||||||
accessedAt: expect.any(Date),
|
accessedAt: expect.any(Date),
|
||||||
|
|
@ -109,6 +111,8 @@ describe('TopicModel - Create', () => {
|
||||||
metadata: null,
|
metadata: null,
|
||||||
trigger: null,
|
trigger: null,
|
||||||
mode: null,
|
mode: null,
|
||||||
|
status: null,
|
||||||
|
completedAt: null,
|
||||||
sessionId,
|
sessionId,
|
||||||
userId,
|
userId,
|
||||||
createdAt: expect.any(Date),
|
createdAt: expect.any(Date),
|
||||||
|
|
|
||||||
|
|
@ -58,8 +58,11 @@ export const tasks = pgTable(
|
||||||
priority: integer('priority').default(0), // 'no' | 'urgent' | 'high' | 'normal' | 'low'
|
priority: integer('priority').default(0), // 'no' | 'urgent' | 'high' | 'normal' | 'low'
|
||||||
sortOrder: integer('sort_order').default(0), // manual sort within parent, lower = higher
|
sortOrder: integer('sort_order').default(0), // manual sort within parent, lower = higher
|
||||||
|
|
||||||
|
// Automation mode (mutually exclusive with each other; null = no automation)
|
||||||
|
automationMode: text('automation_mode').$type<'heartbeat' | 'schedule'>(),
|
||||||
|
|
||||||
// Heartbeat
|
// Heartbeat
|
||||||
heartbeatInterval: integer('heartbeat_interval').default(300), // seconds
|
heartbeatInterval: integer('heartbeat_interval'), // seconds, null = no heartbeat configured
|
||||||
heartbeatTimeout: integer('heartbeat_timeout'), // seconds, null = disabled (default off)
|
heartbeatTimeout: integer('heartbeat_timeout'), // seconds, null = disabled (default off)
|
||||||
lastHeartbeatAt: timestamptz('last_heartbeat_at'),
|
lastHeartbeatAt: timestamptz('last_heartbeat_at'),
|
||||||
|
|
||||||
|
|
@ -97,6 +100,7 @@ export const tasks = pgTable(
|
||||||
index('tasks_parent_task_id_idx').on(t.parentTaskId),
|
index('tasks_parent_task_id_idx').on(t.parentTaskId),
|
||||||
index('tasks_status_idx').on(t.status),
|
index('tasks_status_idx').on(t.status),
|
||||||
index('tasks_priority_idx').on(t.priority),
|
index('tasks_priority_idx').on(t.priority),
|
||||||
|
index('tasks_automation_mode_idx').on(t.automationMode),
|
||||||
index('tasks_heartbeat_idx').on(t.status, t.lastHeartbeatAt),
|
index('tasks_heartbeat_idx').on(t.status, t.lastHeartbeatAt),
|
||||||
],
|
],
|
||||||
);
|
);
|
||||||
|
|
|
||||||
|
|
@ -42,6 +42,8 @@ export const topics = pgTable(
|
||||||
metadata: jsonb('metadata').$type<ChatTopicMetadata | undefined>(),
|
metadata: jsonb('metadata').$type<ChatTopicMetadata | undefined>(),
|
||||||
trigger: text('trigger'), // 'cron' | 'chat' | 'api' | 'eval' - topic creation trigger source
|
trigger: text('trigger'), // 'cron' | 'chat' | 'api' | 'eval' - topic creation trigger source
|
||||||
mode: text('mode'), // 'temp' | 'test' | 'default' - topic usage scenario
|
mode: text('mode'), // 'temp' | 'test' | 'default' - topic usage scenario
|
||||||
|
status: text('status', { enum: ['active', 'completed', 'archived'] }),
|
||||||
|
completedAt: timestamptz('completed_at'),
|
||||||
...timestamps,
|
...timestamps,
|
||||||
},
|
},
|
||||||
(t) => [
|
(t) => [
|
||||||
|
|
@ -52,6 +54,8 @@ export const topics = pgTable(
|
||||||
index('topics_group_id_idx').on(t.groupId),
|
index('topics_group_id_idx').on(t.groupId),
|
||||||
index('topics_agent_id_idx').on(t.agentId),
|
index('topics_agent_id_idx').on(t.agentId),
|
||||||
index('topics_trigger_idx').on(t.trigger),
|
index('topics_trigger_idx').on(t.trigger),
|
||||||
|
index('topics_status_idx').on(t.status),
|
||||||
|
index('topics_user_id_completed_at_idx').on(t.userId, t.completedAt),
|
||||||
index('topics_extract_status_gin_idx').using(
|
index('topics_extract_status_gin_idx').using(
|
||||||
'gin',
|
'gin',
|
||||||
sql`(metadata->'userMemoryExtractStatus') jsonb_path_ops`,
|
sql`(metadata->'userMemoryExtractStatus') jsonb_path_ops`,
|
||||||
|
|
|
||||||
|
|
@ -44,22 +44,35 @@ afterEach(() => {
|
||||||
vi.doUnmock('@/routes/onboarding/config');
|
vi.doUnmock('@/routes/onboarding/config');
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// Each test does vi.resetModules() + dynamic import of the component, which
|
||||||
|
// re-parses antd + @lobehub/ui fresh. On cold CI runs this can blow past the
|
||||||
|
// default 5s timeout even though the test is doing nothing slow itself.
|
||||||
|
const TEST_TIMEOUT_MS = 15_000;
|
||||||
|
|
||||||
describe('ModeSwitch', () => {
|
describe('ModeSwitch', () => {
|
||||||
it('renders both onboarding variants when agent onboarding is enabled', async () => {
|
it(
|
||||||
await renderModeSwitch({ enabled: true, showLabel: true });
|
'renders both onboarding variants when agent onboarding is enabled',
|
||||||
|
async () => {
|
||||||
|
await renderModeSwitch({ enabled: true, showLabel: true });
|
||||||
|
|
||||||
expect(screen.getByText('Choose your onboarding mode')).toBeInTheDocument();
|
expect(screen.getByText('Choose your onboarding mode')).toBeInTheDocument();
|
||||||
expect(screen.getByRole('radio', { name: 'Conversational' })).toBeChecked();
|
expect(screen.getByRole('radio', { name: 'Conversational' })).toBeChecked();
|
||||||
expect(screen.getByRole('radio', { name: 'Classic' })).not.toBeChecked();
|
expect(screen.getByRole('radio', { name: 'Classic' })).not.toBeChecked();
|
||||||
});
|
},
|
||||||
|
TEST_TIMEOUT_MS,
|
||||||
|
);
|
||||||
|
|
||||||
it('hides the onboarding switch entirely when agent onboarding is disabled', async () => {
|
it(
|
||||||
await renderModeSwitch({ enabled: false });
|
'hides the onboarding switch entirely when agent onboarding is disabled',
|
||||||
|
async () => {
|
||||||
|
await renderModeSwitch({ enabled: false });
|
||||||
|
|
||||||
expect(screen.queryByRole('radio', { name: 'Conversational' })).not.toBeInTheDocument();
|
expect(screen.queryByRole('radio', { name: 'Conversational' })).not.toBeInTheDocument();
|
||||||
expect(screen.queryByRole('radio', { name: 'Classic' })).not.toBeInTheDocument();
|
expect(screen.queryByRole('radio', { name: 'Classic' })).not.toBeInTheDocument();
|
||||||
expect(screen.queryByText('Choose your onboarding mode')).not.toBeInTheDocument();
|
expect(screen.queryByText('Choose your onboarding mode')).not.toBeInTheDocument();
|
||||||
});
|
},
|
||||||
|
TEST_TIMEOUT_MS,
|
||||||
|
);
|
||||||
|
|
||||||
it('keeps action buttons visible when agent onboarding is disabled', async () => {
|
it('keeps action buttons visible when agent onboarding is disabled', async () => {
|
||||||
await renderModeSwitch({
|
await renderModeSwitch({
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue