Merge pull request #973 from trheyi/main
Enhance localization support and refactor assistant functions for imp…
This commit is contained in:
commit
5bbd8954d7
16 changed files with 357 additions and 252 deletions
63
neo/api.go
63
neo/api.go
|
|
@ -491,6 +491,11 @@ func (neo *DSL) handleChatLatest(c *gin.Context) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
locale := "en-us"
|
||||||
|
if loc := c.Query("locale"); loc != "" {
|
||||||
|
locale = strings.ToLower(strings.TrimSpace(loc))
|
||||||
|
}
|
||||||
|
|
||||||
// Create a new chat
|
// Create a new chat
|
||||||
if len(chats.Groups) == 0 || len(chats.Groups[0].Chats) == 0 {
|
if len(chats.Groups) == 0 || len(chats.Groups[0].Chats) == 0 {
|
||||||
|
|
||||||
|
|
@ -509,7 +514,7 @@ func (neo *DSL) handleChatLatest(c *gin.Context) {
|
||||||
}
|
}
|
||||||
|
|
||||||
c.JSON(200, map[string]interface{}{"data": map[string]interface{}{
|
c.JSON(200, map[string]interface{}{"data": map[string]interface{}{
|
||||||
"placeholder": ast.GetPlaceholder(),
|
"placeholder": ast.GetPlaceholder(locale),
|
||||||
"assistant_id": ast.ID,
|
"assistant_id": ast.ID,
|
||||||
"assistant_name": ast.Name,
|
"assistant_name": ast.Name,
|
||||||
"assistant_avatar": ast.Avatar,
|
"assistant_avatar": ast.Avatar,
|
||||||
|
|
@ -882,22 +887,18 @@ func (neo *DSL) handleAssistantList(c *gin.Context) {
|
||||||
filter.AssistantID = assistantID
|
filter.AssistantID = assistantID
|
||||||
}
|
}
|
||||||
|
|
||||||
response, err := neo.Store.GetAssistants(filter)
|
locale := "en-us" // Default locale
|
||||||
|
if loc := c.Query("locale"); loc != "" {
|
||||||
|
locale = strings.ToLower(strings.TrimSpace(loc))
|
||||||
|
}
|
||||||
|
|
||||||
|
response, err := neo.Store.GetAssistants(filter, locale)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
c.JSON(500, gin.H{"message": err.Error(), "code": 500})
|
c.JSON(500, gin.H{"message": err.Error(), "code": 500})
|
||||||
c.Done()
|
c.Done()
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
// Translate the response
|
|
||||||
locale := "en-us" // Default locale
|
|
||||||
if loc := c.Query("locale"); loc != "" {
|
|
||||||
locale = strings.ToLower(strings.TrimSpace(loc))
|
|
||||||
}
|
|
||||||
for i, ast := range response.Data {
|
|
||||||
id := ast["assistant_id"].(string)
|
|
||||||
response.Data[i] = assistant.Translate(locale, id, ast).(map[string]interface{})
|
|
||||||
}
|
|
||||||
c.JSON(200, response)
|
c.JSON(200, response)
|
||||||
c.Done()
|
c.Done()
|
||||||
}
|
}
|
||||||
|
|
@ -974,7 +975,13 @@ func (neo *DSL) handleAssistantDetail(c *gin.Context) {
|
||||||
PageSize: 1,
|
PageSize: 1,
|
||||||
}
|
}
|
||||||
|
|
||||||
response, err := neo.Store.GetAssistants(filter)
|
locale := "en-us" // Default locale
|
||||||
|
// Translate the response
|
||||||
|
if loc := c.Query("locale"); loc != "" {
|
||||||
|
locale = strings.ToLower(strings.TrimSpace(loc))
|
||||||
|
}
|
||||||
|
|
||||||
|
response, err := neo.Store.GetAssistants(filter, locale)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
c.JSON(500, gin.H{"message": err.Error(), "code": 500})
|
c.JSON(500, gin.H{"message": err.Error(), "code": 500})
|
||||||
c.Done()
|
c.Done()
|
||||||
|
|
@ -987,14 +994,6 @@ func (neo *DSL) handleAssistantDetail(c *gin.Context) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
locale := "en-us" // Default locale
|
|
||||||
// Translate the response
|
|
||||||
if loc := c.Query("locale"); loc != "" {
|
|
||||||
locale = strings.ToLower(strings.TrimSpace(loc))
|
|
||||||
}
|
|
||||||
id := response.Data[0]["assistant_id"].(string)
|
|
||||||
response.Data[0] = assistant.Translate(locale, id, response.Data[0]).(map[string]interface{})
|
|
||||||
|
|
||||||
c.JSON(200, map[string]interface{}{"data": response.Data[0]})
|
c.JSON(200, map[string]interface{}{"data": response.Data[0]})
|
||||||
c.Done()
|
c.Done()
|
||||||
}
|
}
|
||||||
|
|
@ -1098,28 +1097,18 @@ func (neo *DSL) handleAssistantTags(c *gin.Context) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
tags, err := neo.Store.GetAssistantTags()
|
locale := "en-us" // Default locale
|
||||||
|
if loc := c.Query("locale"); loc != "" {
|
||||||
|
locale = strings.ToLower(strings.TrimSpace(loc))
|
||||||
|
}
|
||||||
|
|
||||||
|
tags, err := neo.Store.GetAssistantTags(locale)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
c.JSON(500, gin.H{"message": err.Error(), "code": 500})
|
c.JSON(500, gin.H{"message": err.Error(), "code": 500})
|
||||||
c.Done()
|
c.Done()
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
// Translate the tags
|
c.JSON(200, gin.H{"data": tags})
|
||||||
locale := "en-us" // Default locale
|
|
||||||
if loc := c.Query("locale"); loc != "" {
|
|
||||||
locale = strings.ToLower(strings.TrimSpace(loc))
|
|
||||||
}
|
|
||||||
|
|
||||||
// Translate the tags
|
|
||||||
items := []map[string]interface{}{}
|
|
||||||
for _, value := range tags {
|
|
||||||
items = append(items, map[string]interface{}{
|
|
||||||
"label": assistant.Translate(locale, "", value).(string),
|
|
||||||
"key": value,
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
c.JSON(200, gin.H{"data": items})
|
|
||||||
c.Done()
|
c.Done()
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -15,6 +15,7 @@ import (
|
||||||
"github.com/yaoapp/kun/exception"
|
"github.com/yaoapp/kun/exception"
|
||||||
"github.com/yaoapp/kun/log"
|
"github.com/yaoapp/kun/log"
|
||||||
chatctx "github.com/yaoapp/yao/neo/context"
|
chatctx "github.com/yaoapp/yao/neo/context"
|
||||||
|
"github.com/yaoapp/yao/neo/i18n"
|
||||||
"github.com/yaoapp/yao/neo/message"
|
"github.com/yaoapp/yao/neo/message"
|
||||||
chatMessage "github.com/yaoapp/yao/neo/message"
|
chatMessage "github.com/yaoapp/yao/neo/message"
|
||||||
)
|
)
|
||||||
|
|
@ -243,20 +244,6 @@ func (next *NextAction) Execute(c *gin.Context, ctx chatctx.Context, contents *c
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("with history error: %s", err.Error())
|
return nil, fmt.Errorf("with history error: %s", err.Error())
|
||||||
}
|
}
|
||||||
|
|
||||||
// Send the progress message from application side instead
|
|
||||||
// Create a new Text
|
|
||||||
// Send loading message and mark as new
|
|
||||||
// if !ctx.Silent {
|
|
||||||
// msg := chatMessage.New().Map(map[string]interface{}{
|
|
||||||
// "new": true,
|
|
||||||
// "role": "assistant",
|
|
||||||
// "type": "loading",
|
|
||||||
// "props": map[string]interface{}{"placeholder": "Calling " + assistant.Name},
|
|
||||||
// })
|
|
||||||
// msg.Assistant(assistant.ID, assistant.Name, assistant.Avatar)
|
|
||||||
// msg.Write(c.Writer)
|
|
||||||
// }
|
|
||||||
newContents := chatMessage.NewContents()
|
newContents := chatMessage.NewContents()
|
||||||
|
|
||||||
// Update the context id
|
// Update the context id
|
||||||
|
|
@ -272,8 +259,19 @@ func (next *NextAction) Execute(c *gin.Context, ctx chatctx.Context, contents *c
|
||||||
}
|
}
|
||||||
|
|
||||||
// GetPlaceholder returns the placeholder of the assistant
|
// GetPlaceholder returns the placeholder of the assistant
|
||||||
func (ast *Assistant) GetPlaceholder() *Placeholder {
|
func (ast *Assistant) GetPlaceholder(locale string) *Placeholder {
|
||||||
return ast.Placeholder
|
|
||||||
|
prompts := []string{}
|
||||||
|
if ast.Placeholder.Prompts != nil {
|
||||||
|
prompts = i18n.Translate(ast.ID, locale, ast.Placeholder.Prompts).([]string)
|
||||||
|
}
|
||||||
|
title := i18n.Translate(ast.ID, locale, ast.Placeholder.Title).(string)
|
||||||
|
description := i18n.Translate(ast.ID, locale, ast.Placeholder.Description).(string)
|
||||||
|
return &Placeholder{
|
||||||
|
Title: title,
|
||||||
|
Description: description,
|
||||||
|
Prompts: prompts,
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Call implements the call functionality
|
// Call implements the call functionality
|
||||||
|
|
|
||||||
|
|
@ -140,6 +140,7 @@ func (ast *Assistant) Map() map[string]interface{} {
|
||||||
"mentionable": ast.Mentionable,
|
"mentionable": ast.Mentionable,
|
||||||
"automated": ast.Automated,
|
"automated": ast.Automated,
|
||||||
"placeholder": ast.Placeholder,
|
"placeholder": ast.Placeholder,
|
||||||
|
"locales": ast.Locales,
|
||||||
"created_at": timeToMySQLFormat(ast.CreatedAt),
|
"created_at": timeToMySQLFormat(ast.CreatedAt),
|
||||||
"updated_at": timeToMySQLFormat(ast.UpdatedAt),
|
"updated_at": timeToMySQLFormat(ast.UpdatedAt),
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,151 +0,0 @@
|
||||||
package assistant
|
|
||||||
|
|
||||||
import (
|
|
||||||
"path/filepath"
|
|
||||||
"strings"
|
|
||||||
|
|
||||||
"github.com/yaoapp/gou/application"
|
|
||||||
"github.com/yaoapp/gou/fs"
|
|
||||||
"github.com/yaoapp/kun/maps"
|
|
||||||
)
|
|
||||||
|
|
||||||
// Locales the locales
|
|
||||||
var Locales = map[string]map[string]I18n{}
|
|
||||||
|
|
||||||
// I18n the i18n struct
|
|
||||||
type I18n struct {
|
|
||||||
Locale string `json:"locale,omitempty" yaml:"locale,omitempty"`
|
|
||||||
Messages map[string]any `json:"messages,omitempty" yaml:"messages,omitempty"`
|
|
||||||
}
|
|
||||||
|
|
||||||
// Parse parse the input
|
|
||||||
func (i18n I18n) Parse(input any) any {
|
|
||||||
|
|
||||||
switch in := input.(type) {
|
|
||||||
case string:
|
|
||||||
trimed := strings.TrimSpace(in)
|
|
||||||
hasExp := strings.HasPrefix(trimed, "{{") && strings.HasSuffix(trimed, "}}")
|
|
||||||
if hasExp {
|
|
||||||
exp := strings.TrimSpace(strings.TrimPrefix(strings.TrimSuffix(trimed, "}}"), "{{"))
|
|
||||||
if _, ok := i18n.Messages[exp]; ok {
|
|
||||||
return i18n.Messages[exp]
|
|
||||||
}
|
|
||||||
return exp
|
|
||||||
}
|
|
||||||
|
|
||||||
if _, ok := i18n.Messages[trimed]; ok {
|
|
||||||
return i18n.Messages[trimed]
|
|
||||||
}
|
|
||||||
|
|
||||||
return in
|
|
||||||
|
|
||||||
case map[string]any:
|
|
||||||
for key, value := range in {
|
|
||||||
in[key] = i18n.Parse(value)
|
|
||||||
}
|
|
||||||
return in
|
|
||||||
|
|
||||||
case []any:
|
|
||||||
for i, value := range in {
|
|
||||||
in[i] = i18n.Parse(value)
|
|
||||||
}
|
|
||||||
return in
|
|
||||||
}
|
|
||||||
|
|
||||||
return input
|
|
||||||
}
|
|
||||||
|
|
||||||
// GetI18n load the i18n from path
|
|
||||||
func GetI18n(path string) (map[string]I18n, error) {
|
|
||||||
app, err := fs.Get("app")
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
// Get the global i18n
|
|
||||||
globalI18ns, hasGlobal := Locales["__global__"]
|
|
||||||
// i18ns
|
|
||||||
localesdir := filepath.Join(path, "locales")
|
|
||||||
var i18ns map[string]I18n = map[string]I18n{}
|
|
||||||
if has, _ := app.Exists(localesdir); has {
|
|
||||||
locales, err := app.ReadDir(localesdir, true)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
// load locales
|
|
||||||
for _, locale := range locales {
|
|
||||||
localeData, err := app.ReadFile(locale)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
var messages maps.Map = map[string]any{}
|
|
||||||
err = application.Parse(locale, localeData, &messages)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
name := strings.ToLower(strings.TrimSuffix(filepath.Base(locale), ".yml"))
|
|
||||||
// Merge the global i18n
|
|
||||||
if hasGlobal {
|
|
||||||
global, has := globalI18ns[name]
|
|
||||||
if has {
|
|
||||||
for key, value := range global.Messages {
|
|
||||||
if _, ok := messages[key]; !ok {
|
|
||||||
messages[key] = value
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
i18ns[name] = I18n{Locale: name, Messages: messages.Dot()}
|
|
||||||
namer := strings.Split(name, "-")
|
|
||||||
if len(namer) > 1 {
|
|
||||||
// Merge the global i18n
|
|
||||||
if hasGlobal {
|
|
||||||
global, has := globalI18ns[namer[0]]
|
|
||||||
if has {
|
|
||||||
for key, value := range global.Messages {
|
|
||||||
if _, ok := messages[key]; !ok {
|
|
||||||
messages[key] = value
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
i18ns[namer[0]] = I18n{Locale: name, Messages: messages.Dot()}
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return i18ns, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// Translate translate the input
|
|
||||||
func Translate(locale string, id string, input any) any {
|
|
||||||
|
|
||||||
locale = strings.ToLower(strings.TrimSpace(locale))
|
|
||||||
i18ns, has := Locales[id]
|
|
||||||
if !has {
|
|
||||||
i18ns = map[string]I18n{}
|
|
||||||
}
|
|
||||||
|
|
||||||
i18n, has := i18ns[locale]
|
|
||||||
if !has {
|
|
||||||
namer := strings.Split(locale, "-")
|
|
||||||
lang := namer[0]
|
|
||||||
i18n, has = i18ns[lang]
|
|
||||||
}
|
|
||||||
if !has {
|
|
||||||
var hasGlobal bool = false
|
|
||||||
i18ns, hasGlobal = Locales["__global__"]
|
|
||||||
if hasGlobal {
|
|
||||||
i18n, has = i18ns[locale]
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if has {
|
|
||||||
return i18n.Parse(input)
|
|
||||||
}
|
|
||||||
|
|
||||||
return input
|
|
||||||
}
|
|
||||||
|
|
@ -14,6 +14,7 @@ import (
|
||||||
"github.com/yaoapp/gou/fs"
|
"github.com/yaoapp/gou/fs"
|
||||||
"github.com/yaoapp/gou/rag/driver"
|
"github.com/yaoapp/gou/rag/driver"
|
||||||
v8 "github.com/yaoapp/gou/runtime/v8"
|
v8 "github.com/yaoapp/gou/runtime/v8"
|
||||||
|
"github.com/yaoapp/yao/neo/i18n"
|
||||||
"github.com/yaoapp/yao/neo/store"
|
"github.com/yaoapp/yao/neo/store"
|
||||||
neovision "github.com/yaoapp/yao/neo/vision"
|
neovision "github.com/yaoapp/yao/neo/vision"
|
||||||
"github.com/yaoapp/yao/openai"
|
"github.com/yaoapp/yao/openai"
|
||||||
|
|
@ -314,7 +315,7 @@ func LoadPath(path string) (*Assistant, error) {
|
||||||
}
|
}
|
||||||
|
|
||||||
// i18ns
|
// i18ns
|
||||||
locales, err := GetI18n(path)
|
locales, err := i18n.GetLocales(path)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
@ -460,8 +461,9 @@ func loadMap(data map[string]interface{}) (*Assistant, error) {
|
||||||
}
|
}
|
||||||
|
|
||||||
// locales
|
// locales
|
||||||
if locales, ok := data["locales"].(map[string]I18n); ok {
|
if locales, ok := data["locales"].(i18n.Map); ok {
|
||||||
Locales[id] = locales
|
assistant.Locales = locales
|
||||||
|
i18n.Locales[id] = locales.FlattenWithGlobal()
|
||||||
}
|
}
|
||||||
|
|
||||||
// Search options
|
// Search options
|
||||||
|
|
|
||||||
|
|
@ -322,7 +322,7 @@ type mockStore struct {
|
||||||
data map[string]map[string]interface{}
|
data map[string]map[string]interface{}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (m *mockStore) GetAssistant(id string) (map[string]interface{}, error) {
|
func (m *mockStore) GetAssistant(id string, locale ...string) (map[string]interface{}, error) {
|
||||||
if data, ok := m.data[id]; ok {
|
if data, ok := m.data[id]; ok {
|
||||||
return data, nil
|
return data, nil
|
||||||
}
|
}
|
||||||
|
|
@ -367,7 +367,7 @@ func (m *mockStore) ListFiles(query map[string]interface{}) ([]map[string]interf
|
||||||
}
|
}
|
||||||
func (m *mockStore) DeleteAllChats(id string) error { return nil }
|
func (m *mockStore) DeleteAllChats(id string) error { return nil }
|
||||||
func (m *mockStore) DeleteChat(id string, chatID string) error { return nil }
|
func (m *mockStore) DeleteChat(id string, chatID string) error { return nil }
|
||||||
func (m *mockStore) GetAssistants(filter store.AssistantFilter) (*store.AssistantResponse, error) {
|
func (m *mockStore) GetAssistants(filter store.AssistantFilter, locale ...string) (*store.AssistantResponse, error) {
|
||||||
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 }
|
||||||
|
|
@ -391,4 +391,6 @@ func (m *mockStore) SaveHistory(sid string, messages []map[string]interface{}, c
|
||||||
}
|
}
|
||||||
func (m *mockStore) UpdateChatTitle(sid string, cid string, title string) error { return nil }
|
func (m *mockStore) UpdateChatTitle(sid string, cid string, title string) error { return nil }
|
||||||
func (m *mockStore) DeleteAssistants(filter store.AssistantFilter) (int64, error) { return 0, nil }
|
func (m *mockStore) DeleteAssistants(filter store.AssistantFilter) (int64, error) { return 0, nil }
|
||||||
func (m *mockStore) GetAssistantTags() ([]string, error) { return []string{}, nil }
|
func (m *mockStore) GetAssistantTags(locale ...string) ([]store.Tag, error) {
|
||||||
|
return []store.Tag{}, nil
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -9,6 +9,7 @@ import (
|
||||||
"github.com/yaoapp/gou/rag/driver"
|
"github.com/yaoapp/gou/rag/driver"
|
||||||
v8 "github.com/yaoapp/gou/runtime/v8"
|
v8 "github.com/yaoapp/gou/runtime/v8"
|
||||||
chatctx "github.com/yaoapp/yao/neo/context"
|
chatctx "github.com/yaoapp/yao/neo/context"
|
||||||
|
"github.com/yaoapp/yao/neo/i18n"
|
||||||
"github.com/yaoapp/yao/neo/message"
|
"github.com/yaoapp/yao/neo/message"
|
||||||
api "github.com/yaoapp/yao/openai"
|
api "github.com/yaoapp/yao/openai"
|
||||||
)
|
)
|
||||||
|
|
@ -25,7 +26,7 @@ type API interface {
|
||||||
Download(ctx context.Context, fileID string) (*FileResponse, error)
|
Download(ctx context.Context, fileID string) (*FileResponse, error)
|
||||||
ReadBase64(ctx context.Context, fileID string) (string, error)
|
ReadBase64(ctx context.Context, fileID string) (string, error)
|
||||||
|
|
||||||
GetPlaceholder() *Placeholder
|
GetPlaceholder(locale string) *Placeholder
|
||||||
Execute(c *gin.Context, ctx chatctx.Context, input interface{}, options map[string]interface{}, callback ...interface{}) (interface{}, error)
|
Execute(c *gin.Context, ctx chatctx.Context, input interface{}, options map[string]interface{}, callback ...interface{}) (interface{}, error)
|
||||||
Call(c *gin.Context, payload APIPayload) (interface{}, error)
|
Call(c *gin.Context, payload APIPayload) (interface{}, error)
|
||||||
}
|
}
|
||||||
|
|
@ -139,6 +140,7 @@ type Assistant struct {
|
||||||
Tools *ToolCalls `json:"tools,omitempty"` // Assistant Tools
|
Tools *ToolCalls `json:"tools,omitempty"` // Assistant Tools
|
||||||
Flows []map[string]interface{} `json:"flows,omitempty"` // Assistant Flows
|
Flows []map[string]interface{} `json:"flows,omitempty"` // Assistant Flows
|
||||||
Placeholder *Placeholder `json:"placeholder,omitempty"` // Assistant Placeholder
|
Placeholder *Placeholder `json:"placeholder,omitempty"` // Assistant Placeholder
|
||||||
|
Locales i18n.Map `json:"locales,omitempty"` // Assistant Locales
|
||||||
Search *SearchOption `json:"search,omitempty" yaml:"search,omitempty"` // Whether this assistant supports search
|
Search *SearchOption `json:"search,omitempty" yaml:"search,omitempty"` // Whether this assistant supports search
|
||||||
Knowledge *KnowledgeOption `json:"knowledge,omitempty" yaml:"knowledge,omitempty"` // Whether this assistant supports knowledge
|
Knowledge *KnowledgeOption `json:"knowledge,omitempty" yaml:"knowledge,omitempty"` // Whether this assistant supports knowledge
|
||||||
CreatedAt int64 `json:"created_at"` // Creation timestamp
|
CreatedAt int64 `json:"created_at"` // Creation timestamp
|
||||||
|
|
|
||||||
222
neo/i18n/i18n.go
Normal file
222
neo/i18n/i18n.go
Normal file
|
|
@ -0,0 +1,222 @@
|
||||||
|
package i18n
|
||||||
|
|
||||||
|
import (
|
||||||
|
"path/filepath"
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
"github.com/yaoapp/gou/application"
|
||||||
|
"github.com/yaoapp/gou/fs"
|
||||||
|
"github.com/yaoapp/kun/maps"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Locales the locales
|
||||||
|
var Locales = map[string]map[string]I18n{}
|
||||||
|
|
||||||
|
// I18n the i18n struct
|
||||||
|
type I18n struct {
|
||||||
|
Locale string `json:"locale,omitempty" yaml:"locale,omitempty"`
|
||||||
|
Messages map[string]any `json:"messages,omitempty" yaml:"messages,omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// Map the i18n map
|
||||||
|
type Map map[string]I18n
|
||||||
|
|
||||||
|
// Parse parse the input
|
||||||
|
func (i18n I18n) Parse(input any) any {
|
||||||
|
|
||||||
|
switch in := input.(type) {
|
||||||
|
case string:
|
||||||
|
trimed := strings.TrimSpace(in)
|
||||||
|
hasExp := strings.HasPrefix(trimed, "{{") && strings.HasSuffix(trimed, "}}")
|
||||||
|
if hasExp {
|
||||||
|
exp := strings.TrimSpace(strings.TrimPrefix(strings.TrimSuffix(trimed, "}}"), "{{"))
|
||||||
|
if _, ok := i18n.Messages[exp]; ok {
|
||||||
|
return i18n.Messages[exp]
|
||||||
|
}
|
||||||
|
return exp
|
||||||
|
}
|
||||||
|
|
||||||
|
if _, ok := i18n.Messages[trimed]; ok {
|
||||||
|
return i18n.Messages[trimed]
|
||||||
|
}
|
||||||
|
|
||||||
|
return in
|
||||||
|
|
||||||
|
case map[string]any:
|
||||||
|
new := map[string]any{}
|
||||||
|
for key, value := range in {
|
||||||
|
new[key] = i18n.Parse(value)
|
||||||
|
}
|
||||||
|
return new
|
||||||
|
|
||||||
|
case []any:
|
||||||
|
new := []any{}
|
||||||
|
for _, value := range in {
|
||||||
|
new = append(new, i18n.Parse(value))
|
||||||
|
}
|
||||||
|
return new
|
||||||
|
|
||||||
|
case []string:
|
||||||
|
new := []string{}
|
||||||
|
for _, value := range in {
|
||||||
|
new = append(new, i18n.Parse(value).(string))
|
||||||
|
}
|
||||||
|
return new
|
||||||
|
}
|
||||||
|
|
||||||
|
return input
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetLocales get the locales from path
|
||||||
|
func GetLocales(path string) (Map, error) {
|
||||||
|
app, err := fs.Get("app")
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
i18ns := Map{}
|
||||||
|
localesdir := filepath.Join(path, "locales")
|
||||||
|
if has, _ := app.Exists(localesdir); has {
|
||||||
|
locales, err := app.ReadDir(localesdir, true)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// load locales
|
||||||
|
for _, locale := range locales {
|
||||||
|
localeData, err := app.ReadFile(locale)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
var messages maps.Map = map[string]any{}
|
||||||
|
err = application.Parse(locale, localeData, &messages)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
name := strings.ToLower(strings.TrimSuffix(filepath.Base(locale), ".yml"))
|
||||||
|
i18ns[name] = I18n{Locale: name, Messages: messages}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return i18ns, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Flatten flatten the i18n map
|
||||||
|
func (i18ns Map) Flatten() Map {
|
||||||
|
new := Map{}
|
||||||
|
for lang, i18n := range i18ns {
|
||||||
|
new[lang] = I18n{Locale: lang, Messages: maps.MapOf(i18n.Messages).Dot()}
|
||||||
|
|
||||||
|
// Add short lang
|
||||||
|
parts := strings.Split(lang, "-")
|
||||||
|
|
||||||
|
// en
|
||||||
|
if parts[0] != lang {
|
||||||
|
new[parts[0]] = new[lang]
|
||||||
|
}
|
||||||
|
|
||||||
|
// us
|
||||||
|
if len(parts) > 1 {
|
||||||
|
new[parts[1]] = new[lang]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return new
|
||||||
|
}
|
||||||
|
|
||||||
|
// FlattenWithGlobal flatten the i18n map with global i18n
|
||||||
|
func (i18ns Map) FlattenWithGlobal() Map {
|
||||||
|
|
||||||
|
// New i18n map
|
||||||
|
new := Map{}
|
||||||
|
|
||||||
|
// Global i18n
|
||||||
|
globalI18ns, hasGlobal := Locales["__global__"]
|
||||||
|
|
||||||
|
// Extend the i18n map with global i18n
|
||||||
|
for lang, i18n := range i18ns {
|
||||||
|
new[lang] = I18n{Locale: lang, Messages: maps.MapOf(i18n.Messages).Dot()}
|
||||||
|
if hasGlobal {
|
||||||
|
if global, has := globalI18ns[lang]; has {
|
||||||
|
for key, value := range global.Messages {
|
||||||
|
if _, ok := new[lang].Messages[key]; !ok {
|
||||||
|
new[lang].Messages[key] = value
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Add short lang
|
||||||
|
parts := strings.Split(lang, "-")
|
||||||
|
|
||||||
|
// en
|
||||||
|
if parts[0] != lang {
|
||||||
|
new[parts[0]] = new[lang]
|
||||||
|
}
|
||||||
|
|
||||||
|
// us
|
||||||
|
if len(parts) > 1 {
|
||||||
|
new[parts[1]] = new[lang]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return new
|
||||||
|
}
|
||||||
|
|
||||||
|
// Translate translate the input
|
||||||
|
func Translate(assistantID string, locale string, input any) any {
|
||||||
|
|
||||||
|
locale = strings.ToLower(strings.TrimSpace(locale))
|
||||||
|
i18ns, has := Locales[assistantID]
|
||||||
|
if !has {
|
||||||
|
i18ns = map[string]I18n{}
|
||||||
|
}
|
||||||
|
|
||||||
|
i18n, has := i18ns[locale]
|
||||||
|
if !has {
|
||||||
|
parts := strings.Split(locale, "-")
|
||||||
|
if len(parts) > 1 {
|
||||||
|
i18n, has = i18ns[parts[1]]
|
||||||
|
}
|
||||||
|
if !has {
|
||||||
|
i18n, has = i18ns[parts[0]]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if !has {
|
||||||
|
var hasGlobal bool = false
|
||||||
|
i18ns, hasGlobal = Locales["__global__"]
|
||||||
|
if hasGlobal {
|
||||||
|
i18n, has = i18ns[locale]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if has {
|
||||||
|
return i18n.Parse(input)
|
||||||
|
}
|
||||||
|
|
||||||
|
return input
|
||||||
|
}
|
||||||
|
|
||||||
|
// TranslateGlobal translate the input with global i18n
|
||||||
|
func TranslateGlobal(locale string, input any) any {
|
||||||
|
locale = strings.ToLower(strings.TrimSpace(locale))
|
||||||
|
i18ns, has := Locales["__global__"]
|
||||||
|
if !has {
|
||||||
|
i18ns = map[string]I18n{}
|
||||||
|
}
|
||||||
|
|
||||||
|
i18n, has := i18ns[locale]
|
||||||
|
if !has {
|
||||||
|
parts := strings.Split(locale, "-")
|
||||||
|
if len(parts) > 1 {
|
||||||
|
i18n, has = i18ns[parts[1]]
|
||||||
|
}
|
||||||
|
if !has {
|
||||||
|
i18n, has = i18ns[parts[0]]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if has {
|
||||||
|
return i18n.Parse(input)
|
||||||
|
}
|
||||||
|
return input
|
||||||
|
}
|
||||||
|
|
@ -8,6 +8,7 @@ import (
|
||||||
"github.com/yaoapp/gou/connector"
|
"github.com/yaoapp/gou/connector"
|
||||||
"github.com/yaoapp/yao/config"
|
"github.com/yaoapp/yao/config"
|
||||||
"github.com/yaoapp/yao/neo/assistant"
|
"github.com/yaoapp/yao/neo/assistant"
|
||||||
|
"github.com/yaoapp/yao/neo/i18n"
|
||||||
"github.com/yaoapp/yao/neo/store"
|
"github.com/yaoapp/yao/neo/store"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
@ -86,11 +87,11 @@ func Load(cfg config.Config) error {
|
||||||
|
|
||||||
// initGlobalI18n initialize the global i18n
|
// initGlobalI18n initialize the global i18n
|
||||||
func initGlobalI18n() error {
|
func initGlobalI18n() error {
|
||||||
locales, err := assistant.GetI18n("neo")
|
locales, err := i18n.GetLocales("neo")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
assistant.Locales["__global__"] = locales
|
i18n.Locales["__global__"] = locales.Flatten()
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -395,7 +395,12 @@ func processAssistantSearch(process *process.Process) interface{} {
|
||||||
exception.New("Neo store is not initialized", 500).Throw()
|
exception.New("Neo store is not initialized", 500).Throw()
|
||||||
}
|
}
|
||||||
|
|
||||||
res, err := neo.Store.GetAssistants(filter)
|
locale := "en"
|
||||||
|
if len(process.Args) > 1 {
|
||||||
|
locale = process.ArgsString(1)
|
||||||
|
}
|
||||||
|
|
||||||
|
res, err := neo.Store.GetAssistants(filter, locale)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
exception.New("get assistants error: %s", 500, err).Throw()
|
exception.New("get assistants error: %s", 500, err).Throw()
|
||||||
}
|
}
|
||||||
|
|
@ -419,7 +424,11 @@ func processAssistantFind(process *process.Process) interface{} {
|
||||||
PageSize: 1,
|
PageSize: 1,
|
||||||
}
|
}
|
||||||
|
|
||||||
res, err := neo.Store.GetAssistants(filter)
|
locale := "en"
|
||||||
|
if len(process.Args) > 1 {
|
||||||
|
locale = process.ArgsString(1)
|
||||||
|
}
|
||||||
|
res, err := neo.Store.GetAssistants(filter, locale)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
exception.New("Failed to find assistant: %s", 500, err.Error()).Throw()
|
exception.New("Failed to find assistant: %s", 500, err.Error()).Throw()
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -64,21 +64,21 @@ func (m *Mongo) DeleteAssistant(assistantID string) error {
|
||||||
}
|
}
|
||||||
|
|
||||||
// GetAssistants retrieves a list of assistants
|
// GetAssistants retrieves a list of assistants
|
||||||
func (m *Mongo) GetAssistants(filter AssistantFilter) (*AssistantResponse, error) {
|
func (m *Mongo) GetAssistants(filter AssistantFilter, locale ...string) (*AssistantResponse, error) {
|
||||||
return &AssistantResponse{}, nil
|
return &AssistantResponse{}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// GetAssistant retrieves a single assistant by ID
|
// GetAssistant retrieves a single assistant by ID
|
||||||
func (m *Mongo) GetAssistant(assistantID string) (map[string]interface{}, error) {
|
func (m *Mongo) GetAssistant(assistantID string, locale ...string) (map[string]interface{}, error) {
|
||||||
return map[string]interface{}{}, nil
|
return nil, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// DeleteAssistants deletes assistants based on filter conditions (not implemented)
|
// DeleteAssistants deletes assistants based on filter conditions (not implemented)
|
||||||
func (mongo *Mongo) DeleteAssistants(filter AssistantFilter) (int64, error) {
|
func (m *Mongo) DeleteAssistants(filter AssistantFilter) (int64, error) {
|
||||||
return 0, nil
|
return 0, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// GetAssistantTags retrieves all unique tags from assistants
|
// GetAssistantTags retrieves all unique tags from assistants
|
||||||
func (conv *Mongo) GetAssistantTags() ([]string, error) {
|
func (m *Mongo) GetAssistantTags(locale ...string) ([]Tag, error) {
|
||||||
return []string{}, nil
|
return []Tag{}, nil
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -64,21 +64,21 @@ func (r *Redis) DeleteAssistant(assistantID string) error {
|
||||||
}
|
}
|
||||||
|
|
||||||
// GetAssistants retrieves a list of assistants
|
// GetAssistants retrieves a list of assistants
|
||||||
func (r *Redis) GetAssistants(filter AssistantFilter) (*AssistantResponse, error) {
|
func (r *Redis) GetAssistants(filter AssistantFilter, locale ...string) (*AssistantResponse, error) {
|
||||||
return &AssistantResponse{}, nil
|
return &AssistantResponse{}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// GetAssistant retrieves a single assistant by ID
|
// GetAssistant retrieves a single assistant by ID
|
||||||
func (r *Redis) GetAssistant(assistantID string) (map[string]interface{}, error) {
|
func (r *Redis) GetAssistant(assistantID string, locale ...string) (map[string]interface{}, error) {
|
||||||
return map[string]interface{}{}, nil
|
return nil, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// DeleteAssistants deletes assistants based on filter conditions (not implemented)
|
// DeleteAssistants deletes assistants based on filter conditions (not implemented)
|
||||||
func (redis *Redis) DeleteAssistants(filter AssistantFilter) (int64, error) {
|
func (r *Redis) DeleteAssistants(filter AssistantFilter) (int64, error) {
|
||||||
return 0, nil
|
return 0, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// GetAssistantTags retrieves all unique tags from assistants
|
// GetAssistantTags retrieves all unique tags from assistants
|
||||||
func (conv *Redis) GetAssistantTags() ([]string, error) {
|
func (r *Redis) GetAssistantTags(locale ...string) ([]Tag, error) {
|
||||||
return []string{}, nil
|
return []Tag{}, nil
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -73,6 +73,12 @@ type AssistantResponse struct {
|
||||||
Total int64 `json:"total"` // Total number of items
|
Total int64 `json:"total"` // Total number of items
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Tag represents a tag
|
||||||
|
type Tag struct {
|
||||||
|
Value string `json:"value"`
|
||||||
|
Label string `json:"label"`
|
||||||
|
}
|
||||||
|
|
||||||
// Store defines the conversation storage interface
|
// Store defines the conversation storage interface
|
||||||
// Provides basic operations required for conversation management
|
// Provides basic operations required for conversation management
|
||||||
type Store interface {
|
type Store interface {
|
||||||
|
|
@ -147,19 +153,19 @@ type Store interface {
|
||||||
// GetAssistants retrieves a list of assistants
|
// GetAssistants retrieves a list of assistants
|
||||||
// filter: Filter conditions
|
// filter: Filter conditions
|
||||||
// Returns: Paginated assistant list and potential error
|
// Returns: Paginated assistant list and potential error
|
||||||
GetAssistants(filter AssistantFilter) (*AssistantResponse, error)
|
GetAssistants(filter AssistantFilter, locale ...string) (*AssistantResponse, error)
|
||||||
|
|
||||||
|
// GetAssistantTags retrieves all unique tags from assistants
|
||||||
|
// Returns: List of tags and potential error
|
||||||
|
GetAssistantTags(locale ...string) ([]Tag, error)
|
||||||
|
|
||||||
// GetAssistant retrieves a single assistant by ID
|
// GetAssistant retrieves a single assistant by ID
|
||||||
// assistantID: Assistant ID
|
// assistantID: Assistant ID
|
||||||
// Returns: Assistant information and potential error
|
// Returns: Assistant information and potential error
|
||||||
GetAssistant(assistantID string) (map[string]interface{}, error)
|
GetAssistant(assistantID string, locale ...string) (map[string]interface{}, error)
|
||||||
|
|
||||||
// DeleteAssistants deletes assistants based on filter conditions
|
// DeleteAssistants deletes assistants based on filter conditions
|
||||||
// filter: Filter conditions
|
// filter: Filter conditions
|
||||||
// Returns: Number of deleted records and potential error
|
// Returns: Number of deleted records and potential error
|
||||||
DeleteAssistants(filter AssistantFilter) (int64, error)
|
DeleteAssistants(filter AssistantFilter) (int64, error)
|
||||||
|
|
||||||
// GetAssistantTags retrieves all unique tags from assistants
|
|
||||||
// Returns: List of tags and potential error
|
|
||||||
GetAssistantTags() ([]string, error)
|
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -3,6 +3,7 @@ package store
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
"math"
|
"math"
|
||||||
|
"strings"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/google/uuid"
|
"github.com/google/uuid"
|
||||||
|
|
@ -13,6 +14,7 @@ import (
|
||||||
"github.com/yaoapp/xun/capsule"
|
"github.com/yaoapp/xun/capsule"
|
||||||
"github.com/yaoapp/xun/dbal/query"
|
"github.com/yaoapp/xun/dbal/query"
|
||||||
"github.com/yaoapp/xun/dbal/schema"
|
"github.com/yaoapp/xun/dbal/schema"
|
||||||
|
"github.com/yaoapp/yao/neo/i18n"
|
||||||
)
|
)
|
||||||
|
|
||||||
// Package conversation provides functionality for managing chat conversations and assistants.
|
// Package conversation provides functionality for managing chat conversations and assistants.
|
||||||
|
|
@ -145,7 +147,7 @@ func (conv *Xun) initHistoryTable() error {
|
||||||
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.Boolean("silent").SetDefault(false).Index()
|
||||||
table.TimestampTz("created_at").SetDefaultRaw("NOW()").Index()
|
table.TimestampTz("created_at").SetDefaultRaw("CURRENT_TIMESTAMP").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()
|
||||||
})
|
})
|
||||||
|
|
@ -188,7 +190,7 @@ func (conv *Xun) initChatTable() error {
|
||||||
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.Boolean("silent").SetDefault(false).Index()
|
||||||
table.TimestampTz("created_at").SetDefaultRaw("NOW()").Index()
|
table.TimestampTz("created_at").SetDefaultRaw("CURRENT_TIMESTAMP").Index()
|
||||||
table.TimestampTz("updated_at").Null().Index()
|
table.TimestampTz("updated_at").Null().Index()
|
||||||
})
|
})
|
||||||
|
|
||||||
|
|
@ -237,15 +239,16 @@ func (conv *Xun) initAssistantTable() error {
|
||||||
table.JSON("placeholder").Null() // assistant placeholder
|
table.JSON("placeholder").Null() // assistant placeholder
|
||||||
table.JSON("options").Null() // assistant options
|
table.JSON("options").Null() // assistant options
|
||||||
table.JSON("prompts").Null() // assistant prompts
|
table.JSON("prompts").Null() // assistant prompts
|
||||||
table.JSON("flows").Null() // assistant flows
|
table.JSON("workflow").Null() // assistant workflow
|
||||||
table.JSON("files").Null() // assistant files
|
table.JSON("knowledge").Null() // assistant knowledge
|
||||||
table.JSON("tools").Null() // assistant tools
|
table.JSON("tools").Null() // assistant tools
|
||||||
table.JSON("tags").Null() // assistant tags
|
table.JSON("tags").Null() // assistant tags
|
||||||
table.Boolean("readonly").SetDefault(false).Index() // assistant readonly
|
table.Boolean("readonly").SetDefault(false).Index() // assistant readonly
|
||||||
table.JSON("permissions").Null() // assistant permissions
|
table.JSON("permissions").Null() // assistant permissions
|
||||||
|
table.JSON("locales").Null() // assistant i18n
|
||||||
table.Boolean("automated").SetDefault(true).Index() // assistant autoable
|
table.Boolean("automated").SetDefault(true).Index() // assistant autoable
|
||||||
table.Boolean("mentionable").SetDefault(true).Index() // Whether this assistant can appear in @ mention list
|
table.Boolean("mentionable").SetDefault(true).Index() // Whether this assistant can appear in @ mention list
|
||||||
table.TimestampTz("created_at").SetDefaultRaw("NOW()").Index()
|
table.TimestampTz("created_at").SetDefaultRaw("CURRENT_TIMESTAMP").Index()
|
||||||
table.TimestampTz("updated_at").Null().Index()
|
table.TimestampTz("updated_at").Null().Index()
|
||||||
})
|
})
|
||||||
|
|
||||||
|
|
@ -261,7 +264,7 @@ func (conv *Xun) initAssistantTable() error {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
fields := []string{"id", "assistant_id", "type", "name", "avatar", "connector", "description", "path", "sort", "built_in", "placeholder", "options", "prompts", "flows", "files", "tools", "tags", "mentionable", "created_at", "updated_at"}
|
fields := []string{"id", "assistant_id", "type", "name", "avatar", "connector", "description", "path", "sort", "built_in", "placeholder", "options", "prompts", "workflow", "knowledge", "tools", "tags", "readonly", "permissions", "locales", "automated", "mentionable", "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)
|
||||||
|
|
@ -963,7 +966,7 @@ func (conv *Xun) SaveAssistant(assistant map[string]interface{}) (interface{}, e
|
||||||
}
|
}
|
||||||
|
|
||||||
// Process JSON fields
|
// Process JSON fields
|
||||||
jsonFields := []string{"tags", "options", "prompts", "flows", "files", "tools", "permissions", "placeholder"}
|
jsonFields := []string{"tags", "options", "prompts", "workflow", "knowledge", "tools", "permissions", "placeholder", "locales"}
|
||||||
for _, field := range jsonFields {
|
for _, field := range jsonFields {
|
||||||
if val, ok := assistantCopy[field]; ok && val != nil {
|
if val, ok := assistantCopy[field]; ok && val != nil {
|
||||||
// If it's a string, try to parse it first
|
// If it's a string, try to parse it first
|
||||||
|
|
@ -1049,7 +1052,7 @@ func (conv *Xun) DeleteAssistant(assistantID string) error {
|
||||||
}
|
}
|
||||||
|
|
||||||
// GetAssistants retrieves assistants with pagination and filtering
|
// GetAssistants retrieves assistants with pagination and filtering
|
||||||
func (conv *Xun) GetAssistants(filter AssistantFilter) (*AssistantResponse, error) {
|
func (conv *Xun) GetAssistants(filter AssistantFilter, locale ...string) (*AssistantResponse, error) {
|
||||||
qb := conv.query.New().
|
qb := conv.query.New().
|
||||||
Table(conv.getAssistantTable())
|
Table(conv.getAssistantTable())
|
||||||
|
|
||||||
|
|
@ -1159,7 +1162,7 @@ func (conv *Xun) GetAssistants(filter AssistantFilter) (*AssistantResponse, erro
|
||||||
|
|
||||||
// Convert rows to map slice and parse JSON fields
|
// Convert rows to map slice and parse JSON fields
|
||||||
data := make([]map[string]interface{}, len(rows))
|
data := make([]map[string]interface{}, len(rows))
|
||||||
jsonFields := []string{"tags", "options", "prompts", "flows", "files", "tools", "permissions", "placeholder"}
|
jsonFields := []string{"tags", "options", "prompts", "workflow", "knowledge", "tools", "permissions", "placeholder"}
|
||||||
for i, row := range rows {
|
for i, row := range rows {
|
||||||
data[i] = row
|
data[i] = row
|
||||||
// Only parse JSON fields if they are selected or no select filter is provided
|
// Only parse JSON fields if they are selected or no select filter is provided
|
||||||
|
|
@ -1182,6 +1185,15 @@ func (conv *Xun) GetAssistants(filter AssistantFilter) (*AssistantResponse, erro
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Translate Data
|
||||||
|
if len(locale) > 0 {
|
||||||
|
lang := strings.ToLower(locale[0])
|
||||||
|
for i, row := range data {
|
||||||
|
assistantID := row["assistant_id"].(string)
|
||||||
|
data[i] = i18n.Translate(assistantID, lang, row).(map[string]interface{})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
return &AssistantResponse{
|
return &AssistantResponse{
|
||||||
Data: data,
|
Data: data,
|
||||||
Page: filter.Page,
|
Page: filter.Page,
|
||||||
|
|
@ -1194,7 +1206,7 @@ func (conv *Xun) GetAssistants(filter AssistantFilter) (*AssistantResponse, erro
|
||||||
}
|
}
|
||||||
|
|
||||||
// GetAssistant retrieves a single assistant by ID
|
// GetAssistant retrieves a single assistant by ID
|
||||||
func (conv *Xun) GetAssistant(assistantID string) (map[string]interface{}, error) {
|
func (conv *Xun) GetAssistant(assistantID string, locale ...string) (map[string]interface{}, error) {
|
||||||
row, err := conv.query.New().
|
row, err := conv.query.New().
|
||||||
Table(conv.getAssistantTable()).
|
Table(conv.getAssistantTable()).
|
||||||
Where("assistant_id", assistantID).
|
Where("assistant_id", assistantID).
|
||||||
|
|
@ -1213,9 +1225,12 @@ func (conv *Xun) GetAssistant(assistantID string) (map[string]interface{}, error
|
||||||
}
|
}
|
||||||
|
|
||||||
// Parse JSON fields
|
// Parse JSON fields
|
||||||
jsonFields := []string{"tags", "options", "prompts", "flows", "files", "tools", "permissions", "placeholder"}
|
jsonFields := []string{"tags", "options", "prompts", "workflow", "knowledge", "tools", "permissions", "placeholder"}
|
||||||
conv.parseJSONFields(data, jsonFields)
|
conv.parseJSONFields(data, jsonFields)
|
||||||
|
if len(locale) > 0 {
|
||||||
|
lang := strings.ToLower(locale[0])
|
||||||
|
return i18n.Translate(assistantID, lang, data).(map[string]interface{}), nil
|
||||||
|
}
|
||||||
return data, nil
|
return data, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -1281,7 +1296,7 @@ func (conv *Xun) DeleteAssistants(filter AssistantFilter) (int64, error) {
|
||||||
}
|
}
|
||||||
|
|
||||||
// GetAssistantTags retrieves all unique tags from assistants
|
// GetAssistantTags retrieves all unique tags from assistants
|
||||||
func (conv *Xun) GetAssistantTags() ([]string, error) {
|
func (conv *Xun) GetAssistantTags(locale ...string) ([]Tag, error) {
|
||||||
q := conv.newQuery().Table(conv.getAssistantTable())
|
q := conv.newQuery().Table(conv.getAssistantTable())
|
||||||
rows, err := q.Select("tags").Where("type", "assistant").GroupBy("tags").Get()
|
rows, err := q.Select("tags").Where("type", "assistant").GroupBy("tags").Get()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
|
@ -1300,10 +1315,18 @@ func (conv *Xun) GetAssistantTags() ([]string, error) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
lang := "en"
|
||||||
|
if len(locale) > 0 {
|
||||||
|
lang = locale[0]
|
||||||
|
}
|
||||||
|
|
||||||
// Convert map keys to slice
|
// Convert map keys to slice
|
||||||
tags := make([]string, 0, len(tagSet))
|
tags := make([]Tag, 0, len(tagSet))
|
||||||
for tag := range tagSet {
|
for tag := range tagSet {
|
||||||
tags = append(tags, tag)
|
tags = append(tags, Tag{
|
||||||
|
Value: tag,
|
||||||
|
Label: i18n.TranslateGlobal(lang, tag).(string),
|
||||||
|
})
|
||||||
}
|
}
|
||||||
return tags, nil
|
return tags, nil
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -983,7 +983,8 @@ func TestGetAssistantTags(t *testing.T) {
|
||||||
}
|
}
|
||||||
|
|
||||||
for _, tag := range tags {
|
for _, tag := range tags {
|
||||||
if !expectedTags[tag] {
|
value := tag.Value
|
||||||
|
if !expectedTags[value] {
|
||||||
t.Errorf("Unexpected tag found: %s", tag)
|
t.Errorf("Unexpected tag found: %s", tag)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -466,6 +466,7 @@ func processXgen(process *process.Process) interface{} {
|
||||||
}
|
}
|
||||||
|
|
||||||
// Set User ENV
|
// Set User ENV
|
||||||
|
lang := config.Conf.Lang
|
||||||
if process.NumOfArgs() > 0 {
|
if process.NumOfArgs() > 0 {
|
||||||
payload := process.ArgsMap(0, map[string]interface{}{
|
payload := process.ArgsMap(0, map[string]interface{}{
|
||||||
"now": time.Now().Unix(),
|
"now": time.Now().Unix(),
|
||||||
|
|
@ -476,8 +477,7 @@ func processXgen(process *process.Process) interface{} {
|
||||||
if v, ok := payload["sid"].(string); ok && v != "" {
|
if v, ok := payload["sid"].(string); ok && v != "" {
|
||||||
sid = v
|
sid = v
|
||||||
}
|
}
|
||||||
|
lang = strings.ToLower(fmt.Sprintf("%v", payload["lang"]))
|
||||||
lang := strings.ToLower(fmt.Sprintf("%v", payload["lang"]))
|
|
||||||
session.Global().ID(sid).Set("__yao_lang", lang)
|
session.Global().ID(sid).Set("__yao_lang", lang)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -571,7 +571,7 @@ func processXgen(process *process.Process) interface{} {
|
||||||
"assistant_name": ast.Name,
|
"assistant_name": ast.Name,
|
||||||
"assistant_avatar": ast.Avatar,
|
"assistant_avatar": ast.Avatar,
|
||||||
"assistant_deleteable": false,
|
"assistant_deleteable": false,
|
||||||
"placeholder": ast.Placeholder,
|
"placeholder": ast.GetPlaceholder(lang),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
agent["connectors"] = connector.AIConnectors
|
agent["connectors"] = connector.AIConnectors
|
||||||
|
|
|
||||||
Loading…
Add table
Reference in a new issue