Go: CLI chat with text, image, video (#14573)

### What problem does this PR solve?

```
RAGFlow(user)> chat with 'glm-4.6v-flash@test@zhipu-ai' message 'What are the pics talk about?' image 'https://cdn.bigmodel.cn/static/logo/register.png' 'https://cdn.bigmodel.cn/static/logo/api-key.png'
Answer: The first picture shows a login/register modal with options for phone number login, account login, and WeChat QR code login, along with a prompt for new users to get a 20 million tokens experience package. The second picture displays the API keys management page of a platform, including a warning about API key security and a table listing existing API keys with details like creation time and usage history.
Time: 31.600545
RAGFlow(user)> chat with 'glm-4.6v-flash@test@zhipu-ai' message 'What are the video talk about?' video 'https://cdn.bigmodel.cn/agent-demos/lark/113123.mov'
Answer: Based on the sequence of frames provided, the video is a demonstration of a web search and navigation process.

1.  The video starts with a blank Google search page.
2.  The user types "智谱" (which is the Chinese name for the company Zhipu AI) into the search box.
3.  The search is initiated and the page shows "About 0 results".
4.  The search results load, showing information about Zhipu AI, including its website.
5.  The user clicks on the main website link (www.zhipuai.cn).
6.  The video ends by showing the homepage of Zhipu AI's website, titled "Z.ai GLM Large Model Open Platform".

In summary, the video is about searching for the company "智谱" (Zhipu AI) on Google and then navigating to its official website.
Time: 76.582520
```

### Type of change

- [x] New Feature (non-breaking change which adds functionality)

Signed-off-by: Jin Hai <haijin.chn@gmail.com>
This commit is contained in:
Jin Hai
2026-05-05 18:14:39 +08:00
committed by GitHub
parent 24af0875e5
commit 3a51c27a75
4 changed files with 289 additions and 99 deletions

View File

@@ -19,8 +19,10 @@ package cli
import (
"bufio"
"context"
"encoding/base64"
"encoding/json"
"fmt"
netUrl "net/url"
"os"
ce "ragflow/internal/cli/filesystem"
"strings"
@@ -1514,6 +1516,14 @@ func (c *RAGFlowClient) EnableOrDisableModel(cmd *Command, status string) (Respo
return &result, nil
}
func isValidURL(str string) bool {
u, err := netUrl.Parse(str)
if err != nil {
return false
}
return u.Scheme != "" && u.Host != ""
}
func (c *RAGFlowClient) ChatToModel(cmd *Command) (ResponseIf, error) {
if c.ServerType != "user" {
return nil, fmt.Errorf("this command is only allowed in USER mode")
@@ -1539,7 +1549,102 @@ func (c *RAGFlowClient) ChatToModel(cmd *Command) (ResponseIf, error) {
return nil, fmt.Errorf("model name not provided and no current model set. Use 'use model' command first")
}
message := cmd.Params["message"].(string)
formattedMessages := []map[string]interface{}{}
messages, ok := cmd.Params["messages"].([]string)
if !ok {
return nil, fmt.Errorf("messages not provided")
}
contents := []map[string]interface{}{}
if len(messages) > 0 {
for _, message := range messages {
contents = append(contents, map[string]interface{}{
"type": "text",
"text": message,
})
}
}
images, ok := cmd.Params["images"].([]string)
if !ok {
return nil, fmt.Errorf("images not provided")
}
if len(images) > 0 {
for _, image := range images {
if isValidURL(image) {
contents = append(contents, map[string]interface{}{
"type": "image_url",
"image_url": map[string]string{
"url": image,
},
})
} else {
// image is a path, read the file and turn it into base64
imageContent, err := os.ReadFile(image)
if err != nil {
return nil, fmt.Errorf("failed to read image: %w", err)
}
contents = append(contents, map[string]interface{}{
"type": "image_file",
"image_file": map[string]interface{}{
"content": base64.StdEncoding.EncodeToString(imageContent),
},
})
}
}
}
videos, ok := cmd.Params["videos"].([]string)
if !ok {
return nil, fmt.Errorf("images not provided")
}
if len(videos) > 0 {
for _, video := range videos {
if isValidURL(video) {
contents = append(contents, map[string]interface{}{
"type": "video_url",
"video_url": map[string]interface{}{
"url": video,
},
})
} else {
return nil, fmt.Errorf("invalid video URL: %s", video)
}
}
}
//audios, ok := cmd.Params["audios"].([]string)
//if !ok {
// return nil, fmt.Errorf("images not provided")
//}
files, ok := cmd.Params["files"].([]string)
if !ok {
return nil, fmt.Errorf("images not provided")
}
if len(files) > 0 {
for _, file := range files {
if isValidURL(file) {
contents = append(contents, map[string]interface{}{
"type": "file_url",
"file_url": map[string]interface{}{
"url": file,
},
})
} else {
return nil, fmt.Errorf("invalid file URL: %s", file)
}
}
}
formattedText := map[string]interface{}{
"role": "user",
"content": contents,
}
formattedMessages = append(formattedMessages, formattedText)
thinking := cmd.Params["thinking"].(bool)
stream := cmd.Params["stream"].(bool)
effort := cmd.Params["effort"].(string)
@@ -1547,26 +1652,26 @@ func (c *RAGFlowClient) ChatToModel(cmd *Command) (ResponseIf, error) {
url := "/chat/completions"
message = strings.TrimSpace(message)
var content interface{} = message
if strings.HasPrefix(message, "[") && strings.HasSuffix(message, "]") {
var parts []map[string]interface{}
if err := json.Unmarshal([]byte(message), &parts); err == nil {
content = parts
}
}
formattedMessage := []map[string]interface{}{
{
"role": "user",
"content": content,
},
}
//message = strings.TrimSpace(message)
//var content interface{} = message
//if strings.HasPrefix(message, "[") && strings.HasSuffix(message, "]") {
// var parts []map[string]interface{}
// if err := json.Unmarshal([]byte(message), &parts); err == nil {
// content = parts
// }
//}
//formattedMessage := []map[string]interface{}{
// {
// "role": "user",
// "content": content,
// },
//}
payload := map[string]interface{}{
"provider_name": providerName,
"instance_name": instanceName,
"model_name": modelName,
"messages": formattedMessage,
"messages": formattedMessages,
"stream": stream,
"thinking": thinking,
}