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' // curl -X GET 'http://localhost:5099/api/__yao/neo/mentions?keywords=assistant&token=xxx'
router.GET(path+"/mentions", append(middlewares, neo.handleMentions)...) 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: // 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 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' \ // 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) 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) err := neo.Answer(ctx, content, c)
// Error handling // Error handling
@ -485,7 +494,7 @@ func (neo *DSL) handleChatLatest(c *gin.Context) {
// 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 {
assistantID := neo.Use assistantID := neo.Use.Default
queryAssistantID := c.Query("assistant_id") queryAssistantID := c.Query("assistant_id")
if queryAssistantID != "" { if queryAssistantID != "" {
assistantID = queryAssistantID assistantID = queryAssistantID
@ -504,7 +513,7 @@ func (neo *DSL) handleChatLatest(c *gin.Context) {
"assistant_id": ast.ID, "assistant_id": ast.ID,
"assistant_name": ast.Name, "assistant_name": ast.Name,
"assistant_avatar": ast.Avatar, "assistant_avatar": ast.Avatar,
"assistant_deleteable": neo.Use != ast.ID, "assistant_deleteable": neo.Use.Default != ast.ID,
}}) }})
c.Done() c.Done()
return return
@ -530,7 +539,7 @@ func (neo *DSL) handleChatLatest(c *gin.Context) {
chat.Chat["assistant_id"] = neo.Use chat.Chat["assistant_id"] = neo.Use
// Get the assistant info // Get the assistant info
ast, err := assistant.Get(neo.Use) ast, err := assistant.Get(neo.Use.Default)
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()
@ -573,7 +582,7 @@ func (neo *DSL) handleChatDetail(c *gin.Context) {
chat.Chat["assistant_id"] = neo.Use chat.Chat["assistant_id"] = neo.Use
// Get the assistant info // Get the assistant info
ast, err := assistant.Get(neo.Use) ast, err := assistant.Get(neo.Use.Default)
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()
@ -583,7 +592,7 @@ func (neo *DSL) handleChatDetail(c *gin.Context) {
chat.Chat["assistant_avatar"] = ast.Avatar 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.JSON(200, map[string]interface{}{"data": chat})
c.Done() c.Done()
} }
@ -659,20 +668,6 @@ func (neo *DSL) handleChatUpdate(c *gin.Context) {
return 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 == "" { if body.Title == "" {
c.JSON(400, gin.H{"message": "title is required", "code": 400}) c.JSON(400, gin.H{"message": "title is required", "code": 400})
c.Done() c.Done()
@ -813,155 +808,81 @@ func (r *generateResponse) send(key string) {
// handleGenerateTitle handles generating a chat title // handleGenerateTitle handles generating a chat title
func (neo *DSL) handleGenerateTitle(c *gin.Context) { func (neo *DSL) handleGenerateTitle(c *gin.Context) {
var content string // Set headers for SSE
if c.Request.Method == "GET" { c.Header("Content-Type", "text/event-stream;charset=utf-8")
content = c.Query("content") c.Header("Cache-Control", "no-cache")
} else { c.Header("Connection", "keep-alive")
var body struct {
Content string `json:"content"` sid := c.GetString("__sid")
} if sid == "" {
if err := c.BindJSON(&body); err != nil { sid = uuid.New().String()
// 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
} }
resp := &generateResponse{ content := c.Query("content")
c: c, if content == "" {
sid: c.GetString("__sid"), msg := message.New().Error("content is required").Done()
content: content, msg.Write(c.Writer)
}
// 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() {
return return
} }
ctx, cancel := chatctx.NewWithCancel(resp.sid, c.Query("chat_id"), "") chatID := fmt.Sprintf("generate_title_%d", time.Now().UnixNano())
defer cancel()
// Use silent mode for regular HTTP requests, streaming for SSE // Set the context with validated chat_id
silent := !strings.Contains(c.GetHeader("Accept"), "text/event-stream") ctx, cancel := chatctx.NewWithCancel(sid, chatID, c.Query("context"))
resp.result, resp.err = neo.GenerateChatTitle(ctx, resp.content, c, silent) defer cancel()
resp.send("result") 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 // handleGeneratePrompts handles generating prompts
func (neo *DSL) handleGeneratePrompts(c *gin.Context) { func (neo *DSL) handleGeneratePrompts(c *gin.Context) {
var content string // Set headers for SSE
if c.Request.Method == "GET" { c.Header("Content-Type", "text/event-stream;charset=utf-8")
content = c.Query("content") c.Header("Cache-Control", "no-cache")
} else { c.Header("Connection", "keep-alive")
var body struct {
Content string `json:"content"` sid := c.GetString("__sid")
} if sid == "" {
if err := c.BindJSON(&body); err != nil { sid = uuid.New().String()
// 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
} }
resp := &generateResponse{ content := c.Query("content")
c: c, if content == "" {
sid: c.GetString("__sid"), msg := message.New().Error("content is required").Done()
content: content, msg.Write(c.Writer)
}
// 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() {
return 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 cancel()
defer ctx.Release() // Release the context after the request is done
// Use silent mode for regular HTTP requests, streaming for SSE // Set the assistant ID
silent := !strings.Contains(c.GetHeader("Accept"), "text/event-stream") ctx = chatctx.WithHistoryVisible(ctx, false)
resp.result, resp.err = neo.GeneratePrompts(ctx, resp.content, c, silent) ctx = chatctx.WithAssistantID(ctx, neo.Use.Prompt)
resp.send("result") err := neo.Answer(ctx, content, c)
}
// handleGenerateCustom handles generating custom content // Error handling
func (neo *DSL) handleGenerateCustom(c *gin.Context) { if err != nil {
var content, genType, systemPrompt string message.New().Done().Error(err).Write(c.Writer)
c.Done()
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() {
return 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 // handleAssistantList handles listing assistants

View file

@ -242,7 +242,8 @@ func (obj *objectCall) run(info *v8go.FunctionCallbackInfo) *v8go.Value {
chatCtx.AssistantID = assistantID chatCtx.AssistantID = assistantID
chatCtx.ChatID = fmt.Sprintf("call_%s", uuid.New().String()) // New chat id chatCtx.ChatID = fmt.Sprintf("call_%s", uuid.New().String()) // New chat id
chatCtx.Silent = options.Silent 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 // Define the callback function
var cb func(msg *chatMessage.Message) = nil var cb func(msg *chatMessage.Message) = nil

View file

@ -12,24 +12,26 @@ import (
// Context the context // Context the context
type Context struct { type Context struct {
context.Context context.Context
Sid string `json:"sid" yaml:"-"` // Session ID Sid string `json:"sid" yaml:"-"` // Session ID
ChatID string `json:"chat_id,omitempty"` // Chat ID, use to select chat ChatID string `json:"chat_id,omitempty"` // Chat ID, use to select chat
AssistantID string `json:"assistant_id,omitempty"` // Assistant ID, use to select assistant AssistantID string `json:"assistant_id,omitempty"` // Assistant ID, use to select assistant
Stack string `json:"stack,omitempty"` // will be removed in the future Stack string `json:"stack,omitempty"` // will be removed in the future
Path string `json:"pathname,omitempty"` // wiil be rename to path Path string `json:"pathname,omitempty"` // wiil be rename to path
FormData map[string]interface{} `json:"formdata,omitempty"` FormData map[string]interface{} `json:"formdata,omitempty"`
Field *Field `json:"field,omitempty"` Field *Field `json:"field,omitempty"`
Namespace string `json:"namespace,omitempty"` Namespace string `json:"namespace,omitempty"`
Config map[string]interface{} `json:"config,omitempty"` Config map[string]interface{} `json:"config,omitempty"`
Signal interface{} `json:"signal,omitempty"` Signal interface{} `json:"signal,omitempty"`
Silent bool `json:"silent,omitempty"` // Silent mode Silent bool `json:"silent,omitempty"` // Silent mode
Retry bool `json:"retry,omitempty"` // Retry mode ClientType string `json:"client_type,omitempty"` // The request client type. SDK, Desktop, Web, JSSDK, etc. the default is Web
RetryTimes uint8 `json:"retry_times,omitempty"` // Retry times HistoryVisible bool `json:"history_visible,omitempty"` // History visible, default is true, if false, the history will not be displayed in the UI
Upload *FileUpload `json:"upload,omitempty"` Retry bool `json:"retry,omitempty"` // Retry mode
Version bool `json:"version,omitempty"` // Version support RetryTimes uint8 `json:"retry_times,omitempty"` // Retry times
RAG bool `json:"rag,omitempty"` // RAG support Upload *FileUpload `json:"upload,omitempty"`
Args []interface{} `json:"args,omitempty"` // Arguments for call Version bool `json:"version,omitempty"` // Version support
SharedSpace plan.Space `json:"-"` // Shared space 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 // Field the context field
@ -49,10 +51,59 @@ type FileUpload struct {
TempFile string `json:"temp_file,omitempty"` 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 // New create a new context
func New(sid, cid, payload string) 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 == "" { if payload == "" {
return ctx return ctx
} }
@ -77,6 +128,29 @@ func WithAssistantID(ctx Context, assistantID string) Context {
return ctx 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 // NewWithTimeout create a new context with timeout
func NewWithTimeout(sid, cid, payload string, timeout time.Duration) (Context, context.CancelFunc) { func NewWithTimeout(sid, cid, payload string, timeout time.Duration) (Context, context.CancelFunc) {
ctx := New(sid, cid, payload) ctx := New(sid, cid, payload)
@ -127,6 +201,12 @@ func (ctx *Context) Map() map[string]interface{} {
data["silent"] = ctx.Silent data["silent"] = ctx.Silent
} }
// History visible
data["history_visible"] = ctx.HistoryVisible
// Client type
data["client_type"] = ctx.ClientType
// Retry mode // Retry mode
if ctx.Retry { if ctx.Retry {
data["retry"] = 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) { func (neo *DSL) HookCreate(ctx chatctx.Context, messages []map[string]interface{}, c *gin.Context) (CreateResponse, error) {
// Default assistant // Default assistant
assistantID := neo.Use assistantID := neo.Use.Default
if ctx.AssistantID != "" { if ctx.AssistantID != "" {
assistantID = ctx.AssistantID assistantID = ctx.AssistantID
} }

View file

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

View file

@ -6,10 +6,8 @@ import (
"strings" "strings"
"github.com/gin-gonic/gin" "github.com/gin-gonic/gin"
"github.com/yaoapp/kun/log"
"github.com/yaoapp/yao/neo/assistant" "github.com/yaoapp/yao/neo/assistant"
chatctx "github.com/yaoapp/yao/neo/context" chatctx "github.com/yaoapp/yao/neo/context"
"github.com/yaoapp/yao/neo/message"
) )
// Answer reply the message // Answer reply the message
@ -34,237 +32,6 @@ func (neo *DSL) Select(id string) (assistant.API, error) {
return assistant.Get(id) 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 // Upload upload a file
func (neo *DSL) Upload(ctx chatctx.Context, c *gin.Context) (*assistant.File, error) { func (neo *DSL) Upload(ctx chatctx.Context, c *gin.Context) (*assistant.File, error) {
// Get the file // 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 // Get silent flag from context
var silent bool = false var silent bool = false
var historyVisible bool = true
if context != nil { if context != nil {
if silentVal, ok := context["silent"]; ok { if silentVal, ok := context["silent"]; ok {
switch v := silentVal.(type) { switch v := silentVal.(type) {
@ -620,6 +621,20 @@ func (conv *Xun) SaveHistory(sid string, messages []map[string]interface{}, cid
silent = v != 0 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 // First ensure chat record exists
@ -639,7 +654,7 @@ func (conv *Xun) SaveHistory(sid string, messages []map[string]interface{}, cid
"chat_id": cid, "chat_id": cid,
"sid": userID, "sid": userID,
"assistant_id": assistantID, "assistant_id": assistantID,
"silent": silent, "silent": silent || historyVisible == false,
"created_at": time.Now(), "created_at": time.Now(),
}) })
@ -653,7 +668,7 @@ func (conv *Xun) SaveHistory(sid string, messages []map[string]interface{}, cid
Where("sid", userID). Where("sid", userID).
Update(map[string]interface{}{ Update(map[string]interface{}{
"assistant_id": assistantID, "assistant_id": assistantID,
"silent": silent, "silent": silent || historyVisible == false,
}) })
if err != nil { if err != nil {
return err return err

View file

@ -14,7 +14,7 @@ import (
type DSL struct { type DSL struct {
ID string `json:"-" yaml:"-"` ID string `json:"-" yaml:"-"`
Name string `json:"name,omitempty" yaml:"name,omitempty"` 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"` Guard string `json:"guard,omitempty" yaml:"guard,omitempty"`
Connector string `json:"connector" yaml:"connector"` Connector string `json:"connector" yaml:"connector"`
StoreSetting store.Setting `json:"store" yaml:"store"` StoreSetting store.Setting `json:"store" yaml:"store"`
@ -34,6 +34,13 @@ type DSL struct {
GuardHandlers []gin.HandlerFunc `json:"-" yaml:"-"` 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 // VisionSetting the vision setting
type VisionSetting struct { type VisionSetting struct {
Storage driver.StorageConfig `json:"storage" yaml:"storage"` Storage driver.StorageConfig `json:"storage" yaml:"storage"`

View file

@ -59,6 +59,8 @@ class Agent {
private chat_id?: string; private chat_id?: string;
private es: EventSource | null; private es: EventSource | null;
private context: Record<string, any>; private context: Record<string, any>;
private silent?: boolean = false;
private history_visible?: boolean = false;
/** /**
* Agent constructor * Agent constructor
@ -72,6 +74,28 @@ class Agent {
this.chat_id = option.chat_id; this.chat_id = option.chat_id;
this.es = null; this.es = null;
this.context = option.context || {}; 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 contentRaw = encodeURIComponent(JSON.stringify(content));
const contextRaw = encodeURIComponent(JSON.stringify(context)); const contextRaw = encodeURIComponent(JSON.stringify(context));
const token = this.token; 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 chatId = this.chat_id || this.makeChatID();
const assistantParam = `&assistant_id=${this.assistant_id}`; const assistantParam = `&assistant_id=${this.assistant_id}`;
const status_endpoint = `${this.host}/status?content=${contentRaw}&context=${contextRaw}&token=${token}&chat_id=${chatId}${assistantParam}`; 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) => { const handleError = async (error: any) => {
try { try {
@ -447,6 +473,8 @@ type AgentInput =
interface AgentOption { interface AgentOption {
host?: string; host?: string;
token: string; token: string;
silent?: boolean | string | number;
history_visible?: boolean | string | number;
chat_id?: string; chat_id?: string;
context?: Record<string, any>; context?: Record<string, any>;
} }