From f75e5a5fbe02cf61965343241505b428794df2ab Mon Sep 17 00:00:00 2001 From: Max Date: Sat, 25 Jan 2025 14:03:45 +0800 Subject: [PATCH] Add Placeholder support for Assistants in Neo API - Introduced a new Placeholder struct to provide additional metadata for assistants - Updated Assistant struct to include an optional Placeholder field - Modified loading and mapping methods to handle Placeholder data - Extended database schema and test cases to support Placeholder functionality This enhancement provides a flexible way to add descriptive metadata to assistants, improving their discoverability and configuration. --- neo/assistant/assistant.go | 1 + neo/assistant/load.go | 35 +++++++++++++++++++++++++++++++++++ neo/assistant/types.go | 8 ++++++++ neo/store/xun.go | 9 +++++---- neo/store/xun_test.go | 20 ++++++++++++++++++++ 5 files changed, 69 insertions(+), 4 deletions(-) diff --git a/neo/assistant/assistant.go b/neo/assistant/assistant.go index 00b36062..b5244035 100644 --- a/neo/assistant/assistant.go +++ b/neo/assistant/assistant.go @@ -136,6 +136,7 @@ func (ast *Assistant) Map() map[string]interface{} { "tags": ast.Tags, "mentionable": ast.Mentionable, "automated": ast.Automated, + "placeholder": ast.Placeholder, "created_at": timeToMySQLFormat(ast.CreatedAt), "updated_at": timeToMySQLFormat(ast.UpdatedAt), } diff --git a/neo/assistant/load.go b/neo/assistant/load.go index 13563acb..7fd638f1 100644 --- a/neo/assistant/load.go +++ b/neo/assistant/load.go @@ -294,6 +294,41 @@ func loadMap(data map[string]interface{}) (*Assistant, error) { assistant.Type = v } + // Placeholder + if v, ok := data["placeholder"]; ok { + + switch vv := v.(type) { + case string: + placeholder, err := jsoniter.Marshal(vv) + if err != nil { + return nil, err + } + assistant.Placeholder = &Placeholder{} + err = jsoniter.Unmarshal(placeholder, assistant.Placeholder) + if err != nil { + return nil, err + } + + case map[string]interface{}: + raw, err := jsoniter.Marshal(vv) + if err != nil { + return nil, err + } + + assistant.Placeholder = &Placeholder{} + err = jsoniter.Unmarshal(raw, assistant.Placeholder) + if err != nil { + return nil, err + } + + case *Placeholder: + assistant.Placeholder = vv + + case nil: + assistant.Placeholder = nil + } + } + // Mentionable if v, ok := data["mentionable"].(bool); ok { assistant.Mentionable = v diff --git a/neo/assistant/types.go b/neo/assistant/types.go index 52c58ad5..ca3b24a2 100644 --- a/neo/assistant/types.go +++ b/neo/assistant/types.go @@ -129,6 +129,7 @@ type Assistant struct { Prompts []Prompt `json:"prompts,omitempty"` // AI Prompts Functions []Function `json:"functions,omitempty"` // Assistant Functions Flows []map[string]interface{} `json:"flows,omitempty"` // Assistant Flows + Placeholder *Placeholder `json:"placeholder,omitempty"` // Assistant Placeholder Script *v8.Script `json:"-" yaml:"-"` // Assistant Script CreatedAt int64 `json:"created_at"` // Creation timestamp UpdatedAt int64 `json:"updated_at"` // Last update timestamp @@ -137,6 +138,13 @@ type Assistant struct { initHook bool // Whether this assistant has an init hook } +// Placeholder the assistant placeholder +type Placeholder struct { + Title string `json:"title,omitempty"` + Description string `json:"description,omitempty"` + Prompts []string `json:"prompts,omitempty"` +} + // VisionCapableModels list of LLM models that support vision capabilities var VisionCapableModels = map[string]bool{ // OpenAI Models diff --git a/neo/store/xun.go b/neo/store/xun.go index 3c810cf0..cfa69140 100644 --- a/neo/store/xun.go +++ b/neo/store/xun.go @@ -232,6 +232,7 @@ func (conv *Xun) initAssistantTable() error { table.String("path", 200).Null() // assistant storage path table.Integer("sort").SetDefault(9999).Index() // assistant sort order table.Boolean("built_in").SetDefault(false).Index() // whether this is a built-in assistant + table.JSON("placeholder").Null() // assistant placeholder table.JSON("options").Null() // assistant options table.JSON("prompts").Null() // assistant prompts table.JSON("flows").Null() // assistant flows @@ -258,7 +259,7 @@ func (conv *Xun) initAssistantTable() error { return err } - fields := []string{"id", "assistant_id", "type", "name", "avatar", "connector", "description", "path", "sort", "built_in", "options", "prompts", "flows", "files", "functions", "tags", "mentionable", "created_at", "updated_at"} + fields := []string{"id", "assistant_id", "type", "name", "avatar", "connector", "description", "path", "sort", "built_in", "placeholder", "options", "prompts", "flows", "files", "functions", "tags", "mentionable", "created_at", "updated_at"} for _, field := range fields { if !tab.HasColumn(field) { return fmt.Errorf("%s is required", field) @@ -766,7 +767,7 @@ func (conv *Xun) SaveAssistant(assistant map[string]interface{}) (interface{}, e } // Process JSON fields - jsonFields := []string{"tags", "options", "prompts", "flows", "files", "functions", "permissions"} + jsonFields := []string{"tags", "options", "prompts", "flows", "files", "functions", "permissions", "placeholder"} for _, field := range jsonFields { if val, ok := assistantCopy[field]; ok && val != nil { // If it's a string, try to parse it first @@ -948,7 +949,7 @@ func (conv *Xun) GetAssistants(filter AssistantFilter) (*AssistantResponse, erro // Convert rows to map slice and parse JSON fields data := make([]map[string]interface{}, len(rows)) - jsonFields := []string{"tags", "options", "prompts", "flows", "files", "functions", "permissions"} + jsonFields := []string{"tags", "options", "prompts", "flows", "files", "functions", "permissions", "placeholder"} for i, row := range rows { data[i] = row // Only parse JSON fields if they are selected or no select filter is provided @@ -1002,7 +1003,7 @@ func (conv *Xun) GetAssistant(assistantID string) (map[string]interface{}, error } // Parse JSON fields - jsonFields := []string{"tags", "options", "prompts", "flows", "files", "functions", "permissions"} + jsonFields := []string{"tags", "options", "prompts", "flows", "files", "functions", "permissions", "placeholder"} conv.parseJSONFields(data, jsonFields) return data, nil diff --git a/neo/store/xun_test.go b/neo/store/xun_test.go index 4dbd936a..aeff7154 100644 --- a/neo/store/xun_test.go +++ b/neo/store/xun_test.go @@ -465,6 +465,7 @@ func TestXunAssistantCRUD(t *testing.T) { // Test case 1: JSON fields as strings tagsJSON := `["tag1", "tag2", "tag3"]` optionsJSON := `{"model": "gpt-4"}` + placeholderJSON := `{"title": "Test Title", "description": "Test Description", "prompts": ["prompt1", "prompt2"]}` assistant := map[string]interface{}{ "name": "Test Assistant", "type": "assistant", @@ -476,6 +477,7 @@ func TestXunAssistantCRUD(t *testing.T) { "built_in": true, "tags": tagsJSON, "options": optionsJSON, + "placeholder": placeholderJSON, "mentionable": true, "automated": true, } @@ -500,6 +502,11 @@ func TestXunAssistantCRUD(t *testing.T) { assert.Equal(t, int64(1), assistantData["built_in"]) assert.Equal(t, []interface{}{"tag1", "tag2", "tag3"}, assistantData["tags"]) assert.Equal(t, map[string]interface{}{"model": "gpt-4"}, assistantData["options"]) + assert.Equal(t, map[string]interface{}{ + "title": "Test Title", + "description": "Test Description", + "prompts": []interface{}{"prompt1", "prompt2"}, + }, assistantData["placeholder"]) assert.Equal(t, int64(1), assistantData["mentionable"]) assert.Equal(t, int64(1), assistantData["automated"]) @@ -520,6 +527,11 @@ func TestXunAssistantCRUD(t *testing.T) { "files": []string{"file1", "file2"}, "functions": []map[string]interface{}{{"name": "func1"}, {"name": "func2"}}, "permissions": map[string]interface{}{"read": true, "write": true}, + "placeholder": map[string]interface{}{ + "title": "Test Title 2", + "description": "Test Description 2", + "prompts": []string{"prompt3", "prompt4"}, + }, "mentionable": true, "automated": true, } @@ -545,6 +557,11 @@ func TestXunAssistantCRUD(t *testing.T) { map[string]interface{}{"name": "func2"}, }, assistant2Data["functions"]) assert.Equal(t, map[string]interface{}{"read": true, "write": true}, assistant2Data["permissions"]) + assert.Equal(t, map[string]interface{}{ + "title": "Test Title 2", + "description": "Test Description 2", + "prompts": []interface{}{"prompt3", "prompt4"}, + }, assistant2Data["placeholder"]) assert.Equal(t, int64(1), assistant2Data["mentionable"]) assert.Equal(t, int64(1), assistant2Data["automated"]) @@ -564,6 +581,7 @@ func TestXunAssistantCRUD(t *testing.T) { "files": nil, "functions": nil, "permissions": nil, + "placeholder": nil, "mentionable": true, "automated": true, } @@ -586,6 +604,7 @@ func TestXunAssistantCRUD(t *testing.T) { assert.Nil(t, assistant3Data["files"]) assert.Nil(t, assistant3Data["functions"]) assert.Nil(t, assistant3Data["permissions"]) + assert.Nil(t, assistant3Data["placeholder"]) assert.Equal(t, int64(1), assistant3Data["mentionable"]) assert.Equal(t, int64(1), assistant3Data["automated"]) @@ -655,6 +674,7 @@ func TestXunAssistantCRUD(t *testing.T) { assert.Nil(t, item["files"]) assert.Nil(t, item["functions"]) assert.Nil(t, item["permissions"]) + assert.Nil(t, item["placeholder"]) break } }