diff --git a/neo/api.go b/neo/api.go index 8dad44c6..c830f1a0 100644 --- a/neo/api.go +++ b/neo/api.go @@ -338,7 +338,7 @@ func (neo *DSL) handleChatDetail(c *gin.Context) { return } - c.JSON(200, chat) + c.JSON(200, map[string]interface{}{"data": chat}) c.Done() } @@ -382,7 +382,8 @@ func (neo *DSL) handleChatUpdate(c *gin.Context) { // Get title from request body var body struct { - Title string `json:"title"` + Title string `json:"title"` + Content string `json:"content"` } if err := c.BindJSON(&body); err != nil { c.JSON(400, gin.H{"message": "invalid request body", "code": 400}) @@ -390,6 +391,20 @@ func (neo *DSL) handleChatUpdate(c *gin.Context) { return } + // If content is not empty, Generate the chat title + if body.Content != "" { + ctx, cancel := NewContextWithCancel(sid, c.Query("chat_id"), "") + defer cancel() + + title, err := neo.GenerateChatTitle(ctx, body.Content, c) + 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() @@ -403,6 +418,6 @@ func (neo *DSL) handleChatUpdate(c *gin.Context) { return } - c.JSON(200, gin.H{"message": "success"}) + c.JSON(200, gin.H{"message": "ok", "title": body.Title, "chat_id": chatID}) c.Done() } diff --git a/neo/neo.go b/neo/neo.go index affdb80c..73b1aa3f 100644 --- a/neo/neo.go +++ b/neo/neo.go @@ -58,6 +58,102 @@ func (neo *DSL) GetMentions(keywords string) ([]Mention, error) { return neo.HookMention(context.Background(), keywords) } +// GenerateChatTitle generate the chat title +func (neo *DSL) GenerateChatTitle(ctx Context, input string, c *gin.Context) (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. + ` + + messages := []map[string]interface{}{ + {"role": "system", "content": prompts}, + {"role": "user", "content": input}, + } + + res, err := neo.HookCreate(ctx, messages, c) + if err != nil { + return "", err + } + + // Select Assistant + ast, err := neo.selectAssistant(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) + + content := []byte{} + + // Chat with AI in background + go func() { + err := ast.Chat(c.Request.Context(), messages, neo.Option, func(data []byte) int { + select { + case <-clientBreak: + return 0 // break + + default: + msg := message.NewOpenAI(data) + if msg == nil { + return 1 // continue + } + + // Handle error + if msg.Type == "error" { + fail <- fmt.Errorf("%s", msg.Message.Text) + return 0 // break + } + + // Append content and send message + content = msg.Append(content) + + // Complete the stream + if msg.Message.Done { + done <- true + return 0 // break + } + + return 1 // continue + } + }) + + if err != nil { + log.Error("Chat error: %s", err.Error()) + message.New().Error(err).Done().Write(c.Writer) + } + + // Save chat history + if len(content) > 0 { + neo.saveHistory(ctx.Sid, ctx.ChatID, content, messages) + } + + done <- true + }() + + // Wait for completion or client disconnect + select { + case <-done: + return string(content), nil + case err := <-fail: + return "", err + case <-c.Writer.CloseNotify(): + clientBreak <- true + return "", nil + } +} + // Upload upload a file func (neo *DSL) Upload(ctx Context, c *gin.Context) (*assistant.File, error) { // Get the file