Merge pull request #944 from trheyi/main

feat: Enhance context management and update asset metadata in SUI
This commit is contained in:
Max 2025-05-08 20:06:24 +08:00 committed by GitHub
commit ac55508e05
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
10 changed files with 335 additions and 501 deletions

File diff suppressed because one or more lines are too long

View file

@ -134,15 +134,6 @@ func (neo *DSL) API(router *gin.Engine, path string) error {
// curl -X GET 'http://localhost:5099/api/__yao/neo/mentions?keywords=assistant&token=xxx'
router.GET(path+"/mentions", append(middlewares, neo.handleMentions)...)
// Generation endpoints
// Generate custom content example:
// curl -X GET 'http://localhost:5099/api/__yao/neo/generate?content=Generate+something&type=custom&system_prompt=You+are+a+helpful+assistant&chat_id=chat_123&token=xxx'
// curl -X POST 'http://localhost:5099/api/__yao/neo/generate' \
// -H 'Content-Type: application/json' \
// -d '{"content": "Generate something", "type": "custom", "system_prompt": "You are a helpful assistant", "chat_id": "chat_123", "token": "xxx"}'
router.GET(path+"/generate", append(middlewares, neo.handleGenerateCustom)...)
router.POST(path+"/generate", append(middlewares, neo.handleGenerateCustom)...)
// Generate title example:
// curl -X GET 'http://localhost:5099/api/__yao/neo/generate/title?content=Chat+content&chat_id=chat_123&token=xxx'
// curl -X POST 'http://localhost:5099/api/__yao/neo/generate/title' \
@ -238,6 +229,24 @@ func (neo *DSL) handleChat(c *gin.Context) {
ctx = chatctx.WithAssistantID(ctx, assistantID)
}
// Set the silent mode
silent := c.Query("silent")
if silent == "true" || silent == "1" {
ctx = chatctx.WithSilent(ctx, true)
}
// Set the history visible
historyVisible := c.Query("history_visible")
if historyVisible != "" {
ctx = chatctx.WithHistoryVisible(ctx, historyVisible == "true" || historyVisible == "1")
}
// Set the client type
clientType := c.Query("client_type")
if clientType != "" {
ctx = chatctx.WithClientType(ctx, clientType)
}
err := neo.Answer(ctx, content, c)
// Error handling
@ -485,7 +494,7 @@ func (neo *DSL) handleChatLatest(c *gin.Context) {
// Create a new chat
if len(chats.Groups) == 0 || len(chats.Groups[0].Chats) == 0 {
assistantID := neo.Use
assistantID := neo.Use.Default
queryAssistantID := c.Query("assistant_id")
if queryAssistantID != "" {
assistantID = queryAssistantID
@ -504,7 +513,7 @@ func (neo *DSL) handleChatLatest(c *gin.Context) {
"assistant_id": ast.ID,
"assistant_name": ast.Name,
"assistant_avatar": ast.Avatar,
"assistant_deleteable": neo.Use != ast.ID,
"assistant_deleteable": neo.Use.Default != ast.ID,
}})
c.Done()
return
@ -530,7 +539,7 @@ func (neo *DSL) handleChatLatest(c *gin.Context) {
chat.Chat["assistant_id"] = neo.Use
// Get the assistant info
ast, err := assistant.Get(neo.Use)
ast, err := assistant.Get(neo.Use.Default)
if err != nil {
c.JSON(500, gin.H{"message": err.Error(), "code": 500})
c.Done()
@ -573,7 +582,7 @@ func (neo *DSL) handleChatDetail(c *gin.Context) {
chat.Chat["assistant_id"] = neo.Use
// Get the assistant info
ast, err := assistant.Get(neo.Use)
ast, err := assistant.Get(neo.Use.Default)
if err != nil {
c.JSON(500, gin.H{"message": err.Error(), "code": 500})
c.Done()
@ -583,7 +592,7 @@ func (neo *DSL) handleChatDetail(c *gin.Context) {
chat.Chat["assistant_avatar"] = ast.Avatar
}
chat.Chat["assistant_deleteable"] = neo.Use != chat.Chat["assistant_id"]
chat.Chat["assistant_deleteable"] = neo.Use.Default != chat.Chat["assistant_id"]
c.JSON(200, map[string]interface{}{"data": chat})
c.Done()
}
@ -659,20 +668,6 @@ func (neo *DSL) handleChatUpdate(c *gin.Context) {
return
}
// If content is not empty, Generate the chat title
if body.Content != "" {
ctx, cancel := chatctx.NewWithCancel(sid, c.Query("chat_id"), "")
defer cancel()
title, err := neo.GenerateChatTitle(ctx, body.Content, c, true)
if err != nil {
c.JSON(500, gin.H{"message": err.Error(), "code": 500})
c.Done()
return
}
body.Title = title
}
if body.Title == "" {
c.JSON(400, gin.H{"message": "title is required", "code": 400})
c.Done()
@ -813,155 +808,81 @@ func (r *generateResponse) send(key string) {
// handleGenerateTitle handles generating a chat title
func (neo *DSL) handleGenerateTitle(c *gin.Context) {
var content string
if c.Request.Method == "GET" {
content = c.Query("content")
} else {
var body struct {
Content string `json:"content"`
}
if err := c.BindJSON(&body); err != nil {
// For SSE requests, send error message in SSE format
if strings.Contains(c.GetHeader("Accept"), "text/event-stream") {
c.Header("Content-Type", "text/event-stream;charset=utf-8")
c.Header("Cache-Control", "no-cache")
c.Header("Connection", "keep-alive")
msg := message.New().Error("invalid request body").Done()
msg.Write(c.Writer)
return
}
c.JSON(400, gin.H{"message": "invalid request body", "code": 400})
return
}
content = body.Content
// Set headers for SSE
c.Header("Content-Type", "text/event-stream;charset=utf-8")
c.Header("Cache-Control", "no-cache")
c.Header("Connection", "keep-alive")
sid := c.GetString("__sid")
if sid == "" {
sid = uuid.New().String()
}
resp := &generateResponse{
c: c,
sid: c.GetString("__sid"),
content: content,
}
// For SSE requests, set headers before validation
if strings.Contains(c.GetHeader("Accept"), "text/event-stream") {
c.Header("Content-Type", "text/event-stream;charset=utf-8")
c.Header("Cache-Control", "no-cache")
c.Header("Connection", "keep-alive")
}
if !resp.validate() {
content := c.Query("content")
if content == "" {
msg := message.New().Error("content is required").Done()
msg.Write(c.Writer)
return
}
ctx, cancel := chatctx.NewWithCancel(resp.sid, c.Query("chat_id"), "")
defer cancel()
chatID := fmt.Sprintf("generate_title_%d", time.Now().UnixNano())
// Use silent mode for regular HTTP requests, streaming for SSE
silent := !strings.Contains(c.GetHeader("Accept"), "text/event-stream")
resp.result, resp.err = neo.GenerateChatTitle(ctx, resp.content, c, silent)
resp.send("result")
// Set the context with validated chat_id
ctx, cancel := chatctx.NewWithCancel(sid, chatID, c.Query("context"))
defer cancel()
defer ctx.Release() // Release the context after the request is done
// Set the assistant ID
ctx = chatctx.WithHistoryVisible(ctx, false)
ctx = chatctx.WithAssistantID(ctx, neo.Use.Title)
err := neo.Answer(ctx, content, c)
// Error handling
if err != nil {
message.New().Done().Error(err).Write(c.Writer)
c.Done()
return
}
}
// handleGeneratePrompts handles generating prompts
func (neo *DSL) handleGeneratePrompts(c *gin.Context) {
var content string
if c.Request.Method == "GET" {
content = c.Query("content")
} else {
var body struct {
Content string `json:"content"`
}
if err := c.BindJSON(&body); err != nil {
// For SSE requests, send error message in SSE format
if strings.Contains(c.GetHeader("Accept"), "text/event-stream") {
c.Header("Content-Type", "text/event-stream;charset=utf-8")
c.Header("Cache-Control", "no-cache")
c.Header("Connection", "keep-alive")
msg := message.New().Error("invalid request body").Done()
msg.Write(c.Writer)
return
}
c.JSON(400, gin.H{"message": "invalid request body", "code": 400})
return
}
content = body.Content
// Set headers for SSE
c.Header("Content-Type", "text/event-stream;charset=utf-8")
c.Header("Cache-Control", "no-cache")
c.Header("Connection", "keep-alive")
sid := c.GetString("__sid")
if sid == "" {
sid = uuid.New().String()
}
resp := &generateResponse{
c: c,
sid: c.GetString("__sid"),
content: content,
}
// For SSE requests, set headers before validation
if strings.Contains(c.GetHeader("Accept"), "text/event-stream") {
c.Header("Content-Type", "text/event-stream;charset=utf-8")
c.Header("Cache-Control", "no-cache")
c.Header("Connection", "keep-alive")
}
if !resp.validate() {
content := c.Query("content")
if content == "" {
msg := message.New().Error("content is required").Done()
msg.Write(c.Writer)
return
}
ctx, cancel := chatctx.NewWithCancel(resp.sid, c.Query("chat_id"), "")
chatID := fmt.Sprintf("generate_prompts_%d", time.Now().UnixNano())
// Set the context with validated chat_id
ctx, cancel := chatctx.NewWithCancel(sid, chatID, c.Query("context"))
defer cancel()
defer ctx.Release() // Release the context after the request is done
// Use silent mode for regular HTTP requests, streaming for SSE
silent := !strings.Contains(c.GetHeader("Accept"), "text/event-stream")
resp.result, resp.err = neo.GeneratePrompts(ctx, resp.content, c, silent)
resp.send("result")
}
// Set the assistant ID
ctx = chatctx.WithHistoryVisible(ctx, false)
ctx = chatctx.WithAssistantID(ctx, neo.Use.Prompt)
err := neo.Answer(ctx, content, c)
// handleGenerateCustom handles generating custom content
func (neo *DSL) handleGenerateCustom(c *gin.Context) {
var content, genType, systemPrompt string
if c.Request.Method == "GET" {
content = c.Query("content")
genType = c.Query("type")
systemPrompt = c.Query("system_prompt")
} else {
var body struct {
Content string `json:"content"`
Type string `json:"type"`
SystemPrompt string `json:"system_prompt"`
}
if err := c.BindJSON(&body); err != nil {
c.JSON(400, gin.H{"message": "invalid request body", "code": 400})
return
}
content = body.Content
genType = body.Type
systemPrompt = body.SystemPrompt
}
resp := &generateResponse{
c: c,
sid: c.GetString("__sid"),
content: content,
}
if !resp.validate() {
// Error handling
if err != nil {
message.New().Done().Error(err).Write(c.Writer)
c.Done()
return
}
// Additional validations for custom generation
if genType == "" {
c.JSON(400, gin.H{"message": "type is required", "code": 400})
return
}
if systemPrompt == "" {
c.JSON(400, gin.H{"message": "system_prompt is required", "code": 400})
return
}
ctx, cancel := chatctx.NewWithCancel(resp.sid, c.Query("chat_id"), "")
defer cancel()
// Use silent mode for regular HTTP requests, streaming for SSE
silent := !strings.Contains(c.GetHeader("Accept"), "text/event-stream")
resp.result, resp.err = neo.GenerateWithAI(ctx, resp.content, genType, systemPrompt, c, silent)
resp.send("result")
}
// handleAssistantList handles listing assistants

View file

@ -242,7 +242,8 @@ func (obj *objectCall) run(info *v8go.FunctionCallbackInfo) *v8go.Value {
chatCtx.AssistantID = assistantID
chatCtx.ChatID = fmt.Sprintf("call_%s", uuid.New().String()) // New chat id
chatCtx.Silent = options.Silent
chatCtx.Args = goArgs // Arguments for call
chatCtx.ClientType = chatctx.ClientTypeAgent // Set the client type to agent
chatCtx.Args = goArgs // Arguments for call
// Define the callback function
var cb func(msg *chatMessage.Message) = nil

View file

@ -12,24 +12,26 @@ import (
// Context the context
type Context struct {
context.Context
Sid string `json:"sid" yaml:"-"` // Session ID
ChatID string `json:"chat_id,omitempty"` // Chat ID, use to select chat
AssistantID string `json:"assistant_id,omitempty"` // Assistant ID, use to select assistant
Stack string `json:"stack,omitempty"` // will be removed in the future
Path string `json:"pathname,omitempty"` // wiil be rename to path
FormData map[string]interface{} `json:"formdata,omitempty"`
Field *Field `json:"field,omitempty"`
Namespace string `json:"namespace,omitempty"`
Config map[string]interface{} `json:"config,omitempty"`
Signal interface{} `json:"signal,omitempty"`
Silent bool `json:"silent,omitempty"` // Silent mode
Retry bool `json:"retry,omitempty"` // Retry mode
RetryTimes uint8 `json:"retry_times,omitempty"` // Retry times
Upload *FileUpload `json:"upload,omitempty"`
Version bool `json:"version,omitempty"` // Version support
RAG bool `json:"rag,omitempty"` // RAG support
Args []interface{} `json:"args,omitempty"` // Arguments for call
SharedSpace plan.Space `json:"-"` // Shared space
Sid string `json:"sid" yaml:"-"` // Session ID
ChatID string `json:"chat_id,omitempty"` // Chat ID, use to select chat
AssistantID string `json:"assistant_id,omitempty"` // Assistant ID, use to select assistant
Stack string `json:"stack,omitempty"` // will be removed in the future
Path string `json:"pathname,omitempty"` // wiil be rename to path
FormData map[string]interface{} `json:"formdata,omitempty"`
Field *Field `json:"field,omitempty"`
Namespace string `json:"namespace,omitempty"`
Config map[string]interface{} `json:"config,omitempty"`
Signal interface{} `json:"signal,omitempty"`
Silent bool `json:"silent,omitempty"` // Silent mode
ClientType string `json:"client_type,omitempty"` // The request client type. SDK, Desktop, Web, JSSDK, etc. the default is Web
HistoryVisible bool `json:"history_visible,omitempty"` // History visible, default is true, if false, the history will not be displayed in the UI
Retry bool `json:"retry,omitempty"` // Retry mode
RetryTimes uint8 `json:"retry_times,omitempty"` // Retry times
Upload *FileUpload `json:"upload,omitempty"`
Version bool `json:"version,omitempty"` // Version support
RAG bool `json:"rag,omitempty"` // RAG support
Args []interface{} `json:"args,omitempty"` // Arguments for call
SharedSpace plan.Space `json:"-"` // Shared space
}
// Field the context field
@ -49,10 +51,59 @@ type FileUpload struct {
TempFile string `json:"temp_file,omitempty"`
}
const (
// ClientTypeAgent is the client type for Agent
ClientTypeAgent = "agent"
// ClientTypeWeb is the client type for Web (Default UI)
ClientTypeWeb = "web"
// ClientTypeSDK is the client type for SDK
ClientTypeSDK = "android"
// ClientTypeIOS is the client type for IOS
ClientTypeIOS = "ios"
// ClientTypeJSSDK is the client type for JSSDK
ClientTypeJSSDK = "jssdk"
// ClientTypeMacOS is the client type for MacOS Desktop
ClientTypeMacOS = "macos"
// ClientTypeWindows is the client type for Windows Desktop
ClientTypeWindows = "windows"
// ClientTypeLinux is the client type for Linux Desktop
ClientTypeLinux = "linux"
)
// SupportedClientTypes is the supported client types
var SupportedClientTypes = map[string]bool{
ClientTypeAgent: true,
ClientTypeWeb: true,
ClientTypeSDK: true,
ClientTypeIOS: true,
ClientTypeJSSDK: true,
ClientTypeMacOS: true,
ClientTypeWindows: true,
ClientTypeLinux: true,
}
// New create a new context
func New(sid, cid, payload string) Context {
ctx := Context{Context: context.Background(), Sid: sid, ChatID: cid, SharedSpace: plan.NewMemorySharedSpace()}
// Validate the client type
ctx := Context{
Context: context.Background(),
SharedSpace: plan.NewMemorySharedSpace(),
Sid: sid,
ChatID: cid,
HistoryVisible: true,
ClientType: ClientTypeWeb,
Silent: false,
}
if payload == "" {
return ctx
}
@ -77,6 +128,29 @@ func WithAssistantID(ctx Context, assistantID string) Context {
return ctx
}
// WithSilent set the silent mode
func WithSilent(ctx Context, silent bool) Context {
ctx.Silent = silent
return ctx
}
// WithClientType set the client type
func WithClientType(ctx Context, clientType string) Context {
// Validate the client type
if !SupportedClientTypes[clientType] {
log.Error("[Neo] Invalid client type: %s", clientType)
return ctx
}
ctx.ClientType = clientType
return ctx
}
// WithHistoryVisible set the history visible
func WithHistoryVisible(ctx Context, historyVisible bool) Context {
ctx.HistoryVisible = historyVisible
return ctx
}
// NewWithTimeout create a new context with timeout
func NewWithTimeout(sid, cid, payload string, timeout time.Duration) (Context, context.CancelFunc) {
ctx := New(sid, cid, payload)
@ -127,6 +201,12 @@ func (ctx *Context) Map() map[string]interface{} {
data["silent"] = ctx.Silent
}
// History visible
data["history_visible"] = ctx.HistoryVisible
// Client type
data["client_type"] = ctx.ClientType
// Retry mode
if ctx.Retry {
data["retry"] = ctx.Retry

View file

@ -15,7 +15,7 @@ import (
func (neo *DSL) HookCreate(ctx chatctx.Context, messages []map[string]interface{}, c *gin.Context) (CreateResponse, error) {
// Default assistant
assistantID := neo.Use
assistantID := neo.Use.Default
if ctx.AssistantID != "" {
assistantID = ctx.AssistantID
}

View file

@ -47,6 +47,21 @@ func Load(cfg config.Config) error {
setting.StoreSetting.MaxSize = 100
}
// Default Assistant
if setting.Use == nil {
setting.Use = &Use{Default: "neo"}
}
// Title Assistant
if setting.Use.Title == "" {
setting.Use.Title = setting.Use.Default
}
// Prompt Assistant
if setting.Use.Prompt == "" {
setting.Use.Prompt = setting.Use.Default
}
Neo = &setting
// Store Setting
@ -185,8 +200,8 @@ func initAssistant() error {
// defaultAssistant get the default assistant
func defaultAssistant() (*assistant.Assistant, error) {
if Neo.Use != "" {
return assistant.Get(Neo.Use)
if Neo.Use != nil && Neo.Use.Default != "" {
return assistant.Get(Neo.Use.Default)
}
name := Neo.Name

View file

@ -6,10 +6,8 @@ import (
"strings"
"github.com/gin-gonic/gin"
"github.com/yaoapp/kun/log"
"github.com/yaoapp/yao/neo/assistant"
chatctx "github.com/yaoapp/yao/neo/context"
"github.com/yaoapp/yao/neo/message"
)
// Answer reply the message
@ -34,237 +32,6 @@ func (neo *DSL) Select(id string) (assistant.API, error) {
return assistant.Get(id)
}
// GeneratePrompts generate prompts for the AI assistant
func (neo *DSL) GeneratePrompts(ctx chatctx.Context, input string, c *gin.Context, silent ...bool) (string, error) {
prompts := `
Optimize the prompts for the AI assistant
1. Optimize prompts based on the user's input
2. The prompts should be clear and specific
3. The prompts should be in the same language as the input
4. Keep the prompts concise but comprehensive
5. DO NOT ASK USER FOR MORE INFORMATION, JUST GENERATE PROMPTS
6. DO NOT ANSWER THE QUESTION, JUST GENERATE PROMPTS
`
isSilent := false
if len(silent) > 0 {
isSilent = silent[0]
}
return neo.GenerateWithAI(ctx, input, "prompts", prompts, c, isSilent)
}
// GenerateChatTitle generate the chat title
func (neo *DSL) GenerateChatTitle(ctx chatctx.Context, input string, c *gin.Context, silent ...bool) (string, error) {
prompts := `
Help me generate a title for the chat
1. The title should be a short and concise description of the chat.
2. The title should be a single sentence.
3. The title should be in same language as the chat.
4. The title should be no more than 50 characters.
5. ANSWER ONLY THE TITLE CONTENT, FOR EXAMPLE: Chat with AI is a valid title, but "Chat with AI" is not a valid title.
`
isSilent := false
if len(silent) > 0 {
isSilent = silent[0]
}
return neo.GenerateWithAI(ctx, input, "title", prompts, c, isSilent)
}
// GenerateWithAI generate content with AI, type can be "title", "prompts", etc.
func (neo *DSL) GenerateWithAI(ctx chatctx.Context, input string, messageType string, systemPrompt string, c *gin.Context, silent bool) (string, error) {
messages := []map[string]interface{}{
{"role": "system", "content": systemPrompt},
{
"role": "user",
"content": input,
"type": messageType,
"name": ctx.Sid,
},
}
res, err := neo.HookCreate(ctx, messages, c)
if err != nil {
return "", err
}
// Select Assistant
ast, err := neo.Select(res.AssistantID)
if err != nil {
return "", err
}
if ast == nil {
msg := message.New().Error("assistant is not initialized").Done()
msg.Write(c.Writer)
return "", fmt.Errorf("assistant is not initialized")
}
clientBreak := make(chan bool, 1)
done := make(chan bool, 1)
fail := make(chan error, 1)
contents := message.NewContents()
// Chat with AI in background
go func() {
msgList := []message.Message{}
for _, vv := range messages {
msg := message.New().Map(vv)
if content, ok := vv["content"].(string); ok {
msgs, err := message.NewContent(content)
if err == nil {
for _, v := range msgs {
v.AssistantID = msg.AssistantID
v.AssistantName = msg.AssistantName
v.AssistantAvatar = msg.AssistantAvatar
v.Role = msg.Role
v.Name = msg.Name
v.Mentions = msg.Mentions
msgList = append(msgList, v)
}
}
}
}
errorRaw := ""
isFirstThink := true
isThinking := false
currentMessageID := ""
tokenID := ""
beganAt := int64(0)
err := ast.Chat(c.Request.Context(), msgList, neo.Option, func(data []byte) int {
select {
case <-clientBreak:
return 0 // break
default:
msg := message.NewOpenAI(data, isThinking)
if msg == nil {
return 1 // continue
}
if msg.Pending {
errorRaw += msg.Text
return 1 // continue
}
// Handle error
if msg.Type == "error" {
fail <- fmt.Errorf("%s", msg.Text)
return 0 // break
}
// for api reasoning_content response
if msg.Type == "think" {
if isFirstThink {
msg.Text = "<think>\n" + msg.Text // add the think begin tag
isFirstThink = false
isThinking = true
}
}
// for api reasoning_content response
if isThinking && msg.Type != "think" {
// add the think close tag
end := message.New().Map(map[string]interface{}{"text": "\n</think>\n", "type": "think", "delta": true})
end.Write(c.Writer)
end.ID = currentMessageID
end.AppendTo(contents)
isThinking = false
// Clear the token and make a new line
contents.NewText([]byte{}, message.Extra{ID: currentMessageID})
contents.ClearToken(currentMessageID)
}
// Append content and send message
msg.AppendTo(contents)
// Scan the tokens
contents.ScanTokens(currentMessageID, tokenID, beganAt, func(params message.ScanCallbackParams) {
currentMessageID = params.MessageID
msg.ID = params.MessageID
msg.Type = params.Token
msg.Text = "" // clear the text
msg.Props = map[string]interface{}{"text": params.Text} // Update props
// End of the token clear the text
if params.Begin {
msg.Begin = beganAt
return
}
// End of the token clear the text
if params.End {
msg.End = params.EndAt
return
}
// New message with the tails
if params.Tails != "" {
newMsg, err := message.NewString(params.Tails, params.MessageID)
if err != nil {
return
}
msgList = append(msgList, *newMsg)
}
})
if !silent {
value := msg.String()
if value != "" {
message.New().
Map(map[string]interface{}{
"text": value,
"delta": true,
"done": msg.IsDone,
}).
Write(c.Writer)
}
}
// Complete the stream
if msg.IsDone {
value := msg.String()
if value == "" {
msg.Write(c.Writer)
}
done <- true
return 0 // break
}
return 1 // continue
}
})
if err != nil {
log.Error("Chat error: %s", err.Error())
if !silent {
message.New().Error(err).Done().Write(c.Writer)
}
}
if errorRaw != "" {
msg, err := message.NewStringError(errorRaw)
if err != nil {
log.Error("Error parsing error message: %s", err.Error())
}
msg.Write(c.Writer)
}
done <- true
}()
// Wait for completion or client disconnect
select {
case <-done:
return contents.Text(), nil
case err := <-fail:
return "", err
case <-c.Writer.CloseNotify():
clientBreak <- true
return "", nil
}
}
// Upload upload a file
func (neo *DSL) Upload(ctx chatctx.Context, c *gin.Context) (*assistant.File, error) {
// Get the file

View file

@ -607,6 +607,7 @@ func (conv *Xun) SaveHistory(sid string, messages []map[string]interface{}, cid
// Get silent flag from context
var silent bool = false
var historyVisible bool = true
if context != nil {
if silentVal, ok := context["silent"]; ok {
switch v := silentVal.(type) {
@ -620,6 +621,20 @@ func (conv *Xun) SaveHistory(sid string, messages []map[string]interface{}, cid
silent = v != 0
}
}
// Get history visible from context
if historyVisibleVal, ok := context["history_visible"]; ok {
switch v := historyVisibleVal.(type) {
case bool:
historyVisible = v
case string:
historyVisible = v == "true" || v == "1" || v == "yes"
case int:
historyVisible = v != 0
case float64:
historyVisible = v != 0
}
}
}
// First ensure chat record exists
@ -639,7 +654,7 @@ func (conv *Xun) SaveHistory(sid string, messages []map[string]interface{}, cid
"chat_id": cid,
"sid": userID,
"assistant_id": assistantID,
"silent": silent,
"silent": silent || historyVisible == false,
"created_at": time.Now(),
})
@ -653,7 +668,7 @@ func (conv *Xun) SaveHistory(sid string, messages []map[string]interface{}, cid
Where("sid", userID).
Update(map[string]interface{}{
"assistant_id": assistantID,
"silent": silent,
"silent": silent || historyVisible == false,
})
if err != nil {
return err

View file

@ -14,7 +14,7 @@ import (
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
Use *Use `json:"use,omitempty" yaml:"use,omitempty"` // Which assistant to use default, title, prompt
Guard string `json:"guard,omitempty" yaml:"guard,omitempty"`
Connector string `json:"connector" yaml:"connector"`
StoreSetting store.Setting `json:"store" yaml:"store"`
@ -34,6 +34,13 @@ type DSL struct {
GuardHandlers []gin.HandlerFunc `json:"-" yaml:"-"`
}
// Use the use setting for the assistant
type Use struct {
Default string `json:"default,omitempty" yaml:"default,omitempty"`
Title string `json:"title,omitempty" yaml:"title,omitempty"`
Prompt string `json:"prompt,omitempty" yaml:"prompt,omitempty"`
}
// VisionSetting the vision setting
type VisionSetting struct {
Storage driver.StorageConfig `json:"storage" yaml:"storage"`

View file

@ -59,6 +59,8 @@ class Agent {
private chat_id?: string;
private es: EventSource | null;
private context: Record<string, any>;
private silent?: boolean = false;
private history_visible?: boolean = false;
/**
* Agent constructor
@ -72,6 +74,28 @@ class Agent {
this.chat_id = option.chat_id;
this.es = null;
this.context = option.context || {};
// Set silent mode, default is true
if (option.silent !== undefined) {
this.silent =
option.silent === true ||
option.silent === "true" ||
option.silent === 1 ||
option.silent === "1"
? false
: true;
}
// Set history visible mode, default is false
if (option.history_visible !== undefined) {
this.history_visible =
option.history_visible === true ||
option.history_visible === "true" ||
option.history_visible === 1 ||
option.history_visible === "1"
? true
: false;
}
}
/**
@ -152,10 +176,12 @@ class Agent {
const contentRaw = encodeURIComponent(JSON.stringify(content));
const contextRaw = encodeURIComponent(JSON.stringify(context));
const token = this.token;
const silent = this.silent ? "true" : "false";
const history_visible = this.history_visible ? "true" : "false";
const chatId = this.chat_id || this.makeChatID();
const assistantParam = `&assistant_id=${this.assistant_id}`;
const status_endpoint = `${this.host}/status?content=${contentRaw}&context=${contextRaw}&token=${token}&chat_id=${chatId}${assistantParam}`;
const endpoint = `${this.host}?content=${contentRaw}&context=${contextRaw}&token=${token}&chat_id=${chatId}${assistantParam}`;
const endpoint = `${this.host}?client_type=jssdk&content=${contentRaw}&context=${contextRaw}&token=${token}&silent=${silent}&history_visible=${history_visible}&chat_id=${chatId}${assistantParam}`;
const handleError = async (error: any) => {
try {
@ -447,6 +473,8 @@ type AgentInput =
interface AgentOption {
host?: string;
token: string;
silent?: boolean | string | number;
history_visible?: boolean | string | number;
chat_id?: string;
context?: Record<string, any>;
}