Files
ragflow/internal/cli/common_command.go
Jin Hai 6d9430a125 Add think chat to CLI (#13922)
### What problem does this PR solve?

Now user can use 'think mode' to chat with LLM

### Type of change

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

---------

Signed-off-by: Jin Hai <haijin.chn@gmail.com>
2026-04-03 18:11:23 +08:00

403 lines
11 KiB
Go

//
// Copyright 2026 The InfiniFlow Authors. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
//
package cli
import (
"bufio"
"encoding/json"
"fmt"
"os"
"strings"
"golang.org/x/term"
)
// LoginUserInteractive performs interactive login with username and password
func (c *RAGFlowClient) LoginUserInteractive(username, password string) error {
// First, ping the server to check if it's available
// For admin mode, use /admin/ping with useAPIBase=true
// For user mode, use /system/ping with useAPIBase=false
var pingPath string
var useAPIBase bool
if c.ServerType == "admin" {
pingPath = "/admin/ping"
useAPIBase = true
} else {
pingPath = "/system/ping"
useAPIBase = false
}
resp, err := c.HTTPClient.Request("GET", pingPath, useAPIBase, "web", nil, nil)
if err != nil {
fmt.Printf("Error: %v\n", err)
fmt.Println("Can't access server for login (connection failed)")
return err
}
if resp.StatusCode != 200 {
fmt.Println("Server is down")
return fmt.Errorf("server is down")
}
// Check response - admin returns JSON with message "PONG", user returns plain "pong"
resJSON, err := resp.JSON()
if err == nil {
// Admin mode returns {"code":0,"message":"PONG"}
if msg, ok := resJSON["message"].(string); !ok || msg != "pong" {
fmt.Println("Server is down")
return fmt.Errorf("server is down")
}
} else {
// User mode returns plain "pong"
if string(resp.Body) != "pong" {
fmt.Println("Server is down")
return fmt.Errorf("server is down")
}
}
// If password is not provided, prompt for it
if password == "" {
fmt.Printf("password for %s: ", username)
var err error
password, err = readPassword()
if err != nil {
return fmt.Errorf("failed to read password: %w", err)
}
password = strings.TrimSpace(password)
}
// Login
token, err := c.loginUser(username, password)
if err != nil {
fmt.Printf("Error: %v\n", err)
fmt.Println("Can't access server for login (connection failed)")
return err
}
c.HTTPClient.LoginToken = token
fmt.Printf("Login user %s successfully\n", username)
return nil
}
// LoginUser performs user login
func (c *RAGFlowClient) LoginUser(cmd *Command) error {
// First, ping the server to check if it's available
// For admin mode, use /admin/ping with useAPIBase=true
// For user mode, use /system/ping with useAPIBase=false
var pingPath string
var useAPIBase bool
if c.ServerType == "admin" {
pingPath = "/admin/ping"
useAPIBase = true
} else {
pingPath = "/system/ping"
useAPIBase = false
}
resp, err := c.HTTPClient.Request("GET", pingPath, useAPIBase, "web", nil, nil)
if err != nil {
fmt.Printf("Error: %v\n", err)
fmt.Println("Can't access server for login (connection failed)")
return err
}
if resp.StatusCode != 200 {
fmt.Println("Server is down")
return fmt.Errorf("server is down")
}
// Check response - admin returns JSON with message "PONG", user returns plain "pong"
resJSON, err := resp.JSON()
if err == nil {
// Admin mode returns {"code":0,"message":"PONG"}
if msg, ok := resJSON["message"].(string); !ok || msg != "pong" {
fmt.Println("Server is down")
return fmt.Errorf("server is down")
}
} else {
// User mode returns plain "pong"
if string(resp.Body) != "pong" {
fmt.Println("Server is down")
return fmt.Errorf("server is down")
}
}
email, ok := cmd.Params["email"].(string)
if !ok {
return fmt.Errorf("email not provided")
}
password, ok := cmd.Params["password"].(string)
if !ok {
// Get password from user input (hidden)
fmt.Printf("password for %s: ", email)
password, err = readPassword()
if err != nil {
return fmt.Errorf("failed to read password: %w", err)
}
password = strings.TrimSpace(password)
}
// Login
token, err := c.loginUser(email, password)
if err != nil {
fmt.Printf("Error: %v\n", err)
fmt.Println("Can't access server for login (connection failed)")
return err
}
c.HTTPClient.LoginToken = token
fmt.Printf("Login user %s successfully\n", email)
return nil
}
// loginUser performs the actual login request
func (c *RAGFlowClient) loginUser(email, password string) (string, error) {
// Encrypt password using scrypt (same as Python implementation)
encryptedPassword, err := EncryptPassword(password)
if err != nil {
return "", fmt.Errorf("failed to encrypt password: %w", err)
}
payload := map[string]interface{}{
"email": email,
"password": encryptedPassword,
}
var path string
if c.ServerType == "admin" {
path = "/admin/login"
} else {
path = "/user/login"
}
resp, err := c.HTTPClient.Request("POST", path, c.ServerType == "admin", "", nil, payload)
if err != nil {
return "", err
}
var result SimpleResponse
if err = json.Unmarshal(resp.Body, &result); err != nil {
return "", fmt.Errorf("login failed: invalid JSON (%w)", err)
}
if result.Code != 0 {
return "", fmt.Errorf("login failed: %s", result.Message)
}
token := resp.Headers.Get("Authorization")
if token == "" {
return "", fmt.Errorf("login failed: missing Authorization header")
}
return token, nil
}
func (c *RAGFlowClient) Logout() (ResponseIf, error) {
if c.HTTPClient.LoginToken == "" {
return nil, fmt.Errorf("not logged in")
}
var path string
if c.ServerType == "admin" {
path = "/admin/logout"
} else {
path = "/user/logout"
}
resp, err := c.HTTPClient.Request("GET", path, c.ServerType == "admin", "web", nil, nil)
if err != nil {
return nil, err
}
var result SimpleResponse
if err = json.Unmarshal(resp.Body, &result); err != nil {
return nil, fmt.Errorf("login failed: invalid JSON (%w)", err)
}
if result.Code != 0 {
return nil, fmt.Errorf("login failed: %s", result.Message)
}
return &result, nil
}
func (c *RAGFlowClient) ListAvailableProviders(cmd *Command) (ResponseIf, error) {
var endPoint string
if c.ServerType == "admin" {
endPoint = fmt.Sprintf("/admin/providers?available=true")
} else {
endPoint = fmt.Sprintf("/providers?available=true")
}
resp, err := c.HTTPClient.Request("GET", endPoint, true, "web", nil, nil)
if err != nil {
return nil, fmt.Errorf("failed to list providers: %w", err)
}
if resp.StatusCode != 200 {
return nil, fmt.Errorf("failed to list providers: HTTP %d, body: %s", resp.StatusCode, string(resp.Body))
}
var result CommonResponse
if err = json.Unmarshal(resp.Body, &result); err != nil {
return nil, fmt.Errorf("failed to list providers: invalid JSON (%w)", err)
}
if result.Code != 0 {
return nil, fmt.Errorf("%s", result.Message)
}
result.Duration = resp.Duration
return &result, nil
}
func (c *RAGFlowClient) ShowProvider(cmd *Command) (ResponseIf, error) {
providerName, ok := cmd.Params["provider_name"].(string)
if !ok {
return nil, fmt.Errorf("provider_name not provided")
}
var endPoint string
if c.ServerType == "admin" {
endPoint = fmt.Sprintf("/admin/providers/%s", providerName)
} else {
endPoint = fmt.Sprintf("/providers/%s", providerName)
}
resp, err := c.HTTPClient.Request("GET", endPoint, true, "web", nil, nil)
if err != nil {
return nil, fmt.Errorf("failed to show provider: %w", err)
}
if resp.StatusCode != 200 {
return nil, fmt.Errorf("failed to show provider: HTTP %d, body: %s", resp.StatusCode, string(resp.Body))
}
var result CommonDataResponse
if err = json.Unmarshal(resp.Body, &result); err != nil {
return nil, fmt.Errorf("failed to show provider: invalid JSON (%w)", err)
}
if result.Code != 0 {
return nil, fmt.Errorf("%s", result.Message)
}
result.Duration = resp.Duration
return &result, nil
}
func (c *RAGFlowClient) ListModels(cmd *Command) (ResponseIf, error) {
providerName, ok := cmd.Params["provider_name"].(string)
if !ok {
return nil, fmt.Errorf("provider_name not provided")
}
var endPoint string
if c.ServerType == "admin" {
endPoint = fmt.Sprintf("/admin/providers/%s/models", providerName)
} else {
endPoint = fmt.Sprintf("/providers/%s/models", providerName)
}
resp, err := c.HTTPClient.Request("GET", endPoint, true, "web", nil, nil)
if err != nil {
return nil, fmt.Errorf("failed to list models: %w", err)
}
if resp.StatusCode != 200 {
return nil, fmt.Errorf("failed to list models: HTTP %d, body: %s", resp.StatusCode, string(resp.Body))
}
var result CommonResponse
if err = json.Unmarshal(resp.Body, &result); err != nil {
return nil, fmt.Errorf("failed to list models: invalid JSON (%w)", err)
}
if result.Code != 0 {
return nil, fmt.Errorf("%s", result.Message)
}
result.Duration = resp.Duration
return &result, nil
}
func (c *RAGFlowClient) ShowModel(cmd *Command) (ResponseIf, error) {
providerName, ok := cmd.Params["provider_name"].(string)
if !ok {
return nil, fmt.Errorf("provider_name not provided")
}
modelName, ok := cmd.Params["model_name"].(string)
if !ok {
return nil, fmt.Errorf("model_name not provided")
}
var endPoint string
if c.ServerType == "admin" {
endPoint = fmt.Sprintf("/admin/providers/%s/models/%s", providerName, modelName)
} else {
endPoint = fmt.Sprintf("/providers/%s/models/%s", providerName, modelName)
}
resp, err := c.HTTPClient.Request("GET", endPoint, true, "web", nil, nil)
if err != nil {
return nil, fmt.Errorf("failed to show model: %w", err)
}
if resp.StatusCode != 200 {
return nil, fmt.Errorf("failed to show model: HTTP %d, body: %s", resp.StatusCode, string(resp.Body))
}
var result CommonDataResponse
if err = json.Unmarshal(resp.Body, &result); err != nil {
return nil, fmt.Errorf("failed to show model: invalid JSON (%w)", err)
}
if result.Code != 0 {
return nil, fmt.Errorf("%s", result.Message)
}
result.Duration = resp.Duration
return &result, nil
}
// readPassword reads password from terminal without echoing
func readPassword() (string, error) {
if !term.IsTerminal(int(os.Stdin.Fd())) {
return readPasswordFallback()
}
fmt.Print("Password: ")
passwordBytes, err := term.ReadPassword(int(os.Stdin.Fd()))
fmt.Println()
if err != nil {
return "", err
}
return strings.TrimSpace(string(passwordBytes)), nil
}
// readPasswordFallback reads password as plain text (fallback mode)
func readPasswordFallback() (string, error) {
fmt.Print("Password (will be visible): ")
reader := bufio.NewReader(os.Stdin)
password, err := reader.ReadString('\n')
if err != nil {
return "", err
}
return strings.TrimSpace(password), nil
}