Version: 0.9.32.dev.260419

后端:
1. 会话历史接口切换为统一时间线读取,并兼容 extra.resume 恢复协议
  - api/agent.go:新增 resume->confirm_action 映射(approve/reject/cancel),恢复请求缺 conversation_id 时拦截;GetConversationHistory 改为 GetConversationTimeline
  - routers/routers.go:路由从 GET /conversation-history 切换为 GET /conversation-timeline
  - model/agent.go:删除 GetConversationHistoryItem 旧 DTO
2. 新增会话时间线持久化链路(MySQL + Redis)
  - 新增 model/agent_timeline.go:定义 timeline kind、AgentTimelineEvent、持久化/返回结构
  - 新增 dao/agent_timeline.go:写入事件、按 seq 查询、查询 max seq
  - inits/mysql.go:AutoMigrate 增加 AgentTimelineEvent
  - dao/cache.go:新增 timeline list/seq key,支持 incr/set seq、append/list、全量回填与删除
  - 新增 service/agentsvc/agent_timeline.go:时间线读写编排(Redis 优先、DB 回源、seq 分配与冲突重试、extra 事件映射)
3. 聊天主链路改为写入 timeline,旧 history 服务下线
  - service/agentsvc/agent.go:普通聊天用户/助手消息改为 appendConversationTimelineEvent
  - service/agentsvc/agent_newagent.go:透传 resume_interaction_id;注入 emitter extra hook 持久化卡片事件;正文写入 timeline
  - 删除 service/agentsvc/agent_history.go:下线 conversation-history 旧缓存编排
4. newAgent 恢复与确认防串单增强
  - newAgent/model/graph_run_state.go:AgentGraphRequest 新增 ResumeInteractionID
  - newAgent/node/agent_nodes.go:透传 ResumeInteractionID
  - newAgent/node/chat.go:增加 stale_resume 校验;accept/reject 兼容 approve/cancel;非法动作返回 invalid_confirm_action
  - newAgent/stream/emitter.go:新增 extraEventHook / SetExtraEventHook,在 extra-only 与 confirm 事件触发
5. 日程暂存后同步刷新预览缓存,避免读到拖拽前旧数据
  - service/agentsvc/agent_schedule_state.go:Save 后重建并覆盖 preview 缓存,保留 trace/candidate 等字段
6. 缓存失效策略调整到 timeline 口径
  - middleware/cache_deleter.go:移除 conversation-history 失效逻辑;ChatHistory/AgentChat/AgentTimelineEvent 加入忽略集合

前端:
7. 新增时间线接口与类型定义
  - frontend/src/api/schedule_agent.ts:新增 TimelineEvent/TimelineToolPayload/TimelineConfirmPayload 与 getConversationTimeline
8. AssistantPanel 全面对接 timeline 重建消息与卡片
  - frontend/src/components/dashboard/AssistantPanel.vue:移除旧 history merge/normalize,新增 rebuildStateFromTimeline;支持 execution mode(always_execute);支持 resume-only 发送;修复 confirm 弹层手动关闭后重复弹出;会话标题显示放宽;流式中隐藏 action bar
9. 精排弹窗健壮性与交互动效优化
  - frontend/src/components/assistant/ScheduleFineTuneModal.vue:previewData 支持 nullable,新增 visible 控制与 watch 初始化,补齐空值保护并调整弹窗动画

仓库:
10. 新增前端时间线接入说明文档
  - docs/frontend/newagent_timeline_对接说明.md:接口、kind、payload、刷新重建与迁移建议
This commit is contained in:
Losita
2026-04-19 19:03:41 +08:00
parent 668af5f6c0
commit 0f749e9f5a
22 changed files with 1565 additions and 540 deletions

View File

