完善日程的ai接口

This commit is contained in:
2026-06-11 15:15:07 +08:00
parent 94cf23152c
commit 774020fe75
6 changed files with 563 additions and 14 deletions
+1 -1
View File
@@ -319,7 +319,7 @@ func handleChat(ctx *gin.Context) {
toolNames = append(toolNames, tool.Function.Name)
}
emitTrace("function_tools", "prepare", "success", "已启用 Function Calling 工具", map[string]interface{}{"tools": toolNames})
openaiMsgs = append([]openaiMessage{{Role: "system", Content: "可用工具使用规则:当用户询问“我是谁”“当前登录用户是谁”“我的用户信息”等当前身份问题时,调用 ops_ai_assistant_current_user;工具返回 loggedIn=true 时按工具结果回答当前用户信息,返回 loggedIn=false 时说明不知道并提示需要登录才能获取信息。当用户询问采购订单列表、采购订单详情、采购状态或数量统计时,调用 ops_ai_assistant_purchase_query;该工具只允许查询,禁止新增、修改、删除采购数据。当用户询问本月、今天、本周、下周等相对日期的日程时,先调用 time 获取明确 start_date/end_date,再调用 ops_ai_assistant_schedule_query 查询日程。不要臆造工具结果中不存在的信息。"}}, openaiMsgs...)
openaiMsgs = append([]openaiMessage{{Role: "system", Content: "可用工具使用规则:当用户询问“我是谁”“当前登录用户是谁”“我的用户信息”等当前身份问题时,调用 ops_ai_assistant_current_user;工具返回 loggedIn=true 时按工具结果回答当前用户信息,返回 loggedIn=false 时说明不知道并提示需要登录才能获取信息。当用户询问采购订单列表、采购订单详情、采购状态或数量统计时,调用 ops_ai_assistant_purchase_query;该工具只允许查询,禁止新增、修改、删除采购数据。当用户询问本月、今天、本周、下周等相对日期的日程时,先调用 time 获取明确 start_date/end_date,再调用 ops_ai_assistant_schedule_query 查询日程。用户询问有哪些日历或创建日程但未提供 calendar_id 时,调用 ops_ai_assistant_calendar_list 获取可用日历;用户明确要求新增/修改/删除日程时,分别调用 ops_ai_assistant_schedule_create、ops_ai_assistant_schedule_update、ops_ai_assistant_schedule_delete,写入工具必须基于用户明确指令和明确日期,不要自行猜测日历或事件 ID。不要臆造工具结果中不存在的信息。"}}, openaiMsgs...)
var toolExecuted bool
openaiMsgs, toolExecuted, err = runOpenAIToolLoop(ctx.Request.Context(), profile, openaiMsgs, functionTools, currentUser, tracker, emitTrace)
if err != nil {
+9 -1
View File
@@ -111,7 +111,11 @@ func ensureBuiltinAIChatTools() error {
{Name: "time", Enabled: true, Description: "解析当前时间、相对日期和日期范围。", SortOrder: 0},
{Name: "ops_ai_assistant_current_user", Enabled: true, Description: "返回当前登录用户信息;未登录时提示需要登录才能获取信息。", SortOrder: 5},
{Name: "ops_ai_assistant_purchase_query", Enabled: true, Description: "查询采购订单列表、详情和状态数量统计。", SortOrder: 8},
{Name: "ops_ai_assistant_schedule_query", Enabled: true, Description: "按日期范围查询当前用户可见的 OPS 日历/日程。", SortOrder: 10},
{Name: "ops_ai_assistant_calendar_list", Enabled: true, Description: "查询当前用户可见的 OPS 日历列表。", SortOrder: 10},
{Name: "ops_ai_assistant_schedule_query", Enabled: true, Description: "按日期范围查询当前用户可见的 OPS 日历/日程。", SortOrder: 12},
{Name: "ops_ai_assistant_schedule_create", Enabled: true, Description: "创建 OPS 日历日程。", SortOrder: 14},
{Name: "ops_ai_assistant_schedule_update", Enabled: true, Description: "修改 OPS 日历日程。", SortOrder: 16},
{Name: "ops_ai_assistant_schedule_delete", Enabled: true, Description: "删除 OPS 日历日程。", SortOrder: 18},
}
for _, builtin := range builtins {
var existing TabAIChatTool
@@ -208,7 +212,11 @@ func seedAIChatConfigFromYAMLIfEmpty() error {
{Name: "time", Enabled: true, Description: "解析当前时间、相对日期和日期范围。"},
{Name: "ops_ai_assistant_current_user", Enabled: true, Description: "返回当前登录用户信息;未登录时提示需要登录才能获取信息。"},
{Name: "ops_ai_assistant_purchase_query", Enabled: true, Description: "查询采购订单列表、详情和状态数量统计。"},
{Name: "ops_ai_assistant_calendar_list", Enabled: true, Description: "查询当前用户可见的 OPS 日历列表。"},
{Name: "ops_ai_assistant_schedule_query", Enabled: true, Description: "按日期范围查询当前用户可见的 OPS 日历/日程。"},
{Name: "ops_ai_assistant_schedule_create", Enabled: true, Description: "创建 OPS 日历日程。"},
{Name: "ops_ai_assistant_schedule_update", Enabled: true, Description: "修改 OPS 日历日程。"},
{Name: "ops_ai_assistant_schedule_delete", Enabled: true, Description: "删除 OPS 日历日程。"},
}
}
for i, tool := range tools {
+197 -12
View File
@@ -179,23 +179,208 @@ func (calendarScheduleProvider) QuerySchedules(ctx context.Context, query agents
result := make([]agents.ScheduleEvent, 0, len(events))
for _, event := range events {
result = append(result, agents.ScheduleEvent{
ID: event.ID,
CalendarID: event.CalendarID,
UserID: event.UserID,
Title: event.Title,
StartDate: event.StartDate,
EndDate: event.EndDate,
ScheduleType: event.ScheduleType,
IsPublic: event.IsPublic,
Remark: event.Remark,
AccessNote: event.AccessNote,
CanEdit: event.CanEdit,
result = append(result, calendarScheduleEventToAgent(event))
}
return result, nil
}
func (calendarScheduleProvider) ListCalendars(ctx context.Context, userID uint) ([]agents.CalendarListOutput, error) {
select {
case <-ctx.Done():
return nil, ctx.Err()
default:
}
var calendars []TabCalendar
if err := models.DB.Where("deleted_at IS NULL AND (is_public = ? OR user_id = ?)", true, userID).Order("created_at DESC").Find(&calendars).Error; err != nil {
return nil, err
}
result := make([]agents.CalendarListOutput, 0, len(calendars))
for _, cal := range calendars {
result = append(result, agents.CalendarListOutput{
ID: cal.ID,
Name: cal.Name,
Description: cal.Description,
Color: cal.Color,
IsPublic: cal.IsPublic,
CanEdit: canModifyCalendar(userID, cal.UserID),
})
}
return result, nil
}
func (calendarScheduleProvider) CreateScheduleEvent(ctx context.Context, userID uint, input agents.CreateScheduleInput) (*agents.ScheduleEvent, error) {
select {
case <-ctx.Done():
return nil, ctx.Err()
default:
}
calendar := TabCalendar{}
if err := models.DB.Where("id = ? AND deleted_at IS NULL", input.CalendarID).First(&calendar).Error; err != nil {
return nil, err
}
if !calendar.IsPublic && !canModifyCalendar(userID, calendar.UserID) {
return nil, gorm.ErrRecordNotFound
}
startDate, err := time.Parse("2006-01-02", input.StartDate)
if err != nil {
return nil, err
}
endDate, err := time.Parse("2006-01-02", input.EndDate)
if err != nil {
return nil, err
}
if input.ScheduleType == "" {
input.ScheduleType = "work"
}
event := TabCalendarEvent{
CalendarID: input.CalendarID,
UserID: userID,
Title: input.Title,
StartDate: &startDate,
EndDate: &endDate,
ScheduleType: input.ScheduleType,
IsPublic: input.IsPublic,
Remark: input.Remark,
}
if err := models.DB.Create(&event).Error; err != nil {
return nil, err
}
newContent, _ := json.Marshal(event)
models.DB.Create(&TabCalendarLog{CalendarID: event.CalendarID, EventID: event.ID, UserID: userID, ActionType: "create_event", NewContent: string(newContent), Remark: "AI 助手创建日程"})
return calendarEventToAgent(event, userID), nil
}
func (calendarScheduleProvider) UpdateScheduleEvent(ctx context.Context, userID uint, input agents.UpdateScheduleInput) (*agents.ScheduleEvent, error) {
select {
case <-ctx.Done():
return nil, ctx.Err()
default:
}
oldEvent := TabCalendarEvent{}
if err := models.DB.Where("id = ?", input.EventID).First(&oldEvent).Error; err != nil {
return nil, err
}
if !canModifyCalendarEvent(userID, oldEvent) {
return nil, gorm.ErrRecordNotFound
}
startDate, err := time.Parse("2006-01-02", input.StartDate)
if err != nil {
return nil, err
}
endDate, err := time.Parse("2006-01-02", input.EndDate)
if err != nil {
return nil, err
}
if input.ScheduleType == "" {
input.ScheduleType = "work"
}
updates := map[string]interface{}{
"title": input.Title,
"start_date": &startDate,
"end_date": &endDate,
"schedule_type": input.ScheduleType,
"is_public": input.IsPublic,
"remark": input.Remark,
}
if err := models.DB.Model(&oldEvent).Updates(updates).Error; err != nil {
return nil, err
}
oldContent, _ := json.Marshal(oldEvent)
newContent, _ := json.Marshal(updates)
models.DB.Create(&TabCalendarLog{CalendarID: oldEvent.CalendarID, EventID: oldEvent.ID, UserID: userID, ActionType: "update_event", OldContent: string(oldContent), NewContent: string(newContent), Remark: "AI 助手更新日程"})
var event TabCalendarEvent
if err := models.DB.Where("id = ?", input.EventID).First(&event).Error; err != nil {
return nil, err
}
return calendarEventToAgent(event, userID), nil
}
func (calendarScheduleProvider) DeleteScheduleEvent(ctx context.Context, userID uint, eventID uint) error {
select {
case <-ctx.Done():
return ctx.Err()
default:
}
event := TabCalendarEvent{}
if err := models.DB.Where("id = ?", eventID).First(&event).Error; err != nil {
return err
}
if !canModifyCalendarEvent(userID, event) {
return gorm.ErrRecordNotFound
}
if err := models.DB.Delete(&event).Error; err != nil {
return err
}
oldContent, _ := json.Marshal(event)
models.DB.Create(&TabCalendarLog{CalendarID: event.CalendarID, EventID: event.ID, UserID: userID, ActionType: "delete_event", OldContent: string(oldContent), Remark: "AI 助手删除日程"})
return nil
}
func calendarScheduleEventToAgent(event CalendarScheduleEvent) agents.ScheduleEvent {
return agents.ScheduleEvent{
ID: event.ID,
CalendarID: event.CalendarID,
UserID: event.UserID,
Title: event.Title,
StartDate: event.StartDate,
EndDate: event.EndDate,
ScheduleType: event.ScheduleType,
IsPublic: event.IsPublic,
Remark: event.Remark,
AccessNote: event.AccessNote,
CanEdit: event.CanEdit,
}
}
func calendarEventToAgent(event TabCalendarEvent, userID uint) *agents.ScheduleEvent {
accessNote := ""
canEdit := false
if userID > 0 {
var calendar TabCalendar
if models.DB.Where("id = ?", event.CalendarID).First(&calendar).Error == nil {
canEdit = canModifyCalendar(userID, event.UserID) || calendar.UserID == userID
}
if !event.IsPublic {
accessNote = "非公开日程,仅因当前用户具备相关权限可见"
}
}
return &agents.ScheduleEvent{
ID: event.ID,
CalendarID: event.CalendarID,
UserID: event.UserID,
Title: event.Title,
StartDate: formatDatePtr(event.StartDate),
EndDate: formatDatePtr(event.EndDate),
ScheduleType: event.ScheduleType,
IsPublic: event.IsPublic,
Remark: event.Remark,
AccessNote: accessNote,
CanEdit: canEdit,
}
}
func canModifyCalendarEvent(userID uint, event TabCalendarEvent) bool {
if userID == event.UserID {
return true
}
var calendar TabCalendar
if models.DB.Where("id = ?", event.CalendarID).First(&calendar).Error == nil {
return canModifyCalendar(userID, calendar.UserID)
}
return false
}
func QueryCalendarSchedulesForAI(query CalendarScheduleQuery) ([]CalendarScheduleEvent, error) {
startDate := dateOnly(query.StartDate)
endDate := dateOnly(query.EndDate)