mirror of
https://github.com/infiniflow/ragflow.git
synced 2026-05-20 00:07:00 +08:00
### What problem does this PR solve? Now each model support region with different URL ### Type of change - [x] New Feature (non-breaking change which adds functionality) --------- Signed-off-by: Jin Hai <haijin.chn@gmail.com>
530 lines
14 KiB
Go
530 lines
14 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
|
|
}
|
|
|
|
func (c *RAGFlowClient) SetDefaultModel(cmd *Command) (ResponseIf, error) {
|
|
|
|
modelType, ok := cmd.Params["model_type"].(string)
|
|
if !ok {
|
|
return nil, fmt.Errorf("model_type not provided")
|
|
}
|
|
|
|
compositeModelName, ok := cmd.Params["composite_model_name"].(string)
|
|
if !ok {
|
|
return nil, fmt.Errorf("model_name not provided")
|
|
}
|
|
|
|
var providerName, instanceName, modelName string
|
|
names := strings.Split(compositeModelName, "/")
|
|
if len(names) != 3 {
|
|
return nil, fmt.Errorf("model name must be in format 'provider/instance/model'")
|
|
}
|
|
providerName = names[0]
|
|
instanceName = names[1]
|
|
modelName = names[2]
|
|
|
|
payload := map[string]interface{}{
|
|
"model_type": modelType,
|
|
"model_provider": providerName,
|
|
"model_instance": instanceName,
|
|
"model_name": modelName,
|
|
}
|
|
|
|
resp, err := c.HTTPClient.Request("PATCH", "/models", true, "web", nil, payload)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("failed to set default model: %w", err)
|
|
}
|
|
|
|
if resp.StatusCode != 200 {
|
|
return nil, fmt.Errorf("failed to set default model: HTTP %d, body: %s", resp.StatusCode, string(resp.Body))
|
|
}
|
|
|
|
var result SimpleResponse
|
|
if err = json.Unmarshal(resp.Body, &result); err != nil {
|
|
return nil, fmt.Errorf("failed to set default model: 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) ResetDefaultModel(cmd *Command) (ResponseIf, error) {
|
|
|
|
modelType, ok := cmd.Params["model_type"].(string)
|
|
if !ok {
|
|
return nil, fmt.Errorf("model_type not provided")
|
|
}
|
|
|
|
payload := map[string]interface{}{
|
|
"model_type": modelType,
|
|
}
|
|
|
|
resp, err := c.HTTPClient.Request("PATCH", "/models", true, "web", nil, payload)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("failed to reset default model: %w", err)
|
|
}
|
|
|
|
if resp.StatusCode != 200 {
|
|
return nil, fmt.Errorf("failed to reset default model: HTTP %d, body: %s", resp.StatusCode, string(resp.Body))
|
|
}
|
|
|
|
var result SimpleResponse
|
|
if err = json.Unmarshal(resp.Body, &result); err != nil {
|
|
return nil, fmt.Errorf("failed to reset default model: 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) ListDefaultModels(cmd *Command) (ResponseIf, error) {
|
|
resp, err := c.HTTPClient.Request("GET", "/models", true, "web", nil, nil)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("failed to list default models: %w", err)
|
|
}
|
|
|
|
if resp.StatusCode != 200 {
|
|
return nil, fmt.Errorf("failed to list default 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 default models: 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
|
|
}
|
|
|
|
// FlattenMap recursively flattens a nested map into dot-notation keys
|
|
func FlattenMap(data map[string]interface{}, prefix string, result *[]map[string]interface{}) {
|
|
for key, value := range data {
|
|
// Build the current key path
|
|
currentKey := key
|
|
if prefix != "" {
|
|
currentKey = prefix + "." + key
|
|
}
|
|
|
|
// Check if the value is another nested map
|
|
if nestedMap, ok := value.(map[string]interface{}); ok {
|
|
// Recursively process the nested map
|
|
FlattenMap(nestedMap, currentKey, result)
|
|
} else {
|
|
// Leaf node: append to result slice
|
|
resultItem := map[string]interface{}{
|
|
"key": currentKey,
|
|
"value": value,
|
|
}
|
|
*result = append(*result, resultItem)
|
|
}
|
|
}
|
|
}
|