Files
smartmate/backend/conv/task-class.go
Losita ba8e8e2a82 Version: 0.9.39.dev.260423
后端:
1. 记忆系统移除 todo_hint 类型——随口记已由 Task 系统承接,todo_hint 语义重叠且无完成追踪
- 全链路清理:常量、校验、默认重要度、30 天 TTL、读取预算、LLM 抽取提示词枚举
- 总预算从四类收缩为三类(preference / constraint / fact)

2. 记忆抽取触发点从 chat-persist 移至 graph-completion——避免随口记消息被误提取为 constraint/preference
- chat-persist consumer 不再自动入队 memory.extract.requested,仅负责聊天历史落库
- graph 完成后新增条件发布:检测 UsedQuickNote 标记,调用过 quick_note_create 则跳过记忆抽取
- ResetForNextRun 重置 UsedQuickNote,防止跨轮残留导致后续正常消息记忆抽取被误跳过

3. 任务类查询接口返回 items 补充数据库主键 ID(前端拖拽编排依赖此字段)

前端:
4. 排程视图新增手动编排模式——侧边栏任务块拖拽入周课表 + 悬浮删除热区 + 建议块虚线标识
- TaskClassSidebar 拖拽发起 + 预览态嵌入时间格式化(含周次/星期)
- WeekPlanningBoard 外部拖入 / 内部移动 / 悬浮删除区交互
- ScheduleView 手动编排状态机(进入/退出/取消/覆盖确认)+ apply 时同步处理新增与删除
2026-04-23 23:07:04 +08:00