@@ -260,13 +260,19 @@ watch([() => tasks.value.length, () => todayEvents.value.length, pageLoading], a
</div>
</section>
<el-dialog v-model="createTaskDialogVisible" title="添加任务" width="460px" align-center class="dashboard-dialog">
<el-dialog
v-model="createTaskDialogVisible"
title="添加新任务"
width="440px"
align-center
class="dashboard-dialog premium-dialog"
>
<el-form label-position="top">
<el-form-item label="任务标题">
<el-input v-model="taskForm.title" maxlength="255" placeholder="例如:完成数据库复习" />
</el-form-item>
<el-form-item label="优先级象限">
<el-select v-model="taskForm.priority_group" class="dashboard-dialog__select">
<el-select v-model="taskForm.priority_group" class="dashboard-dialog__select" popper-class="premium-select-popper" placement="bottom-start">
<el-option :value="1" label="1 - 重要且紧急" />
<el-option :value="2" label="2 - 重要不紧急" />
<el-option :value="3" label="3 - 简单不重要" />
@@ -274,12 +280,22 @@ watch([() => tasks.value.length, () => todayEvents.value.length, pageLoading], a
</el-select>
</el-form-item>
<el-form-item label="截止时间">
<el-date-picker v-model="taskForm.deadline_at" type="datetime" placeholder="可选" class="dashboard-dialog__select" />
<el-date-picker
v-model="taskForm.deadline_at"
type="datetime"
placeholder="可选"
class="dashboard-dialog__select"
popper-class="premium-select-popper"
/>
</el-form-item>
</el-form>
<template #footer>
<el-button @click="createTaskDialogVisible = false">取消</el-button>
<el-button type="primary" :loading="createTaskLoading" @click="handleCreateTask">保存任务</el-button>
<div class="premium-dialog__footer">
<button class="premium-btn premium-btn--ghost" @click="createTaskDialogVisible = false">取消</button>
<button class="premium-btn premium-btn--primary" :disabled="createTaskLoading" @click="handleCreateTask">
{{ createTaskLoading ? '保存中...' : '确认添加' }}
</button>
</div>
</template>
</el-dialog>
</template>
@@ -365,4 +381,196 @@ watch([() => tasks.value.length, () => todayEvents.value.length, pageLoading], a
.dashboard-import__shape { position: absolute; right: -50px; bottom: -50px; width: 220px; height: 220px; opacity: 0.1; pointer-events: none; }
.dashboard-import__shape-ring { position: absolute; inset: 0; border: 40px solid #3b82f6; border-radius: 50%; }
.dashboard-import__shape-core { position: absolute; inset: 80px; background: #3b82f6; border-radius: 50%; }
/* --- Premium Dialog Styles --- */
:global(.premium-dialog) {
border-radius: 20px !important;
background: #ffffff !important;
border: 1px solid rgba(15, 23, 42, 0.08) !important;
box-shadow: 0 25px 50px -12px rgba(0, 0, 0, 0.25) !important;
overflow: hidden;
}
:global(.premium-dialog .el-dialog__header) {
padding: 24px 28px 12px !important;
margin-right: 0 !important;
text-align: left;
}
:global(.premium-dialog .el-dialog__title) {
font-size: 18px !important;
font-weight: 800 !important;
color: #0f172a !important;
letter-spacing: -0.02em;
}
:global(.premium-dialog .el-dialog__body) {
padding: 12px 28px 20px !important;
}
:global(.premium-dialog .el-dialog__footer) {
padding: 0 !important;
}
.premium-dialog__footer {
padding: 16px 28px 24px;
background: #f8fafc;
display: flex;
justify-content: flex-end;
gap: 12px;
border-top: 1px solid #f1f5f9;
}
.premium-btn {
height: 40px;
padding: 0 20px;
border-radius: 12px;
font-size: 14px;
font-weight: 700;
cursor: pointer;
transition: all 0.2s cubic-bezier(0.4, 0, 0.2, 1);
border: none;
display: inline-flex;
align-items: center;
justify-content: center;
}
.premium-btn--primary {
background: #3b82f6;
color: #ffffff;
box-shadow: 0 4px 12px rgba(59, 130, 246, 0.2);
}
.premium-btn--primary:hover:not(:disabled) {
background: #2563eb;
transform: translateY(-1px);
box-shadow: 0 6px 16px rgba(59, 130, 246, 0.3);
}
.premium-btn--ghost {
background: #ffffff;
color: #64748b;
border: 1px solid #e2e8f0;
}
.premium-btn--ghost:hover {
background: #f1f5f9;
color: #0f172a;
}
/* 弹出动画覆写 */
:global(.dialog-fade-enter-active .premium-dialog) {
animation: premium-dialog-pop 0.45s cubic-bezier(0.34, 1.56, 0.64, 1) both;
}
@keyframes premium-dialog-pop {
0% { opacity: 0; transform: scale(0.92) translateY(20px); }
60% { opacity: 1; transform: scale(1.02) translateY(-2px); }
100% { opacity: 1; transform: scale(1) translateY(0); }
}
:global(.el-overlay) {
backdrop-filter: blur(6px);
background: rgba(15, 23, 42, 0.35) !important;
}
/* 表单美化 */
:global(.premium-dialog .el-form-item__label) {
font-weight: 700 !important;
color: #475569 !important;
font-size: 13px !important;
margin-bottom: 6px !important;
}
:global(.premium-dialog .el-input__wrapper),
:global(.premium-dialog .el-select__wrapper) {
background-color: #f8fafc !important;
box-shadow: 0 0 0 1px #e2e8f0 inset !important;
border-radius: 10px !important;
padding: 4px 12px !important;
}
:global(.premium-dialog .el-input__wrapper.is-focus),
:global(.premium-dialog .el-select__wrapper.is-focused) {
box-shadow: 0 0 0 2px rgba(59, 130, 246, 0.2) inset !important;
background-color: #ffffff !important;
}
:global(.premium-dialog .el-dialog__headerbtn) {
top: 20px !important;
right: 20px !important;
width: 32px !important;
height: 32px !important;
border-radius: 50% !important;
background: #f1f5f9 !important;
transition: all 0.2s !important;
display: flex !important;
align-items: center !important;
justify-content: center !important;
}
:global(.premium-dialog .el-dialog__headerbtn:hover) {
background: #e2e8f0 !important;
transform: rotate(90deg);
}
:global(.premium-dialog .el-dialog__headerbtn .el-dialog__close) {
color: #64748b !important;
font-size: 16px !important;
font-weight: 800 !important;
}
/* 统一输入框高度与背景 */
:global(.premium-dialog .el-input__inner),
:global(.premium-dialog .el-select .el-input__inner) {
height: 38px !important;
color: #0f172a !important;
font-weight: 600 !important;
}
/* --- 下拉菜单扁平化 --- */
:global(.premium-select-popper) {
border-radius: 16px !important;
border: 1px solid rgba(15, 23, 42, 0.08) !important;
box-shadow: 0 12px 30px -5px rgba(0, 0, 0, 0.12) !important;
background: #ffffff !important;
overflow: hidden !important;
margin-top: 8px !important;
}
:global(.premium-select-popper .el-select-dropdown__list) {
padding: 6px !important;
}
:global(.premium-select-popper .el-select-dropdown__item) {
border-radius: 10px !important;
height: 38px !important;
line-height: 38px !important;
margin-bottom: 2px !important;
font-weight: 600 !important;
color: #475569 !important;
padding: 0 12px !important;
}
:global(.premium-select-popper .el-select-dropdown__item.is-selected) {
background: #eff6ff !important;
color: #3b82f6 !important;
}
:global(.premium-select-popper .el-select-dropdown__item:hover) {
background: #f1f5f9 !important;
color: #0f172a !important;
}
:global(.premium-select-popper .el-popper__arrow) {
display: none !important;
}
/* 时间选择器特定深度覆盖 */
:global(.premium-select-popper.el-picker-popper) {
padding: 0 !important;
}
:global(.premium-select-popper .el-picker-panel) {
background: transparent !important;
}
</style>