Add silent mode support for chat history and filtering
- Implement silent flag in chat and history storage - Add filter options to retrieve chats and messages with silent mode - Update store interfaces to support silent message filtering - Enhance GetChats, GetHistory, and related methods to handle silent messages - Add comprehensive test cases for silent mode functionality
This commit is contained in:
parent
dcc126ef95
commit
392676f2cb
10 changed files with 582 additions and 69 deletions
|
|
@ -314,20 +314,28 @@ func (ast *Assistant) Call(c *gin.Context, payload APIPayload) (interface{}, err
|
||||||
func (ast *Assistant) handleChatStream(c *gin.Context, ctx chatctx.Context, messages []chatMessage.Message, options map[string]interface{}, contents *chatMessage.Contents, callback ...interface{}) (interface{}, error) {
|
func (ast *Assistant) handleChatStream(c *gin.Context, ctx chatctx.Context, messages []chatMessage.Message, options map[string]interface{}, contents *chatMessage.Contents, callback ...interface{}) (interface{}, error) {
|
||||||
clientBreak := make(chan bool, 1)
|
clientBreak := make(chan bool, 1)
|
||||||
done := make(chan bool, 1)
|
done := make(chan bool, 1)
|
||||||
|
var result interface{} = nil
|
||||||
|
var err error = nil
|
||||||
|
|
||||||
// Chat with AI in background
|
// Chat with AI in background
|
||||||
go func() {
|
go func() {
|
||||||
err := ast.streamChat(c, ctx, messages, options, clientBreak, done, contents, callback...)
|
var res interface{} = nil
|
||||||
|
res, err = ast.streamChat(c, ctx, messages, options, clientBreak, contents, callback...)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
chatMessage.New().Error(err).Done().Write(c.Writer)
|
chatMessage.New().Error(err).Done().Write(c.Writer)
|
||||||
|
err = fmt.Errorf("stream chat error %s", err.Error())
|
||||||
}
|
}
|
||||||
|
result = res
|
||||||
done <- true
|
done <- true
|
||||||
}()
|
}()
|
||||||
|
|
||||||
// Wait for completion or client disconnect
|
// Wait for completion or client disconnect
|
||||||
select {
|
select {
|
||||||
case <-done:
|
case <-done:
|
||||||
return nil, nil
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return result, nil
|
||||||
case <-c.Writer.CloseNotify():
|
case <-c.Writer.CloseNotify():
|
||||||
clientBreak <- true
|
clientBreak <- true
|
||||||
return nil, nil
|
return nil, nil
|
||||||
|
|
@ -341,10 +349,9 @@ func (ast *Assistant) streamChat(
|
||||||
messages []chatMessage.Message,
|
messages []chatMessage.Message,
|
||||||
options map[string]interface{},
|
options map[string]interface{},
|
||||||
clientBreak chan bool,
|
clientBreak chan bool,
|
||||||
done chan bool,
|
|
||||||
contents *chatMessage.Contents,
|
contents *chatMessage.Contents,
|
||||||
callback ...interface{},
|
callback ...interface{},
|
||||||
) error {
|
) (interface{}, error) {
|
||||||
|
|
||||||
var cb interface{}
|
var cb interface{}
|
||||||
if len(callback) > 0 {
|
if len(callback) > 0 {
|
||||||
|
|
@ -358,6 +365,8 @@ func (ast *Assistant) streamChat(
|
||||||
|
|
||||||
toolsCount := 0
|
toolsCount := 0
|
||||||
currentMessageID := ""
|
currentMessageID := ""
|
||||||
|
var result interface{} = nil // To save the result
|
||||||
|
var content string = "" // To save the content
|
||||||
err := ast.Chat(c.Request.Context(), messages, options, func(data []byte) int {
|
err := ast.Chat(c.Request.Context(), messages, options, func(data []byte) int {
|
||||||
select {
|
select {
|
||||||
case <-clientBreak:
|
case <-clientBreak:
|
||||||
|
|
@ -511,6 +520,11 @@ func (ast *Assistant) streamChat(
|
||||||
msgType = "tool"
|
msgType = "tool"
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Add the text content to the content
|
||||||
|
if msgType == "text" || msgType == "" {
|
||||||
|
content += msg.Text // Save the content
|
||||||
|
}
|
||||||
|
|
||||||
output := chatMessage.New().Map(map[string]interface{}{
|
output := chatMessage.New().Map(map[string]interface{}{
|
||||||
"text": delta,
|
"text": delta,
|
||||||
"type": msgType,
|
"type": msgType,
|
||||||
|
|
@ -555,8 +569,6 @@ func (ast *Assistant) streamChat(
|
||||||
// Some error occurred in the hook, return the error
|
// Some error occurred in the hook, return the error
|
||||||
if hookErr != nil {
|
if hookErr != nil {
|
||||||
chatMessage.New().Error(hookErr.Error()).Done().Callback(cb).Write(c.Writer)
|
chatMessage.New().Error(hookErr.Error()).Done().Callback(cb).Write(c.Writer)
|
||||||
|
|
||||||
done <- true
|
|
||||||
return 0 // break
|
return 0 // break
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -569,10 +581,14 @@ func (ast *Assistant) streamChat(
|
||||||
if err != nil {
|
if err != nil {
|
||||||
chatMessage.New().Error(err.Error()).Done().Callback(cb).Write(c.Writer)
|
chatMessage.New().Error(err.Error()).Done().Callback(cb).Write(c.Writer)
|
||||||
}
|
}
|
||||||
done <- true
|
|
||||||
return 0 // break
|
return 0 // break
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// if the result is not nil, save the result
|
||||||
|
if res != nil && res.Result != nil {
|
||||||
|
result = res.Result
|
||||||
|
}
|
||||||
|
|
||||||
// The default output
|
// The default output
|
||||||
output := chatMessage.New().Done()
|
output := chatMessage.New().Done()
|
||||||
if res != nil && res.Output != nil {
|
if res != nil && res.Output != nil {
|
||||||
|
|
@ -587,7 +603,6 @@ func (ast *Assistant) streamChat(
|
||||||
}
|
}
|
||||||
|
|
||||||
output.Callback(cb).Write(c.Writer)
|
output.Callback(cb).Write(c.Writer)
|
||||||
done <- true
|
|
||||||
return 0 // break
|
return 0 // break
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -597,21 +612,27 @@ func (ast *Assistant) streamChat(
|
||||||
|
|
||||||
// Handle error
|
// Handle error
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
// raw error
|
// raw error
|
||||||
if errorRaw != "" {
|
if errorRaw != "" {
|
||||||
msg, err := chatMessage.NewStringError(errorRaw)
|
msg, err := chatMessage.NewStringError(errorRaw)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("error: %s", err.Error())
|
return nil, fmt.Errorf("stream chat error %s", err.Error())
|
||||||
}
|
}
|
||||||
msg.Retry = ctx.Retry
|
msg.Retry = ctx.Retry
|
||||||
msg.Silent = ctx.Silent
|
msg.Silent = ctx.Silent
|
||||||
msg.Done().Callback(cb).Write(c.Writer)
|
msg.Done().Callback(cb).Write(c.Writer)
|
||||||
}
|
}
|
||||||
|
|
||||||
return nil
|
// If the result is not nil, return the result
|
||||||
|
if result != nil {
|
||||||
|
return result, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Return the content
|
||||||
|
return strings.TrimSpace(content), nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// saveChatHistory saves the chat history if storage is available
|
// saveChatHistory saves the chat history if storage is available
|
||||||
|
|
|
||||||
|
|
@ -23,23 +23,23 @@ type objectCall struct{}
|
||||||
|
|
||||||
// OptionsCall is the options for the call function
|
// OptionsCall is the options for the call function
|
||||||
type OptionsCall struct {
|
type OptionsCall struct {
|
||||||
Retry OptionsCallRetry `json:"retry,omitempty"`
|
Retry OptionsCallRetry `json:"retry,omitempty"` // Retry options
|
||||||
Options map[string]interface{} `json:"options,omitempty"`
|
Options map[string]interface{} `json:"options,omitempty"` // LLM API options
|
||||||
|
Silent bool `json:"silent,omitempty"` // Silent mode, default is true
|
||||||
}
|
}
|
||||||
|
|
||||||
// OptionsCallRetry is the retry options for the call function
|
// OptionsCallRetry is the retry options for the call function
|
||||||
type OptionsCallRetry struct {
|
type OptionsCallRetry struct {
|
||||||
Times int `json:"times,omitempty"`
|
Times int `json:"times,omitempty"` // Retry times, default is 3
|
||||||
Delay int `json:"delay,omitempty"`
|
Delay int `json:"delay,omitempty"` // Retry delay, default is 200
|
||||||
DelayMax int `json:"delay_max,omitempty"`
|
DelayMax int `json:"delay_max,omitempty"` // Retry delay max, default is 5000
|
||||||
Prompt string `json:"prompt,omitempty"`
|
Prompt string `json:"prompt,omitempty"` // Retry prompt, default is "Please fix the error. \n {{ error }}"
|
||||||
}
|
}
|
||||||
|
|
||||||
// allowedEvents is the allowed events for the call function
|
// allowedEvents is the allowed events for the call function
|
||||||
var allowedEvents = map[string]bool{
|
var allowedEvents = map[string]bool{
|
||||||
"done": true,
|
"done": true,
|
||||||
"retry": true,
|
"retry": true,
|
||||||
"error": true,
|
|
||||||
"message": true,
|
"message": true,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -188,10 +188,11 @@ func (obj *objectCall) run(info *v8go.FunctionCallbackInfo) *v8go.Value {
|
||||||
Retry: OptionsCallRetry{
|
Retry: OptionsCallRetry{
|
||||||
Times: 3,
|
Times: 3,
|
||||||
Delay: 200,
|
Delay: 200,
|
||||||
DelayMax: 5000,
|
DelayMax: 1000,
|
||||||
Prompt: "Please fix the error. \n {{ error }}",
|
Prompt: "{{ input }}\n**Answer is not correct, please try again.**\nError:\n{{ error }} \nAssistant's last answer:\n{{ output }}",
|
||||||
},
|
},
|
||||||
Options: map[string]interface{}{},
|
Silent: true,
|
||||||
|
Options: map[string]interface{}{}, // LLM API options
|
||||||
}
|
}
|
||||||
|
|
||||||
// Get the options
|
// Get the options
|
||||||
|
|
@ -241,8 +242,8 @@ func (obj *objectCall) run(info *v8go.FunctionCallbackInfo) *v8go.Value {
|
||||||
// Update the chat context
|
// Update the chat context
|
||||||
var chatCtx chatctx.Context = global.ChatContext
|
var chatCtx chatctx.Context = global.ChatContext
|
||||||
chatCtx.AssistantID = assistantID
|
chatCtx.AssistantID = assistantID
|
||||||
chatCtx.ChatID = fmt.Sprintf("chat_%s", uuid.New().String()) // New chat id
|
chatCtx.ChatID = fmt.Sprintf("call_%s", uuid.New().String()) // New chat id
|
||||||
chatCtx.Silent = true
|
chatCtx.Silent = options.Silent // Check the silent mode
|
||||||
|
|
||||||
// Define the callback function
|
// Define the callback function
|
||||||
var cb func(msg *chatMessage.Message) = nil
|
var cb func(msg *chatMessage.Message) = nil
|
||||||
|
|
@ -274,7 +275,7 @@ func (obj *objectCall) run(info *v8go.FunctionCallbackInfo) *v8go.Value {
|
||||||
}
|
}
|
||||||
|
|
||||||
// Trigger the done event
|
// Trigger the done event
|
||||||
_, err = obj.trigger(info, "done", jsArgs...)
|
doneResult, err := obj.trigger(info, "done", jsArgs...)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
result, err = obj.retry(jsArgs, err, input, output, info, options)
|
result, err = obj.retry(jsArgs, err, input, output, info, options)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
|
@ -282,6 +283,11 @@ func (obj *objectCall) run(info *v8go.FunctionCallbackInfo) *v8go.Value {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Return the done result
|
||||||
|
if doneResult != nil && !doneResult.IsUndefined() {
|
||||||
|
return doneResult
|
||||||
|
}
|
||||||
|
|
||||||
// Return Value
|
// Return Value
|
||||||
switch v := result.(type) {
|
switch v := result.(type) {
|
||||||
case *v8go.Value:
|
case *v8go.Value:
|
||||||
|
|
|
||||||
|
|
@ -371,12 +371,18 @@ func (m *mockStore) GetAssistants(filter store.AssistantFilter) (*store.Assistan
|
||||||
return nil, nil
|
return nil, nil
|
||||||
}
|
}
|
||||||
func (m *mockStore) GetChat(id string, chatID string) (*store.ChatInfo, error) { return nil, nil }
|
func (m *mockStore) GetChat(id string, chatID string) (*store.ChatInfo, error) { return nil, nil }
|
||||||
|
func (m *mockStore) GetChatWithFilter(id string, chatID string, filter store.ChatFilter) (*store.ChatInfo, error) {
|
||||||
|
return nil, nil
|
||||||
|
}
|
||||||
func (m *mockStore) GetChats(id string, filter store.ChatFilter) (*store.ChatGroupResponse, error) {
|
func (m *mockStore) GetChats(id string, filter store.ChatFilter) (*store.ChatGroupResponse, error) {
|
||||||
return nil, nil
|
return nil, nil
|
||||||
}
|
}
|
||||||
func (m *mockStore) GetHistory(id string, chatID string) ([]map[string]interface{}, error) {
|
func (m *mockStore) GetHistory(id string, chatID string) ([]map[string]interface{}, error) {
|
||||||
return nil, nil
|
return nil, nil
|
||||||
}
|
}
|
||||||
|
func (m *mockStore) GetHistoryWithFilter(id string, chatID string, filter store.ChatFilter) ([]map[string]interface{}, error) {
|
||||||
|
return nil, nil
|
||||||
|
}
|
||||||
func (m *mockStore) SaveAssistant(assistant map[string]interface{}) (interface{}, error) {
|
func (m *mockStore) SaveAssistant(assistant map[string]interface{}) (interface{}, error) {
|
||||||
return nil, nil
|
return nil, nil
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -59,10 +59,10 @@ func (ast *Assistant) InitObject(v8ctx *v8.Context, c *gin.Context, context chat
|
||||||
v8ctx.WithGlobal("context", context.Map())
|
v8ctx.WithGlobal("context", context.Map())
|
||||||
|
|
||||||
// Add methods to the script contexts
|
// Add methods to the script contexts
|
||||||
v8ctx.WithFunction("Plan", jsPlan) // Create a new plan object
|
|
||||||
v8ctx.WithFunction("Send", jsSend)
|
v8ctx.WithFunction("Send", jsSend)
|
||||||
v8ctx.WithFunction("Call", jsCall)
|
|
||||||
v8ctx.WithFunction("Assets", jsAssets)
|
v8ctx.WithFunction("Assets", jsAssets)
|
||||||
|
v8ctx.WithFunction("MakeCall", jsCall) // Create a new call object
|
||||||
|
v8ctx.WithFunction("MakePlan", jsPlan) // Create a new plan object
|
||||||
|
|
||||||
// Shared space methods
|
// Shared space methods
|
||||||
v8ctx.WithFunction("Set", jsSet)
|
v8ctx.WithFunction("Set", jsSet)
|
||||||
|
|
|
||||||
|
|
@ -113,6 +113,12 @@ func (ctx *Context) Map() map[string]interface{} {
|
||||||
if ctx.Stack != "" {
|
if ctx.Stack != "" {
|
||||||
data["stack"] = ctx.Stack
|
data["stack"] = ctx.Stack
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Silent mode
|
||||||
|
if ctx.Silent {
|
||||||
|
data["silent"] = ctx.Silent
|
||||||
|
}
|
||||||
|
|
||||||
if ctx.Path != "" {
|
if ctx.Path != "" {
|
||||||
data["pathname"] = ctx.Path
|
data["pathname"] = ctx.Path
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -18,11 +18,21 @@ func (m *Mongo) GetChat(sid string, cid string) (*ChatInfo, error) {
|
||||||
return &ChatInfo{}, nil
|
return &ChatInfo{}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// GetChatWithFilter retrieves a single chat's information with filter options
|
||||||
|
func (m *Mongo) GetChatWithFilter(sid string, cid string, filter ChatFilter) (*ChatInfo, error) {
|
||||||
|
return &ChatInfo{}, nil
|
||||||
|
}
|
||||||
|
|
||||||
// GetHistory retrieves chat history
|
// GetHistory retrieves chat history
|
||||||
func (m *Mongo) GetHistory(sid string, cid string) ([]map[string]interface{}, error) {
|
func (m *Mongo) GetHistory(sid string, cid string) ([]map[string]interface{}, error) {
|
||||||
return []map[string]interface{}{}, nil
|
return []map[string]interface{}{}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// GetHistoryWithFilter retrieves chat history with filter options
|
||||||
|
func (m *Mongo) GetHistoryWithFilter(sid string, cid string, filter ChatFilter) ([]map[string]interface{}, error) {
|
||||||
|
return []map[string]interface{}{}, nil
|
||||||
|
}
|
||||||
|
|
||||||
// SaveHistory saves chat history
|
// SaveHistory saves chat history
|
||||||
func (m *Mongo) SaveHistory(sid string, messages []map[string]interface{}, cid string, context map[string]interface{}) error {
|
func (m *Mongo) SaveHistory(sid string, messages []map[string]interface{}, cid string, context map[string]interface{}) error {
|
||||||
return nil
|
return nil
|
||||||
|
|
|
||||||
|
|
@ -18,11 +18,21 @@ func (r *Redis) GetChat(sid string, cid string) (*ChatInfo, error) {
|
||||||
return &ChatInfo{}, nil
|
return &ChatInfo{}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// GetChatWithFilter retrieves a single chat's information with filter options
|
||||||
|
func (r *Redis) GetChatWithFilter(sid string, cid string, filter ChatFilter) (*ChatInfo, error) {
|
||||||
|
return &ChatInfo{}, nil
|
||||||
|
}
|
||||||
|
|
||||||
// GetHistory retrieves chat history
|
// GetHistory retrieves chat history
|
||||||
func (r *Redis) GetHistory(sid string, cid string) ([]map[string]interface{}, error) {
|
func (r *Redis) GetHistory(sid string, cid string) ([]map[string]interface{}, error) {
|
||||||
return []map[string]interface{}{}, nil
|
return []map[string]interface{}{}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// GetHistoryWithFilter retrieves chat history with filter options
|
||||||
|
func (r *Redis) GetHistoryWithFilter(sid string, cid string, filter ChatFilter) ([]map[string]interface{}, error) {
|
||||||
|
return []map[string]interface{}{}, nil
|
||||||
|
}
|
||||||
|
|
||||||
// SaveHistory saves chat history
|
// SaveHistory saves chat history
|
||||||
func (r *Redis) SaveHistory(sid string, messages []map[string]interface{}, cid string, context map[string]interface{}) error {
|
func (r *Redis) SaveHistory(sid string, messages []map[string]interface{}, cid string, context map[string]interface{}) error {
|
||||||
return nil
|
return nil
|
||||||
|
|
|
||||||
|
|
@ -24,6 +24,7 @@ type ChatFilter struct {
|
||||||
Page int `json:"page,omitempty"` // Page number, starting from 1
|
Page int `json:"page,omitempty"` // Page number, starting from 1
|
||||||
PageSize int `json:"pagesize,omitempty"` // Number of items per page
|
PageSize int `json:"pagesize,omitempty"` // Number of items per page
|
||||||
Order string `json:"order,omitempty"` // Sort order: desc/asc
|
Order string `json:"order,omitempty"` // Sort order: desc/asc
|
||||||
|
Silent *bool `json:"silent,omitempty"` // Include silent messages (default: false)
|
||||||
}
|
}
|
||||||
|
|
||||||
// ChatGroup represents the chat group structure
|
// ChatGroup represents the chat group structure
|
||||||
|
|
@ -86,12 +87,26 @@ type Store interface {
|
||||||
// Returns: Chat information and potential error
|
// Returns: Chat information and potential error
|
||||||
GetChat(sid string, cid string) (*ChatInfo, error)
|
GetChat(sid string, cid string) (*ChatInfo, error)
|
||||||
|
|
||||||
|
// GetChatWithFilter retrieves a single chat's information with filter options
|
||||||
|
// sid: Session ID
|
||||||
|
// cid: Chat ID
|
||||||
|
// filter: Filter conditions
|
||||||
|
// Returns: Chat information and potential error
|
||||||
|
GetChatWithFilter(sid string, cid string, filter ChatFilter) (*ChatInfo, error)
|
||||||
|
|
||||||
// GetHistory retrieves chat history
|
// GetHistory retrieves chat history
|
||||||
// sid: Session ID
|
// sid: Session ID
|
||||||
// cid: Chat ID
|
// cid: Chat ID
|
||||||
// Returns: History record list and potential error
|
// Returns: History record list and potential error
|
||||||
GetHistory(sid string, cid string) ([]map[string]interface{}, error)
|
GetHistory(sid string, cid string) ([]map[string]interface{}, error)
|
||||||
|
|
||||||
|
// GetHistoryWithFilter retrieves chat history with filter options
|
||||||
|
// sid: Session ID
|
||||||
|
// cid: Chat ID
|
||||||
|
// filter: Filter conditions
|
||||||
|
// Returns: History record list and potential error
|
||||||
|
GetHistoryWithFilter(sid string, cid string, filter ChatFilter) ([]map[string]interface{}, error)
|
||||||
|
|
||||||
// SaveHistory saves chat history
|
// SaveHistory saves chat history
|
||||||
// sid: Session ID
|
// sid: Session ID
|
||||||
// messages: Message list
|
// messages: Message list
|
||||||
|
|
|
||||||
266
neo/store/xun.go
266
neo/store/xun.go
|
|
@ -3,7 +3,6 @@ package store
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
"math"
|
"math"
|
||||||
"strings"
|
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/google/uuid"
|
"github.com/google/uuid"
|
||||||
|
|
@ -145,6 +144,7 @@ func (conv *Xun) initHistoryTable() error {
|
||||||
table.String("assistant_name", 200).Null()
|
table.String("assistant_name", 200).Null()
|
||||||
table.String("assistant_avatar", 200).Null()
|
table.String("assistant_avatar", 200).Null()
|
||||||
table.JSON("mentions").Null()
|
table.JSON("mentions").Null()
|
||||||
|
table.Boolean("silent").SetDefault(false).Index()
|
||||||
table.TimestampTz("created_at").SetDefaultRaw("NOW()").Index()
|
table.TimestampTz("created_at").SetDefaultRaw("NOW()").Index()
|
||||||
table.TimestampTz("updated_at").Null().Index()
|
table.TimestampTz("updated_at").Null().Index()
|
||||||
table.TimestampTz("expired_at").Null().Index()
|
table.TimestampTz("expired_at").Null().Index()
|
||||||
|
|
@ -162,7 +162,7 @@ func (conv *Xun) initHistoryTable() error {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
fields := []string{"id", "sid", "cid", "uid", "role", "name", "content", "context", "assistant_id", "assistant_name", "assistant_avatar", "mentions", "created_at", "updated_at", "expired_at"}
|
fields := []string{"id", "sid", "cid", "uid", "role", "name", "content", "context", "assistant_id", "assistant_name", "assistant_avatar", "mentions", "silent", "created_at", "updated_at", "expired_at"}
|
||||||
for _, field := range fields {
|
for _, field := range fields {
|
||||||
if !tab.HasColumn(field) {
|
if !tab.HasColumn(field) {
|
||||||
return fmt.Errorf("%s is required", field)
|
return fmt.Errorf("%s is required", field)
|
||||||
|
|
@ -187,6 +187,7 @@ func (conv *Xun) initChatTable() error {
|
||||||
table.String("title", 200).Null()
|
table.String("title", 200).Null()
|
||||||
table.String("assistant_id", 200).Null().Index()
|
table.String("assistant_id", 200).Null().Index()
|
||||||
table.String("sid", 255).Index()
|
table.String("sid", 255).Index()
|
||||||
|
table.Boolean("silent").SetDefault(false).Index()
|
||||||
table.TimestampTz("created_at").SetDefaultRaw("NOW()").Index()
|
table.TimestampTz("created_at").SetDefaultRaw("NOW()").Index()
|
||||||
table.TimestampTz("updated_at").Null().Index()
|
table.TimestampTz("updated_at").Null().Index()
|
||||||
})
|
})
|
||||||
|
|
@ -203,7 +204,7 @@ func (conv *Xun) initChatTable() error {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
fields := []string{"id", "chat_id", "title", "assistant_id", "sid", "created_at", "updated_at"}
|
fields := []string{"id", "chat_id", "title", "assistant_id", "sid", "silent", "created_at", "updated_at"}
|
||||||
for _, field := range fields {
|
for _, field := range fields {
|
||||||
if !tab.HasColumn(field) {
|
if !tab.HasColumn(field) {
|
||||||
return fmt.Errorf("%s is required", field)
|
return fmt.Errorf("%s is required", field)
|
||||||
|
|
@ -319,53 +320,90 @@ func (conv *Xun) UpdateChatTitle(sid string, cid string, title string) error {
|
||||||
|
|
||||||
// GetChats get the chat list with grouping by date
|
// GetChats get the chat list with grouping by date
|
||||||
func (conv *Xun) GetChats(sid string, filter ChatFilter) (*ChatGroupResponse, error) {
|
func (conv *Xun) GetChats(sid string, filter ChatFilter) (*ChatGroupResponse, error) {
|
||||||
|
// Default behavior: exclude silent chats
|
||||||
|
if filter.Silent == nil {
|
||||||
|
silentFalse := false
|
||||||
|
filter.Silent = &silentFalse
|
||||||
|
}
|
||||||
|
|
||||||
|
return conv.getChatsWithFilter(sid, filter)
|
||||||
|
}
|
||||||
|
|
||||||
|
// getChatsWithFilter get the chats with filter options
|
||||||
|
func (conv *Xun) getChatsWithFilter(sid string, filter ChatFilter) (*ChatGroupResponse, error) {
|
||||||
userID, err := conv.getUserID(sid)
|
userID, err := conv.getUserID(sid)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
// Set defaults
|
// Set default values
|
||||||
if filter.PageSize <= 0 {
|
|
||||||
filter.PageSize = 100
|
|
||||||
}
|
|
||||||
if filter.Page <= 0 {
|
if filter.Page <= 0 {
|
||||||
filter.Page = 1
|
filter.Page = 1
|
||||||
}
|
}
|
||||||
|
if filter.PageSize <= 0 {
|
||||||
|
filter.PageSize = 20
|
||||||
|
}
|
||||||
if filter.Order == "" {
|
if filter.Order == "" {
|
||||||
filter.Order = "desc"
|
filter.Order = "desc"
|
||||||
}
|
}
|
||||||
|
|
||||||
// Build base query
|
|
||||||
qb := conv.newQueryChat().
|
|
||||||
Select("chat_id", "title", "assistant_id", "created_at", "updated_at").
|
|
||||||
Where("sid", userID).
|
|
||||||
Where("chat_id", "!=", "")
|
|
||||||
|
|
||||||
// Add keyword filter
|
|
||||||
if filter.Keywords != "" {
|
|
||||||
keyword := strings.TrimSpace(filter.Keywords)
|
|
||||||
if keyword != "" {
|
|
||||||
qb.Where("title", "like", "%"+keyword+"%")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Get total count
|
// Get total count
|
||||||
total, err := qb.Clone().Count()
|
qbCount := conv.newQueryChat().
|
||||||
|
Where("sid", userID)
|
||||||
|
|
||||||
|
// Apply silent filter if provided
|
||||||
|
if filter.Silent != nil {
|
||||||
|
if *filter.Silent {
|
||||||
|
// Include all chats (both silent and non-silent)
|
||||||
|
} else {
|
||||||
|
// Only include non-silent chats
|
||||||
|
qbCount.Where("silent", false)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Apply keyword filter if provided
|
||||||
|
if filter.Keywords != "" {
|
||||||
|
qbCount.Where("title", "like", fmt.Sprintf("%%%s%%", filter.Keywords))
|
||||||
|
}
|
||||||
|
|
||||||
|
total, err := qbCount.Count()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
// Calculate pagination
|
// Calculate last page
|
||||||
offset := (filter.Page - 1) * filter.PageSize
|
|
||||||
lastPage := int(math.Ceil(float64(total) / float64(filter.PageSize)))
|
lastPage := int(math.Ceil(float64(total) / float64(filter.PageSize)))
|
||||||
|
if lastPage < 1 {
|
||||||
|
lastPage = 1
|
||||||
|
}
|
||||||
|
|
||||||
// Get paginated results
|
// Get chats with pagination
|
||||||
rows, err := qb.
|
qb := conv.newQueryChat().
|
||||||
OrderBy("updated_at", filter.Order).
|
Select("chat_id", "title", "assistant_id", "silent", "created_at", "updated_at").
|
||||||
OrderBy("created_at", filter.Order).
|
Where("sid", userID)
|
||||||
|
|
||||||
|
// Apply silent filter if provided
|
||||||
|
if filter.Silent != nil {
|
||||||
|
if *filter.Silent {
|
||||||
|
// Include all chats (both silent and non-silent)
|
||||||
|
} else {
|
||||||
|
// Only include non-silent chats
|
||||||
|
qb.Where("silent", false)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Apply keyword filter if provided
|
||||||
|
if filter.Keywords != "" {
|
||||||
|
qb.Where("title", "like", fmt.Sprintf("%%%s%%", filter.Keywords))
|
||||||
|
}
|
||||||
|
|
||||||
|
// Apply pagination
|
||||||
|
offset := (filter.Page - 1) * filter.PageSize
|
||||||
|
qb.OrderBy("updated_at", filter.Order).
|
||||||
Offset(offset).
|
Offset(offset).
|
||||||
Limit(filter.PageSize).
|
Limit(filter.PageSize)
|
||||||
Get()
|
|
||||||
|
rows, err := qb.Get()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
@ -385,16 +423,16 @@ func (conv *Xun) GetChats(sid string, filter ChatFilter) (*ChatGroupResponse, er
|
||||||
"Even Earlier": {},
|
"Even Earlier": {},
|
||||||
}
|
}
|
||||||
|
|
||||||
// Get assistant details for all chats
|
// Collect assistant IDs to fetch their details
|
||||||
assistantIDs := []interface{}{}
|
assistantIDs := []interface{}{}
|
||||||
assistantMap := make(map[string]map[string]interface{})
|
|
||||||
|
|
||||||
for _, row := range rows {
|
for _, row := range rows {
|
||||||
if assistantID := row.Get("assistant_id"); assistantID != nil && assistantID != "" {
|
if assistantID := row.Get("assistant_id"); assistantID != nil && assistantID != "" {
|
||||||
assistantIDs = append(assistantIDs, assistantID)
|
assistantIDs = append(assistantIDs, assistantID)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Fetch assistant details
|
||||||
|
assistantMap := map[string]map[string]interface{}{}
|
||||||
if len(assistantIDs) > 0 {
|
if len(assistantIDs) > 0 {
|
||||||
assistants, err := conv.query.New().
|
assistants, err := conv.query.New().
|
||||||
Table(conv.getAssistantTable()).
|
Table(conv.getAssistantTable()).
|
||||||
|
|
@ -425,6 +463,7 @@ func (conv *Xun) GetChats(sid string, filter ChatFilter) (*ChatGroupResponse, er
|
||||||
"chat_id": chatID,
|
"chat_id": chatID,
|
||||||
"title": row.Get("title"),
|
"title": row.Get("title"),
|
||||||
"assistant_id": row.Get("assistant_id"),
|
"assistant_id": row.Get("assistant_id"),
|
||||||
|
"silent": row.Get("silent"),
|
||||||
}
|
}
|
||||||
|
|
||||||
// Add assistant details if available
|
// Add assistant details if available
|
||||||
|
|
@ -502,11 +541,14 @@ func (conv *Xun) GetHistory(sid string, cid string) ([]map[string]interface{}, e
|
||||||
}
|
}
|
||||||
|
|
||||||
qb := conv.newQuery().
|
qb := conv.newQuery().
|
||||||
Select("role", "name", "content", "context", "assistant_id", "assistant_name", "assistant_avatar", "mentions", "uid", "created_at", "updated_at").
|
Select("role", "name", "content", "context", "assistant_id", "assistant_name", "assistant_avatar", "mentions", "uid", "silent", "created_at", "updated_at").
|
||||||
Where("sid", userID).
|
Where("sid", userID).
|
||||||
Where("cid", cid).
|
Where("cid", cid).
|
||||||
OrderBy("id", "desc")
|
OrderBy("id", "desc")
|
||||||
|
|
||||||
|
// By default, exclude silent messages
|
||||||
|
qb.Where("silent", false)
|
||||||
|
|
||||||
if conv.setting.TTL > 0 {
|
if conv.setting.TTL > 0 {
|
||||||
qb.Where("expired_at", ">", time.Now())
|
qb.Where("expired_at", ">", time.Now())
|
||||||
}
|
}
|
||||||
|
|
@ -533,6 +575,7 @@ func (conv *Xun) GetHistory(sid string, cid string) ([]map[string]interface{}, e
|
||||||
"assistant_avatar": row.Get("assistant_avatar"),
|
"assistant_avatar": row.Get("assistant_avatar"),
|
||||||
"mentions": row.Get("mentions"),
|
"mentions": row.Get("mentions"),
|
||||||
"uid": row.Get("uid"),
|
"uid": row.Get("uid"),
|
||||||
|
"silent": row.Get("silent"),
|
||||||
"created_at": row.Get("created_at"),
|
"created_at": row.Get("created_at"),
|
||||||
"updated_at": row.Get("updated_at"),
|
"updated_at": row.Get("updated_at"),
|
||||||
}
|
}
|
||||||
|
|
@ -562,6 +605,23 @@ func (conv *Xun) SaveHistory(sid string, messages []map[string]interface{}, cid
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Get silent flag from context
|
||||||
|
var silent bool = false
|
||||||
|
if context != nil {
|
||||||
|
if silentVal, ok := context["silent"]; ok {
|
||||||
|
switch v := silentVal.(type) {
|
||||||
|
case bool:
|
||||||
|
silent = v
|
||||||
|
case string:
|
||||||
|
silent = v == "true" || v == "1" || v == "yes"
|
||||||
|
case int:
|
||||||
|
silent = v != 0
|
||||||
|
case float64:
|
||||||
|
silent = v != 0
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// First ensure chat record exists
|
// First ensure chat record exists
|
||||||
exists, err := conv.newQueryChat().
|
exists, err := conv.newQueryChat().
|
||||||
Where("chat_id", cid).
|
Where("chat_id", cid).
|
||||||
|
|
@ -579,6 +639,7 @@ func (conv *Xun) SaveHistory(sid string, messages []map[string]interface{}, cid
|
||||||
"chat_id": cid,
|
"chat_id": cid,
|
||||||
"sid": userID,
|
"sid": userID,
|
||||||
"assistant_id": assistantID,
|
"assistant_id": assistantID,
|
||||||
|
"silent": silent,
|
||||||
"created_at": time.Now(),
|
"created_at": time.Now(),
|
||||||
})
|
})
|
||||||
|
|
||||||
|
|
@ -586,19 +647,18 @@ func (conv *Xun) SaveHistory(sid string, messages []map[string]interface{}, cid
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
// Update assistant_id if it exists
|
// Update assistant_id and silent if needed
|
||||||
if assistantID != nil {
|
|
||||||
_, err = conv.newQueryChat().
|
_, err = conv.newQueryChat().
|
||||||
Where("chat_id", cid).
|
Where("chat_id", cid).
|
||||||
Where("sid", userID).
|
Where("sid", userID).
|
||||||
Update(map[string]interface{}{
|
Update(map[string]interface{}{
|
||||||
"assistant_id": assistantID,
|
"assistant_id": assistantID,
|
||||||
|
"silent": silent,
|
||||||
})
|
})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
// Save message history
|
// Save message history
|
||||||
defer conv.clean()
|
defer conv.clean()
|
||||||
|
|
@ -650,6 +710,7 @@ func (conv *Xun) SaveHistory(sid string, messages []map[string]interface{}, cid
|
||||||
"assistant_id": nil,
|
"assistant_id": nil,
|
||||||
"assistant_name": nil,
|
"assistant_name": nil,
|
||||||
"assistant_avatar": nil,
|
"assistant_avatar": nil,
|
||||||
|
"silent": silent,
|
||||||
"created_at": now,
|
"created_at": now,
|
||||||
"updated_at": nil,
|
"updated_at": nil,
|
||||||
"expired_at": expiredAt,
|
"expired_at": expiredAt,
|
||||||
|
|
@ -736,7 +797,7 @@ func (conv *Xun) GetChat(sid string, cid string) (*ChatInfo, error) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Get chat history
|
// Get chat history with default filter (silent=false)
|
||||||
history, err := conv.GetHistory(sid, cid)
|
history, err := conv.GetHistory(sid, cid)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
|
|
@ -748,6 +809,64 @@ func (conv *Xun) GetChat(sid string, cid string) (*ChatInfo, error) {
|
||||||
}, nil
|
}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// GetChatWithFilter get the chat info and its history with filter options
|
||||||
|
func (conv *Xun) GetChatWithFilter(sid string, cid string, filter ChatFilter) (*ChatInfo, error) {
|
||||||
|
userID, err := conv.getUserID(sid)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get chat info
|
||||||
|
qb := conv.newQueryChat().
|
||||||
|
Select("chat_id", "title", "assistant_id").
|
||||||
|
Where("sid", userID).
|
||||||
|
Where("chat_id", cid)
|
||||||
|
|
||||||
|
row, err := qb.First()
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// Return nil if chat_id is nil (means no chat found)
|
||||||
|
if row.Get("chat_id") == nil {
|
||||||
|
return nil, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
chat := map[string]interface{}{
|
||||||
|
"chat_id": row.Get("chat_id"),
|
||||||
|
"title": row.Get("title"),
|
||||||
|
"assistant_id": row.Get("assistant_id"),
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get assistant details if assistant_id exists
|
||||||
|
if assistantID := row.Get("assistant_id"); assistantID != nil && assistantID != "" {
|
||||||
|
assistant, err := conv.query.New().
|
||||||
|
Table(conv.getAssistantTable()).
|
||||||
|
Select("name", "avatar").
|
||||||
|
Where("assistant_id", assistantID).
|
||||||
|
First()
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
if assistant != nil {
|
||||||
|
chat["assistant_name"] = assistant.Get("name")
|
||||||
|
chat["assistant_avatar"] = assistant.Get("avatar")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get chat history with filter
|
||||||
|
history, err := conv.GetHistoryWithFilter(sid, cid, filter)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return &ChatInfo{
|
||||||
|
Chat: chat,
|
||||||
|
History: history,
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
|
||||||
// DeleteChat deletes a specific chat and its history
|
// DeleteChat deletes a specific chat and its history
|
||||||
func (conv *Xun) DeleteChat(sid string, cid string) error {
|
func (conv *Xun) DeleteChat(sid string, cid string) error {
|
||||||
userID, err := conv.getUserID(sid)
|
userID, err := conv.getUserID(sid)
|
||||||
|
|
@ -1164,3 +1283,74 @@ func (conv *Xun) GetAssistantTags() ([]string, error) {
|
||||||
}
|
}
|
||||||
return tags, nil
|
return tags, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// GetHistoryWithFilter get the history with filter options
|
||||||
|
func (conv *Xun) GetHistoryWithFilter(sid string, cid string, filter ChatFilter) ([]map[string]interface{}, error) {
|
||||||
|
userID, err := conv.getUserID(sid)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
qb := conv.newQuery().
|
||||||
|
Select("role", "name", "content", "context", "assistant_id", "assistant_name", "assistant_avatar", "mentions", "uid", "silent", "created_at", "updated_at").
|
||||||
|
Where("sid", userID).
|
||||||
|
Where("cid", cid).
|
||||||
|
OrderBy("id", "desc")
|
||||||
|
|
||||||
|
// Apply silent filter if provided, otherwise exclude silent messages by default
|
||||||
|
if filter.Silent != nil {
|
||||||
|
if *filter.Silent {
|
||||||
|
// Include all messages (both silent and non-silent)
|
||||||
|
} else {
|
||||||
|
// Only include non-silent messages
|
||||||
|
qb.Where("silent", false)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// Default behavior: exclude silent messages
|
||||||
|
qb.Where("silent", false)
|
||||||
|
}
|
||||||
|
|
||||||
|
if conv.setting.TTL > 0 {
|
||||||
|
qb.Where("expired_at", ">", time.Now())
|
||||||
|
}
|
||||||
|
|
||||||
|
limit := 20
|
||||||
|
if conv.setting.MaxSize > 0 {
|
||||||
|
limit = conv.setting.MaxSize
|
||||||
|
}
|
||||||
|
if filter.PageSize > 0 {
|
||||||
|
limit = filter.PageSize
|
||||||
|
}
|
||||||
|
|
||||||
|
// Apply pagination if provided
|
||||||
|
if filter.Page > 0 {
|
||||||
|
offset := (filter.Page - 1) * limit
|
||||||
|
qb.Offset(offset)
|
||||||
|
}
|
||||||
|
|
||||||
|
rows, err := qb.Limit(limit).Get()
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
res := []map[string]interface{}{}
|
||||||
|
for _, row := range rows {
|
||||||
|
message := map[string]interface{}{
|
||||||
|
"role": row.Get("role"),
|
||||||
|
"name": row.Get("name"),
|
||||||
|
"content": row.Get("content"),
|
||||||
|
"context": row.Get("context"),
|
||||||
|
"assistant_id": row.Get("assistant_id"),
|
||||||
|
"assistant_name": row.Get("assistant_name"),
|
||||||
|
"assistant_avatar": row.Get("assistant_avatar"),
|
||||||
|
"mentions": row.Get("mentions"),
|
||||||
|
"uid": row.Get("uid"),
|
||||||
|
"silent": row.Get("silent"),
|
||||||
|
"created_at": row.Get("created_at"),
|
||||||
|
"updated_at": row.Get("updated_at"),
|
||||||
|
}
|
||||||
|
res = append([]map[string]interface{}{message}, res...)
|
||||||
|
}
|
||||||
|
|
||||||
|
return res, nil
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -996,3 +996,252 @@ func TestGetAssistantTags(t *testing.T) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestXunSaveAndGetHistoryWithSilent(t *testing.T) {
|
||||||
|
test.Prepare(t, config.Conf)
|
||||||
|
defer test.Clean()
|
||||||
|
defer capsule.Schema().DropTableIfExists("__unit_test_conversation_history")
|
||||||
|
defer capsule.Schema().DropTableIfExists("__unit_test_conversation_chat")
|
||||||
|
|
||||||
|
err := capsule.Schema().DropTableIfExists("__unit_test_conversation_history")
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
err = capsule.Schema().DropTableIfExists("__unit_test_conversation_chat")
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
store, err := NewXun(Setting{
|
||||||
|
Connector: "default",
|
||||||
|
Prefix: "__unit_test_conversation_",
|
||||||
|
TTL: 3600,
|
||||||
|
})
|
||||||
|
|
||||||
|
// save the history with silent messages
|
||||||
|
sid := "123456"
|
||||||
|
cid := "silent_test"
|
||||||
|
|
||||||
|
// First save regular messages
|
||||||
|
messages := []map[string]interface{}{
|
||||||
|
{"role": "user", "name": "user1", "content": "hello"},
|
||||||
|
{"role": "assistant", "name": "assistant1", "content": "Hi! How can I help you?"},
|
||||||
|
}
|
||||||
|
context := map[string]interface{}{
|
||||||
|
"assistant_id": "test-assistant-1",
|
||||||
|
}
|
||||||
|
err = store.SaveHistory(sid, messages, cid, context)
|
||||||
|
assert.Nil(t, err)
|
||||||
|
|
||||||
|
// Then save silent messages
|
||||||
|
silentMessages := []map[string]interface{}{
|
||||||
|
{"role": "user", "name": "user1", "content": "silent message"},
|
||||||
|
{"role": "assistant", "name": "assistant1", "content": "This is a silent response"},
|
||||||
|
}
|
||||||
|
silentContext := map[string]interface{}{
|
||||||
|
"assistant_id": "test-assistant-1",
|
||||||
|
"silent": true,
|
||||||
|
}
|
||||||
|
err = store.SaveHistory(sid, silentMessages, cid, silentContext)
|
||||||
|
assert.Nil(t, err)
|
||||||
|
|
||||||
|
// Get history without filter (should only return non-silent messages)
|
||||||
|
data, err := store.GetHistory(sid, cid)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
assert.Equal(t, 2, len(data))
|
||||||
|
for _, msg := range data {
|
||||||
|
// Check if silent is false, handling different types
|
||||||
|
isSilent := false
|
||||||
|
switch v := msg["silent"].(type) {
|
||||||
|
case bool:
|
||||||
|
isSilent = v
|
||||||
|
case int:
|
||||||
|
isSilent = v != 0
|
||||||
|
case int64:
|
||||||
|
isSilent = v != 0
|
||||||
|
case float64:
|
||||||
|
isSilent = v != 0
|
||||||
|
}
|
||||||
|
assert.False(t, isSilent, "message should not be silent")
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get history with silent=true filter (should return all messages)
|
||||||
|
silentTrue := true
|
||||||
|
filter := ChatFilter{
|
||||||
|
Silent: &silentTrue,
|
||||||
|
}
|
||||||
|
allData, err := store.GetHistoryWithFilter(sid, cid, filter)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
assert.Equal(t, 4, len(allData))
|
||||||
|
|
||||||
|
// Count silent messages
|
||||||
|
silentCount := 0
|
||||||
|
for _, msg := range allData {
|
||||||
|
// Check if silent is true, handling different types
|
||||||
|
isSilent := false
|
||||||
|
switch v := msg["silent"].(type) {
|
||||||
|
case bool:
|
||||||
|
isSilent = v
|
||||||
|
case int:
|
||||||
|
isSilent = v != 0
|
||||||
|
case int64:
|
||||||
|
isSilent = v != 0
|
||||||
|
case float64:
|
||||||
|
isSilent = v != 0
|
||||||
|
}
|
||||||
|
if isSilent {
|
||||||
|
silentCount++
|
||||||
|
}
|
||||||
|
}
|
||||||
|
assert.Equal(t, 2, silentCount)
|
||||||
|
|
||||||
|
// Get chat with filter (should include silent messages)
|
||||||
|
chat, err := store.GetChatWithFilter(sid, cid, filter)
|
||||||
|
assert.Nil(t, err)
|
||||||
|
assert.Equal(t, 4, len(chat.History))
|
||||||
|
|
||||||
|
// Get chat without filter (should exclude silent messages)
|
||||||
|
chatNoSilent, err := store.GetChat(sid, cid)
|
||||||
|
assert.Nil(t, err)
|
||||||
|
assert.Equal(t, 2, len(chatNoSilent.History))
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestXunGetChatsWithSilent(t *testing.T) {
|
||||||
|
test.Prepare(t, config.Conf)
|
||||||
|
defer test.Clean()
|
||||||
|
defer capsule.Schema().DropTableIfExists("__unit_test_conversation_history")
|
||||||
|
defer capsule.Schema().DropTableIfExists("__unit_test_conversation_chat")
|
||||||
|
defer capsule.Schema().DropTableIfExists("__unit_test_conversation_assistant")
|
||||||
|
|
||||||
|
// Drop tables before test
|
||||||
|
err := capsule.Schema().DropTableIfExists("__unit_test_conversation_history")
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
err = capsule.Schema().DropTableIfExists("__unit_test_conversation_chat")
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
err = capsule.Schema().DropTableIfExists("__unit_test_conversation_assistant")
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
store, err := NewXun(Setting{
|
||||||
|
Connector: "default",
|
||||||
|
Prefix: "__unit_test_conversation_",
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Create test assistant
|
||||||
|
assistant := map[string]interface{}{
|
||||||
|
"assistant_id": "test-assistant-1",
|
||||||
|
"name": "Test Assistant 1",
|
||||||
|
"avatar": "avatar1.png",
|
||||||
|
"type": "assistant",
|
||||||
|
"connector": "test",
|
||||||
|
}
|
||||||
|
_, err = store.SaveAssistant(assistant)
|
||||||
|
assert.Nil(t, err)
|
||||||
|
|
||||||
|
// Save some test chats
|
||||||
|
sid := "test_user"
|
||||||
|
messages := []map[string]interface{}{
|
||||||
|
{"role": "user", "content": "test message"},
|
||||||
|
}
|
||||||
|
|
||||||
|
// Create regular chats
|
||||||
|
for i := 0; i < 3; i++ {
|
||||||
|
chatID := fmt.Sprintf("regular_chat_%d", i)
|
||||||
|
title := fmt.Sprintf("Regular Chat %d", i)
|
||||||
|
context := map[string]interface{}{
|
||||||
|
"assistant_id": "test-assistant-1",
|
||||||
|
"silent": false,
|
||||||
|
}
|
||||||
|
|
||||||
|
// Save history to create the chat
|
||||||
|
err = store.SaveHistory(sid, messages, chatID, context)
|
||||||
|
assert.Nil(t, err)
|
||||||
|
|
||||||
|
// Update the chat title
|
||||||
|
err = store.UpdateChatTitle(sid, chatID, title)
|
||||||
|
assert.Nil(t, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Create silent chats
|
||||||
|
for i := 0; i < 2; i++ {
|
||||||
|
chatID := fmt.Sprintf("silent_chat_%d", i)
|
||||||
|
title := fmt.Sprintf("Silent Chat %d", i)
|
||||||
|
context := map[string]interface{}{
|
||||||
|
"assistant_id": "test-assistant-1",
|
||||||
|
"silent": true,
|
||||||
|
}
|
||||||
|
|
||||||
|
// Save history to create the chat
|
||||||
|
err = store.SaveHistory(sid, messages, chatID, context)
|
||||||
|
assert.Nil(t, err)
|
||||||
|
|
||||||
|
// Update the chat title
|
||||||
|
err = store.UpdateChatTitle(sid, chatID, title)
|
||||||
|
assert.Nil(t, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Test GetChats with default filter (should exclude silent chats)
|
||||||
|
defaultFilter := ChatFilter{
|
||||||
|
PageSize: 10,
|
||||||
|
Order: "desc",
|
||||||
|
}
|
||||||
|
defaultGroups, err := store.GetChats(sid, defaultFilter)
|
||||||
|
assert.Nil(t, err)
|
||||||
|
assert.NotNil(t, defaultGroups)
|
||||||
|
|
||||||
|
// Count total chats in all groups
|
||||||
|
totalDefaultChats := 0
|
||||||
|
for _, group := range defaultGroups.Groups {
|
||||||
|
totalDefaultChats += len(group.Chats)
|
||||||
|
}
|
||||||
|
assert.Equal(t, 3, totalDefaultChats, "Default filter should only return non-silent chats")
|
||||||
|
|
||||||
|
// Test GetChats with silent=true filter (should include all chats)
|
||||||
|
silentTrue := true
|
||||||
|
silentFilter := ChatFilter{
|
||||||
|
PageSize: 10,
|
||||||
|
Order: "desc",
|
||||||
|
Silent: &silentTrue,
|
||||||
|
}
|
||||||
|
silentGroups, err := store.GetChats(sid, silentFilter)
|
||||||
|
assert.Nil(t, err)
|
||||||
|
assert.NotNil(t, silentGroups)
|
||||||
|
|
||||||
|
// Count total chats in all groups
|
||||||
|
totalSilentChats := 0
|
||||||
|
for _, group := range silentGroups.Groups {
|
||||||
|
totalSilentChats += len(group.Chats)
|
||||||
|
}
|
||||||
|
assert.Equal(t, 5, totalSilentChats, "Silent filter should return all chats")
|
||||||
|
|
||||||
|
// Test GetChats with silent=false filter (should only include non-silent chats)
|
||||||
|
silentFalse := false
|
||||||
|
nonSilentFilter := ChatFilter{
|
||||||
|
PageSize: 10,
|
||||||
|
Order: "desc",
|
||||||
|
Silent: &silentFalse,
|
||||||
|
}
|
||||||
|
nonSilentGroups, err := store.GetChats(sid, nonSilentFilter)
|
||||||
|
assert.Nil(t, err)
|
||||||
|
assert.NotNil(t, nonSilentGroups)
|
||||||
|
|
||||||
|
// Count total chats in all groups
|
||||||
|
totalNonSilentChats := 0
|
||||||
|
for _, group := range nonSilentGroups.Groups {
|
||||||
|
totalNonSilentChats += len(group.Chats)
|
||||||
|
}
|
||||||
|
assert.Equal(t, 3, totalNonSilentChats, "Non-silent filter should only return non-silent chats")
|
||||||
|
}
|
||||||
|
|
|
||||||
Loading…
Add table
Reference in a new issue