201 lines
6.0 KiB
Go
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
package conv
import (
"errors"
"time"
"github.com/LoveLosita/smartflow/backend/model"
"github.com/LoveLosita/smartflow/backend/respond"
)
const dateLayout = "2006-01-02"
func parseDatePtr(s string) (*time.Time, error) {
if s == "" {
return nil, nil
}
t, err := time.ParseInLocation(dateLayout, s, time.Local)
if err != nil {
return nil, err
}
return &t, nil
}
func ProcessUserAddTaskClassRequest(req *model.UserAddTaskClassRequest, userID int) (*model.TaskClass, []model.TaskClassItem, error) {
startDate, err := parseDatePtr(req.StartDate)
if err != nil {
return nil, nil, respond.WrongParamType
}
endDate, err := parseDatePtr(req.EndDate)
if err != nil {
return nil, nil, respond.WrongParamType
}
//1.填充section1,2
taskClass := model.TaskClass{
Name: &req.Name,
Mode: &req.Mode,
StartDate: startDate,
EndDate: endDate,
UserID: &userID,
}
//2.填充section3
taskClass.TotalSlots = &req.Config.TotalSlots
taskClass.AllowFillerCourse = &req.Config.AllowFillerCourse
taskClass.Strategy = &req.Config.Strategy
/*//处理 ExcludedSlots 切片为 JSON 字符串
if len(req.Config.ExcludedSlots) > 0 {
//转换为 JSON 字符串
excludedSlotsJSON := "["
for i, slot := range req.Config.ExcludedSlots {
excludedSlotsJSON += string(rune(slot + '0')) //简单转换为字符
if i != len(req.Config.ExcludedSlots)-1 {
excludedSlotsJSON += ","
}
}
excludedSlotsJSON += "]"
taskClass.ExcludedSlots = &excludedSlotsJSON
} else {
emptyJSON := "[]"
taskClass.ExcludedSlots = &emptyJSON
}*/
taskClass.ExcludedSlots = req.Config.ExcludedSlots // 直接复用 IntSlice 类型,前端也能正确解析为 []int
//3.开始构建 items
var items []model.TaskClassItem
for _, itemReq := range req.Items {
item := model.TaskClassItem{ //填充section 2
Order: &itemReq.Order,
Content: &itemReq.Content,
EmbeddedTime: itemReq.EmbeddedTime,
Status: nil,
}
items = append(items, item)
}
return &taskClass, items, nil
}
func timeOrZero(t *time.Time) time.Time {
if t == nil {
return time.Time{}
}
return *t
}
func TaskClassModelToResponse(taskClasses []model.TaskClass) *model.UserGetTaskClassesResponse {
var resp model.UserGetTaskClassesResponse
for _, tc := range taskClasses {
tcResp := model.TaskClassSummary{
ID: tc.ID,
Name: *tc.Name,
Mode: *tc.Mode,
StartDate: timeOrZero(tc.StartDate),
EndDate: timeOrZero(tc.EndDate),
TotalSlots: *tc.TotalSlots,
Strategy: *tc.Strategy,
}
resp.TaskClasses = append(resp.TaskClasses, tcResp)
}
return &resp
}
func ProcessUserGetCompleteTaskClassRequest(taskClass *model.TaskClass) (*model.UserAddTaskClassRequest, error) {
if taskClass == nil {
return nil, errors.New("源数据对象不可为空")
}
// 1. 映射基础信息 (处理指针解引用)
req := &model.UserAddTaskClassRequest{
Name: safeStr(taskClass.Name),
Mode: safeStr(taskClass.Mode),
StartDate: formatTime(taskClass.StartDate),
EndDate: formatTime(taskClass.EndDate),
}
// 2. 映射配置信息 (Config Section)
req.Config = model.UserAddTaskClassConfig{
TotalSlots: safeInt(taskClass.TotalSlots),
AllowFillerCourse: safeBool(taskClass.AllowFillerCourse),
Strategy: safeStr(taskClass.Strategy),
}
/*// 3. 处理 ExcludedSlots JSON 字符串 -> []int
if taskClass.ExcludedSlots != nil && *taskClass.ExcludedSlots != "" {
var excluded []int
// 直接使用标准反序列化,比手动处理 rune 字符要健壮得多
if err := json.Unmarshal([]byte(*taskClass.ExcludedSlots), &excluded); err == nil {
req.Config.ExcludedSlots = excluded
}
}*/
req.Config.ExcludedSlots = taskClass.ExcludedSlots // 直接复用 IntSlice 类型,前端也能正确解析为 []int
// 4. 映射子项信息 (Items Section)
// 此时 items 已经通过 Preload 加载到了 taskClass.Items 中
req.Items = make([]model.UserAddTaskClassItemRequest, 0, len(taskClass.Items))
for _, item := range taskClass.Items {
itemReq := model.UserAddTaskClassItemRequest{
ID: item.ID, // 填充数据库主键 ID前端拖拽编排依赖此字段
Order: safeInt(item.Order),
Content: safeStr(item.Content),
EmbeddedTime: item.EmbeddedTime, // 结构体指针直接复用
}
req.Items = append(req.Items, itemReq)
}
return req, nil
}
// UserInsertTaskItemRequestToModel 用于将填入空闲时段日程的请求转换为 Schedule 模型
func UserInsertTaskItemRequestToModel(req *model.UserInsertTaskClassItemToScheduleRequest, item *model.TaskClassItem, taskID *int, userID, startSection, endSection int) ([]model.Schedule, *model.ScheduleEvent, error) {
var schedules []model.Schedule
for section := startSection; section <= endSection; section++ {
req1 := &model.Schedule{
UserID: userID,
EmbeddedTaskID: taskID,
Week: req.Week,
DayOfWeek: req.DayOfWeek,
Section: section,
Status: "normal",
}
schedules = append(schedules, *req1)
}
startTime, endTime, err := RelativeTimeToRealTime(req.Week, req.DayOfWeek, startSection, endSection)
if err != nil {
return nil, nil, err
}
req2 := &model.ScheduleEvent{
UserID: userID, // 由调用方填充
Name: safeStr(item.Content), // 任务内容作为事件名称
Type: "task",
RelID: &item.ID, // 关联到 TaskClassItem 的 ID
CanBeEmbedded: false, // 任务事件允许嵌入其他任务(如果需要的话)
StartTime: startTime,
EndTime: endTime,
}
return schedules, req2, nil
}
// --- 🛡️ 辅助工具函数:保持代码清爽并防止 Panic ---
func safeStr(s *string) string {
if s == nil {
return ""
}
return *s
}
func safeInt(i *int) int {
if i == nil {
return 0
}
return *i
}
func safeBool(b *bool) bool {
if b == nil {
return true
}
return *b
}
func formatTime(t *time.Time) string {
if t == nil {
return ""
}
// 务必使用 2006-01-02 格式以匹配前端校验
return t.Format("2006-01-02")
}