diff --git a/neo/api.go b/neo/api.go index be5640a4..1ecc7dd3 100644 --- a/neo/api.go +++ b/neo/api.go @@ -15,8 +15,8 @@ import ( "github.com/yaoapp/gou/connector" "github.com/yaoapp/gou/process" "github.com/yaoapp/yao/helper" - "github.com/yaoapp/yao/neo/conversation" "github.com/yaoapp/yao/neo/message" + "github.com/yaoapp/yao/neo/store" ) // API registers the Neo API endpoints @@ -227,7 +227,7 @@ func (neo *DSL) handleChatList(c *gin.Context) { } // Create filter from query parameters - filter := conversation.ChatFilter{ + filter := store.ChatFilter{ Keywords: c.Query("keywords"), Order: c.Query("order"), } @@ -245,7 +245,7 @@ func (neo *DSL) handleChatList(c *gin.Context) { } } - response, err := neo.Conversation.GetChats(sid, filter) + response, err := neo.Store.GetChats(sid, filter) if err != nil { c.JSON(500, gin.H{"message": err.Error(), "code": 500}) c.Done() @@ -266,7 +266,7 @@ func (neo *DSL) handleChatHistory(c *gin.Context) { } cid := c.Query("chat_id") - history, err := neo.Conversation.GetHistory(sid, cid) + history, err := neo.Store.GetHistory(sid, cid) if err != nil { c.JSON(500, gin.H{"message": err.Error(), "code": 500}) c.Done() @@ -450,7 +450,7 @@ func (neo *DSL) handleChatDetail(c *gin.Context) { return } - chat, err := neo.Conversation.GetChat(sid, chatID) + chat, err := neo.Store.GetChat(sid, chatID) if err != nil { c.JSON(500, gin.H{"message": err.Error(), "code": 500}) c.Done() @@ -475,14 +475,14 @@ func (neo *DSL) handleMentions(c *gin.Context) { mentionable := true // Query mentionable assistants - filter := conversation.AssistantFilter{ + filter := store.AssistantFilter{ Keywords: keywords, Mentionable: &mentionable, Page: 1, PageSize: 20, } - response, err := neo.Conversation.GetAssistants(filter) + response, err := neo.Store.GetAssistants(filter) if err != nil { c.JSON(500, gin.H{"message": err.Error(), "code": 500}) c.Done() @@ -552,7 +552,7 @@ func (neo *DSL) handleChatUpdate(c *gin.Context) { return } - err := neo.Conversation.UpdateChatTitle(sid, chatID, body.Title) + err := neo.Store.UpdateChatTitle(sid, chatID, body.Title) if err != nil { c.JSON(500, gin.H{"message": err.Error(), "code": 500}) c.Done() @@ -579,7 +579,7 @@ func (neo *DSL) handleChatDelete(c *gin.Context) { return } - err := neo.Conversation.DeleteChat(sid, chatID) + err := neo.Store.DeleteChat(sid, chatID) if err != nil { c.JSON(500, gin.H{"message": err.Error(), "code": 500}) c.Done() @@ -599,7 +599,7 @@ func (neo *DSL) handleChatsDeleteAll(c *gin.Context) { return } - err := neo.Conversation.DeleteAllChats(sid) + err := neo.Store.DeleteAllChats(sid) if err != nil { c.JSON(500, gin.H{"message": err.Error(), "code": 500}) c.Done() @@ -840,7 +840,7 @@ func (neo *DSL) handleGenerateCustom(c *gin.Context) { // handleAssistantList handles listing assistants func (neo *DSL) handleAssistantList(c *gin.Context) { // Parse filter parameters - filter := conversation.AssistantFilter{ + filter := store.AssistantFilter{ Page: 1, PageSize: 20, } @@ -894,7 +894,7 @@ func (neo *DSL) handleAssistantList(c *gin.Context) { } } - response, err := neo.Conversation.GetAssistants(filter) + response, err := neo.Store.GetAssistants(filter) if err != nil { c.JSON(500, gin.H{"message": err.Error(), "code": 500}) c.Done() @@ -930,13 +930,13 @@ func (neo *DSL) handleAssistantDetail(c *gin.Context) { return } - filter := conversation.AssistantFilter{ + filter := store.AssistantFilter{ AssistantID: assistantID, Page: 1, PageSize: 1, } - response, err := neo.Conversation.GetAssistants(filter) + response, err := neo.Store.GetAssistants(filter) if err != nil { c.JSON(500, gin.H{"message": err.Error(), "code": 500}) c.Done() @@ -962,7 +962,7 @@ func (neo *DSL) handleAssistantSave(c *gin.Context) { return } - id, err := neo.Conversation.SaveAssistant(assistant) + id, err := neo.Store.SaveAssistant(assistant) if err != nil { c.JSON(500, gin.H{"message": err.Error(), "code": 500}) c.Done() @@ -987,7 +987,7 @@ func (neo *DSL) handleAssistantDelete(c *gin.Context) { return } - err := neo.Conversation.DeleteAssistant(assistantID) + err := neo.Store.DeleteAssistant(assistantID) if err != nil { c.JSON(500, gin.H{"message": err.Error(), "code": 500}) c.Done() diff --git a/neo/assistant/assistant.go b/neo/assistant/assistant.go new file mode 100644 index 00000000..0de36580 --- /dev/null +++ b/neo/assistant/assistant.go @@ -0,0 +1,26 @@ +package assistant + +import "github.com/yaoapp/yao/neo/store" + +// loadedAssistant the loaded assistant +var loadedAssistant = map[string]*Assistant{} + +// LoadLocal create a new assistant from local +func LoadLocal(path string) *Assistant { + return nil +} + +// LoadZip create a new assistant from zip +func LoadZip(zip string) *Assistant { + return nil +} + +// LoadRemote create a new assistant from remote +func LoadRemote(url string) *Assistant { + return nil +} + +// LoadStore create a new assistant from store +func LoadStore(store store.Store) *Assistant { + return nil +} diff --git a/neo/load.go b/neo/load.go index dec5fb2f..0a918d81 100644 --- a/neo/load.go +++ b/neo/load.go @@ -9,7 +9,7 @@ import ( "github.com/yaoapp/gou/application" "github.com/yaoapp/yao/config" "github.com/yaoapp/yao/neo/assistant" - "github.com/yaoapp/yao/neo/conversation" + "github.com/yaoapp/yao/neo/store" ) // Neo the neo AI assistant @@ -23,7 +23,7 @@ func Load(cfg config.Config) error { Prompts: []assistant.Prompt{}, Option: map[string]interface{}{}, Allows: []string{}, - ConversationSetting: conversation.Setting{ + StoreSetting: store.Setting{ Table: "yao_neo_conversation", Connector: "default", }, @@ -39,14 +39,14 @@ func Load(cfg config.Config) error { return err } - if setting.ConversationSetting.MaxSize == 0 { - setting.ConversationSetting.MaxSize = 100 + if setting.StoreSetting.MaxSize == 0 { + setting.StoreSetting.MaxSize = 100 } Neo = &setting - // Conversation Setting - err = Neo.createConversation() + // Store Setting + err = Neo.createStore() if err != nil { return err } diff --git a/neo/neo.go b/neo/neo.go index 0a034a67..97d16767 100644 --- a/neo/neo.go +++ b/neo/neo.go @@ -13,8 +13,8 @@ import ( "github.com/yaoapp/yao/neo/assistant" "github.com/yaoapp/yao/neo/assistant/local" "github.com/yaoapp/yao/neo/assistant/openai" - "github.com/yaoapp/yao/neo/conversation" "github.com/yaoapp/yao/neo/message" + "github.com/yaoapp/yao/neo/store" "github.com/yaoapp/yao/share" ) @@ -473,7 +473,7 @@ func (neo *DSL) createDefaultAssistant() (assistant.API, error) { // chatMessages get the chat messages func (neo *DSL) chatMessages(ctx Context, content ...string) ([]map[string]interface{}, error) { - history, err := neo.Conversation.GetHistory(ctx.Sid, ctx.ChatID) + history, err := neo.Store.GetHistory(ctx.Sid, ctx.ChatID) if err != nil { return nil, err } @@ -493,7 +493,7 @@ func (neo *DSL) chatMessages(ctx Context, content ...string) ([]map[string]inter func (neo *DSL) saveHistory(sid string, chatID string, content []byte, messages []map[string]interface{}) { if len(content) > 0 && sid != "" && len(messages) > 0 { - err := neo.Conversation.SaveHistory( + err := neo.Store.SaveHistory( sid, []map[string]interface{}{ {"role": "user", "content": messages[len(messages)-1]["content"], "name": sid}, @@ -509,39 +509,39 @@ func (neo *DSL) saveHistory(sid string, chatID string, content []byte, messages } } -// createConversation create a new conversation -func (neo *DSL) createConversation() error { +// createStore create a new store +func (neo *DSL) createStore() error { var err error - if neo.ConversationSetting.Connector == "default" || neo.ConversationSetting.Connector == "" { - neo.Conversation, err = conversation.NewXun(neo.ConversationSetting) + if neo.StoreSetting.Connector == "default" || neo.StoreSetting.Connector == "" { + neo.Store, err = store.NewXun(neo.StoreSetting) return err } // other connector - conn, err := connector.Select(neo.ConversationSetting.Connector) + conn, err := connector.Select(neo.StoreSetting.Connector) if err != nil { return err } if conn.Is(connector.DATABASE) { - neo.Conversation, err = conversation.NewXun(neo.ConversationSetting) + neo.Store, err = store.NewXun(neo.StoreSetting) return err } else if conn.Is(connector.REDIS) { - neo.Conversation = conversation.NewRedis() + neo.Store = store.NewRedis() return nil } else if conn.Is(connector.MONGO) { - neo.Conversation = conversation.NewMongo() + neo.Store = store.NewMongo() return nil } else if conn.Is(connector.WEAVIATE) { - neo.Conversation = conversation.NewWeaviate() + neo.Store = store.NewWeaviate() return nil } - return fmt.Errorf("%s conversation connector %s not support", neo.ID, neo.ConversationSetting.Connector) + return fmt.Errorf("%s store connector %s not support", neo.ID, neo.StoreSetting.Connector) } // sendMessage sends a message to the client diff --git a/neo/process.go b/neo/process.go index 7a27cbac..6ccd0a5d 100644 --- a/neo/process.go +++ b/neo/process.go @@ -7,8 +7,8 @@ import ( "github.com/gin-gonic/gin" "github.com/yaoapp/gou/process" "github.com/yaoapp/kun/exception" - "github.com/yaoapp/yao/neo/conversation" "github.com/yaoapp/yao/neo/message" + "github.com/yaoapp/yao/neo/store" ) // GetNeo returns the Neo instance @@ -32,7 +32,6 @@ func init() { // ProcessWrite process the write request func ProcessWrite(process *process.Process) interface{} { - process.ValidateArgNums(2) w, ok := process.Args[0].(gin.ResponseWriter) @@ -63,11 +62,11 @@ func processAssistantCreate(process *process.Process) interface{} { data := process.ArgsMap(0) neo := GetNeo() - if neo.Conversation == nil { - exception.New("Neo conversation is not initialized", 500).Throw() + if neo.Store == nil { + exception.New("Neo store is not initialized", 500).Throw() } - id, err := neo.Conversation.SaveAssistant(data) + id, err := neo.Store.SaveAssistant(data) if err != nil { exception.New("Failed to create assistant: %s", 500, err.Error()).Throw() } @@ -81,11 +80,11 @@ func processAssistantSave(process *process.Process) interface{} { data := process.ArgsMap(0) neo := GetNeo() - if neo.Conversation == nil { - exception.New("Neo conversation is not initialized", 500).Throw() + if neo.Store == nil { + exception.New("Neo store is not initialized", 500).Throw() } - id, err := neo.Conversation.SaveAssistant(data) + id, err := neo.Store.SaveAssistant(data) if err != nil { exception.New("Failed to save assistant: %s", 500, err.Error()).Throw() } @@ -99,11 +98,11 @@ func processAssistantDelete(process *process.Process) interface{} { assistantID := process.ArgsString(0) neo := GetNeo() - if neo.Conversation == nil { - exception.New("Neo conversation is not initialized", 500).Throw() + if neo.Store == nil { + exception.New("Neo store is not initialized", 500).Throw() } - err := neo.Conversation.DeleteAssistant(assistantID) + err := neo.Store.DeleteAssistant(assistantID) if err != nil { exception.New("Failed to delete assistant: %s", 500, err.Error()).Throw() } @@ -114,7 +113,7 @@ func processAssistantDelete(process *process.Process) interface{} { // processAssistantSearch process the assistant search request func processAssistantSearch(process *process.Process) interface{} { params := process.ArgsMap(0) - filter := conversation.AssistantFilter{} + filter := store.AssistantFilter{} // Parse page and pagesize if page, ok := params["page"]; ok { @@ -165,11 +164,11 @@ func processAssistantSearch(process *process.Process) interface{} { // Get assistants neo := GetNeo() - if neo.Conversation == nil { - exception.New("Neo conversation is not initialized", 500).Throw() + if neo.Store == nil { + exception.New("Neo store is not initialized", 500).Throw() } - res, err := neo.Conversation.GetAssistants(filter) + res, err := neo.Store.GetAssistants(filter) if err != nil { exception.New("get assistants error: %s", 500, err).Throw() } @@ -183,17 +182,17 @@ func processAssistantFind(process *process.Process) interface{} { assistantID := process.ArgsString(0) neo := GetNeo() - if neo.Conversation == nil { - exception.New("Neo conversation is not initialized", 500).Throw() + if neo.Store == nil { + exception.New("Neo store is not initialized", 500).Throw() } - filter := conversation.AssistantFilter{ + filter := store.AssistantFilter{ AssistantID: assistantID, Page: 1, PageSize: 1, } - res, err := neo.Conversation.GetAssistants(filter) + res, err := neo.Store.GetAssistants(filter) if err != nil { exception.New("Failed to find assistant: %s", 500, err.Error()).Throw() } diff --git a/neo/conversation/mongo.go b/neo/store/mongo.go similarity index 93% rename from neo/conversation/mongo.go rename to neo/store/mongo.go index d4b20a16..88336b1f 100644 --- a/neo/conversation/mongo.go +++ b/neo/store/mongo.go @@ -1,10 +1,10 @@ -package conversation +package store // Mongo represents a MongoDB-based conversation storage type Mongo struct{} -// NewMongo creates a new MongoDB conversation storage -func NewMongo() *Mongo { +// NewMongo create a new mongo store +func NewMongo() Store { return &Mongo{} } diff --git a/neo/conversation/redis.go b/neo/store/redis.go similarity index 93% rename from neo/conversation/redis.go rename to neo/store/redis.go index fb150570..ad249543 100644 --- a/neo/conversation/redis.go +++ b/neo/store/redis.go @@ -1,10 +1,10 @@ -package conversation +package store // Redis represents a Redis-based conversation storage type Redis struct{} -// NewRedis creates a new Redis conversation storage -func NewRedis() *Redis { +// NewRedis create a new redis store +func NewRedis() Store { return &Redis{} } diff --git a/neo/conversation/types.go b/neo/store/types.go similarity index 98% rename from neo/conversation/types.go rename to neo/store/types.go index 521fc176..ccfa2039 100644 --- a/neo/conversation/types.go +++ b/neo/store/types.go @@ -1,4 +1,4 @@ -package conversation +package store // Setting represents the conversation configuration structure // Used to configure basic conversation parameters including connector, user field, table name, etc. @@ -69,9 +69,9 @@ type AssistantResponse struct { Total int64 `json:"total"` // Total number of items } -// Conversation defines the conversation storage interface +// Store defines the conversation storage interface // Provides basic operations required for conversation management -type Conversation interface { +type Store interface { // GetChats retrieves a list of chats // sid: Session ID // filter: Filter conditions diff --git a/neo/conversation/weaviate.go b/neo/store/weaviate.go similarity index 93% rename from neo/conversation/weaviate.go rename to neo/store/weaviate.go index 8462e843..4b2dda07 100644 --- a/neo/conversation/weaviate.go +++ b/neo/store/weaviate.go @@ -1,10 +1,10 @@ -package conversation +package store // Weaviate represents a Weaviate-based conversation storage type Weaviate struct{} -// NewWeaviate creates a new Weaviate conversation storage -func NewWeaviate() *Weaviate { +// NewWeaviate create a new weaviate store +func NewWeaviate() Store { return &Weaviate{} } diff --git a/neo/conversation/xun.go b/neo/store/xun.go similarity index 99% rename from neo/conversation/xun.go rename to neo/store/xun.go index d6ee4efa..2407bdc2 100644 --- a/neo/conversation/xun.go +++ b/neo/store/xun.go @@ -1,4 +1,4 @@ -package conversation +package store import ( "fmt" @@ -45,8 +45,8 @@ type Xun struct { // DeleteAssistant deletes an assistant by assistant_id // GetAssistants retrieves a paginated list of assistants with filtering -// NewXun create a new conversation -func NewXun(setting Setting) (*Xun, error) { +// NewXun create a new xun store +func NewXun(setting Setting) (Store, error) { conv := &Xun{setting: setting} if setting.Connector == "default" { conv.query = capsule.Global.Query() diff --git a/neo/conversation/xun_test.go b/neo/store/xun_test.go similarity index 79% rename from neo/conversation/xun_test.go rename to neo/store/xun_test.go index 5b74c8a5..a8613c3a 100644 --- a/neo/conversation/xun_test.go +++ b/neo/store/xun_test.go @@ -1,4 +1,4 @@ -package conversation +package store import ( "fmt" @@ -38,7 +38,7 @@ func TestNewXunDefault(t *testing.T) { // Add a small delay to ensure table is created time.Sleep(100 * time.Millisecond) - conv, err := NewXun(Setting{ + store, err := NewXun(Setting{ Connector: "default", Table: "__unit_test_conversation", }) @@ -69,38 +69,47 @@ func TestNewXunDefault(t *testing.T) { } assert.Equal(t, true, has) - // validate the history table - tab, err := conv.schema.GetTable(conv.getHistoryTable()) - if err != nil { - t.Fatal(err) + // Validate table structure by attempting operations + // Test history operations + messages := []map[string]interface{}{ + {"role": "user", "content": "test message"}, + } + err = store.SaveHistory("test_user", messages, "test_chat", nil) + assert.Nil(t, err) + + history, err := store.GetHistory("test_user", "test_chat") + assert.Nil(t, err) + assert.NotEmpty(t, history) + + // Test chat operations + err = store.UpdateChatTitle("test_user", "test_chat", "Test Chat") + assert.Nil(t, err) + + chat, err := store.GetChat("test_user", "test_chat") + assert.Nil(t, err) + assert.NotNil(t, chat) + + // Test assistant operations + assistant := map[string]interface{}{ + "name": "Test Assistant", + "type": "assistant", + "connector": "test", + "description": "Test Description", + "tags": []string{"test"}, + "mentionable": true, + "automated": true, } - fields := []string{"id", "sid", "cid", "uid", "role", "name", "content", "context", "created_at", "updated_at", "expired_at"} - for _, field := range fields { - assert.Equal(t, true, tab.HasColumn(field)) - } + id, err := store.SaveAssistant(assistant) + assert.Nil(t, err) + assert.NotNil(t, id) - // validate the chat table - tab, err = conv.schema.GetTable(conv.getChatTable()) - if err != nil { - t.Fatal(err) - } + // Clean up test data + err = store.DeleteChat("test_user", "test_chat") + assert.Nil(t, err) - chatFields := []string{"id", "chat_id", "title", "sid", "created_at", "updated_at"} - for _, field := range chatFields { - assert.Equal(t, true, tab.HasColumn(field)) - } - - // validate the assistant table - tab, err = conv.schema.GetTable(conv.getAssistantTable()) - if err != nil { - t.Fatal(err) - } - - assistantFields := []string{"id", "assistant_id", "type", "name", "avatar", "connector", "description", "options", "prompts", "flows", "files", "functions", "tags", "readonly", "permissions", "automated", "mentionable", "created_at", "updated_at"} - for _, field := range assistantFields { - assert.Equal(t, true, tab.HasColumn(field)) - } + err = store.DeleteAssistant(id.(string)) + assert.Nil(t, err) } func TestNewXunConnector(t *testing.T) { @@ -128,7 +137,7 @@ func TestNewXunConnector(t *testing.T) { // Add a small delay to ensure table is created time.Sleep(100 * time.Millisecond) - conv, err := NewXun(Setting{ + store, err := NewXun(Setting{ Connector: "mysql", Table: "__unit_test_conversation", }) @@ -159,38 +168,19 @@ func TestNewXunConnector(t *testing.T) { } assert.Equal(t, true, has) - // validate the history table - tab, err := conv.schema.GetTable(conv.getHistoryTable()) - if err != nil { - t.Fatal(err) + // Test basic operations + messages := []map[string]interface{}{ + {"role": "user", "content": "test message"}, } + err = store.SaveHistory("test_user", messages, "test_chat", nil) + assert.Nil(t, err) - fields := []string{"id", "sid", "cid", "uid", "role", "name", "content", "context", "created_at", "updated_at", "expired_at"} - for _, field := range fields { - assert.Equal(t, true, tab.HasColumn(field)) - } + history, err := store.GetHistory("test_user", "test_chat") + assert.Nil(t, err) + assert.NotEmpty(t, history) - // validate the chat table - tab, err = conv.schema.GetTable(conv.getChatTable()) - if err != nil { - t.Fatal(err) - } - - chatFields := []string{"id", "chat_id", "title", "sid", "created_at", "updated_at"} - for _, field := range chatFields { - assert.Equal(t, true, tab.HasColumn(field)) - } - - // validate the assistant table - tab, err = conv.schema.GetTable(conv.getAssistantTable()) - if err != nil { - t.Fatal(err) - } - - assistantFields := []string{"id", "assistant_id", "type", "name", "avatar", "connector", "description", "options", "prompts", "flows", "files", "functions", "tags", "readonly", "permissions", "automated", "mentionable", "created_at", "updated_at"} - for _, field := range assistantFields { - assert.Equal(t, true, tab.HasColumn(field)) - } + err = store.DeleteChat("test_user", "test_chat") + assert.Nil(t, err) } func TestXunSaveAndGetHistory(t *testing.T) { @@ -209,7 +199,7 @@ func TestXunSaveAndGetHistory(t *testing.T) { t.Fatal(err) } - conv, err := NewXun(Setting{ + store, err := NewXun(Setting{ Connector: "default", Table: "__unit_test_conversation", TTL: 3600, @@ -217,14 +207,14 @@ func TestXunSaveAndGetHistory(t *testing.T) { // save the history cid := "123456" - err = conv.SaveHistory("123456", []map[string]interface{}{ + err = store.SaveHistory("123456", []map[string]interface{}{ {"role": "user", "name": "user1", "content": "hello"}, {"role": "assistant", "name": "user1", "content": "Hello there, how"}, }, cid, nil) assert.Nil(t, err) // get the history - data, err := conv.GetHistory("123456", cid) + data, err := store.GetHistory("123456", cid) if err != nil { t.Fatal(err) } @@ -247,7 +237,7 @@ func TestXunSaveAndGetHistoryWithCID(t *testing.T) { t.Fatal(err) } - conv, err := NewXun(Setting{ + store, err := NewXun(Setting{ Connector: "default", Table: "__unit_test_conversation", TTL: 3600, @@ -260,11 +250,11 @@ func TestXunSaveAndGetHistoryWithCID(t *testing.T) { {"role": "user", "name": "user1", "content": "hello"}, {"role": "assistant", "name": "assistant1", "content": "Hi! How can I help you?"}, } - err = conv.SaveHistory(sid, messages, cid, nil) + err = store.SaveHistory(sid, messages, cid, nil) assert.Nil(t, err) // get the history for specific cid - data, err := conv.GetHistory(sid, cid) + data, err := store.GetHistory(sid, cid) if err != nil { t.Fatal(err) } @@ -275,25 +265,25 @@ func TestXunSaveAndGetHistoryWithCID(t *testing.T) { moreMessages := []map[string]interface{}{ {"role": "user", "name": "user1", "content": "another message"}, } - err = conv.SaveHistory(sid, moreMessages, anotherCID, nil) + err = store.SaveHistory(sid, moreMessages, anotherCID, nil) assert.Nil(t, err) // get history for the first cid - should still be 2 messages - data, err = conv.GetHistory(sid, cid) + data, err = store.GetHistory(sid, cid) if err != nil { t.Fatal(err) } assert.Equal(t, 2, len(data)) // get history for the second cid - should be 1 message - data, err = conv.GetHistory(sid, anotherCID) + data, err = store.GetHistory(sid, anotherCID) if err != nil { t.Fatal(err) } assert.Equal(t, 1, len(data)) // get all history for the sid without specifying cid - allData, err := conv.GetHistory(sid, cid) + allData, err := store.GetHistory(sid, cid) if err != nil { t.Fatal(err) } @@ -316,7 +306,7 @@ func TestXunGetChats(t *testing.T) { t.Fatal(err) } - conv, err := NewXun(Setting{ + store, err := NewXun(Setting{ Connector: "default", Table: "__unit_test_conversation", }) @@ -333,22 +323,15 @@ func TestXunGetChats(t *testing.T) { // Create chats with different dates for i := 0; i < 5; i++ { chatID := fmt.Sprintf("chat_%d", i) - // First create the chat with a title - err = conv.newQueryChat().Insert(map[string]interface{}{ - "chat_id": chatID, - "title": fmt.Sprintf("Test Chat %d", i), - "sid": sid, - "created_at": time.Now(), - }) - if err != nil { - t.Fatal(err) - } + title := fmt.Sprintf("Test Chat %d", i) - // Then save the history - err = conv.SaveHistory(sid, messages, chatID, nil) - if err != nil { - t.Fatal(err) - } + // Save history first to create the chat + err = store.SaveHistory(sid, messages, chatID, nil) + assert.Nil(t, err) + + // Update the chat title + err = store.UpdateChatTitle(sid, chatID, title) + assert.Nil(t, err) } // Test getting chats with default filter @@ -356,7 +339,7 @@ func TestXunGetChats(t *testing.T) { PageSize: 10, Order: "desc", } - groups, err := conv.GetChats(sid, filter) + groups, err := store.GetChats(sid, filter) if err != nil { t.Fatal(err) } @@ -365,7 +348,7 @@ func TestXunGetChats(t *testing.T) { // Test with keywords filter.Keywords = "test" - groups, err = conv.GetChats(sid, filter) + groups, err = store.GetChats(sid, filter) if err != nil { t.Fatal(err) } @@ -379,7 +362,7 @@ func TestXunDeleteChat(t *testing.T) { defer capsule.Schema().DropTableIfExists("__unit_test_conversation_history") defer capsule.Schema().DropTableIfExists("__unit_test_conversation_chat") - conv, err := NewXun(Setting{ + store, err := NewXun(Setting{ Connector: "default", Table: "__unit_test_conversation", }) @@ -395,20 +378,20 @@ func TestXunDeleteChat(t *testing.T) { } // Save the chat and history - err = conv.SaveHistory(sid, messages, cid, nil) + err = store.SaveHistory(sid, messages, cid, nil) assert.Nil(t, err) // Verify chat exists - chat, err := conv.GetChat(sid, cid) + chat, err := store.GetChat(sid, cid) assert.Nil(t, err) assert.NotNil(t, chat) // Delete the chat - err = conv.DeleteChat(sid, cid) + err = store.DeleteChat(sid, cid) assert.Nil(t, err) // Verify chat is deleted - chat, err = conv.GetChat(sid, cid) + chat, err = store.GetChat(sid, cid) assert.Nil(t, err) assert.Equal(t, (*ChatInfo)(nil), chat) } @@ -419,7 +402,7 @@ func TestXunDeleteAllChats(t *testing.T) { defer capsule.Schema().DropTableIfExists("__unit_test_conversation_history") defer capsule.Schema().DropTableIfExists("__unit_test_conversation_chat") - conv, err := NewXun(Setting{ + store, err := NewXun(Setting{ Connector: "default", Table: "__unit_test_conversation", }) @@ -436,21 +419,21 @@ func TestXunDeleteAllChats(t *testing.T) { // Save multiple chats for i := 0; i < 3; i++ { cid := fmt.Sprintf("test_chat_%d", i) - err = conv.SaveHistory(sid, messages, cid, nil) + err = store.SaveHistory(sid, messages, cid, nil) assert.Nil(t, err) } // Verify chats exist - response, err := conv.GetChats(sid, ChatFilter{}) + response, err := store.GetChats(sid, ChatFilter{}) assert.Nil(t, err) assert.Greater(t, response.Total, int64(0)) // Delete all chats - err = conv.DeleteAllChats(sid) + err = store.DeleteAllChats(sid) assert.Nil(t, err) // Verify all chats are deleted - response, err = conv.GetChats(sid, ChatFilter{}) + response, err = store.GetChats(sid, ChatFilter{}) assert.Nil(t, err) assert.Equal(t, int64(0), response.Total) } @@ -470,7 +453,7 @@ func TestXunAssistantCRUD(t *testing.T) { // Add a small delay to ensure table is created time.Sleep(100 * time.Millisecond) - conv, err := NewXun(Setting{ + store, err := NewXun(Setting{ Connector: "default", Table: "__unit_test_conversation", }) @@ -495,7 +478,7 @@ func TestXunAssistantCRUD(t *testing.T) { } // Test SaveAssistant (Create) with string JSON - v, err := conv.SaveAssistant(assistant) + v, err := store.SaveAssistant(assistant) assert.Nil(t, err) assistantID := v.(string) assert.NotEmpty(t, assistantID) @@ -519,7 +502,7 @@ func TestXunAssistantCRUD(t *testing.T) { } // Test SaveAssistant (Create) with native types - v, err = conv.SaveAssistant(assistant2) + v, err = store.SaveAssistant(assistant2) assert.Nil(t, err) assistant2ID := v.(string) assert.NotEmpty(t, assistant2ID) @@ -542,13 +525,13 @@ func TestXunAssistantCRUD(t *testing.T) { } // Test SaveAssistant (Create) with nil fields - v, err = conv.SaveAssistant(assistant3) + v, err = store.SaveAssistant(assistant3) assert.Nil(t, err) assistant3ID := v.(string) assert.NotEmpty(t, assistant3ID) // Test GetAssistants to verify JSON fields are properly stored - resp, err := conv.GetAssistants(AssistantFilter{}) + resp, err := store.GetAssistants(AssistantFilter{}) assert.Nil(t, err) assert.Equal(t, 3, len(resp.Data)) @@ -614,11 +597,11 @@ func TestXunAssistantCRUD(t *testing.T) { // Test updating with mixed JSON formats assistant2["assistant_id"] = assistant2ID - _, err = conv.SaveAssistant(assistant2) + _, err = store.SaveAssistant(assistant2) assert.Nil(t, err) // Verify update - resp, err = conv.GetAssistants(AssistantFilter{}) + resp, err = store.GetAssistants(AssistantFilter{}) assert.Nil(t, err) for _, item := range resp.Data { if item["assistant_id"].(string) == assistant2ID { @@ -630,14 +613,14 @@ func TestXunAssistantCRUD(t *testing.T) { } // Test DeleteAssistant - err = conv.DeleteAssistant(assistantID) + err = store.DeleteAssistant(assistantID) assert.Nil(t, err) - err = conv.DeleteAssistant(assistant2ID) + err = store.DeleteAssistant(assistant2ID) assert.Nil(t, err) - err = conv.DeleteAssistant(assistant3ID) + err = store.DeleteAssistant(assistant3ID) assert.Nil(t, err) - resp, err = conv.GetAssistants(AssistantFilter{}) + resp, err = store.GetAssistants(AssistantFilter{}) assert.Nil(t, err) assert.Equal(t, 0, len(resp.Data)) } @@ -658,7 +641,7 @@ func TestXunAssistantPagination(t *testing.T) { // Add a small delay to ensure table is created time.Sleep(100 * time.Millisecond) - conv, err := NewXun(Setting{ + store, err := NewXun(Setting{ Connector: "default", Table: "__unit_test_conversation", }) @@ -692,12 +675,12 @@ func TestXunAssistantPagination(t *testing.T) { "mentionable": mentionable, "automated": automated, } - _, err = conv.SaveAssistant(assistant) + _, err = store.SaveAssistant(assistant) assert.Nil(t, err) } // Test first page - resp, err := conv.GetAssistants(AssistantFilter{ + resp, err := store.GetAssistants(AssistantFilter{ Page: 1, PageSize: 10, }) @@ -709,7 +692,7 @@ func TestXunAssistantPagination(t *testing.T) { assert.Equal(t, 0, resp.Prev) // Test second page - resp, err = conv.GetAssistants(AssistantFilter{ + resp, err = store.GetAssistants(AssistantFilter{ Page: 2, PageSize: 10, }) @@ -719,7 +702,7 @@ func TestXunAssistantPagination(t *testing.T) { assert.Equal(t, 1, resp.Prev) // Test last page - resp, err = conv.GetAssistants(AssistantFilter{ + resp, err = store.GetAssistants(AssistantFilter{ Page: 3, PageSize: 10, }) @@ -729,7 +712,7 @@ func TestXunAssistantPagination(t *testing.T) { assert.Equal(t, 2, resp.Prev) // Test filtering with tags - resp, err = conv.GetAssistants(AssistantFilter{ + resp, err = store.GetAssistants(AssistantFilter{ Tags: []string{"tag0"}, Page: 1, PageSize: 10, @@ -738,7 +721,7 @@ func TestXunAssistantPagination(t *testing.T) { assert.Equal(t, 5, len(resp.Data)) // Test filtering with keywords - resp, err = conv.GetAssistants(AssistantFilter{ + resp, err = store.GetAssistants(AssistantFilter{ Keywords: "Assistant 1", Page: 1, PageSize: 10, @@ -747,7 +730,7 @@ func TestXunAssistantPagination(t *testing.T) { assert.Greater(t, len(resp.Data), 0) // Test filtering with connector - resp, err = conv.GetAssistants(AssistantFilter{ + resp, err = store.GetAssistants(AssistantFilter{ Connector: "connector0", Page: 1, PageSize: 10, @@ -757,7 +740,7 @@ func TestXunAssistantPagination(t *testing.T) { // Test filtering with mentionable mentionableTrue := true - resp, err = conv.GetAssistants(AssistantFilter{ + resp, err = store.GetAssistants(AssistantFilter{ Mentionable: &mentionableTrue, Page: 1, PageSize: 10, @@ -767,7 +750,7 @@ func TestXunAssistantPagination(t *testing.T) { // Test filtering with automated automatedTrue := true - resp, err = conv.GetAssistants(AssistantFilter{ + resp, err = store.GetAssistants(AssistantFilter{ Automated: &automatedTrue, Page: 1, PageSize: 10, @@ -780,7 +763,7 @@ func TestXunAssistantPagination(t *testing.T) { firstAssistantID := resp.Data[0]["assistant_id"].(string) // Test exact match with assistant_id - resp, err = conv.GetAssistants(AssistantFilter{ + resp, err = store.GetAssistants(AssistantFilter{ AssistantID: firstAssistantID, Page: 1, PageSize: 10, @@ -790,7 +773,7 @@ func TestXunAssistantPagination(t *testing.T) { assert.Equal(t, firstAssistantID, resp.Data[0]["assistant_id"]) // Test assistant_id with other filters - resp, err = conv.GetAssistants(AssistantFilter{ + resp, err = store.GetAssistants(AssistantFilter{ AssistantID: firstAssistantID, Select: []string{"name", "assistant_id", "description"}, Page: 1, @@ -807,7 +790,7 @@ func TestXunAssistantPagination(t *testing.T) { assert.NotContains(t, resp.Data[0], "options") // Test non-existent assistant_id - resp, err = conv.GetAssistants(AssistantFilter{ + resp, err = store.GetAssistants(AssistantFilter{ AssistantID: "non-existent-id", Page: 1, PageSize: 10, @@ -816,7 +799,7 @@ func TestXunAssistantPagination(t *testing.T) { assert.Equal(t, 0, len(resp.Data)) // Test combined filters - resp, err = conv.GetAssistants(AssistantFilter{ + resp, err = store.GetAssistants(AssistantFilter{ Tags: []string{"tag0"}, Keywords: "Assistant", Connector: "connector0", @@ -828,7 +811,7 @@ func TestXunAssistantPagination(t *testing.T) { assert.Nil(t, err) // Test filtering with select fields - resp, err = conv.GetAssistants(AssistantFilter{ + resp, err = store.GetAssistants(AssistantFilter{ Select: []string{"name", "description", "tags"}, Page: 1, PageSize: 10, @@ -851,7 +834,7 @@ func TestXunAssistantPagination(t *testing.T) { } // Test filtering with select fields and other filters combined - resp, err = conv.GetAssistants(AssistantFilter{ + resp, err = store.GetAssistants(AssistantFilter{ Tags: []string{"tag0"}, Keywords: "Assistant", Select: []string{"name", "tags"}, diff --git a/neo/types.go b/neo/types.go index 0582cba3..bba705d0 100644 --- a/neo/types.go +++ b/neo/types.go @@ -5,30 +5,30 @@ import ( "github.com/gin-gonic/gin" "github.com/yaoapp/yao/neo/assistant" - "github.com/yaoapp/yao/neo/conversation" + "github.com/yaoapp/yao/neo/store" ) // DSL AI assistant type DSL struct { - ID string `json:"-" yaml:"-"` - Name string `json:"name,omitempty" yaml:"name,omitempty"` - Use string `json:"use,omitempty" yaml:"use,omitempty"` // Which assistant to use default - Guard string `json:"guard,omitempty" yaml:"guard,omitempty"` - Connector string `json:"connector" yaml:"connector"` - ConversationSetting conversation.Setting `json:"conversation" yaml:"conversation"` - Option map[string]interface{} `json:"option" yaml:"option"` - Prepare string `json:"prepare,omitempty" yaml:"prepare,omitempty"` - Create string `json:"create,omitempty" yaml:"create,omitempty"` - Write string `json:"write,omitempty" yaml:"write,omitempty"` - AssistantListHook string `json:"assistants,omitempty" yaml:"assistants,omitempty"` // Get the assistant list from the hook - MentionHook string `json:"mentions,omitempty"` // Get the mention list from the hook - Prompts []assistant.Prompt `json:"prompts,omitempty" yaml:"prompts,omitempty"` - Allows []string `json:"allows,omitempty" yaml:"allows,omitempty"` - Assistant assistant.API `json:"-" yaml:"-"` // The default assistant - Conversation conversation.Conversation `json:"-" yaml:"-"` - GuardHandlers []gin.HandlerFunc `json:"-" yaml:"-"` - AssistantList []assistant.Assistant `json:"-" yaml:"-"` - AssistantMaps map[string]assistant.Assistant `json:"-" yaml:"-"` + ID string `json:"-" yaml:"-"` + Name string `json:"name,omitempty" yaml:"name,omitempty"` + Use string `json:"use,omitempty" yaml:"use,omitempty"` // Which assistant to use default + Guard string `json:"guard,omitempty" yaml:"guard,omitempty"` + Connector string `json:"connector" yaml:"connector"` + StoreSetting store.Setting `json:"store" yaml:"store"` + Option map[string]interface{} `json:"option" yaml:"option"` + Prepare string `json:"prepare,omitempty" yaml:"prepare,omitempty"` + Create string `json:"create,omitempty" yaml:"create,omitempty"` + Write string `json:"write,omitempty" yaml:"write,omitempty"` + AssistantListHook string `json:"assistants,omitempty" yaml:"assistants,omitempty"` // Get the assistant list from the hook + MentionHook string `json:"mentions,omitempty"` // Get the mention list from the hook + Prompts []assistant.Prompt `json:"prompts,omitempty" yaml:"prompts,omitempty"` + Allows []string `json:"allows,omitempty" yaml:"allows,omitempty"` + Assistant assistant.API `json:"-" yaml:"-"` // The default assistant + Store store.Store `json:"-" yaml:"-"` + GuardHandlers []gin.HandlerFunc `json:"-" yaml:"-"` + AssistantList []assistant.Assistant `json:"-" yaml:"-"` + AssistantMaps map[string]assistant.Assistant `json:"-" yaml:"-"` } // Mention list