diff --git a/cmd/ragflow_cli.go b/cmd/ragflow_cli.go index f4e33c1e7c..1ac3c84e86 100644 --- a/cmd/ragflow_cli.go +++ b/cmd/ragflow_cli.go @@ -26,57 +26,109 @@ import ( "ragflow/internal/cli" ) -func main() { - // Parse command line arguments (skip program name) - args, err := cli.ParseConnectionArgs(os.Args[1:]) +func newMain() { + parseArgs, err := cli.ParseArgs(os.Args[1:]) if err != nil { - fmt.Printf("Error: %v\n", err) - os.Exit(1) + return } - // Initialize logger with appropriate level + if parseArgs.ShowHelp { + cli.PrintUsage() + return + } + + //parseArgs.Print() logLevel := "warn" // Default to warn (quiet mode) - if args.Verbose { + if parseArgs.Verbose { logLevel = "info" } + if err = common.Init(logLevel, ""); err != nil { fmt.Printf("Warning: Failed to initialize logger: %v\n", err) } - // Show help and exit - if args.ShowHelp { - cli.PrintUsage() - os.Exit(0) - } - - // Create CLI instance with parsed arguments - cliApp, err := cli.NewCLIWithArgs(args) + client, err := cli.NewCLIWithConfig(parseArgs) if err != nil { fmt.Printf("Failed to create CLI: %v\n", err) os.Exit(1) } - // Handle interrupt signal sigChan := make(chan os.Signal, 1) signal.Notify(sigChan, syscall.SIGINT, syscall.SIGTERM) go func() { <-sigChan - cliApp.Cleanup() + client.Cleanup() os.Exit(0) }() - // Check if we have a single command to execute - if args.Command != nil { - // Single command mode - if err = cliApp.RunSingleCommand(args.Command); err != nil { + if parseArgs.Command != nil { + if err = client.RunSingleCommand(parseArgs.Command); err != nil { fmt.Printf("Error: %v\n", err) os.Exit(1) } } else { - // Interactive mode - if err = cliApp.Run(); err != nil { + if err = client.NewRun(); err != nil { fmt.Printf("CLI error: %v\n", err) os.Exit(1) } } + + return +} + +func main() { + + newMain() + //// Parse command line arguments (skip program name) + //args, err := cli.ParseConnectionArgs(os.Args[1:]) + //if err != nil { + // fmt.Printf("Error: %v\n", err) + // os.Exit(1) + //} + // + //// Initialize logger with appropriate level + //logLevel := "warn" // Default to warn (quiet mode) + //if args.Verbose { + // logLevel = "info" + //} + //if err = common.Init(logLevel); err != nil { + // fmt.Printf("Warning: Failed to initialize logger: %v\n", err) + //} + // + //// Show help and exit + //if args.ShowHelp { + // cli.PrintUsage() + // os.Exit(0) + //} + // + //// Create CLI instance with parsed arguments + //cliApp, err := cli.NewCLIWithArgs(args) + //if err != nil { + // fmt.Printf("Failed to create CLI: %v\n", err) + // os.Exit(1) + //} + // + //// Handle interrupt signal + //sigChan := make(chan os.Signal, 1) + //signal.Notify(sigChan, syscall.SIGINT, syscall.SIGTERM) + //go func() { + // <-sigChan + // cliApp.Cleanup() + // os.Exit(0) + //}() + // + //// Check if we have a single command to execute + //if args.Command != nil { + // // Single command mode + // if err = cliApp.RunSingleCommand(args.Command); err != nil { + // fmt.Printf("Error: %v\n", err) + // os.Exit(1) + // } + //} else { + // // Interactive mode + // if err = cliApp.Run(); err != nil { + // fmt.Printf("CLI error: %v\n", err) + // os.Exit(1) + // } + //} } diff --git a/internal/admin/router.go b/internal/admin/router.go index dde8d93844..500b03ad86 100644 --- a/internal/admin/router.go +++ b/internal/admin/router.go @@ -51,7 +51,7 @@ func (r *Router) Setup(engine *gin.Engine) { protected.Use(r.handler.AuthMiddleware()) { - protected.GET("/logout", r.handler.Logout) + protected.POST("/logout", r.handler.Logout) // Auth protected.GET("/auth", r.handler.AuthCheck) diff --git a/internal/cli/admin_command.go b/internal/cli/admin_command.go index cf36daf129..7ebb3a0a7a 100644 --- a/internal/cli/admin_command.go +++ b/internal/cli/admin_command.go @@ -24,7 +24,7 @@ import ( // PingServer pings the server to check if it's alive // Returns benchmark result map if iterations > 1, otherwise prints status -func (c *RAGFlowClient) PingAdmin(cmd *Command) (ResponseIf, error) { +func (c *CLI) PingAdmin(cmd *Command) (ResponseIf, error) { // Get iterations from command params (for benchmark) iterations := 1 if val, ok := cmd.Params["iterations"].(int); ok && val > 1 { @@ -33,11 +33,11 @@ func (c *RAGFlowClient) PingAdmin(cmd *Command) (ResponseIf, error) { if iterations > 1 { // Benchmark mode: multiple iterations - return c.HTTPClient.RequestWithIterations("GET", "/admin/ping", "web", nil, nil, iterations) + return c.AdminServerClient.RequestWithIterations("GET", "/admin/ping", "web", nil, nil, iterations) } // Single mode - resp, err := c.HTTPClient.Request("GET", "/admin/ping", "web", nil, nil) + resp, err := c.AdminServerClient.Request("GET", "/admin/ping", "web", nil, nil) if err != nil { fmt.Printf("Error: %v\n", err) fmt.Println("Server is down") @@ -58,7 +58,7 @@ func (c *RAGFlowClient) PingAdmin(cmd *Command) (ResponseIf, error) { // Show admin version to show RAGFlow admin version // Returns benchmark result map if iterations > 1, otherwise prints status -func (c *RAGFlowClient) ShowAdminVersion(cmd *Command) (ResponseIf, error) { +func (c *CLI) ShowAdminVersion(cmd *Command) (ResponseIf, error) { // Get iterations from command params (for benchmark) iterations := 1 if val, ok := cmd.Params["iterations"].(int); ok && val > 1 { @@ -67,11 +67,11 @@ func (c *RAGFlowClient) ShowAdminVersion(cmd *Command) (ResponseIf, error) { if iterations > 1 { // Benchmark mode: multiple iterations - return c.HTTPClient.RequestWithIterations("GET", "/admin/version", "web", nil, nil, iterations) + return c.AdminServerClient.RequestWithIterations("GET", "/admin/version", "web", nil, nil, iterations) } // Single mode - resp, err := c.HTTPClient.Request("GET", "/admin/version", "web", nil, nil) + resp, err := c.AdminServerClient.Request("GET", "/admin/version", "web", nil, nil) if err != nil { return nil, fmt.Errorf("failed to show admin version: %w", err) } @@ -93,9 +93,9 @@ func (c *RAGFlowClient) ShowAdminVersion(cmd *Command) (ResponseIf, error) { } // ListRoles to list roles (admin mode only) -func (c *RAGFlowClient) ListRoles(cmd *Command) (ResponseIf, error) { - if c.ServerType != "admin" { - return nil, fmt.Errorf("this command is only allowed in ADMIN mode") +func (c *CLI) ListRoles(cmd *Command) (ResponseIf, error) { + if c.Config.CLIMode != AdminMode || c.AdminServerClient.LoginToken == nil { + return nil, fmt.Errorf("this command is only allowed in ADMIN mode or already login") } // Check for benchmark iterations @@ -106,10 +106,10 @@ func (c *RAGFlowClient) ListRoles(cmd *Command) (ResponseIf, error) { if iterations > 1 { // Benchmark mode - return raw result for benchmark stats - return c.HTTPClient.RequestWithIterations("GET", "/admin/roles", "admin", nil, nil, iterations) + return c.AdminServerClient.RequestWithIterations("GET", "/admin/roles", "admin", nil, nil, iterations) } - resp, err := c.HTTPClient.Request("GET", "/admin/roles", "admin", nil, nil) + resp, err := c.AdminServerClient.Request("GET", "/admin/roles", "admin", nil, nil) if err != nil { return nil, fmt.Errorf("failed to list roles: %w", err) } @@ -136,9 +136,9 @@ func (c *RAGFlowClient) ListRoles(cmd *Command) (ResponseIf, error) { } // ShowRole to show role (admin mode only) -func (c *RAGFlowClient) ShowRole(cmd *Command) (ResponseIf, error) { - if c.ServerType != "admin" { - return nil, fmt.Errorf("this command is only allowed in ADMIN mode") +func (c *CLI) ShowRole(cmd *Command) (ResponseIf, error) { + if c.Config.CLIMode != AdminMode || c.AdminServerClient.LoginToken == nil { + return nil, fmt.Errorf("this command is only allowed in ADMIN mode or already login") } roleName := cmd.Params["role_name"].(string) @@ -153,10 +153,10 @@ func (c *RAGFlowClient) ShowRole(cmd *Command) (ResponseIf, error) { if iterations > 1 { // Benchmark mode - return raw result for benchmark stats - return c.HTTPClient.RequestWithIterations("GET", endPoint, "admin", nil, nil, iterations) + return c.AdminServerClient.RequestWithIterations("GET", endPoint, "admin", nil, nil, iterations) } - resp, err := c.HTTPClient.Request("GET", endPoint, "admin", nil, nil) + resp, err := c.AdminServerClient.Request("GET", endPoint, "admin", nil, nil) if err != nil { return nil, fmt.Errorf("failed to show role: %w", err) } @@ -179,9 +179,9 @@ func (c *RAGFlowClient) ShowRole(cmd *Command) (ResponseIf, error) { } // CreateRole creates a new role (admin mode only) -func (c *RAGFlowClient) CreateRole(cmd *Command) (ResponseIf, error) { - if c.ServerType != "admin" { - return nil, fmt.Errorf("this command is only allowed in ADMIN mode") +func (c *CLI) CreateRole(cmd *Command) (ResponseIf, error) { + if c.Config.CLIMode != AdminMode || c.AdminServerClient.LoginToken == nil { + return nil, fmt.Errorf("this command is only allowed in ADMIN mode or already login") } roleName, ok := cmd.Params["role_name"].(string) @@ -197,7 +197,7 @@ func (c *RAGFlowClient) CreateRole(cmd *Command) (ResponseIf, error) { payload["description"] = description } - resp, err := c.HTTPClient.Request("POST", "/admin/roles", "admin", nil, payload) + resp, err := c.AdminServerClient.Request("POST", "/admin/roles", "admin", nil, payload) if err != nil { return nil, fmt.Errorf("failed to create role: %w", err) } @@ -220,9 +220,9 @@ func (c *RAGFlowClient) CreateRole(cmd *Command) (ResponseIf, error) { } // DropRole deletes the role (admin mode only) -func (c *RAGFlowClient) DropRole(cmd *Command) (ResponseIf, error) { - if c.ServerType != "admin" { - return nil, fmt.Errorf("this command is only allowed in ADMIN mode") +func (c *CLI) DropRole(cmd *Command) (ResponseIf, error) { + if c.Config.CLIMode != AdminMode || c.AdminServerClient.LoginToken == nil { + return nil, fmt.Errorf("this command is only allowed in ADMIN mode or already login") } roleName, ok := cmd.Params["role_name"].(string) @@ -230,7 +230,7 @@ func (c *RAGFlowClient) DropRole(cmd *Command) (ResponseIf, error) { return nil, fmt.Errorf("role_name not provided") } - resp, err := c.HTTPClient.Request("DELETE", fmt.Sprintf("/admin/roles/%s", roleName), "admin", nil, nil) + resp, err := c.AdminServerClient.Request("DELETE", fmt.Sprintf("/admin/roles/%s", roleName), "admin", nil, nil) if err != nil { return nil, fmt.Errorf("failed to drop role: %w", err) } @@ -253,9 +253,9 @@ func (c *RAGFlowClient) DropRole(cmd *Command) (ResponseIf, error) { } // AlterRole alters the role rights (admin mode only) -func (c *RAGFlowClient) AlterRole(cmd *Command) (ResponseIf, error) { - if c.ServerType != "admin" { - return nil, fmt.Errorf("this command is only allowed in ADMIN mode") +func (c *CLI) AlterRole(cmd *Command) (ResponseIf, error) { + if c.Config.CLIMode != AdminMode || c.AdminServerClient.LoginToken == nil { + return nil, fmt.Errorf("this command is only allowed in ADMIN mode or already login") } roleName, ok := cmd.Params["role_name"].(string) @@ -271,7 +271,7 @@ func (c *RAGFlowClient) AlterRole(cmd *Command) (ResponseIf, error) { payload["description"] = description } - resp, err := c.HTTPClient.Request("PUT", fmt.Sprintf("/admin/roles/%s", roleName), "admin", nil, payload) + resp, err := c.AdminServerClient.Request("PUT", fmt.Sprintf("/admin/roles/%s", roleName), "admin", nil, payload) if err != nil { return nil, fmt.Errorf("failed to alter role: %w", err) } @@ -294,9 +294,9 @@ func (c *RAGFlowClient) AlterRole(cmd *Command) (ResponseIf, error) { } // GrantAdmin grants admin privileges to a user (admin mode only) -func (c *RAGFlowClient) GrantAdmin(cmd *Command) (ResponseIf, error) { - if c.ServerType != "admin" { - return nil, fmt.Errorf("this command is only allowed in ADMIN mode") +func (c *CLI) GrantAdmin(cmd *Command) (ResponseIf, error) { + if c.Config.CLIMode != AdminMode || c.AdminServerClient.LoginToken == nil { + return nil, fmt.Errorf("this command is only allowed in ADMIN mode or already login") } userName, ok := cmd.Params["user_name"].(string) @@ -304,7 +304,7 @@ func (c *RAGFlowClient) GrantAdmin(cmd *Command) (ResponseIf, error) { return nil, fmt.Errorf("user_name not provided") } - resp, err := c.HTTPClient.Request("PUT", fmt.Sprintf("/admin/users/%s/admin", userName), "admin", nil, nil) + resp, err := c.AdminServerClient.Request("PUT", fmt.Sprintf("/admin/users/%s/admin", userName), "admin", nil, nil) if err != nil { return nil, fmt.Errorf("failed to grant admin: %w", err) } @@ -327,9 +327,9 @@ func (c *RAGFlowClient) GrantAdmin(cmd *Command) (ResponseIf, error) { } // RevokeAdmin revokes admin privileges from a user (admin mode only) -func (c *RAGFlowClient) RevokeAdmin(cmd *Command) (ResponseIf, error) { - if c.ServerType != "admin" { - return nil, fmt.Errorf("this command is only allowed in ADMIN mode") +func (c *CLI) RevokeAdmin(cmd *Command) (ResponseIf, error) { + if c.Config.CLIMode != AdminMode || c.AdminServerClient.LoginToken == nil { + return nil, fmt.Errorf("this command is only allowed in ADMIN mode or already login") } userName, ok := cmd.Params["user_name"].(string) @@ -337,7 +337,7 @@ func (c *RAGFlowClient) RevokeAdmin(cmd *Command) (ResponseIf, error) { return nil, fmt.Errorf("user_name not provided") } - resp, err := c.HTTPClient.Request("DELETE", fmt.Sprintf("/admin/users/%s/admin", userName), "admin", nil, nil) + resp, err := c.AdminServerClient.Request("DELETE", fmt.Sprintf("/admin/users/%s/admin", userName), "admin", nil, nil) if err != nil { return nil, fmt.Errorf("failed to revoke admin: %w", err) } @@ -360,9 +360,9 @@ func (c *RAGFlowClient) RevokeAdmin(cmd *Command) (ResponseIf, error) { } // CreateUser creates a new user (admin mode only) -func (c *RAGFlowClient) CreateUser(cmd *Command) (ResponseIf, error) { - if c.ServerType != "admin" { - return nil, fmt.Errorf("this command is only allowed in ADMIN mode") +func (c *CLI) CreateUser(cmd *Command) (ResponseIf, error) { + if c.Config.CLIMode != AdminMode || c.AdminServerClient.LoginToken == nil { + return nil, fmt.Errorf("this command is only allowed in ADMIN mode or already login") } userName, ok := cmd.Params["user_name"].(string) @@ -387,7 +387,7 @@ func (c *RAGFlowClient) CreateUser(cmd *Command) (ResponseIf, error) { "role": "user", } - resp, err := c.HTTPClient.Request("POST", "/admin/users", "admin", nil, payload) + resp, err := c.AdminServerClient.Request("POST", "/admin/users", "admin", nil, payload) if err != nil { return nil, fmt.Errorf("failed to create user: %w", err) } @@ -410,9 +410,9 @@ func (c *RAGFlowClient) CreateUser(cmd *Command) (ResponseIf, error) { } // ActivateUser activates or deactivates a user (admin mode only) -func (c *RAGFlowClient) ActivateUser(cmd *Command) (ResponseIf, error) { - if c.ServerType != "admin" { - return nil, fmt.Errorf("this command is only allowed in ADMIN mode") +func (c *CLI) ActivateUser(cmd *Command) (ResponseIf, error) { + if c.Config.CLIMode != AdminMode || c.AdminServerClient.LoginToken == nil { + return nil, fmt.Errorf("this command is only allowed in ADMIN mode or already login") } userName, ok := cmd.Params["user_name"].(string) @@ -434,7 +434,7 @@ func (c *RAGFlowClient) ActivateUser(cmd *Command) (ResponseIf, error) { "activate_status": activateStatus, } - resp, err := c.HTTPClient.Request("PUT", fmt.Sprintf("/admin/users/%s/activate", userName), "admin", nil, payload) + resp, err := c.AdminServerClient.Request("PUT", fmt.Sprintf("/admin/users/%s/activate", userName), "admin", nil, payload) if err != nil { return nil, fmt.Errorf("failed to update user status: %w", err) } @@ -457,9 +457,9 @@ func (c *RAGFlowClient) ActivateUser(cmd *Command) (ResponseIf, error) { } // AlterUserPassword changes a user's password (admin mode only) -func (c *RAGFlowClient) AlterUserPassword(cmd *Command) (ResponseIf, error) { - if c.ServerType != "admin" { - return nil, fmt.Errorf("this command is only allowed in ADMIN mode") +func (c *CLI) AlterUserPassword(cmd *Command) (ResponseIf, error) { + if c.Config.CLIMode != AdminMode || c.AdminServerClient.LoginToken == nil { + return nil, fmt.Errorf("this command is only allowed in ADMIN mode or already login") } userName, ok := cmd.Params["user_name"].(string) @@ -482,7 +482,7 @@ func (c *RAGFlowClient) AlterUserPassword(cmd *Command) (ResponseIf, error) { "new_password": encryptedPassword, } - resp, err := c.HTTPClient.Request("PUT", fmt.Sprintf("/admin/users/%s/password", userName), "admin", nil, payload) + resp, err := c.AdminServerClient.Request("PUT", fmt.Sprintf("/admin/users/%s/password", userName), "admin", nil, payload) if err != nil { return nil, fmt.Errorf("failed to change user password: %w", err) } @@ -511,9 +511,9 @@ type listServicesResponse struct { } // ListServices lists all services (admin mode only) -func (c *RAGFlowClient) ListServices(cmd *Command) (ResponseIf, error) { - if c.ServerType != "admin" { - return nil, fmt.Errorf("this command is only allowed in ADMIN mode") +func (c *CLI) ListServices(cmd *Command) (ResponseIf, error) { + if c.Config.CLIMode != AdminMode || c.AdminServerClient.LoginToken == nil { + return nil, fmt.Errorf("this command is only allowed in ADMIN mode or already login") } // Check for benchmark iterations @@ -524,10 +524,10 @@ func (c *RAGFlowClient) ListServices(cmd *Command) (ResponseIf, error) { if iterations > 1 { // Benchmark mode - return raw result for benchmark stats - return c.HTTPClient.RequestWithIterations("GET", "/admin/services", "admin", nil, nil, iterations) + return c.AdminServerClient.RequestWithIterations("GET", "/admin/services", "admin", nil, nil, iterations) } - resp, err := c.HTTPClient.Request("GET", "/admin/services", "admin", nil, nil) + resp, err := c.AdminServerClient.Request("GET", "/admin/services", "admin", nil, nil) if err != nil { return nil, fmt.Errorf("failed to list services: %w", err) } @@ -554,9 +554,9 @@ func (c *RAGFlowClient) ListServices(cmd *Command) (ResponseIf, error) { } // Show service show service (admin mode only) -func (c *RAGFlowClient) ShowService(cmd *Command) (ResponseIf, error) { - if c.ServerType != "admin" { - return nil, fmt.Errorf("this command is only allowed in ADMIN mode") +func (c *CLI) ShowService(cmd *Command) (ResponseIf, error) { + if c.Config.CLIMode != AdminMode || c.AdminServerClient.LoginToken == nil { + return nil, fmt.Errorf("this command is only allowed in ADMIN mode or already login") } serviceIndex := cmd.Params["number"].(int) @@ -571,10 +571,10 @@ func (c *RAGFlowClient) ShowService(cmd *Command) (ResponseIf, error) { if iterations > 1 { // Benchmark mode - return raw result for benchmark stats - return c.HTTPClient.RequestWithIterations("GET", endPoint, "admin", nil, nil, iterations) + return c.AdminServerClient.RequestWithIterations("GET", endPoint, "admin", nil, nil, iterations) } - resp, err := c.HTTPClient.Request("GET", endPoint, "admin", nil, nil) + resp, err := c.AdminServerClient.Request("GET", endPoint, "admin", nil, nil) if err != nil { return nil, fmt.Errorf("failed to show service: %w", err) } @@ -610,9 +610,9 @@ func normalizeVariableRows(rows []map[string]interface{}) { } // ListVariables lists all system variables (admin mode only). -func (c *RAGFlowClient) ListVariables(cmd *Command) (ResponseIf, error) { - if c.ServerType != "admin" { - return nil, fmt.Errorf("this command is only allowed in ADMIN mode") +func (c *CLI) ListVariables(cmd *Command) (ResponseIf, error) { + if c.Config.CLIMode != AdminMode || c.AdminServerClient.LoginToken == nil { + return nil, fmt.Errorf("this command is only allowed in ADMIN mode or already login") } iterations := 1 @@ -621,10 +621,10 @@ func (c *RAGFlowClient) ListVariables(cmd *Command) (ResponseIf, error) { } if iterations > 1 { - return c.HTTPClient.RequestWithIterations("GET", "/admin/variables", "admin", nil, nil, iterations) + return c.AdminServerClient.RequestWithIterations("GET", "/admin/variables", "admin", nil, nil, iterations) } - resp, err := c.HTTPClient.Request("GET", "/admin/variables", "admin", nil, nil) + resp, err := c.AdminServerClient.Request("GET", "/admin/variables", "admin", nil, nil) if err != nil { return nil, fmt.Errorf("failed to list variables: %w", err) } @@ -648,9 +648,9 @@ func (c *RAGFlowClient) ListVariables(cmd *Command) (ResponseIf, error) { } // ShowVariable shows system variables by exact name or name prefix (admin mode only). -func (c *RAGFlowClient) ShowVariable(cmd *Command) (ResponseIf, error) { - if c.ServerType != "admin" { - return nil, fmt.Errorf("this command is only allowed in ADMIN mode") +func (c *CLI) ShowVariable(cmd *Command) (ResponseIf, error) { + if c.Config.CLIMode != AdminMode || c.AdminServerClient.LoginToken == nil { + return nil, fmt.Errorf("this command is only allowed in ADMIN mode or already login") } varName, ok := cmd.Params["var_name"].(string) @@ -665,10 +665,10 @@ func (c *RAGFlowClient) ShowVariable(cmd *Command) (ResponseIf, error) { payload := map[string]interface{}{"var_name": varName} if iterations > 1 { - return c.HTTPClient.RequestWithIterations("GET", "/admin/variables", "admin", nil, payload, iterations) + return c.AdminServerClient.RequestWithIterations("GET", "/admin/variables", "admin", nil, payload, iterations) } - resp, err := c.HTTPClient.Request("GET", "/admin/variables", "admin", nil, payload) + resp, err := c.AdminServerClient.Request("GET", "/admin/variables", "admin", nil, payload) if err != nil { return nil, fmt.Errorf("failed to show variable: %w", err) } @@ -692,9 +692,9 @@ func (c *RAGFlowClient) ShowVariable(cmd *Command) (ResponseIf, error) { } // SetVariable updates a system variable (admin mode only). -func (c *RAGFlowClient) SetVariable(cmd *Command) (ResponseIf, error) { - if c.ServerType != "admin" { - return nil, fmt.Errorf("this command is only allowed in ADMIN mode") +func (c *CLI) SetVariable(cmd *Command) (ResponseIf, error) { + if c.Config.CLIMode != AdminMode || c.AdminServerClient.LoginToken == nil { + return nil, fmt.Errorf("this command is only allowed in ADMIN mode or already login") } varName, ok := cmd.Params["var_name"].(string) @@ -710,7 +710,7 @@ func (c *RAGFlowClient) SetVariable(cmd *Command) (ResponseIf, error) { "var_name": varName, "var_value": varValue, } - resp, err := c.HTTPClient.Request("PUT", "/admin/variables", "admin", nil, payload) + resp, err := c.AdminServerClient.Request("PUT", "/admin/variables", "admin", nil, payload) if err != nil { return nil, fmt.Errorf("failed to set variable: %w", err) } @@ -734,9 +734,9 @@ func (c *RAGFlowClient) SetVariable(cmd *Command) (ResponseIf, error) { // ListUsers lists all users (admin mode only) // Returns (result_map, error) - result_map is non-nil for benchmark mode -func (c *RAGFlowClient) ListUsers(cmd *Command) (ResponseIf, error) { - if c.ServerType != "admin" { - return nil, fmt.Errorf("this command is only allowed in ADMIN mode") +func (c *CLI) ListUsers(cmd *Command) (ResponseIf, error) { + if c.Config.CLIMode != AdminMode || c.AdminServerClient.LoginToken == nil { + return nil, fmt.Errorf("this command is only allowed in ADMIN mode or already login") } // Check for benchmark iterations @@ -747,10 +747,10 @@ func (c *RAGFlowClient) ListUsers(cmd *Command) (ResponseIf, error) { if iterations > 1 { // Benchmark mode - return raw result for benchmark stats - return c.HTTPClient.RequestWithIterations("GET", "/admin/users", "admin", nil, nil, iterations) + return c.AdminServerClient.RequestWithIterations("GET", "/admin/users", "admin", nil, nil, iterations) } - resp, err := c.HTTPClient.Request("GET", "/admin/users", "admin", nil, nil) + resp, err := c.AdminServerClient.Request("GET", "/admin/users", "admin", nil, nil) if err != nil { return nil, fmt.Errorf("failed to list users: %w", err) } @@ -777,9 +777,9 @@ func (c *RAGFlowClient) ListUsers(cmd *Command) (ResponseIf, error) { } // DropUser deletes a user (admin mode only) -func (c *RAGFlowClient) DropUser(cmd *Command) (ResponseIf, error) { - if c.ServerType != "admin" { - return nil, fmt.Errorf("this command is only allowed in ADMIN mode") +func (c *CLI) DropUser(cmd *Command) (ResponseIf, error) { + if c.Config.CLIMode != AdminMode || c.AdminServerClient.LoginToken == nil { + return nil, fmt.Errorf("this command is only allowed in ADMIN mode or already login") } userName, ok := cmd.Params["user_name"].(string) @@ -787,7 +787,7 @@ func (c *RAGFlowClient) DropUser(cmd *Command) (ResponseIf, error) { return nil, fmt.Errorf("user_name not provided") } - resp, err := c.HTTPClient.Request("DELETE", fmt.Sprintf("/admin/users/%s", userName), "admin", nil, nil) + resp, err := c.AdminServerClient.Request("DELETE", fmt.Sprintf("/admin/users/%s", userName), "admin", nil, nil) if err != nil { return nil, fmt.Errorf("failed to drop user: %w", err) } @@ -810,9 +810,9 @@ func (c *RAGFlowClient) DropUser(cmd *Command) (ResponseIf, error) { } // Show user show user (admin mode only) -func (c *RAGFlowClient) ShowUser(cmd *Command) (ResponseIf, error) { - if c.ServerType != "admin" { - return nil, fmt.Errorf("this command is only allowed in ADMIN mode") +func (c *CLI) ShowUser(cmd *Command) (ResponseIf, error) { + if c.Config.CLIMode != AdminMode || c.AdminServerClient.LoginToken == nil { + return nil, fmt.Errorf("this command is only allowed in ADMIN mode or already login") } userName, ok := cmd.Params["user_name"].(string) @@ -820,7 +820,7 @@ func (c *RAGFlowClient) ShowUser(cmd *Command) (ResponseIf, error) { return nil, fmt.Errorf("user_name not provided") } - resp, err := c.HTTPClient.Request("GET", fmt.Sprintf("/admin/users/%s", userName), "admin", nil, nil) + resp, err := c.AdminServerClient.Request("GET", fmt.Sprintf("/admin/users/%s", userName), "admin", nil, nil) if err != nil { return nil, fmt.Errorf("failed to show user: %w", err) } @@ -844,9 +844,9 @@ func (c *RAGFlowClient) ShowUser(cmd *Command) (ResponseIf, error) { // ListUserDatasets lists datasets for a specific user (admin mode) // Returns (result_map, error) - result_map is non-nil for benchmark mode -func (c *RAGFlowClient) ListUserDatasets(cmd *Command) (ResponseIf, error) { - if c.ServerType != "admin" { - return nil, fmt.Errorf("this command is only allowed in ADMIN mode") +func (c *CLI) ListUserDatasets(cmd *Command) (ResponseIf, error) { + if c.Config.CLIMode != AdminMode || c.AdminServerClient.LoginToken == nil { + return nil, fmt.Errorf("this command is only allowed in ADMIN mode or already login") } userName, ok := cmd.Params["user_name"].(string) @@ -862,10 +862,10 @@ func (c *RAGFlowClient) ListUserDatasets(cmd *Command) (ResponseIf, error) { if iterations > 1 { // Benchmark mode - return raw result for benchmark stats - return c.HTTPClient.RequestWithIterations("GET", fmt.Sprintf("/admin/users/%s/datasets", userName), "admin", nil, nil, iterations) + return c.AdminServerClient.RequestWithIterations("GET", fmt.Sprintf("/admin/users/%s/datasets", userName), "admin", nil, nil, iterations) } - resp, err := c.HTTPClient.Request("GET", fmt.Sprintf("/admin/users/%s/datasets", userName), "admin", nil, nil) + resp, err := c.AdminServerClient.Request("GET", fmt.Sprintf("/admin/users/%s/datasets", userName), "admin", nil, nil) if err != nil { return nil, fmt.Errorf("failed to list datasets: %w", err) } @@ -899,9 +899,9 @@ func (c *RAGFlowClient) ListUserDatasets(cmd *Command) (ResponseIf, error) { // ListAgents lists agents for a specific user (admin mode) // Returns (result_map, error) - result_map is non-nil for benchmark mode -func (c *RAGFlowClient) ListAgents(cmd *Command) (ResponseIf, error) { - if c.ServerType != "admin" { - return nil, fmt.Errorf("this command is only allowed in ADMIN mode") +func (c *CLI) ListAgents(cmd *Command) (ResponseIf, error) { + if c.Config.CLIMode != AdminMode || c.AdminServerClient.LoginToken == nil { + return nil, fmt.Errorf("this command is only allowed in ADMIN mode or already login") } userName, ok := cmd.Params["user_name"].(string) @@ -917,10 +917,10 @@ func (c *RAGFlowClient) ListAgents(cmd *Command) (ResponseIf, error) { if iterations > 1 { // Benchmark mode - return raw result for benchmark stats - return c.HTTPClient.RequestWithIterations("GET", fmt.Sprintf("/admin/users/%s/agents", userName), "admin", nil, nil, iterations) + return c.AdminServerClient.RequestWithIterations("GET", fmt.Sprintf("/admin/users/%s/agents", userName), "admin", nil, nil, iterations) } - resp, err := c.HTTPClient.Request("GET", fmt.Sprintf("/admin/users/%s/agents", userName), "admin", nil, nil) + resp, err := c.AdminServerClient.Request("GET", fmt.Sprintf("/admin/users/%s/agents", userName), "admin", nil, nil) if err != nil { return nil, fmt.Errorf("failed to list agents: %w", err) } @@ -953,9 +953,9 @@ func (c *RAGFlowClient) ListAgents(cmd *Command) (ResponseIf, error) { } // GrantPermission grants permission to a role (admin mode only) -func (c *RAGFlowClient) GrantPermission(cmd *Command) (ResponseIf, error) { - if c.ServerType != "admin" { - return nil, fmt.Errorf("this command is only allowed in ADMIN mode") +func (c *CLI) GrantPermission(cmd *Command) (ResponseIf, error) { + if c.Config.CLIMode != AdminMode || c.AdminServerClient.LoginToken == nil { + return nil, fmt.Errorf("this command is only allowed in ADMIN mode or already login") } userName, ok := cmd.Params["user_name"].(string) @@ -963,7 +963,7 @@ func (c *RAGFlowClient) GrantPermission(cmd *Command) (ResponseIf, error) { return nil, fmt.Errorf("user_name not provided") } - resp, err := c.HTTPClient.Request("GET", fmt.Sprintf("/admin/users/%s/keys", userName), "admin", nil, nil) + resp, err := c.AdminServerClient.Request("GET", fmt.Sprintf("/admin/users/%s/keys", userName), "admin", nil, nil) if err != nil { return nil, fmt.Errorf("failed to list tokens: %w", err) } @@ -991,9 +991,9 @@ func (c *RAGFlowClient) GrantPermission(cmd *Command) (ResponseIf, error) { } // RevokePermission revokes permission from a role (admin mode only) -func (c *RAGFlowClient) RevokePermission(cmd *Command) (ResponseIf, error) { - if c.ServerType != "admin" { - return nil, fmt.Errorf("this command is only allowed in ADMIN mode") +func (c *CLI) RevokePermission(cmd *Command) (ResponseIf, error) { + if c.Config.CLIMode != AdminMode || c.AdminServerClient.LoginToken == nil { + return nil, fmt.Errorf("this command is only allowed in ADMIN mode or already login") } roleName, ok := cmd.Params["role_name"].(string) @@ -1023,7 +1023,7 @@ func (c *RAGFlowClient) RevokePermission(cmd *Command) (ResponseIf, error) { "actions": actions, } - resp, err := c.HTTPClient.Request("DELETE", fmt.Sprintf("/admin/roles/%s/permission", roleName), "admin", nil, payload) + resp, err := c.AdminServerClient.Request("DELETE", fmt.Sprintf("/admin/roles/%s/permission", roleName), "admin", nil, payload) if err != nil { return nil, fmt.Errorf("failed to revoke permission: %w", err) } @@ -1051,9 +1051,9 @@ func (c *RAGFlowClient) RevokePermission(cmd *Command) (ResponseIf, error) { } // AlterUserRole alters user's role (admin mode only) -func (c *RAGFlowClient) AlterUserRole(cmd *Command) (ResponseIf, error) { - if c.ServerType != "admin" { - return nil, fmt.Errorf("this command is only allowed in ADMIN mode") +func (c *CLI) AlterUserRole(cmd *Command) (ResponseIf, error) { + if c.Config.CLIMode != AdminMode || c.AdminServerClient.LoginToken == nil { + return nil, fmt.Errorf("this command is only allowed in ADMIN mode or already login") } userName, ok := cmd.Params["user_name"].(string) @@ -1070,7 +1070,7 @@ func (c *RAGFlowClient) AlterUserRole(cmd *Command) (ResponseIf, error) { "role_name": roleName, } - resp, err := c.HTTPClient.Request("PUT", fmt.Sprintf("/admin/users/%s/role", userName), "admin", nil, payload) + resp, err := c.AdminServerClient.Request("PUT", fmt.Sprintf("/admin/users/%s/role", userName), "admin", nil, payload) if err != nil { return nil, fmt.Errorf("failed to alter user role: %w", err) } @@ -1098,9 +1098,9 @@ func (c *RAGFlowClient) AlterUserRole(cmd *Command) (ResponseIf, error) { } // ShowUserPermission shows user's permissions (admin mode only) -func (c *RAGFlowClient) ShowUserPermission(cmd *Command) (ResponseIf, error) { - if c.ServerType != "admin" { - return nil, fmt.Errorf("this command is only allowed in ADMIN mode") +func (c *CLI) ShowUserPermission(cmd *Command) (ResponseIf, error) { + if c.Config.CLIMode != AdminMode || c.AdminServerClient.LoginToken == nil { + return nil, fmt.Errorf("this command is only allowed in ADMIN mode or already login") } userName, ok := cmd.Params["user_name"].(string) @@ -1108,7 +1108,7 @@ func (c *RAGFlowClient) ShowUserPermission(cmd *Command) (ResponseIf, error) { return nil, fmt.Errorf("user_name not provided") } - resp, err := c.HTTPClient.Request("GET", fmt.Sprintf("/admin/users/%s/permission", userName), "admin", nil, nil) + resp, err := c.AdminServerClient.Request("GET", fmt.Sprintf("/admin/users/%s/permission", userName), "admin", nil, nil) if err != nil { return nil, fmt.Errorf("failed to show user permission: %w", err) } @@ -1136,9 +1136,9 @@ func (c *RAGFlowClient) ShowUserPermission(cmd *Command) (ResponseIf, error) { } // GenerateAdminToken generates an API token for a user (admin mode only) -func (c *RAGFlowClient) GenerateAdminToken(cmd *Command) (ResponseIf, error) { - if c.ServerType != "admin" { - return nil, fmt.Errorf("this command is only allowed in ADMIN mode") +func (c *CLI) GenerateAdminToken(cmd *Command) (ResponseIf, error) { + if c.Config.CLIMode != AdminMode || c.AdminServerClient.LoginToken == nil { + return nil, fmt.Errorf("this command is only allowed in ADMIN mode or already login") } userName, ok := cmd.Params["user_name"].(string) @@ -1146,7 +1146,7 @@ func (c *RAGFlowClient) GenerateAdminToken(cmd *Command) (ResponseIf, error) { return nil, fmt.Errorf("user_name not provided") } - resp, err := c.HTTPClient.Request("POST", fmt.Sprintf("/admin/users/%s/keys", userName), "admin", nil, nil) + resp, err := c.AdminServerClient.Request("POST", fmt.Sprintf("/admin/users/%s/keys", userName), "admin", nil, nil) if err != nil { return nil, fmt.Errorf("failed to generate token: %w", err) } @@ -1173,9 +1173,9 @@ func (c *RAGFlowClient) GenerateAdminToken(cmd *Command) (ResponseIf, error) { } // ListAdminTokens lists all API tokens for a user (admin mode only) -func (c *RAGFlowClient) ListAdminTokens(cmd *Command) (ResponseIf, error) { - if c.ServerType != "admin" { - return nil, fmt.Errorf("this command is only allowed in ADMIN mode") +func (c *CLI) ListAdminTokens(cmd *Command) (ResponseIf, error) { + if c.Config.CLIMode != AdminMode || c.AdminServerClient.LoginToken == nil { + return nil, fmt.Errorf("this command is only allowed in ADMIN mode or already login") } userName, ok := cmd.Params["user_name"].(string) @@ -1183,7 +1183,7 @@ func (c *RAGFlowClient) ListAdminTokens(cmd *Command) (ResponseIf, error) { return nil, fmt.Errorf("user_name not provided") } - resp, err := c.HTTPClient.Request("GET", fmt.Sprintf("/admin/users/%s/keys", userName), "admin", nil, nil) + resp, err := c.AdminServerClient.Request("GET", fmt.Sprintf("/admin/users/%s/keys", userName), "admin", nil, nil) if err != nil { return nil, fmt.Errorf("failed to list tokens: %w", err) } @@ -1215,9 +1215,9 @@ func (c *RAGFlowClient) ListAdminTokens(cmd *Command) (ResponseIf, error) { } // DropToken drops an API token for a user (admin mode only) -func (c *RAGFlowClient) DropAdminToken(cmd *Command) (ResponseIf, error) { - if c.ServerType != "admin" { - return nil, fmt.Errorf("this command is only allowed in ADMIN mode") +func (c *CLI) DropAdminToken(cmd *Command) (ResponseIf, error) { + if c.Config.CLIMode != AdminMode || c.AdminServerClient.LoginToken == nil { + return nil, fmt.Errorf("this command is only allowed in ADMIN mode or already login") } userName, ok := cmd.Params["user_name"].(string) @@ -1233,7 +1233,7 @@ func (c *RAGFlowClient) DropAdminToken(cmd *Command) (ResponseIf, error) { // URL encode the token to handle special characters encodedToken := url.QueryEscape(token) - resp, err := c.HTTPClient.Request("DELETE", fmt.Sprintf("/admin/users/%s/keys/%s", userName, encodedToken), "admin", nil, nil) + resp, err := c.AdminServerClient.Request("DELETE", fmt.Sprintf("/admin/users/%s/keys/%s", userName, encodedToken), "admin", nil, nil) if err != nil { return nil, fmt.Errorf("failed to drop token: %w", err) } @@ -1255,12 +1255,12 @@ func (c *RAGFlowClient) DropAdminToken(cmd *Command) (ResponseIf, error) { return &result, nil } -func (c *RAGFlowClient) ListAdminTasks(cmd *Command) (ResponseIf, error) { - if c.ServerType != "admin" { - return nil, fmt.Errorf("this command is only allowed in ADMIN mode") +func (c *CLI) ListAdminTasks(cmd *Command) (ResponseIf, error) { + if c.Config.CLIMode != AdminMode || c.AdminServerClient.LoginToken == nil { + return nil, fmt.Errorf("this command is only allowed in ADMIN mode or already login") } - resp, err := c.HTTPClient.Request("GET", "/admin/tasks", "admin", nil, nil) + resp, err := c.AdminServerClient.Request("GET", "/admin/tasks", "admin", nil, nil) if err != nil { return nil, fmt.Errorf("failed to drop token: %w", err) } @@ -1282,11 +1282,11 @@ func (c *RAGFlowClient) ListAdminTasks(cmd *Command) (ResponseIf, error) { return &result, nil } -func (c *RAGFlowClient) ListAdminIngestors(cmd *Command) (ResponseIf, error) { - if c.ServerType != "admin" { - return nil, fmt.Errorf("this command is only allowed in ADMIN mode") +func (c *CLI) ListAdminIngestors(cmd *Command) (ResponseIf, error) { + if c.Config.CLIMode != AdminMode || c.AdminServerClient.LoginToken == nil { + return nil, fmt.Errorf("this command is only allowed in ADMIN mode or already login") } - resp, err := c.HTTPClient.Request("GET", "/admin/ingestors", "admin", nil, nil) + resp, err := c.AdminServerClient.Request("GET", "/admin/ingestors", "admin", nil, nil) if err != nil { return nil, fmt.Errorf("failed to list ingestors: %w", err) } @@ -1307,12 +1307,12 @@ func (c *RAGFlowClient) ListAdminIngestors(cmd *Command) (ResponseIf, error) { result.Duration = resp.Duration return &result, nil } -func (c *RAGFlowClient) ListAdminIngestionTasks(cmd *Command) (ResponseIf, error) { - if c.ServerType != "admin" { - return nil, fmt.Errorf("this command is only allowed in ADMIN mode") +func (c *CLI) ListAdminIngestionTasks(cmd *Command) (ResponseIf, error) { + if c.Config.CLIMode != AdminMode || c.AdminServerClient.LoginToken == nil { + return nil, fmt.Errorf("this command is only allowed in ADMIN mode or already login") } - resp, err := c.HTTPClient.Request("GET", "/admin/ingestion/tasks", "admin", nil, nil) + resp, err := c.AdminServerClient.Request("GET", "/admin/ingestion/tasks", "admin", nil, nil) if err != nil { return nil, fmt.Errorf("failed to list admin tasks: %w", err) } @@ -1334,9 +1334,9 @@ func (c *RAGFlowClient) ListAdminIngestionTasks(cmd *Command) (ResponseIf, error return &result, nil } -func (c *RAGFlowClient) AdminStartIngestionCommand(cmd *Command) (ResponseIf, error) { - if c.ServerType != "admin" { - return nil, fmt.Errorf("this command is only allowed in ADMIN mode") +func (c *CLI) AdminStartIngestionCommand(cmd *Command) (ResponseIf, error) { + if c.Config.CLIMode != AdminMode || c.AdminServerClient.LoginToken == nil { + return nil, fmt.Errorf("this command is only allowed in ADMIN mode or already login") } fileURI, ok := cmd.Params["uri"].(string) @@ -1348,7 +1348,7 @@ func (c *RAGFlowClient) AdminStartIngestionCommand(cmd *Command) (ResponseIf, er "from": "CLI", } - resp, err := c.HTTPClient.Request("POST", "/admin/ingestion", "admin", nil, payload) + resp, err := c.AdminServerClient.Request("POST", "/admin/ingestion", "admin", nil, payload) if err != nil { return nil, fmt.Errorf("failed to ingest file: %w", err) } @@ -1370,9 +1370,9 @@ func (c *RAGFlowClient) AdminStartIngestionCommand(cmd *Command) (ResponseIf, er return &result, nil } -func (c *RAGFlowClient) AdminStopIngestionCommand(cmd *Command) (ResponseIf, error) { - if c.ServerType != "admin" { - return nil, fmt.Errorf("this command is only allowed in ADMIN mode") +func (c *CLI) AdminStopIngestionCommand(cmd *Command) (ResponseIf, error) { + if c.Config.CLIMode != AdminMode || c.AdminServerClient.LoginToken == nil { + return nil, fmt.Errorf("this command is only allowed in ADMIN mode or already login") } taskID, ok := cmd.Params["task_id"].(string) @@ -1384,7 +1384,7 @@ func (c *RAGFlowClient) AdminStopIngestionCommand(cmd *Command) (ResponseIf, err "from": "CLI", } - resp, err := c.HTTPClient.Request("DELETE", "/admin/ingestion", "admin", nil, payload) + resp, err := c.AdminServerClient.Request("DELETE", "/admin/ingestion", "admin", nil, payload) if err != nil { return nil, fmt.Errorf("failed to ingest file: %w", err) } @@ -1406,9 +1406,9 @@ func (c *RAGFlowClient) AdminStopIngestionCommand(cmd *Command) (ResponseIf, err return &result, nil } -func (c *RAGFlowClient) AdminShutdownIngestor(cmd *Command) (ResponseIf, error) { - if c.ServerType != "admin" { - return nil, fmt.Errorf("this command is only allowed in ADMIN mode") +func (c *CLI) AdminShutdownIngestor(cmd *Command) (ResponseIf, error) { + if c.Config.CLIMode != AdminMode || c.AdminServerClient.LoginToken == nil { + return nil, fmt.Errorf("this command is only allowed in ADMIN mode or already login") } ingestorName, ok := cmd.Params["ingestor_name"].(string) @@ -1419,7 +1419,7 @@ func (c *RAGFlowClient) AdminShutdownIngestor(cmd *Command) (ResponseIf, error) "ingestor_name": ingestorName, } - resp, err := c.HTTPClient.Request("DELETE", "/admin/ingestors", "admin", nil, payload) + resp, err := c.AdminServerClient.Request("DELETE", "/admin/ingestors", "admin", nil, payload) if err != nil { return nil, fmt.Errorf("failed to shutdown ingestor: %w", err) } diff --git a/internal/cli/admin_parser.go b/internal/cli/admin_parser.go index ada5843b9d..dc2af91bc1 100644 --- a/internal/cli/admin_parser.go +++ b/internal/cli/admin_parser.go @@ -23,8 +23,8 @@ func (p *Parser) parseAdminLoginUser() (*Command, error) { cmd := NewCommand("login_user") p.nextToken() // consume LOGIN - if p.curToken.Type != TokenUser { - return nil, fmt.Errorf("expected USER after LOGIN") + if p.curToken.Type != TokenAdmin { + return nil, fmt.Errorf("expected ADMIN after LOGIN") } p.nextToken() @@ -196,6 +196,8 @@ func (p *Parser) parseAdminListCommand() (*Command, error) { return p.parseAdminListIngestors() case TokenIngestion: return p.parseAdminListIngestionTasks() + case TokenAPI: + return p.parseListApiCommand() default: return nil, fmt.Errorf("unknown LIST target: %s", p.curToken.Value) } @@ -418,15 +420,12 @@ func (p *Parser) parseAdminShowCommand() (*Command, error) { return NewCommand("show_token"), nil case TokenCurrent: p.nextToken() - if p.curToken.Type != TokenUser { - return nil, fmt.Errorf("expected USER after CURRENT") - } - p.nextToken() + // Semicolon is optional for SHOW TOKEN if p.curToken.Type == TokenSemicolon { p.nextToken() } - return NewCommand("show_current_user"), nil + return NewCommand("show_current"), nil case TokenUser: return p.parseShowUser() case TokenRole: @@ -439,6 +438,10 @@ func (p *Parser) parseAdminShowCommand() (*Command, error) { return p.parseShowProvider() case TokenModel: return p.parseShowModel() + case TokenAdmin: + return p.parseUserShowAdmin() + case TokenAPI: + return p.parseUserShowAPI() default: return nil, fmt.Errorf("unknown SHOW target: %s", p.curToken.Value) } @@ -1377,7 +1380,7 @@ func (p *Parser) parseAdminImportCommand() (*Command, error) { return cmd, nil } -func (p *Parser) parseAdminSearchCommand() (*Command, error) { +func (p *Parser) parseAdminRetrieveCommand() (*Command, error) { p.nextToken() // consume SEARCH question, err := p.parseQuotedString() if err != nil { @@ -1533,8 +1536,8 @@ func (p *Parser) parseAdminUserStatement() (*Command, error) { return p.parseParseCommand() case TokenImport: return p.parseImportCommand() - case TokenSearch: - return p.parseSearchCommand() + case TokenRetrieve: + return p.parseRetrieveCommand() default: return nil, fmt.Errorf("invalid user statement: %s", p.curToken.Value) } @@ -1637,6 +1640,40 @@ func (p *Parser) parseAdminRestartCommand() (*Command, error) { return cmd, nil } +func (p *Parser) parseAdminAddCommand() (*Command, error) { + p.nextToken() // consume ADD + switch p.curToken.Type { + case TokenAPI: + return p.parseAddAPIServer() + case TokenAdmin: + return p.parseAddAdminServer() + default: + return nil, fmt.Errorf("unknown ADD target: %s", p.curToken.Value) + } +} + +func (p *Parser) parseAdminDeleteCommand() (*Command, error) { + p.nextToken() // consume DELETE + switch p.curToken.Type { + case TokenAPI: + return p.parseDeleteAPIServer() + case TokenAdmin: + return p.parseDeleteAdminServer() + default: + return nil, fmt.Errorf("unknown ADD target: %s", p.curToken.Value) + } +} + +func (p *Parser) parseAdminSaveCommand() (*Command, error) { + p.nextToken() // consume SAVE + switch p.curToken.Type { + case TokenConfig: + return p.parseSaveConfig() + default: + return nil, fmt.Errorf("unknown ADD target: %s", p.curToken.Value) + } +} + func (p *Parser) parseStartIngestion() (*Command, error) { p.nextToken() // consume Start diff --git a/internal/cli/benchmark.go b/internal/cli/benchmark.go index a6c3d6de95..70257d29df 100644 --- a/internal/cli/benchmark.go +++ b/internal/cli/benchmark.go @@ -34,7 +34,7 @@ type BenchmarkResult struct { } // RunBenchmark runs a benchmark with the given concurrency and iterations -func (c *RAGFlowClient) RunBenchmark(cmd *Command) (ResponseIf, error) { +func (c *CLI) RunBenchmark(cmd *Command) (ResponseIf, error) { concurrency, ok := cmd.Params["concurrency"].(int) if !ok { concurrency = 1 @@ -64,7 +64,7 @@ func (c *RAGFlowClient) RunBenchmark(cmd *Command) (ResponseIf, error) { } // runBenchmarkSingle runs benchmark with single concurrency (sequential execution) -func (c *RAGFlowClient) runBenchmarkSingle(iterations int, nestedCmd *Command) (*BenchmarkResponse, error) { +func (c *CLI) runBenchmarkSingle(iterations int, nestedCmd *Command) (*BenchmarkResponse, error) { commandType := nestedCmd.Type // For search_on_datasets, convert dataset names to IDs first @@ -144,7 +144,7 @@ func (c *RAGFlowClient) runBenchmarkSingle(iterations int, nestedCmd *Command) ( } // runBenchmarkConcurrent runs benchmark with multiple concurrent workers -func (c *RAGFlowClient) runBenchmarkConcurrent(concurrency, iterations int, nestedCmd *Command) (*BenchmarkResponse, error) { +func (c *CLI) runBenchmarkConcurrent(concurrency, iterations int, nestedCmd *Command) (*BenchmarkResponse, error) { results := make([]map[string]interface{}, concurrency) var wg sync.WaitGroup @@ -173,8 +173,13 @@ func (c *RAGFlowClient) runBenchmarkConcurrent(concurrency, iterations int, nest defer wg.Done() // Create a new client for each goroutine to avoid race conditions - workerClient := NewRAGFlowClient(c.ServerType) - workerClient.HTTPClient = c.HTTPClient // Share the same HTTP client config + workerClient, err := NewCLIWithConfig(nil) + if err != nil { + fmt.Printf("fail to create worker client: %s", err) + return + } + workerClient.AdminServerClient = c.AdminServerClient + workerClient.APIServerClientMap = c.APIServerClientMap // Execute benchmark silently (no output) responseList := workerClient.executeBenchmarkSilent(nestedCmd, iterations) @@ -218,21 +223,23 @@ func (c *RAGFlowClient) runBenchmarkConcurrent(concurrency, iterations int, nest } // executeBenchmarkSilent executes a command for benchmark without printing output -func (c *RAGFlowClient) executeBenchmarkSilent(cmd *Command, iterations int) []*Response { +func (c *CLI) executeBenchmarkSilent(cmd *Command, iterations int) []*Response { responseList := make([]*Response, 0, iterations) + httpClient := c.APIServerClientMap[c.Config.APIClientConfig.CurrentAPIServer] + for i := 0; i < iterations; i++ { var resp *Response var err error switch cmd.Type { case "ping": - resp, err = c.HTTPClient.Request("GET", "/system/ping", "web", nil, nil) + resp, err = httpClient.Request("GET", "/system/ping", "web", nil, nil) case "list_user_datasets": - resp, err = c.HTTPClient.Request("POST", "/kb/list", "web", nil, nil) + resp, err = httpClient.Request("POST", "/kb/list", "web", nil, nil) case "list_datasets": userName, _ := cmd.Params["user_name"].(string) - resp, err = c.HTTPClient.Request("GET", fmt.Sprintf("/admin/users/%s/datasets", userName), "admin", nil, nil) + resp, err = httpClient.Request("GET", fmt.Sprintf("/admin/users/%s/datasets", userName), "admin", nil, nil) case "search_on_datasets": question, _ := cmd.Params["question"].(string) datasetIDs, _ := cmd.Params["dataset_ids"].([]string) @@ -242,7 +249,7 @@ func (c *RAGFlowClient) executeBenchmarkSilent(cmd *Command, iterations int) []* "similarity_threshold": 0.2, "vector_similarity_weight": 0.3, } - resp, err = c.HTTPClient.Request("POST", "/datasets/search", "web", nil, payload) + resp, err = httpClient.Request("POST", "/datasets/search", "web", nil, payload) default: // For other commands, we would need to add specific handling // For now, mark as failed diff --git a/internal/cli/cli.go b/internal/cli/cli.go index 42a9b37910..5ae5eaa6f4 100644 --- a/internal/cli/cli.go +++ b/internal/cli/cli.go @@ -22,11 +22,11 @@ import ( "errors" "fmt" "os" - "os/signal" + //"os/signal" "path/filepath" "strconv" "strings" - "syscall" + //"syscall" "unicode/utf8" "github.com/peterh/liner" @@ -35,12 +35,23 @@ import ( "ragflow/internal/cli/filesystem" ) +type APIServerConfig struct { + Name string `yaml:"name"` + Host string `yaml:"host"` + UserName *string `yaml:"user_name"` + UserPassword *string `yaml:"password"` + ApiToken *string `yaml:"api_token"` + IP string + Port int +} + // ConfigFile represents the rf.yml configuration file structure type ConfigFile struct { - Host string `yaml:"host"` - APIToken string `yaml:"api_token"` - UserName string `yaml:"user_name"` - Password string `yaml:"password"` + Host string `yaml:"host"` // default API server host + APIToken string `yaml:"api_token"` // default API server api token + UserName string `yaml:"user_name"` // default API server user name + Password string `yaml:"password"` // default API server password + APIServerMap map[string]*APIServerConfig `yaml:"api_servers"` } // OutputFormat represents the output format type @@ -69,6 +80,302 @@ type ConnectionArgs struct { Verbose bool // Enable verbose logging } +type CommandLineMode string + +const ( + APIMode CommandLineMode = "api" + AdminMode CommandLineMode = "admin" + IngestorMode CommandLineMode = "ingestor" // If we want to access ingestor + CollectorMode CommandLineMode = "collector" // If we want to access collector + DefaultAPIServer = "default" +) + +type CommandLineConfig struct { + CLIMode CommandLineMode + AdminClientConfig *AdminModeConfig + APIClientConfig APIModeConfig + ShowHelp bool + Verbose bool + Interactive bool + OutputFormat OutputFormat + Command *string +} + +type AdminModeConfig struct { + AdminHost string + AdminPort int + AdminName *string + AdminPassword *string + //AdminCommand *string +} + +type APIModeConfig struct { + CurrentAPIServer string + APIServerMap map[string]*APIServerConfig +} + +func (c *CommandLineConfig) Print() { + b, err := json.MarshalIndent(c, "", " ") + if err == nil { + fmt.Println(string(b)) + } +} + +func ParseArgs(args []string) (*CommandLineConfig, error) { + commandLineConfig := &CommandLineConfig{ + CLIMode: APIMode, + AdminClientConfig: nil, + ShowHelp: false, + Verbose: false, + Interactive: true, + OutputFormat: OutputFormatTable, + Command: nil, + } + for i := 0; i < len(args); i++ { + arg := args[i] + + switch arg { + case "-o", "--output": + // Parse output format + if i+1 < len(args) && !strings.HasPrefix(args[i+1], "-") { + format := args[i+1] + switch format { + case "plain": + commandLineConfig.OutputFormat = OutputFormatPlain + case "json": + commandLineConfig.OutputFormat = OutputFormatJSON + default: + commandLineConfig.OutputFormat = OutputFormatTable + } + i++ + } + case "-v", "--verbose": + commandLineConfig.Verbose = true + case "--admin", "-admin": + commandLineConfig.CLIMode = AdminMode + case "--help", "-help": + commandLineConfig.ShowHelp = true + default: + if !strings.HasPrefix(arg, "-") { + commandLineConfig.Interactive = false + } + } + } + + var commandArgs []string + var foundCommand bool + + switch commandLineConfig.CLIMode { + case APIMode: + defaultApiServerConfig := &APIServerConfig{ + UserName: nil, + UserPassword: nil, + ApiToken: nil, + } + + configFile := "rf.yml" + for i := 0; i < len(args); i++ { + arg := args[i] + + // If we've found the command, collect remaining args as subcommand args + if foundCommand { + commandArgs = append(commandArgs, arg) + continue + } + + switch arg { + case "-h", "--host": + if i+1 < len(args) && !strings.HasPrefix(args[i+1], "-") { + hostVal := args[i+1] + h, port, err := parseHostPort(hostVal) + if err != nil { + return nil, fmt.Errorf("invalid host format: %v", err) + } + defaultApiServerConfig.IP = h + defaultApiServerConfig.Port = port + i++ + } + case "-t", "--token": + if i+1 < len(args) && !strings.HasPrefix(args[i+1], "-") { + defaultApiServerConfig.ApiToken = &args[i+1] + i++ + } + case "-u", "--user": + if i+1 < len(args) && !strings.HasPrefix(args[i+1], "-") { + defaultApiServerConfig.UserName = &args[i+1] + i++ + } + case "-p", "--password": + if i+1 < len(args) && !strings.HasPrefix(args[i+1], "-") { + defaultApiServerConfig.UserPassword = &args[i+1] + i++ + } + case "-f", "--config": + if i+1 < len(args) && !strings.HasPrefix(args[i+1], "-") { + configFile = args[i+1] + // Convert to absolute path immediately + if !filepath.IsAbs(configFile) { + absPath, err := filepath.Abs(configFile) + if err == nil { + configFile = absPath + } + } + i++ + } + case "-o", "--output": + // Already handled above + if i+1 < len(args) { + i++ + } + continue + case "-v", "--verbose", "--help", "-help": + // Already handled above + continue + case "--admin", "-admin": + return nil, fmt.Errorf("unexpected parameter: --admin") + default: + // Non-flag argument (command) + if !strings.HasPrefix(arg, "-") { + commandArgs = append(commandArgs, arg) + foundCommand = true + } + } + } + + var config ConfigFile + data, err := os.ReadFile(configFile) + if err == nil { + if err = yaml.Unmarshal(data, &config); err != nil { + return nil, fmt.Errorf("failed to parse rf.yml: %v", err) + } + if config.Host != "" { + var h string + var port int + h, port, err = parseHostPort(config.Host) + if err != nil { + return nil, fmt.Errorf("invalid host in config file: %v", err) + } + if defaultApiServerConfig.IP == "" { + defaultApiServerConfig.IP = h + } + if defaultApiServerConfig.Port == 0 { + defaultApiServerConfig.Port = port + } + } + if config.UserName != "" { + if defaultApiServerConfig.UserName == nil { + defaultApiServerConfig.UserName = &config.UserName + } + } + if config.Password != "" { + if defaultApiServerConfig.UserPassword == nil { + defaultApiServerConfig.UserPassword = &config.Password + } + } + if config.APIToken != "" { + if defaultApiServerConfig.ApiToken == nil { + defaultApiServerConfig.ApiToken = &config.APIToken + } + } + } else { + if configFile == "rf.yml" && os.IsNotExist(err) { + } else { + return nil, fmt.Errorf("failed to read %s: %v", configFile, err) + } + } + + if defaultApiServerConfig.IP == "" { + defaultApiServerConfig.IP = "127.0.0.1" + } + if defaultApiServerConfig.Port == 0 { + defaultApiServerConfig.Port = 9384 + } + + commandLineConfig.APIClientConfig.APIServerMap = config.APIServerMap + if commandLineConfig.APIClientConfig.APIServerMap == nil { + commandLineConfig.APIClientConfig.APIServerMap = make(map[string]*APIServerConfig) + } + if commandLineConfig.APIClientConfig.APIServerMap[DefaultAPIServer] != nil { + return nil, fmt.Errorf("'Default' API server config should be in api_servers") + } + commandLineConfig.APIClientConfig.APIServerMap[DefaultAPIServer] = defaultApiServerConfig + commandLineConfig.APIClientConfig.CurrentAPIServer = DefaultAPIServer + case AdminMode: + AdminConfig := &AdminModeConfig{ + AdminHost: "127.0.0.1", + AdminPort: 9383, + //AdminName: "admin@ragflow.io", + //AdminPassword: "admin", + } + + for i := 0; i < len(args); i++ { + arg := args[i] + + // If we've found the command, collect remaining args as subcommand args + if foundCommand { + commandArgs = append(commandArgs, arg) + continue + } + + switch arg { + case "-h", "--host": + if i+1 < len(args) && !strings.HasPrefix(args[i+1], "-") { + hostVal := args[i+1] + h, port, err := parseHostPort(hostVal) + if err != nil { + return nil, fmt.Errorf("invalid host format: %v", err) + } + AdminConfig.AdminHost = h + AdminConfig.AdminPort = port + i++ + } + case "-t", "--token": + return nil, fmt.Errorf("token is invalid in admin mode") + case "-u", "--user": + if i+1 < len(args) && !strings.HasPrefix(args[i+1], "-") { + AdminConfig.AdminName = &args[i+1] + i++ + } + case "-p", "--password": + if i+1 < len(args) && !strings.HasPrefix(args[i+1], "-") { + AdminConfig.AdminPassword = &args[i+1] + i++ + } + case "-f", "--config": + return nil, fmt.Errorf("config is invalid in admin mode") + case "-o", "--output": + // Already handled above + if i+1 < len(args) { + i++ + } + continue + case "-v", "--verbose", "--admin", "-admin", "--help", "-help": + // Already handled above + continue + default: + // Non-flag argument (command) + if !strings.HasPrefix(arg, "-") { + commandArgs = append(commandArgs, arg) + foundCommand = true + } + } + } + commandLineConfig.AdminClientConfig = AdminConfig + } + + commandArgsLen := len(commandArgs) + if commandArgsLen > 0 { + if commandArgsLen == 1 { + commandLineConfig.Command = &commandArgs[0] + } else { + ApiCommand := strings.Join(commandArgs, " ") + commandLineConfig.Command = &ApiCommand + } + } + + return commandLineConfig, nil +} + // LoadDefaultConfigFile reads the rf.yml file from current directory if it exists func LoadDefaultConfigFile() (*ConfigFile, error) { // Try to read rf.yml from current directory @@ -125,225 +432,6 @@ func parseHostPort(hostPort string) (string, int, error) { return host, port, nil } -// ParseConnectionArgs parses command line arguments similar to Python's parse_connection_args -func ParseConnectionArgs(args []string) (*ConnectionArgs, error) { - // First, scan args to check for help, config file, admin mode, and verbose flag - var configFilePath string - var adminMode bool = false - var verboseMode bool = false - foundCommand := false - for i := 0; i < len(args); i++ { - arg := args[i] - // If we found a command (non-flag arg), stop processing global flags - // This allows subcommands like "search --help" to handle their own help - if !strings.HasPrefix(arg, "-") { - foundCommand = true - continue - } - // Only process --help as global help if it's before any command - if !foundCommand && (arg == "--help" || arg == "-help") { - return &ConnectionArgs{ShowHelp: true, Verbose: verboseMode}, nil - } else if (arg == "-f" || arg == "--config") && i+1 < len(args) { - configFilePath = args[i+1] - // Convert to absolute path immediately - if !filepath.IsAbs(configFilePath) { - absPath, err := filepath.Abs(configFilePath) - if err == nil { - configFilePath = absPath - } - } - i++ - } else if (arg == "-o" || arg == "--output") && i+1 < len(args) { - // -o/--output is allowed with config file, skip it and its value - i++ - continue - } else if arg == "--admin" { - adminMode = true - } else if arg == "-v" || arg == "--verbose" { - verboseMode = true - } - } - - // Load config file with priority: -f > rf.yml > none - var config *ConfigFile - var err error - - // Parse arguments manually to support both short and long forms - // and to handle priority: command line > config file > defaults - - result := &ConnectionArgs{ - Verbose: verboseMode, - ConfigFilePath: configFilePath, - } - - if !adminMode { - // Only user mode read config file - if configFilePath != "" { - // User specified config file via -f - config, err = LoadConfigFileFromPath(configFilePath) - if err != nil { - return nil, err - } - } else { - // Try default rf.yml - config, err = LoadDefaultConfigFile() - if err != nil { - return nil, err - } - } - - // Apply config file values first (lower priority) - if config != nil { - // Parse host:port from config file - if config.Host != "" { - h, port, err := parseHostPort(config.Host) - if err != nil { - return nil, fmt.Errorf("invalid host in config file: %v", err) - } - result.Host = h - result.Port = port - } - result.UserName = config.UserName - result.Password = config.Password - result.APIToken = config.APIToken - } - } - - // Get non-flag arguments (command to execute) - var nonFlagArgs []string - - // Override with command line flags (higher priority) - // Handle both short and long forms manually - // Once we encounter a non-flag argument (command), stop parsing global flags - // Remaining args belong to the subcommand - foundCommand = false - for i := 0; i < len(args); i++ { - arg := args[i] - - // If we've found the command, collect remaining args as subcommand args - if foundCommand { - nonFlagArgs = append(nonFlagArgs, arg) - continue - } - - switch arg { - case "-h", "--host": - if i+1 < len(args) && !strings.HasPrefix(args[i+1], "-") { - hostVal := args[i+1] - h, port, err := parseHostPort(hostVal) - if err != nil { - return nil, fmt.Errorf("invalid host format: %v", err) - } - result.Host = h - result.Port = port - i++ - } - case "-t", "--token": - if i+1 < len(args) && !strings.HasPrefix(args[i+1], "-") { - result.APIToken = args[i+1] - i++ - } - case "-u", "--user": - if i+1 < len(args) && !strings.HasPrefix(args[i+1], "-") { - result.UserName = args[i+1] - i++ - } - case "-p", "--password": - if i+1 < len(args) && !strings.HasPrefix(args[i+1], "-") { - result.Password = args[i+1] - i++ - } - case "-f", "--config": - // Skip config file path (already parsed) - if i+1 < len(args) { - i++ - } - case "-o", "--output": - // Parse output format - if i+1 < len(args) && !strings.HasPrefix(args[i+1], "-") { - format := args[i+1] - switch format { - case "plain": - result.OutputFormat = OutputFormatPlain - case "json": - result.OutputFormat = OutputFormatJSON - default: - result.OutputFormat = OutputFormatTable - } - i++ - } - case "-v", "--verbose": - result.Verbose = true - case "--admin", "-admin": - result.AdminMode = true - case "--help", "-help": - // Already handled above - continue - default: - // Non-flag argument (command) - if !strings.HasPrefix(arg, "-") { - nonFlagArgs = append(nonFlagArgs, arg) - foundCommand = true - } - } - } - - // Set defaults if not provided - if result.Host == "" { - result.Host = "127.0.0.1" - } - if result.Port == -1 || result.Port == 0 { - if result.AdminMode { - result.Port = 9383 - } else { - result.Port = 9384 - } - } - - if result.UserName == "" && result.Password != "" { - return nil, fmt.Errorf("username (-u/--user) is required when using password (-p/--password)") - } - - if result.AdminMode { - result.APIToken = "" - if result.UserName == "" { - result.UserName = "admin@ragflow.io" - result.Password = "" - } - } else { - // For user mode - // Validate mutual exclusivity: -t and (-u, -p) are mutually exclusive - hasToken := result.APIToken != "" - hasUserPass := result.UserName != "" || result.Password != "" - - if hasToken && hasUserPass { - return nil, fmt.Errorf("cannot use both API token (-t/--token) and username/password (-u/--user, -p/--password). Please use one authentication method") - } - } - - // Get command from remaining args (non-flag arguments) - if len(nonFlagArgs) > 0 { - // Check if this is SQL mode or ContextEngine mode - // SQL mode: single argument that looks like SQL (e.g., "LIST DATASETS") - // ContextEngine mode: multiple arguments (e.g., "ls", "datasets") - if len(nonFlagArgs) == 1 && looksLikeSQL(nonFlagArgs[0]) { - // SQL mode: single argument that looks like SQL - result.IsSQLMode = true - command := nonFlagArgs[0] - result.Command = &command - } else { - // ContextEngine mode: multiple arguments - result.IsSQLMode = false - result.CommandArgs = nonFlagArgs - // Also store joined version for backward compatibility - command := strings.Join(nonFlagArgs, " ") - result.Command = &command - } - } - - return result, nil -} - // looksLikeSQL checks if a string looks like a SQL command func looksLikeSQL(s string) bool { s = strings.ToUpper(strings.TrimSpace(s)) @@ -421,90 +509,128 @@ const historyFileName = ".ragflow_cli_history" // CLI represents the command line interface type CLI struct { - client *RAGFlowClient - contextEngine *filesystem.Engine - prompt string - running bool - line *liner.State - args *ConnectionArgs - outputFormat OutputFormat // Output format + //client *RAGFlowClient + //contextEngine *filesystem.Engine + running bool + line *liner.State + args *ConnectionArgs + outputFormat OutputFormat // Output format + + APIServerClientMap map[string]*HTTPClient + AdminServerClient *HTTPClient + PasswordPrompt PasswordPromptFunc // Function for password input + ContextEngine *filesystem.Engine // Context Engine for virtual filesystem + CurrentModel *CurrentModel // Current model configuration + Config *CommandLineConfig } // NewCLI creates a new CLI instance -func NewCLI() (*CLI, error) { - return NewCLIWithArgs(nil) -} +//func NewCLI() (*CLI, error) { +// return NewCLIWithArgs(nil) +//} -// NewCLIWithArgs creates a new CLI instance with connection arguments -func NewCLIWithArgs(args *ConnectionArgs) (*CLI, error) { +func NewCLIWithConfig(commandLineConfig *CommandLineConfig) (*CLI, error) { // Create liner first line := liner.NewLiner() - // Determine server type based on --admin or --user flag - // Default to "user" mode if not specified - serverType := "user" - if args != nil && args.AdminMode { - serverType = "admin" + cli := &CLI{ + line: line, + Config: commandLineConfig, } - // Create client with password prompt using liner - client := NewRAGFlowClient(serverType) - client.PasswordPrompt = line.PasswordPrompt - - // Apply connection arguments if provided - if args != nil { - client.HTTPClient.Host = args.Host - if args.Port > 0 { - client.HTTPClient.Port = args.Port + if commandLineConfig.CLIMode == APIMode { + apiServerConfig := commandLineConfig.APIClientConfig.APIServerMap[commandLineConfig.APIClientConfig.CurrentAPIServer] + httpClient := NewHTTPClient() + httpClient.Host = apiServerConfig.IP + httpClient.Port = apiServerConfig.Port + if apiServerConfig.ApiToken != nil { + httpClient.APIToken = apiServerConfig.ApiToken + httpClient.useAPIToken = true + } + cli.APIServerClientMap = map[string]*HTTPClient{ + cli.Config.APIClientConfig.CurrentAPIServer: httpClient, + } + // Auto-login if user and password are provided (from config file) + if apiServerConfig.UserName != nil && apiServerConfig.UserPassword != nil && apiServerConfig.ApiToken == nil { + if err := cli.LoginUserInteractive(*apiServerConfig.UserName, *apiServerConfig.UserPassword); err != nil { + line.Close() + return nil, fmt.Errorf("auto-login failed: %w", err) + } } - if args.APIToken != "" { - client.HTTPClient.APIToken = args.APIToken + engine := filesystem.NewEngine() + + // Register providers + // TODO: if http config change, engine http config won't be updated. They should share the same config + engine.RegisterProvider(filesystem.NewDatasetProvider(&httpClientAdapter{httpClient})) + engine.RegisterProvider(filesystem.NewFileProvider(&httpClientAdapter{httpClient})) + engine.RegisterProvider(filesystem.NewSkillProvider(&httpClientAdapter{httpClient})) + + cli.ContextEngine = engine + } else if commandLineConfig.CLIMode == AdminMode { + httpClient := NewHTTPClient() + httpClient.Host = commandLineConfig.AdminClientConfig.AdminHost + httpClient.Port = commandLineConfig.AdminClientConfig.AdminPort + cli.AdminServerClient = httpClient + + adminServerConfig := commandLineConfig.AdminClientConfig + // Auto-login if user and password are provided (from config file) + if adminServerConfig.AdminName != nil && adminServerConfig.AdminPassword != nil { + if err := cli.LoginUserInteractive(*adminServerConfig.AdminName, *adminServerConfig.AdminPassword); err != nil { + line.Close() + return nil, fmt.Errorf("auto-login failed: %w", err) + } } + + } else { + return nil, fmt.Errorf("invalid CLI mode: %s", commandLineConfig.CLIMode) } - // Apply API token if provided (from config file) - if args.APIToken != "" { - client.HTTPClient.APIToken = args.APIToken - client.HTTPClient.useAPIToken = true - } - - // Set output format - client.OutputFormat = args.OutputFormat - - // Auto-login if user and password are provided (from config file) - if args.UserName != "" && args.Password != "" && args.APIToken == "" { - if err := client.LoginUserInteractive(args.UserName, args.Password); err != nil { - line.Close() - return nil, fmt.Errorf("auto-login failed: %w", err) - } - } - - // Set prompt based on server type - prompt := "RAGFlow(user)> " - if serverType == "admin" { - prompt = "RAGFlow(admin)> " - } - - // Create filesystem engine and register providers - engine := filesystem.NewEngine() - engine.RegisterProvider(filesystem.NewDatasetProvider(&httpClientAdapter{client: client.HTTPClient})) - engine.RegisterProvider(filesystem.NewFileProvider(&httpClientAdapter{client: client.HTTPClient})) - engine.RegisterProvider(filesystem.NewSkillProvider(&httpClientAdapter{client: client.HTTPClient})) - - return &CLI{ - prompt: prompt, - client: client, - contextEngine: engine, - line: line, - args: args, - outputFormat: args.OutputFormat, - }, nil + return cli, nil } // Run starts the interactive CLI -func (c *CLI) Run() error { +func (c *CLI) NewRun() error { // If username is provided without password, prompt for password + cliConfig := c.Config + switch cliConfig.CLIMode { + case APIMode: + apiConfig := c.Config.APIClientConfig.APIServerMap[c.Config.APIClientConfig.CurrentAPIServer] + if apiConfig.UserName != nil && apiConfig.UserPassword == nil && apiConfig.ApiToken == nil { + // provider username but no password or api token + maxAttempts := 3 + for attempt := 1; attempt <= maxAttempts; attempt++ { + fmt.Printf("Please input your password for '%s': ", *apiConfig.UserName) + + password, err := ReadPassword() + + if password == "" { + if attempt < maxAttempts { + fmt.Println("Password cannot be empty, please try again") + continue + } + return errors.New("no password provided after 3 attempts") + } + + apiConfig.UserPassword = &password + + if err = c.VerifyAuth(); err != nil { + if attempt < maxAttempts { + fmt.Printf("Authentication failed: %v (%d/%d attempts)\n", err, attempt, maxAttempts) + continue + } + return fmt.Errorf("authentication failed after %d attempts: %v", maxAttempts, err) + } + + break + } + } + + case AdminMode: + default: + return fmt.Errorf("unexpected CLI mode: %s", cliConfig.CLIMode) + } + if c.args != nil && c.args.UserName != "" && c.args.Password == "" && c.args.APIToken == "" { maxAttempts := 3 for attempt := 1; attempt <= maxAttempts; attempt++ { @@ -557,7 +683,16 @@ func (c *CLI) Run() error { fmt.Println() for c.running { - input, err := c.line.Prompt(c.prompt) + var prompt string + switch cliConfig.CLIMode { + case APIMode: + prompt = fmt.Sprintf("RAGFlow(api/%s)> ", c.Config.APIClientConfig.CurrentAPIServer) + case AdminMode: + prompt = "RAGFlow(admin)> " + default: + return fmt.Errorf("unexpected CLI mode: %s", cliConfig.CLIMode) + } + input, err := c.line.Prompt(prompt) if err != nil { fmt.Printf("Error reading input: %v\n", err) continue @@ -584,7 +719,7 @@ func (c *CLI) Run() error { func (c *CLI) executeNew(input string) error { p := NewParser(input) - cmd, err := p.Parse(c.args.AdminMode) + cmd, err := p.Parse(c.Config.CLIMode) if err != nil { return err } @@ -600,7 +735,7 @@ func (c *CLI) executeNew(input string) error { // Execute the command using the client var result ResponseIf - result, err = c.client.ExecuteCommand(cmd) + result, err = c.ExecuteCommand(cmd) if result != nil { result.PrintOut() } @@ -614,7 +749,7 @@ func (c *CLI) execute(input string) error { // Handle meta commands (start with \) if strings.HasPrefix(input, "\\") { p := NewParser(input) - cmd, err := p.Parse(c.args.AdminMode) + cmd, err := p.Parse(c.Config.CLIMode) if err != nil { return err } @@ -636,7 +771,7 @@ func (c *CLI) execute(input string) error { if isSQLMode { // SQL mode: use parser p := NewParser(input) - cmd, err := p.Parse(c.args.AdminMode) + cmd, err := p.Parse(c.Config.CLIMode) if err != nil { return err } @@ -645,7 +780,7 @@ func (c *CLI) execute(input string) error { } // Execute SQL command using the client var result ResponseIf - result, err = c.client.ExecuteCommand(cmd) + result, err = c.ExecuteCommand(cmd) if result != nil { result.SetOutputFormat(c.outputFormat) result.PrintOut() @@ -674,7 +809,7 @@ func (c *CLI) executeFilesystem(input string) error { } // Check if we have a filesystem engine - if c.contextEngine == nil { + if c.ContextEngine == nil { return fmt.Errorf("filesystem engine not available") } @@ -684,6 +819,8 @@ func (c *CLI) executeFilesystem(input string) error { // Build filesystem command var ceCmd *filesystem.Command + httpClient := c.APIServerClientMap[c.Config.APIClientConfig.CurrentAPIServer] + switch cmdType { case "ls", "list": // Parse list command arguments @@ -729,7 +866,7 @@ func (c *CLI) executeFilesystem(input string) error { } } // Get skill provider and perform search - provider := c.contextEngine.GetProvider("skills") + provider := c.ContextEngine.GetProvider("skills") if provider == nil { return fmt.Errorf("skill provider not available") } @@ -770,7 +907,7 @@ func (c *CLI) executeFilesystem(input string) error { return fmt.Errorf("cat requires a path argument") } // Handle cat command directly since it returns []byte, not *Result - content, err := c.contextEngine.Cat(context.Background(), cmdArgs[0]) + content, err := c.ContextEngine.Cat(context.Background(), cmdArgs[0]) if err != nil { return err } @@ -784,58 +921,58 @@ func (c *CLI) executeFilesystem(input string) error { return nil case "install-skill": // Get the file provider and skill provider from the engine - fileProvider, ok := c.contextEngine.GetProvider("files").(*filesystem.FileProvider) + fileProvider, ok := c.ContextEngine.GetProvider("files").(*filesystem.FileProvider) if !ok { return fmt.Errorf("file provider not available") } - skillProvider := c.contextEngine.GetProvider("skills") + skillProvider := c.ContextEngine.GetProvider("skills") if skillProvider == nil { return fmt.Errorf("skill provider not available") } // Create adapter for HTTPClient - httpAdapter := &httpClientAdapter{client: c.client.HTTPClient} + httpAdapter := &httpClientAdapter{client: httpClient} cmd := filesystem.NewInstallSkillCommand(httpAdapter, fileProvider, skillProvider) return cmd.Execute(cmdArgs) case "uninstall-skill": - skillProvider := c.contextEngine.GetProvider("skills") + skillProvider := c.ContextEngine.GetProvider("skills") if skillProvider == nil { return fmt.Errorf("skill provider not available") } - fileProvider := c.contextEngine.GetProvider("files") + fileProvider := c.ContextEngine.GetProvider("files") if fileProvider == nil { return fmt.Errorf("file provider not available") } // Create adapter for HTTPClient - httpAdapter := &httpClientAdapter{client: c.client.HTTPClient} + httpAdapter := &httpClientAdapter{client: httpClient} fileProv, _ := fileProvider.(*filesystem.FileProvider) cmd := filesystem.NewUninstallSkillCommand(httpAdapter, skillProvider, fileProv) return cmd.Execute(cmdArgs) case "add-skill": fmt.Println("⚠ Warning: 'add-skill' is deprecated. Use 'install-skill' instead.") // Forward to install-skill - fileProvider, ok := c.contextEngine.GetProvider("files").(*filesystem.FileProvider) + fileProvider, ok := c.ContextEngine.GetProvider("files").(*filesystem.FileProvider) if !ok { return fmt.Errorf("file provider not available") } - skillProvider := c.contextEngine.GetProvider("skills") + skillProvider := c.ContextEngine.GetProvider("skills") if skillProvider == nil { return fmt.Errorf("skill provider not available") } - httpAdapter := &httpClientAdapter{client: c.client.HTTPClient} + httpAdapter := &httpClientAdapter{client: httpClient} cmd := filesystem.NewInstallSkillCommand(httpAdapter, fileProvider, skillProvider) return cmd.Execute(cmdArgs) case "delete-skill": fmt.Println("⚠ Warning: 'delete-skill' is deprecated. Use 'uninstall-skill' instead.") // Forward to uninstall-skill - skillProvider := c.contextEngine.GetProvider("skills") + skillProvider := c.ContextEngine.GetProvider("skills") if skillProvider == nil { return fmt.Errorf("skill provider not available") } - fileProvider := c.contextEngine.GetProvider("files") + fileProvider := c.ContextEngine.GetProvider("files") if fileProvider == nil { return fmt.Errorf("file provider not available") } - httpAdapter := &httpClientAdapter{client: c.client.HTTPClient} + httpAdapter := &httpClientAdapter{client: httpClient} fileProv, _ := fileProvider.(*filesystem.FileProvider) cmd := filesystem.NewUninstallSkillCommand(httpAdapter, skillProvider, fileProv) return cmd.Execute(cmdArgs) @@ -845,7 +982,7 @@ func (c *CLI) executeFilesystem(input string) error { } // Execute the command - result, err := c.contextEngine.Execute(context.Background(), ceCmd) + result, err := c.ContextEngine.Execute(context.Background(), ceCmd) if err != nil { return err } @@ -1151,7 +1288,7 @@ func (c *CLI) printSkillSearchResults(result *filesystem.Result, format OutputFo func (c *CLI) handleMetaCommand(cmd *Command) error { command := cmd.Params["command"].(string) - args, _ := cmd.Params["args"].([]string) + //args, _ := cmd.Params["args"].([]string) switch command { case "q", "quit", "exit": @@ -1159,40 +1296,6 @@ func (c *CLI) handleMetaCommand(cmd *Command) error { c.running = false case "?", "h", "help": c.printHelp() - case "c", "clear": - // Clear screen (simple approach) - fmt.Print("\033[H\033[2J") - case "admin": - c.client.ServerType = "admin" - c.prompt = "RAGFlow(admin)> " - fmt.Println("Switched to ADMIN mode") - case "user": - c.client.ServerType = "user" - c.prompt = "RAGFlow(user)> " - fmt.Println("Switched to USER mode") - case "host": - if len(args) == 0 { - fmt.Printf("Current host: %s\n", c.client.HTTPClient.Host) - } else { - c.client.HTTPClient.Host = args[0] - fmt.Printf("Host set to: %s\n", args[0]) - } - case "port": - if len(args) == 0 { - fmt.Printf("Current port: %d\n", c.client.HTTPClient.Port) - } else { - port, err := strconv.Atoi(args[0]) - if err != nil { - return fmt.Errorf("invalid port number: %s", args[0]) - } - if port < 1 || port > 65535 { - return fmt.Errorf("port must be between 1 and 65535") - } - c.client.HTTPClient.Port = port - fmt.Printf("Port set to: %d\n", port) - } - case "status": - fmt.Printf("Server: %s:%d (mode: %s)\n", c.client.HTTPClient.Host, c.client.HTTPClient.Port, c.client.ServerType) default: return fmt.Errorf("unknown meta command: \\%s", command) } @@ -1275,37 +1378,37 @@ func (c *CLI) Cleanup() { } } -// RunInteractive runs the CLI in interactive mode -func RunInteractive() error { - cli, err := NewCLI() - if err != nil { - return fmt.Errorf("failed to create CLI: %v", err) - } - - // Handle interrupt signal - sigChan := make(chan os.Signal, 1) - signal.Notify(sigChan, syscall.SIGINT, syscall.SIGTERM) - go func() { - <-sigChan - cli.Cleanup() - os.Exit(0) - }() - - return cli.Run() -} - // RunSingleCommand executes a single command and exits func (c *CLI) RunSingleCommand(command *string) error { // Ensure cleanup is called on exit to restore terminal settings defer c.Cleanup() // Execute the command - if err := c.execute(*command); err != nil { + if err := c.executeNew(*command); err != nil { return err } return nil } +// VerifyAuth verifies authentication if needed +func (c *CLI) NewVerifyAuth(username, password *string) error { + // Otherwise, use username/password authentication + if username == nil { + return fmt.Errorf("username is required") + } + + if password == nil { + return fmt.Errorf("password is required") + } + + // Create login command with username and password + cmd := NewCommand("login_user") + cmd.Params["email"] = *username + cmd.Params["password"] = *password + _, err := c.ExecuteCommand(cmd) + return err +} + // VerifyAuth verifies authentication if needed func (c *CLI) VerifyAuth() error { if c.args == nil { @@ -1331,7 +1434,7 @@ func (c *CLI) VerifyAuth() error { cmd := NewCommand("login_user") cmd.Params["email"] = c.args.UserName cmd.Params["password"] = c.args.Password - _, err := c.client.ExecuteCommand(cmd) + _, err := c.ExecuteCommand(cmd) return err } diff --git a/internal/cli/cli_http.go b/internal/cli/cli_http.go new file mode 100644 index 0000000000..03abbef220 --- /dev/null +++ b/internal/cli/cli_http.go @@ -0,0 +1,304 @@ +// +// 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 "fmt" + +// ExecuteCommand executes a parsed command +// Returns benchmark result map for commands that support it (e.g., ping_server with iterations > 1) +func (c *CLI) ExecuteCommand(cmd *Command) (ResponseIf, error) { + switch c.Config.CLIMode { + case APIMode: + // Interactive mode: execute command with user privileges + return c.ExecuteUserCommand(cmd) + case AdminMode: + // Admin mode: execute command with admin privileges + return c.ExecuteAdminCommand(cmd) + default: + return nil, fmt.Errorf("invalid server type: %s", c.Config.CLIMode) + } +} + +func (c *CLI) ExecuteAdminCommand(cmd *Command) (ResponseIf, error) { + switch cmd.Type { + case "login_user": + return c.LoginUserByCommand(cmd) + case "logout": + return c.Logout() + case "ping": + return c.PingByCommand(cmd) + case "benchmark": + return c.RunBenchmark(cmd) + case "list_users": + return c.ListUsers(cmd) + case "list_services": + return c.ListServices(cmd) + case "grant_admin": + return c.GrantAdmin(cmd) + case "revoke_admin": + return c.RevokeAdmin(cmd) + case "create_user": + return c.CreateUser(cmd) + case "activate_user": + return c.ActivateUser(cmd) + case "alter_user": + return c.AlterUserPassword(cmd) + case "drop_user": + return c.DropUser(cmd) + case "show_service": + return c.ShowService(cmd) + case "show_version": + return c.ShowAdminVersion(cmd) + case "show_current": + return c.ShowCommonCurrent(cmd) + case "show_user": + return c.ShowUser(cmd) + case "list_variables": + return c.ListVariables(cmd) + case "show_variable": + return c.ShowVariable(cmd) + case "set_variable": + return c.SetVariable(cmd) + case "list_user_datasets": + return c.ListUserDatasets(cmd) + case "list_agents": + return c.ListAgents(cmd) + case "generate_token": + return c.GenerateAdminToken(cmd) + case "list_tokens": + return c.ListAdminTokens(cmd) + case "drop_token": + return c.DropAdminToken(cmd) + case "list_available_providers": + return c.ListAvailableProviders(cmd) + case "show_provider": + return c.ShowProvider(cmd) + case "list_provider_models": + return c.ListModels(cmd) + case "list_supported_models": + return c.ListSupportedModels(cmd) + case "list_instance_models": + return c.ListInstanceModels(cmd) + case "show_provider_model": + return c.ShowProviderModel(cmd) + case "show_model": + return c.ShowModel(cmd) + case "list_all_models": + return c.ListAllModels(cmd) + case "list_admin_tasks": + return c.ListAdminTasks(cmd) + case "admin_list_ingestors": + return c.ListAdminIngestors(cmd) + case "admin_start_ingestion_command": + return c.AdminStartIngestionCommand(cmd) + case "admin_stop_ingestion_command": + return c.AdminStopIngestionCommand(cmd) + case "admin_shutdown_ingestor_command": + return c.AdminShutdownIngestor(cmd) + case "list_admin_ingestion_tasks": + return c.ListAdminIngestionTasks(cmd) + case "show_admin_server": + return c.ShowAdminServer(cmd) + case "show_api_server": + return c.ShowAPIServer(cmd) + case "list_api_server": + return c.ListAPIServer(cmd) + case "add_api_server": + return c.AddAPIServer(cmd) + case "delete_api_server": + return c.DeleteAPIServer(cmd) + case "add_admin_server": + return nil, fmt.Errorf("cannot add admin server in admin mode") + case "delete_admin_server": + return nil, fmt.Errorf("cannot delete admin server in admin mode") + case "save_config_command": + return c.SaveServerConfig(cmd) + // TODO: Implement other commands + default: + return nil, fmt.Errorf("command '%s' would be executed with API", cmd.Type) + } +} +func (c *CLI) ExecuteUserCommand(cmd *Command) (ResponseIf, error) { + switch cmd.Type { + case "register_user": + return c.RegisterUser(cmd) + case "login_user": + return c.LoginUserByCommand(cmd) + case "logout": + return c.Logout() + case "ping": + return c.PingByCommand(cmd) + // Configuration commands + case "list_configs": + return c.ListConfigs(cmd) + case "set_log_level": + return c.SetLogLevel(cmd) + case "benchmark": + return c.RunBenchmark(cmd) + case "list_datasets": + return c.ListDatasets(cmd) + case "list_dataset_documents": + return c.ListDatasetDocumentUserCommand(cmd) + case "search_on_datasets": + return c.SearchOnDatasets(cmd) + case "search_help": + printSearchHelp() + return nil, nil + case "create_token": + return c.CreateToken(cmd) + case "list_tokens": + return c.ListTokens(cmd) + case "drop_token": + return c.DropToken(cmd) + case "set_token": + return c.SetToken(cmd) + case "show_token": + return c.ShowToken(cmd) + case "unset_token": + return c.UnsetToken(cmd) + case "show_version": + return c.ShowServerVersion(cmd) + case "show_current": + return c.ShowCommonCurrent(cmd) + case "list_available_providers": + return c.ListAvailableProviders(cmd) + case "show_provider": + return c.ShowProvider(cmd) + case "list_provider_models": + return c.ListModels(cmd) + case "list_supported_models": + return c.ListSupportedModels(cmd) + case "list_instance_models": + return c.ListInstanceModels(cmd) + case "show_provider_model": + return c.ShowProviderModel(cmd) + case "show_model": + return c.ShowModel(cmd) + case "list_all_models": + return c.ListAllModels(cmd) + // Provider commands + case "add_provider": + return c.AddProvider(cmd) + case "list_providers": + return c.ListProviders(cmd) + case "delete_provider": + return c.DeleteProvider(cmd) + // Provider instance commands + case "create_provider_instance": + return c.CreateProviderInstance(cmd) + case "list_provider_instances": + return c.ListProviderInstances(cmd) + case "show_provider_instance": + return c.ShowProviderInstance(cmd) + case "show_instance_balance": + return c.ShowInstanceBalance(cmd) + case "alter_provider_instance": + return c.AlterProviderInstance(cmd) + case "drop_provider_instance": + return c.DropProviderInstance(cmd) + case "drop_instance_model": + return c.DropInstanceModel(cmd) + case "enable_model": + return c.EnableOrDisableModel(cmd, "enable") + case "disable_model": + return c.EnableOrDisableModel(cmd, "disable") + case "add_custom_model": + return c.AddCustomModel(cmd) + case "chat_to_model": + return c.ChatToModel(cmd) + case "think_chat_to_model": + return c.ChatToModel(cmd) + case "embed_user_text": + return c.EmbedUserText(cmd) + case "rarank_user_document": + return c.RerankUserDocument(cmd) + case "tts_user_command": + return c.TTSUserCommand(cmd) + case "asr_user_command": + return c.ASRUserCommand(cmd) + case "ocr_user_command": + return c.OCRUserCommand(cmd) + case "parse_file_user_command": + return c.ParseFileUserCommand(cmd) + case "check_provider_connection": + return c.CheckProviderConnection(cmd) + case "check_provider_with_key": + return c.CheckProviderWithKey(cmd) + case "use_model": + return c.UseModel(cmd) + case "set_default_model": + return c.SetDefaultModel(cmd) + case "reset_default_model": + return c.ResetDefaultModel(cmd) + case "list_user_default_models": + return c.ListDefaultModels(cmd) + case "list_tasks_user_command": + return c.ListTasksUserCommand(cmd) + case "show_task_user_command": + return c.ShowTaskUserCommand(cmd) + case "create_chunk_store": + return c.CreateChunkStore(cmd) + case "drop_chunk_store": + return c.DropChunkStore(cmd) + case "create_metadata_store": + return c.CreateMetadataStore(cmd) + case "drop_metadata_store": + return c.DropMetadataStore(cmd) + case "insert_chunks_from_file": + return c.InsertChunksFromFile(cmd) + case "insert_metadata_from_file": + return c.InsertMetadataFromFile(cmd) + case "update_chunk": + return c.UpdateChunk(cmd) + case "get_chunk": + return c.GetChunk(cmd) + case "set_meta": + return c.SetMeta(cmd) + case "delete_meta": + return c.DeleteMeta(cmd) + case "rm_tags": + return c.RmTags(cmd) + case "remove_chunks": + return c.RemoveChunks(cmd) + case "get_metadata": + return c.GetMetadata(cmd) + case "parse_documents_user_command": + return c.ParseDocumentsUserCommand(cmd) + case "show_admin_server": + return c.ShowAdminServer(cmd) + case "show_api_server": + return c.ShowAPIServer(cmd) + case "list_api_server": + return c.ListAPIServer(cmd) + case "add_api_server": + return c.AddAPIServer(cmd) + case "delete_api_server": + return c.DeleteAPIServer(cmd) + case "add_admin_server": + return c.AddAdminServer(cmd) + case "delete_admin_server": + return c.DeleteAdminServer(cmd) + case "save_config_command": + return c.SaveServerConfig(cmd) + // ContextEngine commands + case "context_engine_command": + return nil, c.executeFilesystem(cmd.Params["command"].(string)) + default: + return nil, fmt.Errorf("command '%s' would be executed with API", cmd.Type) + } + +} diff --git a/internal/cli/client.go b/internal/cli/client.go index 297cf4715e..806394a053 100644 --- a/internal/cli/client.go +++ b/internal/cli/client.go @@ -84,9 +84,9 @@ func (a *httpClientAdapter) Request(method, path string, authKind string, header // Auto-detect auth kind based on available tokens // If authKind is "auto" or empty, determine based on token availability if authKind == "auto" || authKind == "" { - if a.client.useAPIToken && a.client.APIToken != "" { + if a.client.useAPIToken && a.client.APIToken != nil { authKind = "api" - } else if a.client.LoginToken != "" { + } else if a.client.LoginToken != nil { authKind = "web" } else { authKind = "web" // default @@ -108,261 +108,6 @@ func (a *httpClientAdapter) UploadMultipart(path string, contentType string, bod return a.client.UploadMultipart(path, contentType, body) } -// ExecuteCommand executes a parsed command -// Returns benchmark result map for commands that support it (e.g., ping_server with iterations > 1) -func (c *RAGFlowClient) ExecuteCommand(cmd *Command) (ResponseIf, error) { - switch c.ServerType { - case "admin": - // Admin mode: execute command with admin privileges - return c.ExecuteAdminCommand(cmd) - case "user": - // User mode: execute command with user privileges - return c.ExecuteUserCommand(cmd) - default: - return nil, fmt.Errorf("invalid server type: %s", c.ServerType) - } -} - -func (c *RAGFlowClient) ExecuteAdminCommand(cmd *Command) (ResponseIf, error) { - switch cmd.Type { - case "login_user": - return nil, c.LoginUser(cmd) - case "logout": - return c.Logout() - case "ping": - return c.PingAdmin(cmd) - case "benchmark": - return c.RunBenchmark(cmd) - case "list_users": - return c.ListUsers(cmd) - case "list_services": - return c.ListServices(cmd) - case "grant_admin": - return c.GrantAdmin(cmd) - case "revoke_admin": - return c.RevokeAdmin(cmd) - case "create_user": - return c.CreateUser(cmd) - case "activate_user": - return c.ActivateUser(cmd) - case "alter_user": - return c.AlterUserPassword(cmd) - case "drop_user": - return c.DropUser(cmd) - case "show_service": - return c.ShowService(cmd) - case "show_version": - return c.ShowAdminVersion(cmd) - case "show_user": - return c.ShowUser(cmd) - case "list_variables": - return c.ListVariables(cmd) - case "show_variable": - return c.ShowVariable(cmd) - case "set_variable": - return c.SetVariable(cmd) - case "list_user_datasets": - return c.ListUserDatasets(cmd) - case "list_agents": - return c.ListAgents(cmd) - case "generate_token": - return c.GenerateAdminToken(cmd) - case "list_tokens": - return c.ListAdminTokens(cmd) - case "drop_token": - return c.DropAdminToken(cmd) - case "list_available_providers": - return c.ListAvailableProviders(cmd) - case "show_provider": - return c.ShowProvider(cmd) - case "list_provider_models": - return c.ListModels(cmd) - case "list_supported_models": - return c.ListSupportedModels(cmd) - case "list_instance_models": - return c.ListInstanceModels(cmd) - case "show_provider_model": - return c.ShowProviderModel(cmd) - case "list_all_models": - return c.ListAllModels(cmd) - case "show_model": - return c.ShowModel(cmd) - case "list_admin_tasks": - return c.ListAdminTasks(cmd) - case "admin_list_ingestors": - return c.ListAdminIngestors(cmd) - case "admin_start_ingestion_command": - return c.AdminStartIngestionCommand(cmd) - case "admin_stop_ingestion_command": - return c.AdminStopIngestionCommand(cmd) - case "admin_shutdown_ingestor_command": - return c.AdminShutdownIngestor(cmd) - case "list_admin_ingestion_tasks": - return c.ListAdminIngestionTasks(cmd) - // TODO: Implement other commands - default: - return nil, fmt.Errorf("command '%s' would be executed with API", cmd.Type) - } -} -func (c *RAGFlowClient) ExecuteUserCommand(cmd *Command) (ResponseIf, error) { - switch cmd.Type { - case "register_user": - return c.RegisterUser(cmd) - case "login_user": - return nil, c.LoginUser(cmd) - case "logout": - return c.Logout() - case "ping": - return c.PingServer(cmd) - // Configuration commands - case "list_configs": - return c.ListConfigs(cmd) - case "set_log_level": - return c.SetLogLevel(cmd) - case "benchmark": - return c.RunBenchmark(cmd) - case "list_datasets": - return c.ListDatasets(cmd) - case "list_dataset_documents": - return c.ListDatasetDocumentUserCommand(cmd) - case "search_on_datasets": - return c.SearchOnDatasets(cmd) - case "search_help": - printSearchHelp() - return nil, nil - case "create_token": - return c.CreateToken(cmd) - case "list_tokens": - return c.ListTokens(cmd) - case "drop_token": - return c.DropToken(cmd) - case "set_token": - return c.SetToken(cmd) - case "show_token": - return c.ShowToken(cmd) - case "unset_token": - return c.UnsetToken(cmd) - case "show_version": - return c.ShowServerVersion(cmd) - case "list_available_providers": - return c.ListAvailableProviders(cmd) - case "show_provider": - return c.ShowProvider(cmd) - case "list_provider_models": - return c.ListModels(cmd) - case "list_supported_models": - return c.ListSupportedModels(cmd) - case "list_instance_models": - return c.ListInstanceModels(cmd) - case "show_provider_model": - return c.ShowProviderModel(cmd) - case "list_all_models": - return c.ListAllModels(cmd) - case "show_model": - return c.ShowModel(cmd) - // Provider commands - case "add_provider": - return c.AddProvider(cmd) - case "list_providers": - return c.ListProviders(cmd) - case "delete_provider": - return c.DeleteProvider(cmd) - // Provider instance commands - case "create_provider_instance": - return c.CreateProviderInstance(cmd) - case "list_provider_instances": - return c.ListProviderInstances(cmd) - case "show_provider_instance": - return c.ShowProviderInstance(cmd) - case "show_instance_balance": - return c.ShowInstanceBalance(cmd) - case "alter_provider_instance": - return c.AlterProviderInstance(cmd) - case "drop_provider_instance": - return c.DropProviderInstance(cmd) - case "drop_instance_model": - return c.DropInstanceModel(cmd) - case "enable_model": - return c.EnableOrDisableModel(cmd, "enable") - case "disable_model": - return c.EnableOrDisableModel(cmd, "disable") - case "add_custom_model": - return c.AddCustomModel(cmd) - case "chat_to_model": - return c.ChatToModel(cmd) - case "think_chat_to_model": - return c.ChatToModel(cmd) - case "embed_user_text": - return c.EmbedUserText(cmd) - case "rarank_user_document": - return c.RerankUserDocument(cmd) - case "tts_user_command": - return c.TTSUserCommand(cmd) - case "asr_user_command": - return c.ASRUserCommand(cmd) - case "ocr_user_command": - return c.OCRUserCommand(cmd) - case "parse_file_user_command": - return c.ParseFileUserCommand(cmd) - case "check_provider_connection": - return c.CheckProviderConnection(cmd) - case "check_provider_with_key": - return c.CheckProviderWithKey(cmd) - case "use_model": - return c.UseModel(cmd) - case "show_current_model": - return c.ShowCurrentModel(cmd) - case "set_default_model": - return c.SetDefaultModel(cmd) - case "reset_default_model": - return c.ResetDefaultModel(cmd) - case "list_user_default_models": - return c.ListDefaultModels(cmd) - case "list_tasks_user_command": - return c.ListTasksUserCommand(cmd) - case "show_task_user_command": - return c.ShowTaskUserCommand(cmd) - case "create_chunk_store": - return c.CreateChunkStore(cmd) - case "drop_chunk_store": - return c.DropChunkStore(cmd) - case "create_metadata_store": - return c.CreateMetadataStore(cmd) - case "drop_metadata_store": - return c.DropMetadataStore(cmd) - case "insert_chunks_from_file": - return c.InsertChunksFromFile(cmd) - case "insert_metadata_from_file": - return c.InsertMetadataFromFile(cmd) - case "update_chunk": - return c.UpdateChunk(cmd) - case "get_chunk": - return c.GetChunk(cmd) - case "set_meta": - return c.SetMeta(cmd) - case "delete_meta": - return c.DeleteMeta(cmd) - case "rm_tags": - return c.RmTags(cmd) - case "remove_chunks": - return c.RemoveChunks(cmd) - case "get_metadata": - return c.GetMetadata(cmd) - case "parse_documents_user_command": - return c.ParseDocumentsUserCommand(cmd) - // ContextEngine commands - case "ce_ls": - return c.CEList(cmd) - case "ce_cat": - return c.CECat(cmd) - case "ce_search": - return c.CESearch(cmd) - // TODO: Implement other commands - default: - return nil, fmt.Errorf("command '%s' would be executed with API", cmd.Type) - } -} - // ShowCurrentUser shows the current logged-in user information // TODO: Implement showing current user information when API is available func (c *RAGFlowClient) ShowCurrentUser(cmd *Command) (map[string]interface{}, error) { diff --git a/internal/cli/common_command.go b/internal/cli/common_command.go index b75c3cc025..b1161f4b99 100644 --- a/internal/cli/common_command.go +++ b/internal/cli/common_command.go @@ -18,126 +18,50 @@ package cli import ( "bufio" + "crypto/tls" "encoding/json" "fmt" + "net/http" "os" "strings" + "time" "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 - if c.ServerType == "admin" { - pingPath = "/admin/ping" - } else { - pingPath = "/system/ping" +func (c *CLI) LoginUserByCommand(cmd *Command) (ResponseIf, error) { + email, ok := cmd.Params["email"].(string) + if !ok { + return nil, fmt.Errorf("email not provided") + } + password, ok := cmd.Params["password"].(string) + if !ok { + password = "" } - resp, err := c.HTTPClient.Request("GET", pingPath, "web", nil, nil) + err := c.LoginUserInteractive(email, password) + if err != nil { + return nil, err + } + + var result SimpleResponse + result.Code = 0 + result.SetOutputFormat(c.outputFormat) + result.Message = "Login successful" + + return &result, nil +} + +// LoginUserInteractive performs interactive login with username and password +func (c *CLI) LoginUserInteractive(email, password string) error { + // First, ping the server to check if it's available + _, err := c.PingServer(1) 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 - if c.ServerType == "admin" { - pingPath = "/admin/ping" - } else { - pingPath = "/system/ping" - } - - resp, err := c.HTTPClient.Request("GET", pingPath, "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 { @@ -154,13 +78,87 @@ func (c *RAGFlowClient) LoginUser(cmd *Command) error { return err } - c.HTTPClient.LoginToken = token fmt.Printf("Login user %s successfully\n", email) + + switch c.Config.CLIMode { + case AdminMode: + c.AdminServerClient.LoginToken = &token + c.Config.AdminClientConfig.AdminName = &email + c.Config.AdminClientConfig.AdminPassword = &password + case APIMode: + c.APIServerClientMap[c.Config.APIClientConfig.CurrentAPIServer].LoginToken = &token + c.Config.APIClientConfig.APIServerMap[c.Config.APIClientConfig.CurrentAPIServer].UserName = &email + c.Config.APIClientConfig.APIServerMap[c.Config.APIClientConfig.CurrentAPIServer].UserPassword = &password + default: + return fmt.Errorf("invalid server type") + } return nil } +func (c *CLI) PingByCommand(cmd *Command) (ResponseIf, error) { + iterations := 1 + if iterationsParam, ok := cmd.Params["iterations"]; ok { + iterations = int(iterationsParam.(float64)) + } + return c.PingServer(iterations) +} + +func (c *CLI) PingServer(iterations int) (ResponseIf, error) { + var pingPath string + var resp *Response + var err error + switch c.Config.CLIMode { + case AdminMode: + pingPath = "/admin/ping" + if iterations > 1 { + return c.AdminServerClient.RequestWithIterations("GET", pingPath, "web", nil, nil, iterations) + } + resp, err = c.AdminServerClient.Request("GET", pingPath, "web", nil, nil) + case APIMode: + pingPath = "/system/ping" + if iterations > 1 { + return c.APIServerClientMap[c.Config.APIClientConfig.CurrentAPIServer].RequestWithIterations("GET", pingPath, "web", nil, nil, iterations) + } + resp, err = c.APIServerClientMap[c.Config.APIClientConfig.CurrentAPIServer].Request("GET", pingPath, "web", nil, nil) + default: + return nil, fmt.Errorf("invalid server type") + } + + if err != nil { + fmt.Printf("Error: %v\n", err) + fmt.Println("Can't access server for login (connection failed)") + return nil, err + } + + if resp.StatusCode != 200 { + fmt.Println("Server is down") + return nil, fmt.Errorf("server is down") + } + + var result SimpleResponse + switch c.Config.CLIMode { + case AdminMode: + if err = json.Unmarshal(resp.Body, &result); err != nil { + return nil, fmt.Errorf("list users failed: invalid JSON (%w)", err) + } + case APIMode: + if string(resp.Body) == "pong" { + result.Code = 0 + result.Message = "Pong" + } else { + result.Code = 1 + result.Message = "Ping failed" + } + default: + return nil, fmt.Errorf("invalid server type") + } + + result.Duration = resp.Duration + return &result, nil +} + // loginUser performs the actual login request -func (c *RAGFlowClient) loginUser(email, password string) (string, error) { +func (c *CLI) loginUser(email, password string) (string, error) { // Encrypt password using scrypt (same as Python implementation) encryptedPassword, err := EncryptPassword(password) if err != nil { @@ -172,14 +170,16 @@ func (c *RAGFlowClient) loginUser(email, password string) (string, error) { "password": encryptedPassword, } - var path string - if c.ServerType == "admin" { - path = "/admin/login" - } else { - path = "/auth/login" + var resp *Response + switch c.Config.CLIMode { + case AdminMode: + resp, err = c.AdminServerClient.Request("POST", "/admin/login", "", nil, payload) + case APIMode: + resp, err = c.APIServerClientMap[c.Config.APIClientConfig.CurrentAPIServer].Request("POST", "/auth/login", "", nil, payload) + default: + return "", fmt.Errorf("invalid server type") } - resp, err := c.HTTPClient.Request("POST", path, "", nil, payload) if err != nil { return "", err } @@ -201,19 +201,25 @@ func (c *RAGFlowClient) loginUser(email, password string) (string, error) { return token, nil } -func (c *RAGFlowClient) Logout() (ResponseIf, error) { - if c.HTTPClient.LoginToken == "" { - return nil, fmt.Errorf("not logged in") +func (c *CLI) Logout() (ResponseIf, error) { + + var resp *Response + var err error + switch c.Config.CLIMode { + case AdminMode: + if c.AdminServerClient.LoginToken == nil { + return nil, fmt.Errorf("not logged in") + } + resp, err = c.AdminServerClient.Request("POST", "/admin/logout", "web", nil, nil) + case APIMode: + if c.APIServerClientMap[c.Config.APIClientConfig.CurrentAPIServer].LoginToken == nil { + return nil, fmt.Errorf("not logged in") + } + resp, err = c.APIServerClientMap[c.Config.APIClientConfig.CurrentAPIServer].Request("POST", "/auth/logout", "web", nil, nil) + default: + return nil, fmt.Errorf("invalid server type") } - var path string - if c.ServerType == "admin" { - path = "/admin/logout" - } else { - path = "/auth/logout" - } - - resp, err := c.HTTPClient.Request("POST", path, "web", nil, nil) if err != nil { return nil, err } @@ -227,19 +233,35 @@ func (c *RAGFlowClient) Logout() (ResponseIf, error) { return nil, fmt.Errorf("login failed: %s", result.Message) } + switch c.Config.CLIMode { + case AdminMode: + c.AdminServerClient.LoginToken = nil + c.Config.AdminClientConfig.AdminName = nil + c.Config.AdminClientConfig.AdminPassword = nil + case APIMode: + c.APIServerClientMap[c.Config.APIClientConfig.CurrentAPIServer].LoginToken = nil + c.Config.APIClientConfig.APIServerMap[c.Config.APIClientConfig.CurrentAPIServer].UserName = nil + c.Config.APIClientConfig.APIServerMap[c.Config.APIClientConfig.CurrentAPIServer].UserPassword = nil + default: + return nil, fmt.Errorf("invalid server type") + } + return &result, nil } -func (c *RAGFlowClient) ListAvailableProviders(cmd *Command) (ResponseIf, error) { +func (c *CLI) 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") + var resp *Response + var err error + switch c.Config.CLIMode { + case AdminMode: + resp, err = c.AdminServerClient.Request("GET", "/admin/providers?available=true", "web", nil, nil) + case APIMode: + resp, err = c.APIServerClientMap[c.Config.APIClientConfig.CurrentAPIServer].Request("GET", "/providers?available=true", "web", nil, nil) + default: + return nil, fmt.Errorf("invalid server type") } - resp, err := c.HTTPClient.Request("GET", endPoint, "web", nil, nil) if err != nil { return nil, fmt.Errorf("failed to list providers: %w", err) } @@ -260,20 +282,26 @@ func (c *RAGFlowClient) ListAvailableProviders(cmd *Command) (ResponseIf, error) return &result, nil } -func (c *RAGFlowClient) ShowProvider(cmd *Command) (ResponseIf, error) { +func (c *CLI) ShowProvider(cmd *Command) (ResponseIf, error) { providerName, ok := cmd.Params["provider_name"].(string) if !ok { return nil, fmt.Errorf("provider_name not provided") } + var resp *Response + var err error var endPoint string - if c.ServerType == "admin" { + switch c.Config.CLIMode { + case AdminMode: endPoint = fmt.Sprintf("/admin/providers/%s", providerName) - } else { + resp, err = c.AdminServerClient.Request("GET", endPoint, "web", nil, nil) + case APIMode: endPoint = fmt.Sprintf("/providers/%s", providerName) + resp, err = c.APIServerClientMap[c.Config.APIClientConfig.CurrentAPIServer].Request("GET", endPoint, "web", nil, nil) + default: + return nil, fmt.Errorf("invalid server type") } - resp, err := c.HTTPClient.Request("GET", endPoint, "web", nil, nil) if err != nil { return nil, fmt.Errorf("failed to show provider: %w", err) } @@ -294,21 +322,27 @@ func (c *RAGFlowClient) ShowProvider(cmd *Command) (ResponseIf, error) { return &result, nil } -func (c *RAGFlowClient) ListModels(cmd *Command) (ResponseIf, error) { +func (c *CLI) ListModels(cmd *Command) (ResponseIf, error) { providerName, ok := cmd.Params["provider_name"].(string) if !ok { return nil, fmt.Errorf("provider_name not provided") } + var resp *Response + var err error var endPoint string - if c.ServerType == "admin" { + switch c.Config.CLIMode { + case AdminMode: endPoint = fmt.Sprintf("/admin/providers/%s/models", providerName) - } else { + resp, err = c.AdminServerClient.Request("GET", endPoint, "web", nil, nil) + case APIMode: endPoint = fmt.Sprintf("/providers/%s/models", providerName) + resp, err = c.APIServerClientMap[c.Config.APIClientConfig.CurrentAPIServer].Request("GET", endPoint, "web", nil, nil) + default: + return nil, fmt.Errorf("invalid server type") } - resp, err := c.HTTPClient.Request("GET", endPoint, "web", nil, nil) if err != nil { return nil, fmt.Errorf("failed to list models: %w", err) } @@ -329,7 +363,7 @@ func (c *RAGFlowClient) ListModels(cmd *Command) (ResponseIf, error) { return &result, nil } -func (c *RAGFlowClient) ListSupportedModels(cmd *Command) (ResponseIf, error) { +func (c *CLI) ListSupportedModels(cmd *Command) (ResponseIf, error) { providerName, ok := cmd.Params["provider_name"].(string) if !ok { @@ -340,14 +374,20 @@ func (c *RAGFlowClient) ListSupportedModels(cmd *Command) (ResponseIf, error) { return nil, fmt.Errorf("instance_name not provided") } + var resp *Response + var err error var endPoint string - if c.ServerType == "admin" { + switch c.Config.CLIMode { + case AdminMode: endPoint = fmt.Sprintf("/admin/providers/%s/instances/%s/models?supported=true", providerName, instanceName) - } else { + resp, err = c.AdminServerClient.Request("GET", endPoint, "web", nil, nil) + case APIMode: endPoint = fmt.Sprintf("/providers/%s/instances/%s/models?supported=true", providerName, instanceName) + resp, err = c.APIServerClientMap[c.Config.APIClientConfig.CurrentAPIServer].Request("GET", endPoint, "web", nil, nil) + default: + return nil, fmt.Errorf("invalid server type") } - resp, err := c.HTTPClient.Request("GET", endPoint, "web", nil, nil) if err != nil { return nil, fmt.Errorf("failed to list models: %w", err) } @@ -368,7 +408,7 @@ func (c *RAGFlowClient) ListSupportedModels(cmd *Command) (ResponseIf, error) { return &result, nil } -func (c *RAGFlowClient) ShowProviderModel(cmd *Command) (ResponseIf, error) { +func (c *CLI) ShowProviderModel(cmd *Command) (ResponseIf, error) { providerName, ok := cmd.Params["provider_name"].(string) if !ok { return nil, fmt.Errorf("provider_name not provided") @@ -378,14 +418,19 @@ func (c *RAGFlowClient) ShowProviderModel(cmd *Command) (ResponseIf, error) { return nil, fmt.Errorf("model_name not provided") } + var resp *Response + var err error var endPoint string - if c.ServerType == "admin" { + switch c.Config.CLIMode { + case AdminMode: endPoint = fmt.Sprintf("/admin/providers/%s/models/%s", providerName, modelName) - } else { + resp, err = c.AdminServerClient.Request("GET", endPoint, "web", nil, nil) + case APIMode: endPoint = fmt.Sprintf("/providers/%s/models/%s", providerName, modelName) + resp, err = c.APIServerClientMap[c.Config.APIClientConfig.CurrentAPIServer].Request("GET", endPoint, "web", nil, nil) + default: + return nil, fmt.Errorf("invalid server type") } - - resp, err := c.HTTPClient.Request("GET", endPoint, "web", nil, nil) if err != nil { return nil, fmt.Errorf("failed to show model: %w", err) } @@ -406,7 +451,7 @@ func (c *RAGFlowClient) ShowProviderModel(cmd *Command) (ResponseIf, error) { return &result, nil } -func (c *RAGFlowClient) SetDefaultModel(cmd *Command) (ResponseIf, error) { +func (c *CLI) SetDefaultModel(cmd *Command) (ResponseIf, error) { modelType, ok := cmd.Params["model_type"].(string) if !ok { @@ -434,7 +479,17 @@ func (c *RAGFlowClient) SetDefaultModel(cmd *Command) (ResponseIf, error) { "model_name": modelName, } - resp, err := c.HTTPClient.Request("PATCH", "/models", "web", nil, payload) + var resp *Response + var err error + switch c.Config.CLIMode { + case AdminMode: + resp, err = c.AdminServerClient.Request("PATCH", "/admin/models", "web", nil, payload) + case APIMode: + resp, err = c.APIServerClientMap[c.Config.APIClientConfig.CurrentAPIServer].Request("PATCH", "/models", "web", nil, payload) + default: + return nil, fmt.Errorf("invalid server type") + } + if err != nil { return nil, fmt.Errorf("failed to set default model: %w", err) } @@ -455,7 +510,7 @@ func (c *RAGFlowClient) SetDefaultModel(cmd *Command) (ResponseIf, error) { return &result, nil } -func (c *RAGFlowClient) ResetDefaultModel(cmd *Command) (ResponseIf, error) { +func (c *CLI) ResetDefaultModel(cmd *Command) (ResponseIf, error) { modelType, ok := cmd.Params["model_type"].(string) if !ok { @@ -466,7 +521,17 @@ func (c *RAGFlowClient) ResetDefaultModel(cmd *Command) (ResponseIf, error) { "model_type": modelType, } - resp, err := c.HTTPClient.Request("PATCH", "/models", "web", nil, payload) + var resp *Response + var err error + switch c.Config.CLIMode { + case AdminMode: + resp, err = c.AdminServerClient.Request("PATCH", "/admin/models", "web", nil, payload) + case APIMode: + resp, err = c.APIServerClientMap[c.Config.APIClientConfig.CurrentAPIServer].Request("PATCH", "/models", "web", nil, payload) + default: + return nil, fmt.Errorf("invalid server type") + } + if err != nil { return nil, fmt.Errorf("failed to reset default model: %w", err) } @@ -487,8 +552,19 @@ func (c *RAGFlowClient) ResetDefaultModel(cmd *Command) (ResponseIf, error) { return &result, nil } -func (c *RAGFlowClient) ListDefaultModels(cmd *Command) (ResponseIf, error) { - resp, err := c.HTTPClient.Request("GET", "/models", "web", nil, nil) +func (c *CLI) ListDefaultModels(cmd *Command) (ResponseIf, error) { + + var resp *Response + var err error + switch c.Config.CLIMode { + case AdminMode: + resp, err = c.AdminServerClient.Request("GET", "/admin/models", "web", nil, nil) + case APIMode: + resp, err = c.APIServerClientMap[c.Config.APIClientConfig.CurrentAPIServer].Request("GET", "/models", "web", nil, nil) + default: + return nil, fmt.Errorf("invalid server type") + } + if err != nil { return nil, fmt.Errorf("failed to list default models: %w", err) } @@ -509,7 +585,308 @@ func (c *RAGFlowClient) ListDefaultModels(cmd *Command) (ResponseIf, error) { return &result, nil } -func (c *RAGFlowClient) ListAllModels(cmd *Command) (ResponseIf, error) { +func (c *CLI) ShowCommonCurrent(cmd *Command) (ResponseIf, error) { + var result *CommonDataResponse + + switch c.Config.CLIMode { + case AdminMode: + response, err := c.GetAdminServerInfo() + if err != nil { + return nil, fmt.Errorf("failed to show current: %w", err) + } + result = response.(*CommonDataResponse) + + case APIMode: + response, err := c.GetAPIServerInfo(c.Config.APIClientConfig.CurrentAPIServer) + if err != nil { + return nil, err + } + result = response.(*CommonDataResponse) + + if c.CurrentModel != nil { + if result.Data == nil { + result.Data = make(map[string]interface{}) + } + result.Data["model_provider"] = c.CurrentModel.Provider + result.Data["model_instance"] = c.CurrentModel.Instance + result.Data["model_model"] = c.CurrentModel.Model + } + default: + return nil, fmt.Errorf("invalid server type") + } + + if result == nil { + result = &CommonDataResponse{} + if result.Data == nil { + result.Data = make(map[string]interface{}) + } + } + + result.Data["mode"] = c.Config.CLIMode + result.Data["output"] = c.Config.OutputFormat + result.Data["interactive"] = c.Config.Interactive + result.Data["verbose"] = c.Config.Verbose + + return result, nil +} + +func (c *CLI) ShowAdminServer(cmd *Command) (ResponseIf, error) { + return c.GetAdminServerInfo() +} + +func (c *CLI) ShowAPIServer(cmd *Command) (ResponseIf, error) { + apiServerName, ok := cmd.Params["api_server_name"].(string) + if !ok { + return nil, fmt.Errorf("api_server_name not provided") + } + result, err := c.GetAPIServerInfo(apiServerName) + if err != nil { + return nil, err + } + return result, nil +} + +func (c *CLI) ListAPIServer(cmd *Command) (ResponseIf, error) { + + var result CommonResponse + result.Data = make([]map[string]interface{}, 0) + + for serverName, apiServerConfig := range c.Config.APIClientConfig.APIServerMap { + element := map[string]interface{}{ + "api_server": serverName, + } + element["api_server_ip"] = apiServerConfig.IP + element["api_server_port"] = apiServerConfig.Port + if apiServerConfig.UserName != nil { + element["user_name"] = *apiServerConfig.UserName + } + if apiServerConfig.UserPassword != nil { + element["user_password"] = strings.Repeat("*", len(*apiServerConfig.UserPassword)) + } + if c.APIServerClientMap[serverName].LoginToken != nil { + element["auth"] = "login" + } else if apiServerConfig.ApiToken != nil { + element["auth"] = "api token" + } else { + element["auth"] = "no auth" + } + result.Data = append(result.Data, element) + } + + return &result, nil +} + +func (c *CLI) AddAPIServer(cmd *Command) (ResponseIf, error) { + apiServerName, ok := cmd.Params["server_name"].(string) + if !ok { + return nil, fmt.Errorf("server name not provided") + } + if c.Config.APIClientConfig.APIServerMap[apiServerName] != nil { + return nil, fmt.Errorf("api server already exists") + } + + apiServerIP, ok := cmd.Params["server_ip"].(string) + if !ok { + return nil, fmt.Errorf("server ip not provided") + } + apiServerPort, ok := cmd.Params["server_port"].(int) + if !ok { + return nil, fmt.Errorf("server port not provided") + } + apiServerToken, ok := cmd.Params["server_token"].(string) + if !ok { + apiServerToken = "" + } + + if c.Config.APIClientConfig.APIServerMap == nil { + c.Config.APIClientConfig.APIServerMap = make(map[string]*APIServerConfig) + } + + c.Config.APIClientConfig.APIServerMap[apiServerName] = &APIServerConfig{ + Name: apiServerName, + IP: apiServerIP, + Port: apiServerPort, + } + c.Config.APIClientConfig.APIServerMap[apiServerName].IP = apiServerIP + c.Config.APIClientConfig.APIServerMap[apiServerName].Port = apiServerPort + if apiServerToken != "" { + c.Config.APIClientConfig.APIServerMap[apiServerName].ApiToken = &apiServerToken + } + + transport := &http.Transport{ + TLSClientConfig: &tls.Config{InsecureSkipVerify: true}, + } + + if c.APIServerClientMap == nil { + c.APIServerClientMap = make(map[string]*HTTPClient) + } + + if c.APIServerClientMap[apiServerName] != nil { + return nil, fmt.Errorf("api server: %s already exists", apiServerName) + } + + c.APIServerClientMap[apiServerName] = &HTTPClient{ + Host: apiServerIP, + Port: apiServerPort, + APIVersion: "v1", + ConnectTimeout: 5 * time.Second, + ReadTimeout: 60 * time.Second, + VerifySSL: false, + client: &http.Client{ + Transport: transport, + Timeout: 300 * time.Second, + }, + } + + var result SimpleResponse + result.Code = 0 + result.Message = "api server deleted successfully" + result.Duration = 0 + return &result, nil +} + +func (c *CLI) DeleteAPIServer(cmd *Command) (ResponseIf, error) { + apiServerName, ok := cmd.Params["server_name"].(string) + if !ok { + return nil, fmt.Errorf("server name not provided") + } + if apiServerName == c.Config.APIClientConfig.CurrentAPIServer { + return nil, fmt.Errorf("cannot delete current api server") + } + delete(c.Config.APIClientConfig.APIServerMap, apiServerName) + delete(c.APIServerClientMap, apiServerName) + var result SimpleResponse + result.Code = 0 + result.Message = "api server deleted successfully" + result.Duration = 0 + return &result, nil +} + +func (c *CLI) AddAdminServer(cmd *Command) (ResponseIf, error) { + + if c.AdminServerClient != nil && c.AdminServerClient.LoginToken != nil { + return nil, fmt.Errorf("admin server already login, please logout") + } + + adminServerIP, ok := cmd.Params["server_ip"].(string) + if !ok { + return nil, fmt.Errorf("server ip not provided") + } + adminServerPort, ok := cmd.Params["server_port"].(int) + if !ok { + return nil, fmt.Errorf("server port not provided") + } + + if c.Config.AdminClientConfig == nil { + c.Config.AdminClientConfig = &AdminModeConfig{} + } + + if adminServerIP != "" { + c.Config.AdminClientConfig.AdminHost = adminServerIP + } + if adminServerPort != 0 { + c.Config.AdminClientConfig.AdminPort = adminServerPort + } + + var result SimpleResponse + result.Code = 0 + result.Message = "admin server added successfully" + result.Duration = 0 + return &result, nil +} + +func (c *CLI) DeleteAdminServer(cmd *Command) (ResponseIf, error) { + + if c.AdminServerClient != nil && c.AdminServerClient.LoginToken != nil { + return nil, fmt.Errorf("admin server already login, please logout") + } + + if c.Config.AdminClientConfig == nil { + return nil, fmt.Errorf("admin server not set") + } + + c.Config.AdminClientConfig = nil + + var result SimpleResponse + result.Code = 0 + result.Message = "admin server deleted successfully" + result.Duration = 0 + return &result, nil +} + +func (c *CLI) SaveServerConfig(cmd *Command) (ResponseIf, error) { + + switch c.Config.CLIMode { + case AdminMode: + if c.AdminServerClient.LoginToken == nil { + return nil, fmt.Errorf("admin server isn't already login") + } + case APIMode: + if c.APIServerClientMap[c.Config.APIClientConfig.CurrentAPIServer].APIToken == nil && c.APIServerClientMap[c.Config.APIClientConfig.CurrentAPIServer].LoginToken == nil { + return nil, fmt.Errorf("API token not set. Please login first") + } + default: + return nil, fmt.Errorf("invalid server type") + } + + return nil, nil +} + +func (c *CLI) GetAdminServerInfo() (ResponseIf, error) { + var result CommonDataResponse + result.Data = make(map[string]interface{}) + + if c.Config.AdminClientConfig == nil { + result.Data["admin_server"] = "N/A" + } else { + result.Data["admin_server_ip"] = c.Config.AdminClientConfig.AdminHost + result.Data["admin_server_port"] = c.Config.AdminClientConfig.AdminPort + if c.Config.AdminClientConfig.AdminName != nil { + result.Data["admin_name"] = *c.Config.AdminClientConfig.AdminName + } + if c.Config.AdminClientConfig.AdminPassword != nil { + result.Data["admin_password"] = strings.Repeat("*", len(*c.Config.AdminClientConfig.AdminPassword)) + } + if c.AdminServerClient == nil || c.AdminServerClient.LoginToken == nil { + result.Data["auth"] = "no auth" + } else { + result.Data["auth"] = "login" + } + } + + return &result, nil +} + +func (c *CLI) GetAPIServerInfo(serverName string) (ResponseIf, error) { + var result CommonDataResponse + result.Data = make(map[string]interface{}) + + if c.Config.APIClientConfig.APIServerMap == nil || c.Config.APIClientConfig.APIServerMap[serverName] == nil { + result.Data["api_server"] = "N/A" + } else { + result.Data["api_server"] = serverName + apiServerConfig := c.Config.APIClientConfig.APIServerMap[serverName] + result.Data["api_server_ip"] = apiServerConfig.IP + result.Data["api_server_port"] = apiServerConfig.Port + if apiServerConfig.UserName != nil { + result.Data["user_name"] = *apiServerConfig.UserName + } + + if apiServerConfig.UserPassword != nil { + result.Data["user_password"] = strings.Repeat("*", len(*apiServerConfig.UserPassword)) + } + if c.APIServerClientMap[c.Config.APIClientConfig.CurrentAPIServer].LoginToken != nil { + result.Data["auth"] = "login" + } else if apiServerConfig.ApiToken != nil { + result.Data["auth"] = "api token" + } else { + result.Data["auth"] = "no auth" + } + } + return &result, nil +} + +func (c *CLI) ListAllModels(cmd *Command) (ResponseIf, error) { page, ok := cmd.Params["page"].(int) if !ok { @@ -526,7 +903,17 @@ func (c *RAGFlowClient) ListAllModels(cmd *Command) (ResponseIf, error) { "page_size": pageSize, } - resp, err := c.HTTPClient.Request("GET", "/all-models", "web", nil, payload) + var httpClient *HTTPClient + switch c.Config.CLIMode { + case AdminMode: + httpClient = c.AdminServerClient + case APIMode: + httpClient = c.APIServerClientMap[c.Config.APIClientConfig.CurrentAPIServer] + default: + return nil, fmt.Errorf("invalid server type") + } + + resp, err := httpClient.Request("GET", "/all-models", "web", nil, payload) if err != nil { return nil, fmt.Errorf("failed to list all models: %w", err) } @@ -547,7 +934,7 @@ func (c *RAGFlowClient) ListAllModels(cmd *Command) (ResponseIf, error) { return &result, nil } -func (c *RAGFlowClient) ShowModel(cmd *Command) (ResponseIf, error) { +func (c *CLI) ShowModel(cmd *Command) (ResponseIf, error) { modelName, ok := cmd.Params["model_name"].(string) if !ok { @@ -558,7 +945,17 @@ func (c *RAGFlowClient) ShowModel(cmd *Command) (ResponseIf, error) { "model_name": modelName, } - resp, err := c.HTTPClient.Request("GET", "/all-models", "web", nil, payload) + var httpClient *HTTPClient + switch c.Config.CLIMode { + case AdminMode: + httpClient = c.AdminServerClient + case APIMode: + httpClient = c.APIServerClientMap[c.Config.APIClientConfig.CurrentAPIServer] + default: + return nil, fmt.Errorf("invalid server type") + } + + resp, err := httpClient.Request("GET", "/all-models", "web", nil, payload) if err != nil { return nil, fmt.Errorf("failed to show model: %w", err) } diff --git a/internal/cli/http_client.go b/internal/cli/http_client.go index f8950fd56d..a8ce5e64a9 100644 --- a/internal/cli/http_client.go +++ b/internal/cli/http_client.go @@ -31,8 +31,8 @@ type HTTPClient struct { Host string Port int APIVersion string - APIToken string - LoginToken string + APIToken *string + LoginToken *string ConnectTimeout time.Duration ReadTimeout time.Duration VerifySSL bool @@ -84,15 +84,15 @@ func (c *HTTPClient) Headers(authKind string, extra map[string]string) map[strin switch authKind { case "api": - if c.APIToken != "" { - headers["Authorization"] = fmt.Sprintf("Bearer %s", c.APIToken) - } else if c.LoginToken != "" { + if c.APIToken != nil { + headers["Authorization"] = fmt.Sprintf("Bearer %s", *c.APIToken) + } else if c.LoginToken != nil { // Fallback to login token for API requests (user mode) - headers["Authorization"] = fmt.Sprintf("Bearer %s", c.LoginToken) + headers["Authorization"] = fmt.Sprintf("Bearer %s", *c.LoginToken) } case "web", "admin": - if c.LoginToken != "" { - headers["Authorization"] = c.LoginToken + if c.LoginToken != nil { + headers["Authorization"] = *c.LoginToken } } @@ -121,6 +121,10 @@ func (r *Response) JSON() (map[string]interface{}, error) { // Request makes an HTTP request func (c *HTTPClient) Request(method, path string, authKind string, headers map[string]string, jsonBody map[string]interface{}) (*Response, error) { + if c == nil { + return nil, fmt.Errorf("HTTP Client is nil") + } + url := c.BuildURL(path) mergedHeaders := c.Headers(authKind, headers) @@ -282,10 +286,10 @@ func (c *HTTPClient) UploadMultipart(path string, contentType string, body io.Re // Set headers req.Header.Set("Content-Type", contentType) - if c.APIToken != "" { - req.Header.Set("Authorization", fmt.Sprintf("Bearer %s", c.APIToken)) - } else if c.LoginToken != "" { - req.Header.Set("Authorization", c.LoginToken) + if c.APIToken != nil { + req.Header.Set("Authorization", fmt.Sprintf("Bearer %s", *c.APIToken)) + } else if c.LoginToken != nil { + req.Header.Set("Authorization", *c.LoginToken) } resp, err := c.client.Do(req) diff --git a/internal/cli/lexer.go b/internal/cli/lexer.go index cfd634d005..71f14c98e7 100644 --- a/internal/cli/lexer.go +++ b/internal/cli/lexer.go @@ -221,8 +221,14 @@ func (l *Lexer) lookupIdent(ident string) Token { return Token{Type: TokenActive, Value: ident} case "ADMIN": return Token{Type: TokenAdmin, Value: ident} + case "SERVER": + return Token{Type: TokenServer, Value: ident} + case "API": + return Token{Type: TokenAPI, Value: ident} case "ADD": return Token{Type: TokenAdd, Value: ident} + case "HOST": + return Token{Type: TokenHost, Value: ident} case "DELETE": return Token{Type: TokenDelete, Value: ident} case "PASSWORD": @@ -273,6 +279,8 @@ func (l *Lexer) lookupIdent(ident string) Token { return Token{Type: TokenVars, Value: ident} case "CONFIGS": return Token{Type: TokenConfigs, Value: ident} + case "CONFIG": + return Token{Type: TokenConfig, Value: ident} case "ENVS": return Token{Type: TokenEnvs, Value: ident} case "KEY": @@ -349,6 +357,8 @@ func (l *Lexer) lookupIdent(ident string) Token { return Token{Type: TokenGet, Value: ident} case "SEARCH": return Token{Type: TokenSearch, Value: ident} + case "RETRIEVE": + return Token{Type: TokenRetrieve, Value: ident} case "CURRENT": return Token{Type: TokenCurrent, Value: ident} case "VISION": diff --git a/internal/cli/parser.go b/internal/cli/parser.go index 48493c2b98..4c2fbcb7e1 100644 --- a/internal/cli/parser.go +++ b/internal/cli/parser.go @@ -28,12 +28,13 @@ type Parser struct { lexer *Lexer curToken Token peekToken Token + original string } // NewParser creates a new parser func NewParser(input string) *Parser { l := NewLexer(input) - p := &Parser{lexer: l} + p := &Parser{lexer: l, original: input} // Read two tokens to initialize curToken and peekToken p.nextToken() p.nextToken() @@ -46,7 +47,7 @@ func (p *Parser) nextToken() { } // Parse parses the input and returns a Command -func (p *Parser) Parse(adminCommand bool) (*Command, error) { +func (p *Parser) Parse(cliMode CommandLineMode) (*Command, error) { if p.curToken.Type == TokenEOF { return nil, nil } @@ -56,13 +57,7 @@ func (p *Parser) Parse(adminCommand bool) (*Command, error) { return p.parseMetaCommand() } - // Check for ContextEngine commands (ls, cat, search) - // Note: These are now handled in parseUserCommand to support both SQL-style and CE-style syntax - // if p.curToken.Type == TokenIdentifier && isCECommand(p.curToken.Value) { - // return p.parseCECommand() - // } - - return p.parseCommand(adminCommand) + return p.parseCommand(cliMode) } func (p *Parser) parseMetaCommand() (*Command, error) { @@ -115,8 +110,8 @@ func (p *Parser) parseAdminCommand() (*Command, error) { return p.parseAdminGenerateCommand() case TokenImport: return p.parseAdminImportCommand() - case TokenSearch: - return p.parseAdminSearchCommand() + case TokenRetrieve: + return p.parseAdminRetrieveCommand() case TokenParse: return p.parseAdminParseCommand() case TokenBenchmark: @@ -133,6 +128,12 @@ func (p *Parser) parseAdminCommand() (*Command, error) { return p.parseStartIngestion() case TokenStop: return p.parseStopIngestion() + case TokenAdd: + return p.parseAdminAddCommand() + case TokenDelete: + return p.parseAdminDeleteCommand() + case TokenSave: + return p.parseAdminSaveCommand() default: return nil, fmt.Errorf("unknown command: %s", p.curToken.Value) } @@ -177,8 +178,8 @@ func (p *Parser) parseUserCommand() (*Command, error) { return p.parseImportCommand() case TokenInsert: return p.parseInsertCommand() - case TokenSearch: - return p.parseSearchCommand() + case TokenRetrieve: + return p.parseRetrieveCommand() case TokenParse: return p.parseParseCommand() case TokenBenchmark: @@ -213,10 +214,8 @@ func (p *Parser) parseUserCommand() (*Command, error) { return p.parseOCRCommand() case TokenCheck: return p.parseCheckCommand() - case TokenLS: - return p.parseCEListCommand() - case TokenCat: - return p.parseCECatCommand() + case TokenSave: + return p.parseUserSaveCommand() case TokenUse: return p.parseUseCommand() case TokenUpdate: @@ -226,21 +225,27 @@ func (p *Parser) parseUserCommand() (*Command, error) { case TokenGet: return p.parseGetCommand() + case TokenLS, TokenCat, TokenSearch: + // For context engine + return p.parseContextEngineCommand() default: return nil, fmt.Errorf("unknown command: %s", p.curToken.Value) } } -func (p *Parser) parseCommand(adminCommand bool) (*Command, error) { +func (p *Parser) parseCommand(cliMode CommandLineMode) (*Command, error) { if p.curToken.Type != TokenIdentifier && !isKeyword(p.curToken.Type) { return nil, fmt.Errorf("expected command, got %s", p.curToken.Value) } - if adminCommand { + switch cliMode { + case AdminMode: return p.parseAdminCommand() + case APIMode: + return p.parseUserCommand() + default: + return nil, fmt.Errorf("unknown mode: %s", cliMode) } - - return p.parseUserCommand() } func (p *Parser) expectPeek(tokenType int) error { @@ -385,20 +390,13 @@ func tokenTypeToString(t int) string { return fmt.Sprintf("token(%d)", t) } -// parseCECommand parses ContextEngine commands (ls, search) -func (p *Parser) parseCECommand() (*Command, error) { - cmdName := strings.ToUpper(p.curToken.Value) +func (p *Parser) parseContextEngineCommand() (*Command, error) { + p.nextToken() // consume COMMAND - switch cmdName { - case "LS", "LIST": - return p.parseCEListCommand() - case "CAT": - return p.parseCECatCommand() - case "SEARCH": - return p.parseCESearchCommand() - default: - return nil, fmt.Errorf("unknown ContextEngine command: %s", cmdName) - } + cmd := NewCommand("context_engine_command") + cmd.Params["command"] = p.original + + return cmd, nil } // parseCEListCommand parses the ls command diff --git a/internal/cli/types.go b/internal/cli/types.go index c8634b4fd9..1f144bf012 100644 --- a/internal/cli/types.go +++ b/internal/cli/types.go @@ -42,7 +42,10 @@ const ( TokenAlter TokenActive TokenAdmin + TokenServer + TokenAPI TokenAdd + TokenHost TokenDelete TokenPassword TokenDataset @@ -68,6 +71,7 @@ const ( TokenVar TokenVars TokenConfigs + TokenConfig TokenEnvs TokenKey TokenKeys @@ -96,6 +100,7 @@ const ( TokenParser TokenPipeline TokenSearch + TokenRetrieve TokenCurrent TokenVision TokenEmbedding diff --git a/internal/cli/user_command.go b/internal/cli/user_command.go index 43c8d94614..ccf8611c39 100644 --- a/internal/cli/user_command.go +++ b/internal/cli/user_command.go @@ -34,54 +34,24 @@ import ( "time" ) -// PingServer pings the server to check if it's alive -// Returns benchmark result map if iterations > 1, otherwise prints status -func (c *RAGFlowClient) PingServer(cmd *Command) (ResponseIf, error) { - // Get iterations from command params (for benchmark) - iterations := 1 - if val, ok := cmd.Params["iterations"].(int); ok && val > 1 { - iterations = val - } - - if iterations > 1 { - // Benchmark mode: multiple iterations - return c.HTTPClient.RequestWithIterations("GET", "/system/ping", "web", nil, nil, iterations) - } - - // Single mode - resp, err := c.HTTPClient.Request("GET", "/system/ping", "web", nil, nil) - if err != nil { - fmt.Printf("Error: %v\n", err) - fmt.Println("Server is down") - return nil, err - } - - if resp.StatusCode != 200 { - return nil, fmt.Errorf("failed to ping: HTTP %d, body: %s", resp.StatusCode, string(resp.Body)) - } - - var result SimpleResponse - result.Message = string(resp.Body) - result.Code = 0 - return &result, nil -} - // Show server version to show RAGFlow server version // Returns benchmark result map if iterations > 1, otherwise prints status -func (c *RAGFlowClient) ShowServerVersion(cmd *Command) (ResponseIf, error) { +func (c *CLI) ShowServerVersion(cmd *Command) (ResponseIf, error) { // Get iterations from command params (for benchmark) iterations := 1 if val, ok := cmd.Params["iterations"].(int); ok && val > 1 { iterations = val } + httpClient := c.APIServerClientMap[c.Config.APIClientConfig.CurrentAPIServer] + if iterations > 1 { // Benchmark mode: multiple iterations - return c.HTTPClient.RequestWithIterations("GET", "/system/version", "web", nil, nil, iterations) + return httpClient.RequestWithIterations("GET", "/system/version", "web", nil, nil, iterations) } // Single mode - resp, err := c.HTTPClient.Request("GET", "/system/version", "web", nil, nil) + resp, err := httpClient.Request("GET", "/system/version", "web", nil, nil) if err != nil { return nil, fmt.Errorf("failed to show version: %w", err) } @@ -100,9 +70,9 @@ func (c *RAGFlowClient) ShowServerVersion(cmd *Command) (ResponseIf, error) { return &result, nil } -func (c *RAGFlowClient) ListConfigs(cmd *Command) (ResponseIf, error) { - if c.ServerType != "user" { - return nil, fmt.Errorf("this command is only allowed in ADMIN mode") +func (c *CLI) ListConfigs(cmd *Command) (ResponseIf, error) { + if c.Config.CLIMode != APIMode { + return nil, fmt.Errorf("this command is only allowed in USER mode") } // Get iterations from command params (for benchmark) iterations := 1 @@ -110,13 +80,15 @@ func (c *RAGFlowClient) ListConfigs(cmd *Command) (ResponseIf, error) { iterations = val } + httpClient := c.APIServerClientMap[c.Config.APIClientConfig.CurrentAPIServer] + if iterations > 1 { // Benchmark mode: multiple iterations - return c.HTTPClient.RequestWithIterations("GET", "/system/configs", "web", nil, nil, iterations) + return httpClient.RequestWithIterations("GET", "/system/configs", "web", nil, nil, iterations) } // Single mode - resp, err := c.HTTPClient.Request("GET", "/system/configs", "web", nil, nil) + resp, err := httpClient.Request("GET", "/system/configs", "web", nil, nil) if err != nil { return nil, fmt.Errorf("failed to list configs: %w", err) } @@ -252,9 +224,9 @@ func GetHost(config *map[string]interface{}, serverType, address, port string) s return result } -func (c *RAGFlowClient) SetLogLevel(cmd *Command) (ResponseIf, error) { - if c.ServerType != "user" { - return nil, fmt.Errorf("this command is only allowed in ADMIN mode") +func (c *CLI) SetLogLevel(cmd *Command) (ResponseIf, error) { + if c.Config.CLIMode != APIMode { + return nil, fmt.Errorf("this command is only allowed in USER mode") } if logLevel, ok := cmd.Params["level"].(string); ok { @@ -262,7 +234,8 @@ func (c *RAGFlowClient) SetLogLevel(cmd *Command) (ResponseIf, error) { "level": logLevel, } - resp, err := c.HTTPClient.Request("PUT", "/system/log", "admin", nil, payload) + httpClient := c.APIServerClientMap[c.Config.APIClientConfig.CurrentAPIServer] + resp, err := httpClient.Request("PUT", "/system/log", "admin", nil, payload) if err != nil { return nil, fmt.Errorf("failed to change log level: %w", err) } @@ -283,9 +256,9 @@ func (c *RAGFlowClient) SetLogLevel(cmd *Command) (ResponseIf, error) { return nil, fmt.Errorf("no log level") } -func (c *RAGFlowClient) RegisterUser(cmd *Command) (ResponseIf, error) { - if c.ServerType != "user" { - return nil, fmt.Errorf("this command is only allowed in ADMIN mode") +func (c *CLI) RegisterUser(cmd *Command) (ResponseIf, error) { + if c.Config.CLIMode != APIMode { + return nil, fmt.Errorf("this command is only allowed in USER mode") } // Check for benchmark iterations @@ -325,7 +298,8 @@ func (c *RAGFlowClient) RegisterUser(cmd *Command) (ResponseIf, error) { "nickname": nickname, } - resp, err := c.HTTPClient.Request("POST", "/users", "web", nil, payload) + httpClient := c.APIServerClientMap[c.Config.APIClientConfig.CurrentAPIServer] + resp, err := httpClient.Request("POST", "/users", "web", nil, payload) if err != nil { return nil, fmt.Errorf("failed to register user: %w", err) } @@ -349,8 +323,8 @@ func (c *RAGFlowClient) RegisterUser(cmd *Command) (ResponseIf, error) { // ListDatasets lists datasets for current user (user mode) // Returns (result_map, error) - result_map is non-nil for benchmark mode -func (c *RAGFlowClient) ListDatasets(cmd *Command) (ResponseIf, error) { - if c.ServerType != "user" { +func (c *CLI) ListDatasets(cmd *Command) (ResponseIf, error) { + if c.Config.CLIMode != APIMode { return nil, fmt.Errorf("this command is only allowed in USER mode") } @@ -360,27 +334,29 @@ func (c *RAGFlowClient) ListDatasets(cmd *Command) (ResponseIf, error) { iterations = val } + httpClient := c.APIServerClientMap[c.Config.APIClientConfig.CurrentAPIServer] + // Determine auth kind based on whether API token is being used - if c.HTTPClient.LoginToken == "" && !c.HTTPClient.useAPIToken { + if httpClient.LoginToken == nil && !c.APIServerClientMap[c.Config.APIClientConfig.CurrentAPIServer].useAPIToken { return nil, fmt.Errorf("no authorization") } authKind := "web" - if c.HTTPClient.useAPIToken { + if httpClient.useAPIToken { authKind = "api" } - if c.HTTPClient.LoginToken != "" { + if httpClient.LoginToken != nil { authKind = "web" } if iterations > 1 { // Benchmark mode - return raw result for benchmark stats - return c.HTTPClient.RequestWithIterations("GET", "/datasets", authKind, nil, nil, iterations) + return httpClient.RequestWithIterations("GET", "/datasets", authKind, nil, nil, iterations) } // Normal mode - resp, err := c.HTTPClient.Request("GET", "/datasets", authKind, nil, nil) + resp, err := httpClient.Request("GET", "/datasets", authKind, nil, nil) if err != nil { return nil, fmt.Errorf("failed to list datasets: %w", err) } @@ -403,8 +379,8 @@ func (c *RAGFlowClient) ListDatasets(cmd *Command) (ResponseIf, error) { } // ListDatasetDocumentUserCommand lists dataset documents -func (c *RAGFlowClient) ListDatasetDocumentUserCommand(cmd *Command) (ResponseIf, error) { - if c.ServerType != "user" { +func (c *CLI) ListDatasetDocumentUserCommand(cmd *Command) (ResponseIf, error) { + if c.Config.CLIMode != APIMode { return nil, fmt.Errorf("this command is only allowed in USER mode") } @@ -414,8 +390,9 @@ func (c *RAGFlowClient) ListDatasetDocumentUserCommand(cmd *Command) (ResponseIf iterations = val } + httpClient := c.APIServerClientMap[c.Config.APIClientConfig.CurrentAPIServer] // Determine auth kind based on whether API token is being used - if c.HTTPClient.LoginToken == "" && !c.HTTPClient.useAPIToken { + if httpClient.LoginToken == nil && !httpClient.useAPIToken { return nil, fmt.Errorf("no authorization") } @@ -432,11 +409,11 @@ func (c *RAGFlowClient) ListDatasetDocumentUserCommand(cmd *Command) (ResponseIf if iterations > 1 { // Benchmark mode - return raw result for benchmark stats - return c.HTTPClient.RequestWithIterations("GET", url, "web", nil, nil, iterations) + return httpClient.RequestWithIterations("GET", url, "web", nil, nil, iterations) } // Normal mode - resp, err := c.HTTPClient.Request("GET", url, "web", nil, nil) + resp, err := httpClient.Request("GET", url, "web", nil, nil) if err != nil { return nil, fmt.Errorf("failed to list documents: %w", err) } @@ -459,8 +436,10 @@ func (c *RAGFlowClient) ListDatasetDocumentUserCommand(cmd *Command) (ResponseIf } // getDatasetID gets dataset ID by name -func (c *RAGFlowClient) getDatasetID(datasetName string) (string, error) { - resp, err := c.HTTPClient.Request("GET", "/datasets", "web", nil, nil) +func (c *CLI) getDatasetID(datasetName string) (string, error) { + + httpClient := c.APIServerClientMap[c.Config.APIClientConfig.CurrentAPIServer] + resp, err := httpClient.Request("GET", "/datasets", "web", nil, nil) if err != nil { return "", fmt.Errorf("failed to list datasets: %w", err) } @@ -499,8 +478,8 @@ func (c *RAGFlowClient) getDatasetID(datasetName string) (string, error) { } // GetMetadata gets metadata for one or more datasets -func (c *RAGFlowClient) GetMetadata(cmd *Command) (ResponseIf, error) { - if c.ServerType != "user" { +func (c *CLI) GetMetadata(cmd *Command) (ResponseIf, error) { + if c.Config.CLIMode != APIMode { return nil, fmt.Errorf("this command is only allowed in USER mode") } @@ -522,7 +501,8 @@ func (c *RAGFlowClient) GetMetadata(cmd *Command) (ResponseIf, error) { // Build comma-separated dataset_ids for query param datasetIDsStr := strings.Join(datasetIDs, ",") - resp, err := c.HTTPClient.Request("GET", "/datasets/metadata/flattened?dataset_ids="+datasetIDsStr, "web", nil, nil) + httpClient := c.APIServerClientMap[c.Config.APIClientConfig.CurrentAPIServer] + resp, err := httpClient.Request("GET", "/datasets/metadata/flattened?dataset_ids="+datasetIDsStr, "web", nil, nil) if err != nil { return nil, fmt.Errorf("failed to list metadata: %w", err) } @@ -568,8 +548,8 @@ func formatEmptyArray(v interface{}) string { // SearchOnDatasets searches for chunks in specified datasets // Returns (result_map, error) - result_map is non-nil for benchmark mode -func (c *RAGFlowClient) SearchOnDatasets(cmd *Command) (ResponseIf, error) { - if c.ServerType != "user" { +func (c *CLI) SearchOnDatasets(cmd *Command) (ResponseIf, error) { + if c.Config.CLIMode != APIMode { return nil, fmt.Errorf("this command is only allowed in USER mode") } @@ -670,13 +650,14 @@ func (c *RAGFlowClient) SearchOnDatasets(cmd *Command) (ResponseIf, error) { } } + httpClient := c.APIServerClientMap[c.Config.APIClientConfig.CurrentAPIServer] if iterations > 1 { // Benchmark mode - return raw result for benchmark stats - return c.HTTPClient.RequestWithIterations("POST", "/datasets/search", "web", nil, payload, iterations) + return httpClient.RequestWithIterations("POST", "/datasets/search", "web", nil, payload, iterations) } // Normal mode - resp, err := c.HTTPClient.Request("POST", "/datasets/search", "web", nil, payload) + resp, err := httpClient.Request("POST", "/datasets/search", "web", nil, payload) if err != nil { return nil, fmt.Errorf("failed to search on datasets: %w", err) } @@ -746,12 +727,13 @@ func (c *RAGFlowClient) SearchOnDatasets(cmd *Command) (ResponseIf, error) { } // CreateToken creates a new API token -func (c *RAGFlowClient) CreateToken(cmd *Command) (ResponseIf, error) { - if c.ServerType != "user" { +func (c *CLI) CreateToken(cmd *Command) (ResponseIf, error) { + if c.Config.CLIMode != APIMode { return nil, fmt.Errorf("this command is only allowed in USER mode") } - resp, err := c.HTTPClient.Request("POST", "/system/tokens", "web", nil, nil) + httpClient := c.APIServerClientMap[c.Config.APIClientConfig.CurrentAPIServer] + resp, err := httpClient.Request("POST", "/system/tokens", "web", nil, nil) if err != nil { return nil, fmt.Errorf("failed to create token: %w", err) } @@ -777,12 +759,13 @@ func (c *RAGFlowClient) CreateToken(cmd *Command) (ResponseIf, error) { } // ListTokens lists all API tokens for the current user -func (c *RAGFlowClient) ListTokens(cmd *Command) (ResponseIf, error) { - if c.ServerType != "user" { +func (c *CLI) ListTokens(cmd *Command) (ResponseIf, error) { + if c.Config.CLIMode != APIMode { return nil, fmt.Errorf("this command is only allowed in USER mode") } - resp, err := c.HTTPClient.Request("GET", "/system/tokens", "web", nil, nil) + httpClient := c.APIServerClientMap[c.Config.APIClientConfig.CurrentAPIServer] + resp, err := httpClient.Request("GET", "/system/tokens", "web", nil, nil) if err != nil { return nil, fmt.Errorf("failed to list tokens: %w", err) } @@ -804,8 +787,8 @@ func (c *RAGFlowClient) ListTokens(cmd *Command) (ResponseIf, error) { } // DropToken deletes an API token -func (c *RAGFlowClient) DropToken(cmd *Command) (ResponseIf, error) { - if c.ServerType != "user" { +func (c *CLI) DropToken(cmd *Command) (ResponseIf, error) { + if c.Config.CLIMode != APIMode { return nil, fmt.Errorf("this command is only allowed in USER mode") } @@ -814,7 +797,7 @@ func (c *RAGFlowClient) DropToken(cmd *Command) (ResponseIf, error) { return nil, fmt.Errorf("token not provided") } - resp, err := c.HTTPClient.Request("DELETE", fmt.Sprintf("/system/tokens/%s", token), "web", nil, nil) + resp, err := c.APIServerClientMap[c.Config.APIClientConfig.CurrentAPIServer].Request("DELETE", fmt.Sprintf("/system/tokens/%s", token), "web", nil, nil) if err != nil { return nil, fmt.Errorf("failed to drop token: %w", err) } @@ -836,8 +819,8 @@ func (c *RAGFlowClient) DropToken(cmd *Command) (ResponseIf, error) { } // SetToken sets the API token after validating it -func (c *RAGFlowClient) SetToken(cmd *Command) (ResponseIf, error) { - if c.ServerType != "user" { +func (c *CLI) SetToken(cmd *Command) (ResponseIf, error) { + if c.Config.CLIMode != APIMode { return nil, fmt.Errorf("this command is only allowed in USER mode") } @@ -847,41 +830,41 @@ func (c *RAGFlowClient) SetToken(cmd *Command) (ResponseIf, error) { } // Save current token to restore if validation fails - savedToken := c.HTTPClient.APIToken - savedUseAPIToken := c.HTTPClient.useAPIToken + savedToken := c.APIServerClientMap[c.Config.APIClientConfig.CurrentAPIServer].APIToken + savedUseAPIToken := c.APIServerClientMap[c.Config.APIClientConfig.CurrentAPIServer].useAPIToken // Set the new token temporarily for validation - c.HTTPClient.APIToken = token - c.HTTPClient.useAPIToken = true + c.APIServerClientMap[c.Config.APIClientConfig.CurrentAPIServer].APIToken = &token + c.APIServerClientMap[c.Config.APIClientConfig.CurrentAPIServer].useAPIToken = true // Validate token by calling list tokens API - resp, err := c.HTTPClient.Request("GET", "/tokens", "api", nil, nil) + resp, err := c.APIServerClientMap[c.Config.APIClientConfig.CurrentAPIServer].Request("GET", "/tokens", "api", nil, nil) if err != nil { // Restore original token on error - c.HTTPClient.APIToken = savedToken - c.HTTPClient.useAPIToken = savedUseAPIToken + c.APIServerClientMap[c.Config.APIClientConfig.CurrentAPIServer].APIToken = savedToken + c.APIServerClientMap[c.Config.APIClientConfig.CurrentAPIServer].useAPIToken = savedUseAPIToken return nil, fmt.Errorf("failed to validate token: %w", err) } if resp.StatusCode != 200 { // Restore original token on error - c.HTTPClient.APIToken = savedToken - c.HTTPClient.useAPIToken = savedUseAPIToken + c.APIServerClientMap[c.Config.APIClientConfig.CurrentAPIServer].APIToken = savedToken + c.APIServerClientMap[c.Config.APIClientConfig.CurrentAPIServer].useAPIToken = savedUseAPIToken return nil, fmt.Errorf("token validation failed: HTTP %d, body: %s", resp.StatusCode, string(resp.Body)) } var result CommonResponse if err = json.Unmarshal(resp.Body, &result); err != nil { // Restore original token on error - c.HTTPClient.APIToken = savedToken - c.HTTPClient.useAPIToken = savedUseAPIToken + c.APIServerClientMap[c.Config.APIClientConfig.CurrentAPIServer].APIToken = savedToken + c.APIServerClientMap[c.Config.APIClientConfig.CurrentAPIServer].useAPIToken = savedUseAPIToken return nil, fmt.Errorf("token validation failed: invalid JSON (%w)", err) } if result.Code != 0 { // Restore original token on error - c.HTTPClient.APIToken = savedToken - c.HTTPClient.useAPIToken = savedUseAPIToken + c.APIServerClientMap[c.Config.APIClientConfig.CurrentAPIServer].APIToken = savedToken + c.APIServerClientMap[c.Config.APIClientConfig.CurrentAPIServer].useAPIToken = savedUseAPIToken return nil, fmt.Errorf("token validation failed: %s", result.Message) } @@ -894,23 +877,23 @@ func (c *RAGFlowClient) SetToken(cmd *Command) (ResponseIf, error) { } // ShowToken displays the current API token -func (c *RAGFlowClient) ShowToken(cmd *Command) (ResponseIf, error) { - if c.ServerType != "user" { +func (c *CLI) ShowToken(cmd *Command) (ResponseIf, error) { + if c.Config.CLIMode != APIMode { return nil, fmt.Errorf("this command is only allowed in USER mode") } - if c.HTTPClient.APIToken == "" { + if c.APIServerClientMap[c.Config.APIClientConfig.CurrentAPIServer].APIToken == nil { return nil, fmt.Errorf("no API token is currently set") } - //fmt.Printf("Token: %s\n", c.HTTPClient.APIToken) + //fmt.Printf("Token: %s\n", c.APIServerClientMap[c.Config.APIClientConfig.CurrentAPIServer].APIToken) var result CommonResponse result.Code = 0 result.Message = "" result.Data = []map[string]interface{}{ { - "token": c.HTTPClient.APIToken, + "token": c.APIServerClientMap[c.Config.APIClientConfig.CurrentAPIServer].APIToken, }, } result.Duration = 0 @@ -918,17 +901,17 @@ func (c *RAGFlowClient) ShowToken(cmd *Command) (ResponseIf, error) { } // UnsetToken removes the current API token -func (c *RAGFlowClient) UnsetToken(cmd *Command) (ResponseIf, error) { - if c.ServerType != "user" { +func (c *CLI) UnsetToken(cmd *Command) (ResponseIf, error) { + if c.Config.CLIMode != APIMode { return nil, fmt.Errorf("this command is only allowed in USER mode") } - if c.HTTPClient.APIToken == "" { + if c.APIServerClientMap[c.Config.APIClientConfig.CurrentAPIServer].APIToken == nil { return nil, fmt.Errorf("no API token is currently set") } - c.HTTPClient.APIToken = "" - c.HTTPClient.useAPIToken = false + c.APIServerClientMap[c.Config.APIClientConfig.CurrentAPIServer].APIToken = nil + c.APIServerClientMap[c.Config.APIClientConfig.CurrentAPIServer].useAPIToken = false var result SimpleResponse result.Code = 0 @@ -938,8 +921,8 @@ func (c *RAGFlowClient) UnsetToken(cmd *Command) (ResponseIf, error) { } // CreateChunkStore creates a chunk store in doc engine -func (c *RAGFlowClient) CreateChunkStore(cmd *Command) (ResponseIf, error) { - if c.ServerType != "user" { +func (c *CLI) CreateChunkStore(cmd *Command) (ResponseIf, error) { + if c.Config.CLIMode != APIMode { return nil, fmt.Errorf("this command is only allowed in USER mode") } @@ -964,7 +947,7 @@ func (c *RAGFlowClient) CreateChunkStore(cmd *Command) (ResponseIf, error) { "vector_size": vectorSize, } - resp, err := c.HTTPClient.Request("POST", "/tenant/chunk_store", "web", nil, payload) + resp, err := c.APIServerClientMap[c.Config.APIClientConfig.CurrentAPIServer].Request("POST", "/tenant/chunk_store", "web", nil, payload) if err != nil { return nil, fmt.Errorf("failed to create chunk store: %w", err) } @@ -995,8 +978,8 @@ func (c *RAGFlowClient) CreateChunkStore(cmd *Command) (ResponseIf, error) { } // DropChunkStore drops a chunk store in doc engine -func (c *RAGFlowClient) DropChunkStore(cmd *Command) (ResponseIf, error) { - if c.ServerType != "user" { +func (c *CLI) DropChunkStore(cmd *Command) (ResponseIf, error) { + if c.Config.CLIMode != APIMode { return nil, fmt.Errorf("this command is only allowed in USER mode") } @@ -1015,7 +998,7 @@ func (c *RAGFlowClient) DropChunkStore(cmd *Command) (ResponseIf, error) { "kb_id": datasetID, } - resp, err := c.HTTPClient.Request("DELETE", "/tenant/chunk_store", "web", nil, payload) + resp, err := c.APIServerClientMap[c.Config.APIClientConfig.CurrentAPIServer].Request("DELETE", "/tenant/chunk_store", "web", nil, payload) if err != nil { return nil, fmt.Errorf("failed to drop dataset: %w", err) } @@ -1046,12 +1029,12 @@ func (c *RAGFlowClient) DropChunkStore(cmd *Command) (ResponseIf, error) { } // CreateMetadataStore creates the document metadata store for the tenant -func (c *RAGFlowClient) CreateMetadataStore(cmd *Command) (ResponseIf, error) { - if c.ServerType != "user" { +func (c *CLI) CreateMetadataStore(cmd *Command) (ResponseIf, error) { + if c.Config.CLIMode != APIMode { return nil, fmt.Errorf("this command is only allowed in USER mode") } - resp, err := c.HTTPClient.Request("POST", "/tenant/metadata_store", "web", nil, nil) + resp, err := c.APIServerClientMap[c.Config.APIClientConfig.CurrentAPIServer].Request("POST", "/tenant/metadata_store", "web", nil, nil) if err != nil { return nil, fmt.Errorf("failed to create metadata store: %w", err) } @@ -1082,12 +1065,12 @@ func (c *RAGFlowClient) CreateMetadataStore(cmd *Command) (ResponseIf, error) { } // DropMetadataStore drops the document metadata store for the tenant -func (c *RAGFlowClient) DropMetadataStore(cmd *Command) (ResponseIf, error) { - if c.ServerType != "user" { +func (c *CLI) DropMetadataStore(cmd *Command) (ResponseIf, error) { + if c.Config.CLIMode != APIMode { return nil, fmt.Errorf("this command is only allowed in USER mode") } - resp, err := c.HTTPClient.Request("DELETE", "/tenant/metadata_store", "web", nil, nil) + resp, err := c.APIServerClientMap[c.Config.APIClientConfig.CurrentAPIServer].Request("DELETE", "/tenant/metadata_store", "web", nil, nil) if err != nil { return nil, fmt.Errorf("failed to drop metadata store: %w", err) } @@ -1120,8 +1103,8 @@ func (c *RAGFlowClient) DropMetadataStore(cmd *Command) (ResponseIf, error) { // AddProvider creates a new model provider // ADD PROVIDER // ADD PROVIDER -func (c *RAGFlowClient) AddProvider(cmd *Command) (ResponseIf, error) { - if c.ServerType != "user" { +func (c *CLI) AddProvider(cmd *Command) (ResponseIf, error) { + if c.Config.CLIMode != APIMode { return nil, fmt.Errorf("this command is only allowed in USER mode") } @@ -1135,7 +1118,7 @@ func (c *RAGFlowClient) AddProvider(cmd *Command) (ResponseIf, error) { "provider_name": providerName, } - resp, err := c.HTTPClient.Request("PUT", "/providers", "web", nil, payload) + resp, err := c.APIServerClientMap[c.Config.APIClientConfig.CurrentAPIServer].Request("PUT", "/providers", "web", nil, payload) if err != nil { return nil, fmt.Errorf("failed to add provider: %w", err) } @@ -1159,12 +1142,12 @@ func (c *RAGFlowClient) AddProvider(cmd *Command) (ResponseIf, error) { // ListProviders lists all providers // LIST PROVIDERS -func (c *RAGFlowClient) ListProviders(cmd *Command) (ResponseIf, error) { - if c.ServerType != "user" { +func (c *CLI) ListProviders(cmd *Command) (ResponseIf, error) { + if c.Config.CLIMode != APIMode { return nil, fmt.Errorf("this command is only allowed in USER mode") } - resp, err := c.HTTPClient.Request("GET", "/providers", "web", nil, nil) + resp, err := c.APIServerClientMap[c.Config.APIClientConfig.CurrentAPIServer].Request("GET", "/providers", "web", nil, nil) if err != nil { return nil, fmt.Errorf("failed to list providers: %w", err) } @@ -1188,8 +1171,8 @@ func (c *RAGFlowClient) ListProviders(cmd *Command) (ResponseIf, error) { // DeleteProvider deletes a provider // DELETE PROVIDER -func (c *RAGFlowClient) DeleteProvider(cmd *Command) (ResponseIf, error) { - if c.ServerType != "user" { +func (c *CLI) DeleteProvider(cmd *Command) (ResponseIf, error) { + if c.Config.CLIMode != APIMode { return nil, fmt.Errorf("this command is only allowed in USER mode") } @@ -1205,7 +1188,7 @@ func (c *RAGFlowClient) DeleteProvider(cmd *Command) (ResponseIf, error) { "llm_factory": providerName, } - resp, err := c.HTTPClient.Request("DELETE", url, "web", nil, payload) + resp, err := c.APIServerClientMap[c.Config.APIClientConfig.CurrentAPIServer].Request("DELETE", url, "web", nil, payload) if err != nil { return nil, fmt.Errorf("failed to delete provider: %w", err) } @@ -1229,8 +1212,8 @@ func (c *RAGFlowClient) DeleteProvider(cmd *Command) (ResponseIf, error) { // CreateProviderInstance creates a new provider instance // CREATE PROVIDER INSTANCE KEY URL REGION -func (c *RAGFlowClient) CreateProviderInstance(cmd *Command) (ResponseIf, error) { - if c.ServerType != "user" { +func (c *CLI) CreateProviderInstance(cmd *Command) (ResponseIf, error) { + if c.Config.CLIMode != APIMode { return nil, fmt.Errorf("this command is only allowed in USER mode") } @@ -1268,7 +1251,7 @@ func (c *RAGFlowClient) CreateProviderInstance(cmd *Command) (ResponseIf, error) "region": region, } - resp, err := c.HTTPClient.Request("POST", url, "web", nil, payload) + resp, err := c.APIServerClientMap[c.Config.APIClientConfig.CurrentAPIServer].Request("POST", url, "web", nil, payload) if err != nil { return nil, fmt.Errorf("failed to create provider instance: %w", err) } @@ -1292,8 +1275,8 @@ func (c *RAGFlowClient) CreateProviderInstance(cmd *Command) (ResponseIf, error) // ListProviderInstances lists all instances of a provider // LIST INSTANCES FROM PROVIDER -func (c *RAGFlowClient) ListProviderInstances(cmd *Command) (ResponseIf, error) { - if c.ServerType != "user" { +func (c *CLI) ListProviderInstances(cmd *Command) (ResponseIf, error) { + if c.Config.CLIMode != APIMode { return nil, fmt.Errorf("this command is only allowed in USER mode") } @@ -1304,7 +1287,7 @@ func (c *RAGFlowClient) ListProviderInstances(cmd *Command) (ResponseIf, error) url := fmt.Sprintf("/providers/%s/instances", providerName) - resp, err := c.HTTPClient.Request("GET", url, "web", nil, nil) + resp, err := c.APIServerClientMap[c.Config.APIClientConfig.CurrentAPIServer].Request("GET", url, "web", nil, nil) if err != nil { return nil, fmt.Errorf("failed to list instances: %w", err) } @@ -1328,8 +1311,8 @@ func (c *RAGFlowClient) ListProviderInstances(cmd *Command) (ResponseIf, error) // ShowProviderInstance shows details of a specific instance // SHOW INSTANCE FROM PROVIDER -func (c *RAGFlowClient) ShowProviderInstance(cmd *Command) (ResponseIf, error) { - if c.ServerType != "user" { +func (c *CLI) ShowProviderInstance(cmd *Command) (ResponseIf, error) { + if c.Config.CLIMode != APIMode { return nil, fmt.Errorf("this command is only allowed in USER mode") } @@ -1345,7 +1328,7 @@ func (c *RAGFlowClient) ShowProviderInstance(cmd *Command) (ResponseIf, error) { url := fmt.Sprintf("/providers/%s/instances/%s", providerName, instanceName) - resp, err := c.HTTPClient.Request("GET", url, "web", nil, nil) + resp, err := c.APIServerClientMap[c.Config.APIClientConfig.CurrentAPIServer].Request("GET", url, "web", nil, nil) if err != nil { return nil, fmt.Errorf("failed to show instance: %w", err) } @@ -1369,8 +1352,8 @@ func (c *RAGFlowClient) ShowProviderInstance(cmd *Command) (ResponseIf, error) { // ShowInstanceBalance shows balance of a specific instance // SHOW BALANCE FROM PROVIDER -func (c *RAGFlowClient) ShowInstanceBalance(cmd *Command) (ResponseIf, error) { - if c.ServerType != "user" { +func (c *CLI) ShowInstanceBalance(cmd *Command) (ResponseIf, error) { + if c.Config.CLIMode != APIMode { return nil, fmt.Errorf("this command is only allowed in USER mode") } @@ -1386,7 +1369,7 @@ func (c *RAGFlowClient) ShowInstanceBalance(cmd *Command) (ResponseIf, error) { url := fmt.Sprintf("/providers/%s/instances/%s/balance", providerName, instanceName) - resp, err := c.HTTPClient.Request("GET", url, "web", nil, nil) + resp, err := c.APIServerClientMap[c.Config.APIClientConfig.CurrentAPIServer].Request("GET", url, "web", nil, nil) if err != nil { return nil, fmt.Errorf("failed to show instance: %w", err) } @@ -1410,8 +1393,8 @@ func (c *RAGFlowClient) ShowInstanceBalance(cmd *Command) (ResponseIf, error) { // AlterProviderInstance renames a provider instance // ALTER INSTANCE NAME FROM PROVIDER -func (c *RAGFlowClient) AlterProviderInstance(cmd *Command) (ResponseIf, error) { - if c.ServerType != "user" { +func (c *CLI) AlterProviderInstance(cmd *Command) (ResponseIf, error) { + if c.Config.CLIMode != APIMode { return nil, fmt.Errorf("this command is only allowed in USER mode") } @@ -1436,7 +1419,7 @@ func (c *RAGFlowClient) AlterProviderInstance(cmd *Command) (ResponseIf, error) "llm_name": newName, } - resp, err := c.HTTPClient.Request("PUT", url, "web", nil, payload) + resp, err := c.APIServerClientMap[c.Config.APIClientConfig.CurrentAPIServer].Request("PUT", url, "web", nil, payload) if err != nil { return nil, fmt.Errorf("failed to alter instance: %w", err) } @@ -1460,8 +1443,8 @@ func (c *RAGFlowClient) AlterProviderInstance(cmd *Command) (ResponseIf, error) // DropProviderInstance deletes a provider instance // DROP INSTANCE FROM PROVIDER -func (c *RAGFlowClient) DropProviderInstance(cmd *Command) (ResponseIf, error) { - if c.ServerType != "user" { +func (c *CLI) DropProviderInstance(cmd *Command) (ResponseIf, error) { + if c.Config.CLIMode != APIMode { return nil, fmt.Errorf("this command is only allowed in USER mode") } @@ -1481,7 +1464,7 @@ func (c *RAGFlowClient) DropProviderInstance(cmd *Command) (ResponseIf, error) { url := fmt.Sprintf("/providers/%s/instances", providerName) - resp, err := c.HTTPClient.Request("DELETE", url, "web", nil, payload) + resp, err := c.APIServerClientMap[c.Config.APIClientConfig.CurrentAPIServer].Request("DELETE", url, "web", nil, payload) if err != nil { return nil, fmt.Errorf("failed to drop instance: %w", err) } @@ -1503,11 +1486,10 @@ func (c *RAGFlowClient) DropProviderInstance(cmd *Command) (ResponseIf, error) { return &result, nil } -// DropInstanceModel deletes a provider instance, only works for local deployed model // DROP MODEL FROM // Remove MODEL FROM -func (c *RAGFlowClient) DropInstanceModel(cmd *Command) (ResponseIf, error) { - if c.ServerType != "user" { +func (c *CLI) DropInstanceModel(cmd *Command) (ResponseIf, error) { + if c.Config.CLIMode != APIMode { return nil, fmt.Errorf("this command is only allowed in USER mode") } @@ -1532,7 +1514,7 @@ func (c *RAGFlowClient) DropInstanceModel(cmd *Command) (ResponseIf, error) { url := fmt.Sprintf("/providers/%s/instances/%s/models", providerName, instanceName) - resp, err := c.HTTPClient.Request("DELETE", url, "web", nil, payload) + resp, err := c.APIServerClientMap[c.Config.APIClientConfig.CurrentAPIServer].Request("DELETE", url, "web", nil, payload) if err != nil { return nil, fmt.Errorf("failed to drop instance: %w", err) } @@ -1554,8 +1536,8 @@ func (c *RAGFlowClient) DropInstanceModel(cmd *Command) (ResponseIf, error) { return &result, nil } -func (c *RAGFlowClient) ListInstanceModels(cmd *Command) (ResponseIf, error) { - if c.ServerType != "user" { +func (c *CLI) ListInstanceModels(cmd *Command) (ResponseIf, error) { + if c.Config.CLIMode != APIMode { return nil, fmt.Errorf("this command is only allowed in USER mode") } providerName, ok := cmd.Params["provider_name"].(string) @@ -1570,7 +1552,7 @@ func (c *RAGFlowClient) ListInstanceModels(cmd *Command) (ResponseIf, error) { var endPoint string endPoint = fmt.Sprintf("/providers/%s/instances/%s/models", providerName, instanceName) - resp, err := c.HTTPClient.Request("GET", endPoint, "web", nil, nil) + resp, err := c.APIServerClientMap[c.Config.APIClientConfig.CurrentAPIServer].Request("GET", endPoint, "web", nil, nil) if err != nil { return nil, fmt.Errorf("failed to list instance models: %w", err) } @@ -1591,8 +1573,8 @@ func (c *RAGFlowClient) ListInstanceModels(cmd *Command) (ResponseIf, error) { return &result, nil } -func (c *RAGFlowClient) EnableOrDisableModel(cmd *Command, status string) (ResponseIf, error) { - if c.ServerType != "user" { +func (c *CLI) EnableOrDisableModel(cmd *Command, status string) (ResponseIf, error) { + if c.Config.CLIMode != APIMode { return nil, fmt.Errorf("this command is only allowed in USER mode") } @@ -1617,7 +1599,7 @@ func (c *RAGFlowClient) EnableOrDisableModel(cmd *Command, status string) (Respo "status": status, } - resp, err := c.HTTPClient.Request("PATCH", url, "web", nil, payload) + resp, err := c.APIServerClientMap[c.Config.APIClientConfig.CurrentAPIServer].Request("PATCH", url, "web", nil, payload) if err != nil { return nil, fmt.Errorf("failed to enable/disable model: %w", err) } @@ -1643,8 +1625,8 @@ func isValidURL(str string) bool { return u.Scheme != "" && u.Host != "" } -func (c *RAGFlowClient) ChatToModel(cmd *Command) (ResponseIf, error) { - if c.ServerType != "user" { +func (c *CLI) ChatToModel(cmd *Command) (ResponseIf, error) { + if c.Config.CLIMode != APIMode { return nil, fmt.Errorf("this command is only allowed in USER mode") } @@ -1806,7 +1788,7 @@ func (c *RAGFlowClient) ChatToModel(cmd *Command) (ResponseIf, error) { if stream { // Call stream http api startTime := time.Now() - reader, err := c.HTTPClient.RequestStream("POST", url, "web", nil, payload) + reader, err := c.APIServerClientMap[c.Config.APIClientConfig.CurrentAPIServer].RequestStream("POST", url, "web", nil, payload) if err != nil { return nil, fmt.Errorf("failed to chat model: %w", err) } @@ -1875,7 +1857,7 @@ func (c *RAGFlowClient) ChatToModel(cmd *Command) (ResponseIf, error) { return result, nil } - resp, err := c.HTTPClient.Request("POST", url, "web", nil, payload) + resp, err := c.APIServerClientMap[c.Config.APIClientConfig.CurrentAPIServer].Request("POST", url, "web", nil, payload) if err != nil { return nil, formatRequestError("Chat request", err) } @@ -1896,12 +1878,12 @@ func (c *RAGFlowClient) ChatToModel(cmd *Command) (ResponseIf, error) { return &result, nil } -func (c *RAGFlowClient) EmbedUserText(cmd *Command) (ResponseIf, error) { - if c.HTTPClient.APIToken == "" && c.HTTPClient.LoginToken == "" { +func (c *CLI) EmbedUserText(cmd *Command) (ResponseIf, error) { + if c.APIServerClientMap[c.Config.APIClientConfig.CurrentAPIServer].APIToken == nil && c.APIServerClientMap[c.Config.APIClientConfig.CurrentAPIServer].LoginToken == nil { return nil, fmt.Errorf("API token not set. Please login first") } - if c.ServerType != "user" { + if c.Config.CLIMode != APIMode { return nil, fmt.Errorf("this command is only allowed in USER mode") } @@ -1945,7 +1927,7 @@ func (c *RAGFlowClient) EmbedUserText(cmd *Command) (ResponseIf, error) { url := "/embeddings" - resp, err := c.HTTPClient.Request("POST", url, "web", nil, payload) + resp, err := c.APIServerClientMap[c.Config.APIClientConfig.CurrentAPIServer].Request("POST", url, "web", nil, payload) if err != nil { return nil, fmt.Errorf("failed to embed text: %w", err) } @@ -1963,12 +1945,12 @@ func (c *RAGFlowClient) EmbedUserText(cmd *Command) (ResponseIf, error) { return &result, nil } -func (c *RAGFlowClient) RerankUserDocument(cmd *Command) (ResponseIf, error) { - if c.HTTPClient.APIToken == "" && c.HTTPClient.LoginToken == "" { +func (c *CLI) RerankUserDocument(cmd *Command) (ResponseIf, error) { + if c.APIServerClientMap[c.Config.APIClientConfig.CurrentAPIServer].APIToken == nil && c.APIServerClientMap[c.Config.APIClientConfig.CurrentAPIServer].LoginToken == nil { return nil, fmt.Errorf("API token not set. Please login first") } - if c.ServerType != "user" { + if c.Config.CLIMode != APIMode { return nil, fmt.Errorf("this command is only allowed in USER mode") } @@ -2018,7 +2000,7 @@ func (c *RAGFlowClient) RerankUserDocument(cmd *Command) (ResponseIf, error) { url := "/rerank" - resp, err := c.HTTPClient.Request("POST", url, "web", nil, payload) + resp, err := c.APIServerClientMap[c.Config.APIClientConfig.CurrentAPIServer].Request("POST", url, "web", nil, payload) if err != nil { return nil, fmt.Errorf("failed to rerank document: %w", err) } @@ -2036,12 +2018,12 @@ func (c *RAGFlowClient) RerankUserDocument(cmd *Command) (ResponseIf, error) { return &result, nil } -func (c *RAGFlowClient) TTSUserCommand(cmd *Command) (ResponseIf, error) { - if c.HTTPClient.APIToken == "" && c.HTTPClient.LoginToken == "" { +func (c *CLI) TTSUserCommand(cmd *Command) (ResponseIf, error) { + if c.APIServerClientMap[c.Config.APIClientConfig.CurrentAPIServer].APIToken == nil && c.APIServerClientMap[c.Config.APIClientConfig.CurrentAPIServer].LoginToken == nil { return nil, fmt.Errorf("API token not set. Please login first") } - if c.ServerType != "user" { + if c.Config.CLIMode != APIMode { return nil, fmt.Errorf("this command is only allowed in USER mode") } @@ -2130,7 +2112,7 @@ func (c *RAGFlowClient) TTSUserCommand(cmd *Command) (ResponseIf, error) { url := "/audio/speech" - resp, err := c.HTTPClient.Request("POST", url, "web", nil, payload) + resp, err := c.APIServerClientMap[c.Config.APIClientConfig.CurrentAPIServer].Request("POST", url, "web", nil, payload) if err != nil { return nil, fmt.Errorf("failed to TTS document: %w", err) } @@ -2228,12 +2210,12 @@ func (c *RAGFlowClient) TTSUserCommand(cmd *Command) (ResponseIf, error) { return &result, nil } -func (c *RAGFlowClient) ASRUserCommand(cmd *Command) (ResponseIf, error) { - if c.HTTPClient.APIToken == "" && c.HTTPClient.LoginToken == "" { +func (c *CLI) ASRUserCommand(cmd *Command) (ResponseIf, error) { + if c.APIServerClientMap[c.Config.APIClientConfig.CurrentAPIServer].APIToken == nil && c.APIServerClientMap[c.Config.APIClientConfig.CurrentAPIServer].LoginToken == nil { return nil, fmt.Errorf("API token not set. Please login first") } - if c.ServerType != "user" { + if c.Config.CLIMode != APIMode { return nil, fmt.Errorf("this command is only allowed in USER mode") } @@ -2284,7 +2266,7 @@ func (c *RAGFlowClient) ASRUserCommand(cmd *Command) (ResponseIf, error) { url := "/audio/transcriptions" - resp, err := c.HTTPClient.Request("POST", url, "web", nil, payload) + resp, err := c.APIServerClientMap[c.Config.APIClientConfig.CurrentAPIServer].Request("POST", url, "web", nil, payload) if err != nil { return nil, fmt.Errorf("failed to ASR document: %w", err) } @@ -2315,12 +2297,12 @@ func (c *RAGFlowClient) ASRUserCommand(cmd *Command) (ResponseIf, error) { return &result, nil } -func (c *RAGFlowClient) OCRUserCommand(cmd *Command) (ResponseIf, error) { - if c.HTTPClient.APIToken == "" && c.HTTPClient.LoginToken == "" { +func (c *CLI) OCRUserCommand(cmd *Command) (ResponseIf, error) { + if c.APIServerClientMap[c.Config.APIClientConfig.CurrentAPIServer].APIToken == nil && c.APIServerClientMap[c.Config.APIClientConfig.CurrentAPIServer].LoginToken == nil { return nil, fmt.Errorf("API token not set. Please login first") } - if c.ServerType != "user" { + if c.Config.CLIMode != APIMode { return nil, fmt.Errorf("this command is only allowed in USER mode") } @@ -2378,7 +2360,7 @@ func (c *RAGFlowClient) OCRUserCommand(cmd *Command) (ResponseIf, error) { url := "/file/ocr" - resp, err := c.HTTPClient.Request("POST", url, "web", nil, payload) + resp, err := c.APIServerClientMap[c.Config.APIClientConfig.CurrentAPIServer].Request("POST", url, "web", nil, payload) if err != nil { return nil, fmt.Errorf("failed to OCR document: %w", err) } @@ -2397,12 +2379,12 @@ func (c *RAGFlowClient) OCRUserCommand(cmd *Command) (ResponseIf, error) { return &result, nil } -func (c *RAGFlowClient) ParseFileUserCommand(cmd *Command) (ResponseIf, error) { - if c.HTTPClient.APIToken == "" && c.HTTPClient.LoginToken == "" { +func (c *CLI) ParseFileUserCommand(cmd *Command) (ResponseIf, error) { + if c.APIServerClientMap[c.Config.APIClientConfig.CurrentAPIServer].APIToken == nil && c.APIServerClientMap[c.Config.APIClientConfig.CurrentAPIServer].LoginToken == nil { return nil, fmt.Errorf("API token not set. Please login first") } - if c.ServerType != "user" { + if c.Config.CLIMode != APIMode { return nil, fmt.Errorf("this command is only allowed in USER mode") } @@ -2465,7 +2447,7 @@ func (c *RAGFlowClient) ParseFileUserCommand(cmd *Command) (ResponseIf, error) { url := "/file/parse" - resp, err := c.HTTPClient.Request("POST", url, "web", nil, payload) + resp, err := c.APIServerClientMap[c.Config.APIClientConfig.CurrentAPIServer].Request("POST", url, "web", nil, payload) if err != nil { return nil, fmt.Errorf("failed to PARSE document: %w", err) } @@ -2484,12 +2466,12 @@ func (c *RAGFlowClient) ParseFileUserCommand(cmd *Command) (ResponseIf, error) { return &result, nil } -func (c *RAGFlowClient) ListTasksUserCommand(cmd *Command) (ResponseIf, error) { - if c.HTTPClient.APIToken == "" && c.HTTPClient.LoginToken == "" { +func (c *CLI) ListTasksUserCommand(cmd *Command) (ResponseIf, error) { + if c.APIServerClientMap[c.Config.APIClientConfig.CurrentAPIServer].APIToken == nil && c.APIServerClientMap[c.Config.APIClientConfig.CurrentAPIServer].LoginToken == nil { return nil, fmt.Errorf("API token not set. Please login first") } - if c.ServerType != "user" { + if c.Config.CLIMode != APIMode { return nil, fmt.Errorf("this command is only allowed in USER mode") } @@ -2509,7 +2491,7 @@ func (c *RAGFlowClient) ListTasksUserCommand(cmd *Command) (ResponseIf, error) { url := fmt.Sprintf("/providers/%s/instances/%s/tasks", providerName, instanceName) - resp, err := c.HTTPClient.Request("GET", url, "web", nil, nil) + resp, err := c.APIServerClientMap[c.Config.APIClientConfig.CurrentAPIServer].Request("GET", url, "web", nil, nil) if err != nil { return nil, fmt.Errorf("failed to list tasks: %w", err) } @@ -2527,12 +2509,12 @@ func (c *RAGFlowClient) ListTasksUserCommand(cmd *Command) (ResponseIf, error) { return &result, nil } -func (c *RAGFlowClient) ShowTaskUserCommand(cmd *Command) (ResponseIf, error) { - if c.HTTPClient.APIToken == "" && c.HTTPClient.LoginToken == "" { +func (c *CLI) ShowTaskUserCommand(cmd *Command) (ResponseIf, error) { + if c.APIServerClientMap[c.Config.APIClientConfig.CurrentAPIServer].APIToken == nil && c.APIServerClientMap[c.Config.APIClientConfig.CurrentAPIServer].LoginToken == nil { return nil, fmt.Errorf("API token not set. Please login first") } - if c.ServerType != "user" { + if c.Config.CLIMode != APIMode { return nil, fmt.Errorf("this command is only allowed in USER mode") } @@ -2557,7 +2539,7 @@ func (c *RAGFlowClient) ShowTaskUserCommand(cmd *Command) (ResponseIf, error) { url := fmt.Sprintf("/providers/%s/instances/%s/tasks/%s", providerName, instanceName, taskID) - resp, err := c.HTTPClient.Request("GET", url, "web", nil, nil) + resp, err := c.APIServerClientMap[c.Config.APIClientConfig.CurrentAPIServer].Request("GET", url, "web", nil, nil) if err != nil { return nil, fmt.Errorf("failed to get task: %w", err) } @@ -2575,12 +2557,12 @@ func (c *RAGFlowClient) ShowTaskUserCommand(cmd *Command) (ResponseIf, error) { return &result, nil } -func (c *RAGFlowClient) CheckProviderConnection(cmd *Command) (ResponseIf, error) { - if c.HTTPClient.APIToken == "" && c.HTTPClient.LoginToken == "" { +func (c *CLI) CheckProviderConnection(cmd *Command) (ResponseIf, error) { + if c.APIServerClientMap[c.Config.APIClientConfig.CurrentAPIServer].APIToken == nil && c.APIServerClientMap[c.Config.APIClientConfig.CurrentAPIServer].LoginToken == nil { return nil, fmt.Errorf("API token not set. Please login first") } - if c.ServerType != "user" { + if c.Config.CLIMode != APIMode { return nil, fmt.Errorf("this command is only allowed in USER mode") } @@ -2596,7 +2578,7 @@ func (c *RAGFlowClient) CheckProviderConnection(cmd *Command) (ResponseIf, error url := fmt.Sprintf("/providers/%s/instances/%s/connection", providerName, instanceName) - resp, err := c.HTTPClient.Request("GET", url, "web", nil, nil) + resp, err := c.APIServerClientMap[c.Config.APIClientConfig.CurrentAPIServer].Request("GET", url, "web", nil, nil) if err != nil { return nil, fmt.Errorf("failed to check provider connection: %w", err) } @@ -2614,11 +2596,11 @@ func (c *RAGFlowClient) CheckProviderConnection(cmd *Command) (ResponseIf, error return &result, nil } -func (c *RAGFlowClient) CheckProviderWithKey(cmd *Command) (ResponseIf, error) { - if c.HTTPClient.APIToken == "" && c.HTTPClient.LoginToken == "" { +func (c *CLI) CheckProviderWithKey(cmd *Command) (ResponseIf, error) { + if c.APIServerClientMap[c.Config.APIClientConfig.CurrentAPIServer].APIToken == nil && c.APIServerClientMap[c.Config.APIClientConfig.CurrentAPIServer].LoginToken == nil { return nil, fmt.Errorf("API token not set. Please login first") } - if c.ServerType != "user" { + if c.Config.CLIMode != APIMode { return nil, fmt.Errorf("this command is only allowed in USER mode") } @@ -2653,7 +2635,7 @@ func (c *RAGFlowClient) CheckProviderWithKey(cmd *Command) (ResponseIf, error) { payload["base_url"] = baseURL } - resp, err := c.HTTPClient.Request("GET", url, "api", nil, payload) + resp, err := c.APIServerClientMap[c.Config.APIClientConfig.CurrentAPIServer].Request("GET", url, "api", nil, payload) if err != nil { return nil, fmt.Errorf("failed to check provider connection with key: %w", err) } @@ -2673,11 +2655,11 @@ func (c *RAGFlowClient) CheckProviderWithKey(cmd *Command) (ResponseIf, error) { } // UseModel sets the current model for chat -func (c *RAGFlowClient) UseModel(cmd *Command) (ResponseIf, error) { - if c.HTTPClient.APIToken == "" && c.HTTPClient.LoginToken == "" { +func (c *CLI) UseModel(cmd *Command) (ResponseIf, error) { + if c.APIServerClientMap[c.Config.APIClientConfig.CurrentAPIServer].APIToken == nil && c.APIServerClientMap[c.Config.APIClientConfig.CurrentAPIServer].LoginToken == nil { return nil, fmt.Errorf("API token not set. Please login first") } - if c.ServerType != "user" { + if c.Config.CLIMode != APIMode { return nil, fmt.Errorf("this command is only allowed in USER mode") } @@ -2703,34 +2685,12 @@ func (c *RAGFlowClient) UseModel(cmd *Command) (ResponseIf, error) { return &result, nil } -// ShowCurrentModel displays the current model configuration -func (c *RAGFlowClient) ShowCurrentModel(cmd *Command) (ResponseIf, error) { - if c.ServerType != "user" { - return nil, fmt.Errorf("this command is only allowed in USER mode") - } - - if c.CurrentModel == nil { - return nil, fmt.Errorf("no current model set. Use 'use model' command first") - } - - var result CommonResponse - result.Code = 0 - result.Data = []map[string]interface{}{ - { - "provider": c.CurrentModel.Provider, - "instance": c.CurrentModel.Instance, - "model": c.CurrentModel.Model, - }, - } - return &result, nil -} - -func (c *RAGFlowClient) AddCustomModel(cmd *Command) (ResponseIf, error) { - if c.HTTPClient.APIToken == "" && c.HTTPClient.LoginToken == "" { +func (c *CLI) AddCustomModel(cmd *Command) (ResponseIf, error) { + if c.APIServerClientMap[c.Config.APIClientConfig.CurrentAPIServer].APIToken == nil && c.APIServerClientMap[c.Config.APIClientConfig.CurrentAPIServer].LoginToken == nil { return nil, fmt.Errorf("API token not set. Please login first") } - if c.ServerType != "user" { + if c.Config.CLIMode != APIMode { return nil, fmt.Errorf("this command is only allowed in USER mode") } @@ -2757,7 +2717,7 @@ func (c *RAGFlowClient) AddCustomModel(cmd *Command) (ResponseIf, error) { "models": models, } - resp, err := c.HTTPClient.Request("POST", url, "web", nil, payload) + resp, err := c.APIServerClientMap[c.Config.APIClientConfig.CurrentAPIServer].Request("POST", url, "web", nil, payload) if err != nil { return nil, fmt.Errorf("failed to add custom model: %w", err) } @@ -2779,11 +2739,11 @@ func (c *RAGFlowClient) AddCustomModel(cmd *Command) (ResponseIf, error) { // Context related commands // CECat handles the cat command - shows content using Context Engine -func (c *RAGFlowClient) CECat(cmd *Command) (ResponseIf, error) { - if c.HTTPClient.APIToken == "" && c.HTTPClient.LoginToken == "" { +func (c *CLI) CECat(cmd *Command) (ResponseIf, error) { + if c.APIServerClientMap[c.Config.APIClientConfig.CurrentAPIServer].APIToken == nil && c.APIServerClientMap[c.Config.APIClientConfig.CurrentAPIServer].LoginToken == nil { return nil, fmt.Errorf("API token not set. Please login first") } - if c.ServerType != "user" { + if c.Config.CLIMode != APIMode { return nil, fmt.Errorf("this command is only allowed in USER mode") } @@ -2801,7 +2761,7 @@ func (c *RAGFlowClient) CECat(cmd *Command) (ResponseIf, error) { // Convert to response var response ContextCatResponse - response.OutputFormat = c.OutputFormat + response.OutputFormat = c.outputFormat response.Code = 0 response.Content = string(content) @@ -2809,7 +2769,7 @@ func (c *RAGFlowClient) CECat(cmd *Command) (ResponseIf, error) { } // CEList handles the ls command - lists nodes using Context Engine -func (c *RAGFlowClient) CEList(cmd *Command) (ResponseIf, error) { +func (c *CLI) CEList(cmd *Command) (ResponseIf, error) { // Get path from command params, default to "datasets" path, _ := cmd.Params["path"].(string) if path == "" { @@ -2837,15 +2797,15 @@ func (c *RAGFlowClient) CEList(cmd *Command) (ResponseIf, error) { // Convert to response var response ContextListResponse - response.OutputFormat = c.OutputFormat + response.OutputFormat = c.outputFormat response.Code = 0 - response.Data = ce.FormatNodes(result.Nodes, string(c.OutputFormat)) + response.Data = ce.FormatNodes(result.Nodes, string(c.outputFormat)) return &response, nil } // CESearch handles the search command using Context Engine -func (c *RAGFlowClient) CESearch(cmd *Command) (ResponseIf, error) { +func (c *CLI) CESearch(cmd *Command) (ResponseIf, error) { // Get path and query from command params path, _ := cmd.Params["path"].(string) if path == "" { @@ -2876,17 +2836,17 @@ func (c *RAGFlowClient) CESearch(cmd *Command) (ResponseIf, error) { // Convert to response var response ContextSearchResponse - response.OutputFormat = c.OutputFormat + response.OutputFormat = c.outputFormat response.Code = 0 response.Total = result.Total - response.Data = ce.FormatNodes(result.Nodes, string(c.OutputFormat)) + response.Data = ce.FormatNodes(result.Nodes, string(c.outputFormat)) return &response, nil } // InsertChunksFromFile inserts chunks from a JSON file -func (c *RAGFlowClient) InsertChunksFromFile(cmd *Command) (ResponseIf, error) { - if c.ServerType != "user" { +func (c *CLI) InsertChunksFromFile(cmd *Command) (ResponseIf, error) { + if c.Config.CLIMode != APIMode { return nil, fmt.Errorf("this command is only allowed in USER mode") } filePath, ok := cmd.Params["file_path"].(string) @@ -2898,7 +2858,7 @@ func (c *RAGFlowClient) InsertChunksFromFile(cmd *Command) (ResponseIf, error) { "file_path": filePath, } - resp, err := c.HTTPClient.Request("POST", "/tenant/insert_chunks_from_file", "web", nil, payload) + resp, err := c.APIServerClientMap[c.Config.APIClientConfig.CurrentAPIServer].Request("POST", "/tenant/insert_chunks_from_file", "web", nil, payload) if err != nil { return nil, fmt.Errorf("failed to insert dataset from file: %w", err) } @@ -2929,8 +2889,8 @@ func (c *RAGFlowClient) InsertChunksFromFile(cmd *Command) (ResponseIf, error) { } // InsertMetadataFromFile inserts metadata from a JSON file -func (c *RAGFlowClient) InsertMetadataFromFile(cmd *Command) (ResponseIf, error) { - if c.ServerType != "user" { +func (c *CLI) InsertMetadataFromFile(cmd *Command) (ResponseIf, error) { + if c.Config.CLIMode != APIMode { return nil, fmt.Errorf("this command is only allowed in USER mode") } @@ -2943,7 +2903,7 @@ func (c *RAGFlowClient) InsertMetadataFromFile(cmd *Command) (ResponseIf, error) "file_path": filePath, } - resp, err := c.HTTPClient.Request("POST", "/tenant/insert_metadata_from_file", "web", nil, payload) + resp, err := c.APIServerClientMap[c.Config.APIClientConfig.CurrentAPIServer].Request("POST", "/tenant/insert_metadata_from_file", "web", nil, payload) if err != nil { return nil, fmt.Errorf("failed to insert metadata from file: %w", err) } @@ -2974,8 +2934,8 @@ func (c *RAGFlowClient) InsertMetadataFromFile(cmd *Command) (ResponseIf, error) } // UpdateChunk updates a chunk in a dataset -func (c *RAGFlowClient) UpdateChunk(cmd *Command) (ResponseIf, error) { - if c.ServerType != "user" { +func (c *CLI) UpdateChunk(cmd *Command) (ResponseIf, error) { + if c.Config.CLIMode != APIMode { return nil, fmt.Errorf("this command is only allowed in USER mode") } @@ -3016,7 +2976,7 @@ func (c *RAGFlowClient) UpdateChunk(cmd *Command) (ResponseIf, error) { payload["document_id"] = docID payload["chunk_id"] = chunkID - resp, err := c.HTTPClient.Request("POST", "/chunk/update", "web", nil, payload) + resp, err := c.APIServerClientMap[c.Config.APIClientConfig.CurrentAPIServer].Request("POST", "/chunk/update", "web", nil, payload) if err != nil { return nil, fmt.Errorf("failed to update chunk: %w", err) } @@ -3047,8 +3007,8 @@ func (c *RAGFlowClient) UpdateChunk(cmd *Command) (ResponseIf, error) { } // GetChunk retrieves a chunk by ID -func (c *RAGFlowClient) GetChunk(cmd *Command) (ResponseIf, error) { - if c.ServerType != "user" { +func (c *CLI) GetChunk(cmd *Command) (ResponseIf, error) { + if c.Config.CLIMode != APIMode { return nil, fmt.Errorf("this command is only allowed in USER mode") } @@ -3067,7 +3027,7 @@ func (c *RAGFlowClient) GetChunk(cmd *Command) (ResponseIf, error) { return nil, fmt.Errorf("dataset_id not provided") } - resp, err := c.HTTPClient.Request("GET", fmt.Sprintf("/datasets/%s/documents/%s/chunks/%s", datasetID, docID, chunkID), "web", nil, nil) + resp, err := c.APIServerClientMap[c.Config.APIClientConfig.CurrentAPIServer].Request("GET", fmt.Sprintf("/datasets/%s/documents/%s/chunks/%s", datasetID, docID, chunkID), "web", nil, nil) if err != nil { return nil, fmt.Errorf("failed to get chunk: %w", err) } @@ -3090,8 +3050,8 @@ func (c *RAGFlowClient) GetChunk(cmd *Command) (ResponseIf, error) { } // SetMeta sets metadata for a document -func (c *RAGFlowClient) SetMeta(cmd *Command) (ResponseIf, error) { - if c.ServerType != "user" { +func (c *CLI) SetMeta(cmd *Command) (ResponseIf, error) { + if c.Config.CLIMode != APIMode { return nil, fmt.Errorf("this command is only allowed in USER mode") } @@ -3110,7 +3070,7 @@ func (c *RAGFlowClient) SetMeta(cmd *Command) (ResponseIf, error) { "meta": metaJSON, } - resp, err := c.HTTPClient.Request("POST", "/document/set_meta", "web", nil, payload) + resp, err := c.APIServerClientMap[c.Config.APIClientConfig.CurrentAPIServer].Request("POST", "/document/set_meta", "web", nil, payload) if err != nil { return nil, fmt.Errorf("failed to set metadata: %w", err) } @@ -3142,8 +3102,8 @@ func (c *RAGFlowClient) SetMeta(cmd *Command) (ResponseIf, error) { // DeleteMeta deletes metadata for a document // If keys is provided, deletes specific keys; otherwise deletes entire document metadata -func (c *RAGFlowClient) DeleteMeta(cmd *Command) (ResponseIf, error) { - if c.ServerType != "user" { +func (c *CLI) DeleteMeta(cmd *Command) (ResponseIf, error) { + if c.Config.CLIMode != APIMode { return nil, fmt.Errorf("this command is only allowed in USER mode") } @@ -3161,7 +3121,7 @@ func (c *RAGFlowClient) DeleteMeta(cmd *Command) (ResponseIf, error) { payload["keys"] = keysJSON } - resp, err := c.HTTPClient.Request("POST", "/document/delete_meta", "web", nil, payload) + resp, err := c.APIServerClientMap[c.Config.APIClientConfig.CurrentAPIServer].Request("POST", "/document/delete_meta", "web", nil, payload) if err != nil { return nil, fmt.Errorf("failed to delete metadata: %w", err) } @@ -3192,8 +3152,8 @@ func (c *RAGFlowClient) DeleteMeta(cmd *Command) (ResponseIf, error) { } // RmTags removes tags from chunks in a dataset -func (c *RAGFlowClient) RmTags(cmd *Command) (ResponseIf, error) { - if c.ServerType != "user" { +func (c *CLI) RmTags(cmd *Command) (ResponseIf, error) { + if c.Config.CLIMode != APIMode { return nil, fmt.Errorf("this command is only allowed in USER mode") } @@ -3216,7 +3176,7 @@ func (c *RAGFlowClient) RmTags(cmd *Command) (ResponseIf, error) { "tags": tags, } - resp, err := c.HTTPClient.Request("DELETE", "/datasets/"+kbID+"/tags", "web", nil, payload) + resp, err := c.APIServerClientMap[c.Config.APIClientConfig.CurrentAPIServer].Request("DELETE", "/datasets/"+kbID+"/tags", "web", nil, payload) if err != nil { return nil, fmt.Errorf("failed to remove tags: %w", err) } @@ -3247,8 +3207,8 @@ func (c *RAGFlowClient) RmTags(cmd *Command) (ResponseIf, error) { } // RemoveChunks removes chunks from a document -func (c *RAGFlowClient) RemoveChunks(cmd *Command) (ResponseIf, error) { - if c.ServerType != "user" { +func (c *CLI) RemoveChunks(cmd *Command) (ResponseIf, error) { + if c.Config.CLIMode != APIMode { return nil, fmt.Errorf("this command is only allowed in USER mode") } @@ -3277,7 +3237,7 @@ func (c *RAGFlowClient) RemoveChunks(cmd *Command) (ResponseIf, error) { payload["chunk_ids"] = chunkIDs } - resp, err := c.HTTPClient.Request("DELETE", "/datasets/"+datasetID+"/documents/"+docID+"/chunks", "web", nil, payload) + resp, err := c.APIServerClientMap[c.Config.APIClientConfig.CurrentAPIServer].Request("DELETE", "/datasets/"+datasetID+"/documents/"+docID+"/chunks", "web", nil, payload) if err != nil { return nil, fmt.Errorf("failed to remove chunks: %w", err) } @@ -3316,12 +3276,12 @@ func (c *RAGFlowClient) RemoveChunks(cmd *Command) (ResponseIf, error) { return &result, nil } -func (c *RAGFlowClient) ParseDocumentsUserCommand(cmd *Command) (ResponseIf, error) { - if c.HTTPClient.APIToken == "" && c.HTTPClient.LoginToken == "" { +func (c *CLI) ParseDocumentsUserCommand(cmd *Command) (ResponseIf, error) { + if c.APIServerClientMap[c.Config.APIClientConfig.CurrentAPIServer].APIToken == nil && c.APIServerClientMap[c.Config.APIClientConfig.CurrentAPIServer].LoginToken == nil { return nil, fmt.Errorf("API token not set. Please login first") } - if c.ServerType != "user" { + if c.Config.CLIMode != APIMode { return nil, fmt.Errorf("this command is only allowed in USER mode") } @@ -3342,7 +3302,7 @@ func (c *RAGFlowClient) ParseDocumentsUserCommand(cmd *Command) (ResponseIf, err } // Normal mode - resp, err := c.HTTPClient.Request("POST", url, "web", nil, payload) + resp, err := c.APIServerClientMap[c.Config.APIClientConfig.CurrentAPIServer].Request("POST", url, "web", nil, payload) if err != nil { return nil, fmt.Errorf("failed to list documents: %w", err) } diff --git a/internal/cli/user_parser.go b/internal/cli/user_parser.go index ea1eef4337..006675c6e2 100644 --- a/internal/cli/user_parser.go +++ b/internal/cli/user_parser.go @@ -174,6 +174,8 @@ func (p *Parser) parseListCommand() (*Command, error) { return p.parseListFiles() case TokenQuotedString: return p.parseListQuotedStringCommand() + case TokenAPI: + return p.parseListApiCommand() default: return nil, fmt.Errorf("unknown LIST target: %s", p.curToken.Value) } @@ -432,23 +434,13 @@ func (p *Parser) parseShowCommand() (*Command, error) { return NewCommand("show_token"), nil case TokenCurrent: p.nextToken() - if p.curToken.Type == TokenUser { + + // Semicolon is optional for SHOW TOKEN + if p.curToken.Type == TokenSemicolon { p.nextToken() - // Semicolon is optional for SHOW CURRENT USER - if p.curToken.Type == TokenSemicolon { - p.nextToken() - } - return NewCommand("show_current_user"), nil - } else if p.curToken.Type == TokenModel { - p.nextToken() - // Semicolon is optional for SHOW CURRENT MODEL - if p.curToken.Type == TokenSemicolon { - p.nextToken() - } - return NewCommand("show_current_model"), nil } - return nil, fmt.Errorf("expected USER or MODEL after CURRENT") + return NewCommand("show_current"), nil case TokenUser: return p.parseShowUser() case TokenRole: @@ -469,6 +461,10 @@ func (p *Parser) parseShowCommand() (*Command, error) { return p.parseShowTask() case TokenQuotedString: return p.parseShowQuotedStringCommand() + case TokenAdmin: + return p.parseUserShowAdmin() + case TokenAPI: + return p.parseUserShowAPI() default: return nil, fmt.Errorf("unknown SHOW target: %s", p.curToken.Value) } @@ -666,6 +662,10 @@ func (p *Parser) parseAddCommand() (*Command, error) { return p.parseAddProvider() case TokenModel: return p.parseAddModel() + case TokenAPI: + return p.parseAddAPIServer() + case TokenAdmin: + return p.parseAddAdminServer() default: return nil, fmt.Errorf("unknown ADD target: %s", p.curToken.Value) } @@ -1089,6 +1089,180 @@ A: return cmd, nil } +// syntax: add admin server host '127.0.0.1:9333' user 'ccc' password 'ppp' +func (p *Parser) parseAddAdminServer() (*Command, error) { + p.nextToken() // consume ADMIN + + if p.curToken.Type != TokenServer { + return nil, fmt.Errorf("expected server name") + } + p.nextToken() // consume SERVER + + if p.curToken.Type != TokenHost { + return nil, fmt.Errorf("expected HOST") + } + p.nextToken() + + host, err := p.parseQuotedString() + if err != nil { + return nil, err + } + p.nextToken() + + ip, port, err := parseHostPort(host) + if err != nil { + return nil, err + } + + cmd := NewCommand("add_admin_server") + cmd.Params["server_ip"] = ip + cmd.Params["server_port"] = port + + return cmd, nil +} + +// syntax: add api server 'abc' host '127.0.0.1:9333' token 'xxx' user 'ccc' password 'ppp' +func (p *Parser) parseAddAPIServer() (*Command, error) { + p.nextToken() // consume API + + if p.curToken.Type != TokenServer { + return nil, fmt.Errorf("expected server name") + } + p.nextToken() // consume SERVER + + serverName, err := p.parseQuotedString() + if err != nil { + return nil, err + } + p.nextToken() // consume model name + + if p.curToken.Type != TokenHost { + return nil, fmt.Errorf("expected TO") + } + p.nextToken() + + host, err := p.parseQuotedString() + if err != nil { + return nil, err + } + p.nextToken() + + ip, port, err := parseHostPort(host) + if err != nil { + return nil, err + } + + var token string + +optionsLoop: + for { + switch p.curToken.Type { + case TokenToken: + p.nextToken() + token, err = p.parseQuotedString() + if err != nil { + return nil, err + } + case TokenSemicolon: + p.nextToken() + break optionsLoop // done + default: + // No more options to process + break optionsLoop + } + } + + cmd := NewCommand("add_api_server") + cmd.Params["server_name"] = serverName + cmd.Params["server_ip"] = ip + cmd.Params["server_port"] = port + if token != "" { + cmd.Params["server_token"] = token + } + + return cmd, nil +} + +// syntax: delete api server 'abc' +func (p *Parser) parseDeleteAPIServer() (*Command, error) { + p.nextToken() // consume API + + if p.curToken.Type != TokenServer { + return nil, fmt.Errorf("expected server name") + } + p.nextToken() // consume SERVER + + serverName, err := p.parseQuotedString() + if err != nil { + return nil, err + } + p.nextToken() // consume model name + + cmd := NewCommand("delete_api_server") + cmd.Params["server_name"] = serverName + + // Semicolon is optional + if p.curToken.Type == TokenSemicolon { + p.nextToken() + } + + return cmd, nil +} + +// syntax: delete admin server 'abc' +func (p *Parser) parseDeleteAdminServer() (*Command, error) { + p.nextToken() // consume ADMIN + + if p.curToken.Type != TokenServer { + return nil, fmt.Errorf("expected server name") + } + p.nextToken() // consume SERVER + + cmd := NewCommand("delete_admin_server") + + // Semicolon is optional + if p.curToken.Type == TokenSemicolon { + p.nextToken() + } + + return cmd, nil +} + +func (p *Parser) parseUserSaveCommand() (*Command, error) { + p.nextToken() // consume SAVE + switch p.curToken.Type { + case TokenConfig: + return p.parseSaveConfig() + default: + return nil, fmt.Errorf("unknown ADD target: %s", p.curToken.Value) + } +} + +// syntax: save config as 'path' +func (p *Parser) parseSaveConfig() (*Command, error) { + p.nextToken() // consume CONFIG + + if p.curToken.Type != TokenAs { + return nil, fmt.Errorf("expected AS after CONFIG") + } + p.nextToken() // consume AS + + path, err := p.parseQuotedString() + if err != nil { + return nil, err + } + + cmd := NewCommand("save_config_command") + cmd.Params["path"] = path + + // Semicolon is optional + if p.curToken.Type == TokenSemicolon { + p.nextToken() + } + + return cmd, nil +} + func (p *Parser) parseCreateDataset() (*Command, error) { p.nextToken() // consume DATASET datasetName, err := p.parseQuotedString() @@ -1196,6 +1370,10 @@ func (p *Parser) parseDeleteCommand() (*Command, error) { return p.parseDeleteProvider() case TokenMetadata: return p.parseDeleteMeta() + case TokenAdmin: + return p.parseDeleteAdminServer() + case TokenAPI: + return p.parseDeleteAPIServer() default: return nil, fmt.Errorf("unknown DELETE target: %s", p.curToken.Value) } @@ -2403,7 +2581,7 @@ func (p *Parser) parseInsertMetadataFromFile() (*Command, error) { return cmd, nil } -func (p *Parser) parseSearchCommand() (*Command, error) { +func (p *Parser) parseRetrieveCommand() (*Command, error) { p.nextToken() // consume SEARCH // Handle help flag: -h / --help. The lexer tokenizes each leading @@ -3668,8 +3846,8 @@ func (p *Parser) parseUserStatement() (*Command, error) { return p.parseImportCommand() case TokenInsert: return p.parseInsertCommand() - case TokenSearch: - return p.parseSearchCommand() + case TokenRetrieve: + return p.parseRetrieveCommand() case TokenGet: return p.parseGetCommand() case TokenUpdate: @@ -4172,3 +4350,75 @@ func (p *Parser) parseRemoveChunk() (*Command, error) { return cmd, nil } + +// parseShowTask parses SHOW ADMIN SERVER +func (p *Parser) parseUserShowAdmin() (*Command, error) { + p.nextToken() // consume ADMIN + + var cmd *Command + switch p.curToken.Type { + case TokenServer: + p.nextToken() + cmd = NewCommand("show_admin_server") + default: + return nil, fmt.Errorf("expected SERVER after ADMIN") + } + + // Semicolon is optional + if p.curToken.Type == TokenSemicolon { + p.nextToken() + } + return cmd, nil +} + +// parseShowTask parses SHOW API SERVER +func (p *Parser) parseUserShowAPI() (*Command, error) { + p.nextToken() // consume API + + var cmd *Command + switch p.curToken.Type { + case TokenServer: + p.nextToken() + cmd = NewCommand("show_api_server") + + serverName, err := p.parseQuotedString() + if err != nil { + return nil, fmt.Errorf("expected dataset_name: %w", err) + } + cmd.Params["api_server_name"] = serverName + p.nextToken() + + default: + return nil, fmt.Errorf("expected SERVER after API") + } + + // Semicolon is optional + if p.curToken.Type == TokenSemicolon { + p.nextToken() + } + + return cmd, nil +} + +func (p *Parser) parseListApiCommand() (*Command, error) { + p.nextToken() // consume API + + var cmd *Command + switch p.curToken.Type { + case TokenServer: + p.nextToken() + + cmd = NewCommand("list_api_server") + p.nextToken() + + default: + return nil, fmt.Errorf("expected SERVER after API") + } + + // Semicolon is optional + if p.curToken.Type == TokenSemicolon { + p.nextToken() + } + + return cmd, nil +} diff --git a/web/src/locales/it.ts b/web/src/locales/it.ts index 7192c506b5..61e8e2c293 100644 --- a/web/src/locales/it.ts +++ b/web/src/locales/it.ts @@ -164,7 +164,8 @@ export default { version: 'Versione', skillVersion: 'Versione', skillVersionPlaceholder: 'es. 1.0.0', - versionFormatHelp: 'La versione deve essere in formato semver (es. 1.0.0)', + versionFormatHelp: + 'La versione deve essere in formato semver (es. 1.0.0)', versionRequired: 'La versione è richiesta', selectFilesOrFolder: 'Seleziona file o cartella', uploadDescription: @@ -208,9 +209,12 @@ export default { 'Skill non valida: "name" deve essere minuscolo e URL-safe (solo lettere, numeri, trattini).', invalid_version: 'Skill non valida: "version" deve essere un semver valido (es. 1.0.0).', - invalid_metadata: 'Skill non valida: i metadati contengono campi non validi.', - invalid_file_type: 'Skill non valida: sono ammessi solo file basati su testo.', - invalid_path: 'Skill non valida: il percorso del file contiene caratteri non validi.', + invalid_metadata: + 'Skill non valida: i metadati contengono campi non validi.', + invalid_file_type: + 'Skill non valida: sono ammessi solo file basati su testo.', + invalid_path: + 'Skill non valida: il percorso del file contiene caratteri non validi.', file_too_large: 'Skill non valida: la dimensione del singolo file supera il limite di 5MB.', total_size_exceeded: @@ -236,7 +240,8 @@ export default { accessToken: 'Token di accesso', githubTokenHelp: 'Per repository privati o limiti di frequenza più alti (5000 richieste/ora)', - giteeTokenHelp: 'Per repository privati o limiti di frequenza più alti (2000 richieste/ora)', + giteeTokenHelp: + 'Per repository privati o limiti di frequenza più alti (2000 richieste/ora)', rateLimitInfo: 'Info limite di frequenza', githubRateLimit: 'Repository pubblici: 60 richieste/ora per IP. Usa il token per 5000 richieste/ora.', @@ -255,7 +260,7 @@ export default { similarityThreshold: 'Soglia di similarità', topK: 'Top K risultati', indexFields: 'Campi indice', - indexFieldsDesc: 'Seleziona quali campi includere nell\'indice di ricerca', + indexFieldsDesc: "Seleziona quali campi includere nell'indice di ricerca", fieldName: 'Nome', fieldNameDesc: 'Nome skill', fieldTags: 'Tag', @@ -324,7 +329,7 @@ Procedural Memory: skill apprese, abitudini e procedure automatizzate.`, forget: 'Dimentica', forgetMessageTip: 'Sei sicuro di voler dimenticare?', messageDescription: - 'L\'estrazione della memoria è configurata con i prompt e la temperatura dalle impostazioni avanzate.', + "L'estrazione della memoria è configurata con i prompt e la temperatura dalle impostazioni avanzate.", copied: 'Copiato!', contentEmbed: 'Embedding contenuto', content: 'Contenuto', @@ -381,15 +386,17 @@ Esempio: un messaggio di 1 KB con embedding a 1024 dimensioni usa ~9 KB. Il limi fields: 'campi', selectFiles: 'Selezionati {{count}} file', type: 'Tipo', - fieldNameInvalid: 'Il nome del campo può contenere solo lettere o underscore.', + fieldNameInvalid: + 'Il nome del campo può contenere solo lettere o underscore.', builtIn: 'Integrato', generation: 'Generazione', toMetadataSetting: 'Impostazioni generazione', - toMetadataSettingTip: 'Imposta i metadati automatici nella Configurazione.', + toMetadataSettingTip: + 'Imposta i metadati automatici nella Configurazione.', descriptionTip: - 'Fornisci descrizioni o esempi per guidare l\'LLM nell\'estrazione dei valori per questo campo. Se lasciato vuoto, si baserà sul nome del campo.', + "Fornisci descrizioni o esempi per guidare l'LLM nell'estrazione dei valori per questo campo. Se lasciato vuoto, si baserà sul nome del campo.", restrictDefinedValuesTip: - 'Modalità Enum: limita l\'estrazione dell\'LLM ai soli valori preimpostati. Definisci i valori qui sotto.', + "Modalità Enum: limita l'estrazione dell'LLM ai soli valori preimpostati. Definisci i valori qui sotto.", valueExists: 'Il valore esiste già. Conferma per unire i duplicati e combinare tutti i file associati.', fieldNameExists: @@ -400,7 +407,8 @@ Esempio: un messaggio di 1 KB con embedding a 1024 dimensioni usa ~9 KB. Il limi 'Il nome del campo esiste già. Conferma per unire i duplicati.', fieldExists: 'Il campo esiste già.', fieldSetting: 'Impostazioni campo', - changesAffectNewParses: 'Le modifiche riguardano solo le nuove analisi.', + changesAffectNewParses: + 'Le modifiche riguardano solo le nuove analisi.', restrictDefinedValues: 'Limita ai valori definiti', metadataGenerationSettings: 'Impostazioni generazione metadati', manageMetadata: 'Gestisci metadati', @@ -426,7 +434,8 @@ Esempio: un messaggio di 1 KB con embedding a 1024 dimensioni usa ~9 KB. Il limi deleteSettingValueWarn: `Questo valore sarà eliminato; i metadati esistenti non saranno interessati.`, }, redoAll: 'Cancella i chunk esistenti', - applyAutoMetadataSettings: 'Applica le impostazioni globali dei metadati automatici', + applyAutoMetadataSettings: + 'Applica le impostazioni globali dei metadati automatici', parseFileTip: 'Sei sicuro di voler analizzare?', parseFile: 'Analizza file', emptyMetadata: 'Nessun metadato', @@ -553,7 +562,7 @@ Esempio: un messaggio di 1 KB con embedding a 1024 dimensioni usa ~9 KB. Il limi changeSpecificCategory: 'Cambia categoria specifica', uploadTitle: 'Trascina e rilascia il tuo file qui per caricarlo', uploadDescription: - 'Supporta caricamento singolo o multiplo. Per un RAGFlow distribuito localmente: il limite di dimensione totale dei file per caricamento è 1GB, con un limite batch di 32 file. Non c\'è limite al numero totale di file per account. Per cloud.ragflow.io, il limite di dimensione totale dei file per caricamento è 10MB, con ogni file non superiore a 10MB e un massimo di 128 file per account.', + "Supporta caricamento singolo o multiplo. Per un RAGFlow distribuito localmente: il limite di dimensione totale dei file per caricamento è 1GB, con un limite batch di 32 file. Non c'è limite al numero totale di file per account. Per cloud.ragflow.io, il limite di dimensione totale dei file per caricamento è 10MB, con ogni file non superiore a 10MB e un massimo di 128 file per account.", chunk: 'Chunk', bulk: 'Multiplo', cancel: 'Annulla', @@ -609,7 +618,8 @@ Esempio: un messaggio di 1 KB con embedding a 1024 dimensioni usa ~9 KB. Il limi randomSeedTip: 'Il seme è il punto di partenza per un algoritmo pseudo-casuale che garantisce la riproducibilità dello stesso output in esecuzioni diverse.', datasetDescription: 'Descrivi il tuo dataset', - overlappedPercentTip: 'La percentuale di sovrapposizione tra due chunk adiacenti', + overlappedPercentTip: + 'La percentuale di sovrapposizione tra due chunk adiacenti', globalIndexModelTip: 'Usato per generare grafi della conoscenza, RAPTOR, metadati automatici, parole chiave automatiche e domande automatiche. Le prestazioni del modello influenzeranno la qualità della generazione.', globalIndexModel: 'Modello di indicizzazione', @@ -622,22 +632,24 @@ Esempio: un messaggio di 1 KB con embedding a 1024 dimensioni usa ~9 KB. Il limi mineruOptions: 'Opzioni MinerU', mineruParseMethod: 'Metodo di analisi', mineruParseMethodTip: - 'Metodo per l\'analisi del PDF: auto (rilevamento automatico), txt (estrazione testo), ocr (riconoscimento ottico dei caratteri)', + "Metodo per l'analisi del PDF: auto (rilevamento automatico), txt (estrazione testo), ocr (riconoscimento ottico dei caratteri)", mineruFormulaEnable: 'Riconoscimento formule', mineruFormulaEnableTip: 'Abilita il riconoscimento delle formule. Nota: questo potrebbe non funzionare correttamente per documenti cirillici.', mineruTableEnable: 'Riconoscimento tabelle', - mineruTableEnableTip: 'Abilita il riconoscimento e l\'estrazione delle tabelle.', + mineruTableEnableTip: + "Abilita il riconoscimento e l'estrazione delle tabelle.", paddleocrOptions: 'Opzioni PaddleOCR', paddleocrApiUrl: 'URL API di PaddleOCR', - paddleocrApiUrlTip: 'L\'URL dell\'endpoint API del servizio PaddleOCR', + paddleocrApiUrlTip: "L'URL dell'endpoint API del servizio PaddleOCR", paddleocrApiUrlPlaceholder: 'es. https://paddleocr-server.com/layout-parsing', paddleocrAccessToken: 'Token di accesso AI Studio', - paddleocrAccessTokenTip: 'Token di accesso per l\'API PaddleOCR (opzionale)', + paddleocrAccessTokenTip: + "Token di accesso per l'API PaddleOCR (opzionale)", paddleocrAccessTokenPlaceholder: 'Il tuo token AI Studio (opzionale)', paddleocrAlgorithm: 'Algoritmo PaddleOCR', - paddleocrAlgorithmTip: 'Algoritmo da usare per l\'analisi PaddleOCR', + paddleocrAlgorithmTip: "Algoritmo da usare per l'analisi PaddleOCR", paddleocrSelectAlgorithm: 'Seleziona algoritmo', paddleocrModelNamePlaceholder: 'es. paddleocr-from-env-1', overlappedPercent: 'Percentuale di sovrapposizione(%)', @@ -652,7 +664,8 @@ Esempio: un messaggio di 1 KB con embedding a 1024 dimensioni usa ~9 KB. Il limi baseInfo: 'Base', globalIndex: 'Indice globale', dataSource: 'Fonte dati', - linkSourceSetTip: 'Gestisci il collegamento della fonte dati con questo dataset', + linkSourceSetTip: + 'Gestisci il collegamento della fonte dati con questo dataset', linkDataSource: 'Collega fonte dati', tocExtraction: 'PageIndex', tocExtractionTip: @@ -672,7 +685,8 @@ Esempio: un messaggio di 1 KB con embedding a 1024 dimensioni usa ~9 KB. Il limi setDefaultTip: '', setDefault: 'Imposta come predefinito', editLinkDataPipeline: 'Modifica ingestion pipeline', - linkPipelineSetTip: 'Gestisci il collegamento della ingestion pipeline con questo dataset', + linkPipelineSetTip: + 'Gestisci il collegamento della ingestion pipeline con questo dataset', default: 'Predefinito', dataPipeline: 'Cambia o configura la ingestion pipeline.', linkDataPipeline: 'Collega ingestion pipeline', @@ -688,7 +702,7 @@ Esempio: un messaggio di 1 KB con embedding a 1024 dimensioni usa ~9 KB. Il limi 'Aggiorna la configurazione del tuo dataset qui, in particolare il LLM e i prompt.', name: 'Nome dataset', photo: 'Foto dataset', - photoTip: 'Puoi caricare un\'immagine fino a 4 MB.', + photoTip: "Puoi caricare un'immagine fino a 4 MB.", description: 'Descrizione', language: 'Lingua documento', languageMessage: 'Inserisci la tua lingua!', @@ -696,7 +710,8 @@ Esempio: un messaggio di 1 KB con embedding a 1024 dimensioni usa ~9 KB. Il limi permissions: 'Permessi', embeddingModel: 'Modello di embedding', chunkTokenNumber: 'Dimensione chunk raccomandata', - chunkTokenNumberMessage: 'Il numero di token per chunk per il testo è richiesto', + chunkTokenNumberMessage: + 'Il numero di token per chunk per il testo è richiesto', embeddingModelTip: 'Il modello di embedding predefinito usato dal dataset. Una volta che il dataset contiene chunk, quando si cambia il modello di embedding il sistema estrae casualmente alcuni chunk per una verifica di compatibilità, li ricodifica con il nuovo modello di embedding e calcola la similarità coseno tra i vettori nuovi e quelli vecchi. Il cambio è consentito solo se la similarità media del campione è ≥ 0.9. In caso contrario, è necessario eliminare tutti i chunk nel dataset prima di poterlo modificare.', permissionsTip: @@ -846,13 +861,14 @@ Esempio: un messaggio di 1 KB con embedding a 1024 dimensioni usa ~9 KB. Il limi 'RAPTOR può essere usato per attività di question-answering multi-hop. Naviga alla pagina File, clicca Genera > RAPTOR per abilitarlo. Vedi https://ragflow.io/docs/dev/enable_raptor per i dettagli.', prompt: 'Prompt', promptTip: - 'Usa il prompt di sistema per descrivere il compito per l\'LLM, specificare come dovrebbe rispondere e delineare altri requisiti vari. Il prompt di sistema è spesso usato insieme a chiavi (variabili), che servono come vari input di dati per l\'LLM. Usa una barra `/` o il pulsante (x) per mostrare le chiavi da usare.', + "Usa il prompt di sistema per descrivere il compito per l'LLM, specificare come dovrebbe rispondere e delineare altri requisiti vari. Il prompt di sistema è spesso usato insieme a chiavi (variabili), che servono come vari input di dati per l'LLM. Usa una barra `/` o il pulsante (x) per mostrare le chiavi da usare.", promptMessage: 'Il prompt è richiesto', promptText: `Per favore riassumi i seguenti paragrafi. Fai attenzione ai numeri, non inventare cose. Paragrafi come segue: {cluster_content} Quanto sopra è il contenuto che devi riassumere.`, maxToken: 'Token massimi', - maxTokenTip: 'Il numero massimo di token per chunk di riepilogo generato.', + maxTokenTip: + 'Il numero massimo di token per chunk di riepilogo generato.', maxTokenMessage: 'Token massimi richiesti', threshold: 'Soglia', thresholdTip: @@ -902,12 +918,12 @@ Questa funzionalità di auto-tagging migliora il recupero aggiungendo un ulterio NER: Usa spaCy NER e l'estrazione di parole chiave basata su regole per estrarre entità e relazioni. Non è necessario un LLM per l'estrazione stessa, rendendola veloce ed efficiente nelle risorse.`, graphRagBatchChunkTokenSize: 'Dimensione token chunk batch', graphRagBatchChunkTokenSizeTip: - 'Il limite di token per ogni batch di chunk inviati all\'LLM per l\'estrazione di entità e relazioni del grafo della conoscenza. Non applicato a NER.', + "Il limite di token per ogni batch di chunk inviati all'LLM per l'estrazione di entità e relazioni del grafo della conoscenza. Non applicato a NER.", resolution: 'Risoluzione entità', resolutionTip: `Un interruttore di deduplicazione entità. Quando abilitato, l'LLM combinerà entità simili - es. '2025' e 'l'anno 2025', o 'IT' e 'Information Technology' - per costruire un grafo più accurato`, community: 'Report comunità', communityTip: - 'In un grafo della conoscenza, una comunità è un cluster di entità collegate da relazioni. Puoi far generare all\'LLM un abstract per ogni comunità, noto come report comunità. Vedi qui per maggiori informazioni: https://www.microsoft.com/en-us/research/blog/graphrag-improving-global-search-via-dynamic-community-selection/', + "In un grafo della conoscenza, una comunità è un cluster di entità collegate da relazioni. Puoi far generare all'LLM un abstract per ogni comunità, noto come report comunità. Vedi qui per maggiori informazioni: https://www.microsoft.com/en-us/research/blog/graphrag-improving-global-search-via-dynamic-community-selection/", theDocumentBeingParsedCannotBeDeleted: 'Il documento in fase di analisi non può essere eliminato', lastWeek: 'dalla settimana scorsa', @@ -933,7 +949,8 @@ Questa funzionalità di auto-tagging migliora il recupero aggiungendo un ulterio disabled: 'Disabilitato', keyword: 'Parola chiave', image: 'Immagine', - imageUploaderTitle: 'Carica una nuova immagine per aggiornare questo chunk immagine', + imageUploaderTitle: + 'Carica una nuova immagine per aggiornare questo chunk immagine', function: 'Funzione', chunkMessage: 'Inserisci un valore!', full: 'Testo completo', @@ -1005,7 +1022,7 @@ Questa funzionalità di auto-tagging migliora il recupero aggiungendo un ulterio - **Considera sempre** l'intera cronologia della conversazione.`, systemMessage: 'Inserisci!', systemTip: - 'I tuoi prompt o istruzioni per l\'LLM, inclusi ma non limitati al suo ruolo, la lunghezza desiderata, il tono e la lingua delle sue risposte. Se il tuo modello ha supporto nativo per il ragionamento, puoi aggiungere //no_thinking al prompt per fermare il ragionamento.', + "I tuoi prompt o istruzioni per l'LLM, inclusi ma non limitati al suo ruolo, la lunghezza desiderata, il tono e la lingua delle sue risposte. Se il tuo modello ha supporto nativo per il ragionamento, puoi aggiungere //no_thinking al prompt per fermare il ragionamento.", topN: 'Top N', topNTip: `Non tutti i chunk con punteggio di similarità sopra la 'soglia di similarità' saranno inviati all'LLM. Questo seleziona 'Top N' chunk da quelli recuperati.`, variable: 'Variabile', @@ -1070,7 +1087,7 @@ Questa funzionalità di auto-tagging migliora il recupero aggiungendo un ulterio embedModalTitle: 'Incorpora nella pagina web', published: 'Pubblicato', publishedTooltip: - 'Usa la versione pubblicata per questo embed. Quando abilitato, l\'URL generato include release=true.', + "Usa la versione pubblicata per questo embed. Quando abilitato, l'URL generato include release=true.", embedType: 'Tipo di embed', fullscreenChat: 'Chat a schermo intero (iframe tradizionale)', floatingWidget: 'Widget fluttuante (stile Intercom)', @@ -1104,7 +1121,7 @@ Questa funzionalità di auto-tagging migliora il recupero aggiungendo un ulterio 'Questo ottimizza le query utente usando il contesto in una conversazione multi-round. Quando abilitato, consumerà token LLM aggiuntivi.', howUseId: "Come usare l'ID chat?", description: "Descrizione dell'assistente", - descriptionPlaceholder: "Sono un assistente chat.", + descriptionPlaceholder: 'Sono un assistente chat.', useKnowledgeGraph: 'Usa grafo della conoscenza', useKnowledgeGraphTip: 'Se usare il/i grafo/i della conoscenza nel/i dataset specificato/i durante il recupero per il question answering multi-hop. Quando abilitato, ciò comporterebbe ricerche iterative attraverso chunk di entità, relazioni e report di comunità, aumentando notevolmente il tempo di recupero.', @@ -1127,7 +1144,7 @@ Questa funzionalità di auto-tagging migliora il recupero aggiungendo un ulterio createChat: 'Crea chat', metadata: 'Metadati', metadataTip: - 'Il filtraggio dei metadati è il processo di utilizzo degli attributi dei metadati (come tag, categorie o permessi di accesso) per affinare e controllare il recupero delle informazioni rilevanti all\'interno di un sistema.', + "Il filtraggio dei metadati è il processo di utilizzo degli attributi dei metadati (come tag, categorie o permessi di accesso) per affinare e controllare il recupero delle informazioni rilevanti all'interno di un sistema.", conditions: 'Condizioni', metadataKeys: 'Elementi filtrabili', addCondition: 'Aggiungi condizione', @@ -1153,7 +1170,7 @@ Questa funzionalità di auto-tagging migliora il recupero aggiungendo un ulterio 'Permette a questo modello di chiamare strumenti quando il tipo di modello selezionato supporta le chiamate a strumenti.', deleteModel: 'Elimina modello', bedrockCredentialsHint: - 'Suggerimento: lascia Access Key / Secret Key vuoti per usare l\'autenticazione AWS IAM.', + "Suggerimento: lascia Access Key / Secret Key vuoti per usare l'autenticazione AWS IAM.", awsAuthModeAccessKeySecret: 'Access Key', awsAuthModeIamRole: 'IAM Role', awsAuthModeAssumeRole: 'Assume Role', @@ -1162,10 +1179,11 @@ Questa funzionalità di auto-tagging migliora il recupero aggiungendo un ulterio awsRoleArn: 'AWS Role ARN', awsRoleArnMessage: 'Inserisci AWS Role ARN', awsAssumeRoleTip: - 'Se selezioni questa modalità, l\'istanza Amazon EC2 assumerà il suo ruolo esistente per accedere ai servizi AWS. Non sono richieste credenziali aggiuntive.', + "Se selezioni questa modalità, l'istanza Amazon EC2 assumerà il suo ruolo esistente per accedere ai servizi AWS. Non sono richieste credenziali aggiuntive.", modelEmptyTip: 'Nessun modello disponibile.
Aggiungi modelli dal pannello a destra.', - sourceEmptyTip: 'Nessuna fonte dati aggiunta. Selezionane una qui sotto per connetterti.', + sourceEmptyTip: + 'Nessuna fonte dati aggiunta. Selezionane una qui sotto per connetterti.', seconds: 'secondi', minutes: 'minuti', edit: 'Modifica', @@ -1177,9 +1195,9 @@ Questa funzionalità di auto-tagging migliora il recupero aggiungendo un ulterio connectorNameTip: 'Un nome descrittivo per il connettore', syncDeletedFiles: 'Sincronizza file eliminati', confluenceIsCloudTip: - 'Spunta se questa è un\'istanza Confluence Cloud, deseleziona per Confluence Server/Data Center', + "Spunta se questa è un'istanza Confluence Cloud, deseleziona per Confluence Server/Data Center", confluenceWikiBaseUrlTip: - 'L\'URL base della tua istanza Confluence (es. https://your-domain.atlassian.net/wiki)', + "L'URL base della tua istanza Confluence (es. https://your-domain.atlassian.net/wiki)", confluenceSpaceKeyTip: 'Opzionale: specifica una chiave spazio per limitare la sincronizzazione a uno spazio specifico. Lascia vuoto per sincronizzare tutti gli spazi accessibili. Per più spazi, separa con virgole (es. DEV,DOCS,HR)', s3PrefixTip: `Specifica il percorso della cartella all'interno del tuo bucket S3 da cui recuperare i file. @@ -1215,7 +1233,8 @@ Esempio: Virtual Hosted Style`, 'Sincronizza pagine e database da Notion per il recupero della conoscenza.', google_driveDescription: 'Connetti il tuo Google Drive tramite OAuth e sincronizza cartelle o drive specifici.', - gmailDescription: 'Connetti il tuo Gmail tramite OAuth per sincronizzare le email.', + gmailDescription: + 'Connetti il tuo Gmail tramite OAuth per sincronizzare le email.', webdavDescription: 'Connettiti a server WebDAV per sincronizzare i file.', webdavRemotePathTip: 'Opzionale: specifica un percorso cartella sul server WebDAV (es. /Documents). Lascia vuoto per sincronizzare dalla radice.', @@ -1238,31 +1257,34 @@ Esempio: Virtual Hosted Style`, teamsDescription: 'Connetti Microsoft Teams tramite Microsoft Graph per sincronizzare post e risposte dei canali.', teamsTenantIdTip: - 'ID tenant Azure AD. Richiede un\'app con permessi applicativi Team.ReadBasic.All e ChannelMessage.Read.All (consenso admin).', + "ID tenant Azure AD. Richiede un'app con permessi applicativi Team.ReadBasic.All e ChannelMessage.Read.All (consenso admin).", slackDescription: 'Connetti il tuo workspace Slack per sincronizzare messaggi e thread dei canali.', slackBotTokenTip: - 'Token OAuth utente bot Slack (inizia con xoxb-). L\'app necessita degli scope channels:read, channels:history e users:read.', + "Token OAuth utente bot Slack (inizia con xoxb-). L'app necessita degli scope channels:read, channels:history e users:read.", slackChannelsTip: 'Opzionale: nomi dei canali da sincronizzare (es. general). Lascia vuoto per sincronizzare tutti i canali accessibili.', sharepointDescription: 'Connetti un sito SharePoint tramite Microsoft Graph per sincronizzare le sue librerie documenti.', sharepointSiteUrlTip: - 'URL completo del sito SharePoint da indicizzare, es. https://contoso.sharepoint.com/sites/MySite. Richiede un\'app Azure AD con permessi applicativi Sites.Read.All e Files.Read.All (consenso admin).', - bitbucketDescription: 'Connetti Bitbucket per sincronizzare il contenuto delle PR.', + "URL completo del sito SharePoint da indicizzare, es. https://contoso.sharepoint.com/sites/MySite. Richiede un'app Azure AD con permessi applicativi Sites.Read.All e Files.Read.All (consenso admin).", + bitbucketDescription: + 'Connetti Bitbucket per sincronizzare il contenuto delle PR.', bitbucketTopWorkspaceTip: 'Il workspace Bitbucket da indicizzare (es. "atlassian" da https://bitbucket.org/atlassian/workspace ).', bitbucketRepositorySlugsTip: 'Slug dei repository separati da virgola. Es. repo-one,repo-two', - bitbucketProjectsTip: 'Chiavi progetto separate da virgola. Es. PROJ1,PROJ2', + bitbucketProjectsTip: + 'Chiavi progetto separate da virgola. Es. PROJ1,PROJ2', bitbucketWorkspaceTip: 'Questo connettore indicizzerà tutti i repository nel workspace.', - boxDescription: 'Connetti il tuo drive Box per sincronizzare file e cartelle.', + boxDescription: + 'Connetti il tuo drive Box per sincronizzare file e cartelle.', githubDescription: 'Connetti GitHub per sincronizzare pull request e issue per il recupero.', airtableDescription: - 'Connettiti ad Airtable e sincronizza i file da una tabella specificata all\'interno di un workspace designato.', + "Connettiti ad Airtable e sincronizza i file da una tabella specificata all'interno di un workspace designato.", dingtalkAITableDescription: 'Connettiti a Dingtalk AI Table e sincronizza i record da una tabella specificata.', gitlabDescription: @@ -1276,30 +1298,33 @@ Esempio: Virtual Hosted Style`, moodleDescription: 'Connettiti al tuo LMS Moodle per sincronizzare contenuti dei corsi, forum e risorse.', moodleUrlTip: - 'L\'URL base della tua istanza Moodle (es. https://moodle.university.edu). Non includere /webservice o /login.', + "L'URL base della tua istanza Moodle (es. https://moodle.university.edu). Non includere /webservice o /login.", moodleTokenTip: - 'Genera un token di servizio web in Moodle: vai a Amministrazione del sito → Server → Servizi web → Gestisci token. L\'utente deve essere iscritto ai corsi che vuoi sincronizzare.', + "Genera un token di servizio web in Moodle: vai a Amministrazione del sito → Server → Servizi web → Gestisci token. L'utente deve essere iscritto ai corsi che vuoi sincronizzare.", seafileDescription: 'Connettiti al tuo server SeaFile per sincronizzare file e documenti dalle tue librerie.', seafileUrlTip: - 'L\'URL completo del tuo server SeaFile incluso il protocollo. Esempio: https://seafile.example.com - Non includere una barra finale o alcun percorso dopo il dominio.', + "L'URL completo del tuo server SeaFile incluso il protocollo. Esempio: https://seafile.example.com - Non includere una barra finale o alcun percorso dopo il dominio.", seafileAccountScopeTip: 'Sincronizza tutte le librerie visibili al token API account qui sotto.', - seafileTokenPanelHeading: 'Fornisci uno di questi metodi di autenticazione:', - seafileTokenPanelAccountBullet: '- concede accesso a tutte le tue librerie.', + seafileTokenPanelHeading: + 'Fornisci uno di questi metodi di autenticazione:', + seafileTokenPanelAccountBullet: + '- concede accesso a tutte le tue librerie.', seafileTokenPanelLibraryBullet: '— limitato a una singola libreria (più sicuro).', seafileValidationAccountTokenRequired: - 'Il token API account è richiesto per l\'ambito Intero account', + "Il token API account è richiesto per l'ambito Intero account", seafileValidationTokenRequired: 'Fornisci un token API account o un token libreria', - seafileValidationLibraryIdRequired: 'L\'ID libreria è richiesto', - seafileValidationDirectoryPathRequired: 'Il percorso directory è richiesto', + seafileValidationLibraryIdRequired: "L'ID libreria è richiesto", + seafileValidationDirectoryPathRequired: + 'Il percorso directory è richiesto', seafileSyncScopeTip: 'Controlla cosa viene sincronizzato: ' + '(1) Intero account - Sincronizza tutte le librerie a cui il tuo token ha accesso. Richiede un token API account. ' + - '(2) Singola libreria - Sincronizza tutti i file all\'interno di una libreria specifica. Richiede l\'ID libreria e un token API account o un token API libreria. ' + - '(3) Directory specifica - Sincronizza solo i file all\'interno di una cartella specifica dentro una libreria. Richiede l\'ID libreria, il percorso della cartella all\'interno di quella libreria, e un token API account o un token API libreria.', + "(2) Singola libreria - Sincronizza tutti i file all'interno di una libreria specifica. Richiede l'ID libreria e un token API account o un token API libreria. " + + "(3) Directory specifica - Sincronizza solo i file all'interno di una cartella specifica dentro una libreria. Richiede l'ID libreria, il percorso della cartella all'interno di quella libreria, e un token API account o un token API libreria.", seafileTokenTip: 'Il tuo token API SeaFile a livello di account. ' + 'Concede accesso a tutte le librerie visibili al tuo account. ' + @@ -1309,16 +1334,16 @@ Esempio: Virtual Hosted Style`, 'Un token API limitato a una libreria che concede accesso solo a una libreria specifica. ' + 'Può essere usato al posto del token API account per gli ambiti di sincronizzazione "Singola libreria" e "Directory specifica".', seafileRepoIdTip: - 'L\'identificatore univoco (UUID) della libreria SeaFile che vuoi sincronizzare. ' + - 'Puoi trovarlo nella barra degli indirizzi del browser quando apri la libreria nell\'interfaccia web SeaFile. ' + + "L'identificatore univoco (UUID) della libreria SeaFile che vuoi sincronizzare. " + + "Puoi trovarlo nella barra degli indirizzi del browser quando apri la libreria nell'interfaccia web SeaFile. " + 'Esempio: 7a9e1b3c-4d5f-6a7b-8c9d-0e1f2a3b4c5d. ' + 'Richiesto quando l\'ambito di sincronizzazione è "Singola libreria" o "Directory specifica".', seafileSyncPathTip: - 'Il percorso assoluto della cartella da sincronizzare all\'interno della libreria specificata dall\'ID libreria sopra. ' + + "Il percorso assoluto della cartella da sincronizzare all'interno della libreria specificata dall'ID libreria sopra. " + 'Deve iniziare con una barra in avanti. ' + 'Tutti i file e le sottocartelle sotto questo percorso saranno inclusi ricorsivamente. ' + 'Esempio: /Documents/Reports. ' + - 'Importante: la cartella deve esistere all\'interno della libreria specificata. ' + + "Importante: la cartella deve esistere all'interno della libreria specificata. " + 'I percorsi al di fuori della libreria non sono supportati. ' + 'Usato solo quando l\'ambito di sincronizzazione è "Directory specifica".', seafileIncludeSharedTip: @@ -1337,8 +1362,7 @@ Esempio: Virtual Hosted Style`, 'Opzionale: limita la sincronizzazione a una singola chiave progetto (es. ENG).', jiraJqlTip: 'Filtro JQL opzionale. Lascia vuoto per affidarti ai filtri progetto/tempo.', - jiraBatchSizeTip: - 'Numero massimo di issue richieste da Jira per batch.', + jiraBatchSizeTip: 'Numero massimo di issue richieste da Jira per batch.', jiraCommentsTip: 'Includi i commenti Jira nel documento markdown generato.', jiraAttachmentsTip: @@ -1346,12 +1370,12 @@ Esempio: Virtual Hosted Style`, jiraAttachmentSizeTip: 'Gli allegati più grandi di questo numero di byte saranno saltati.', jiraLabelsTip: - 'Etichette che dovrebbero essere saltate durante l\'indicizzazione (separate da virgola).', + "Etichette che dovrebbero essere saltate durante l'indicizzazione (separate da virgola).", jiraBlacklistTip: 'I commenti la cui email autore corrisponde a queste voci saranno ignorati.', jiraScopedTokenTip: 'Abilita questo quando usi token Atlassian limitati (api.atlassian.com).', - jiraEmailTip: 'Email associata all\'account/token API Jira.', + jiraEmailTip: "Email associata all'account/token API Jira.", jiraTokenTip: 'Token API generato da https://id.atlassian.com/manage-profile/security/api-tokens.', jiraPasswordTip: @@ -1367,7 +1391,7 @@ Esempio: Virtual Hosted Style`, mysqlIdColumnTip: 'Colonna da usare come ID documento univoco. Se non specificata, sarà usato un hash del contenuto.', mysqlTimestampColumnTip: - 'Colonna datetime/timestamp per la sincronizzazione incrementale. Saranno recuperate solo le righe modificate dopo l\'ultima sincronizzazione.', + "Colonna datetime/timestamp per la sincronizzazione incrementale. Saranno recuperate solo le righe modificate dopo l'ultima sincronizzazione.", postgresqlDescription: 'Connettiti al database PostgreSQL per sincronizzare i dati dalle tabelle usando query SQL.', postgresqlQueryTip: @@ -1379,23 +1403,23 @@ Esempio: Virtual Hosted Style`, postgresqlIdColumnTip: 'Colonna da usare come ID documento univoco. Se non specificata, sarà usato un hash del contenuto.', postgresqlTimestampColumnTip: - 'Colonna datetime/timestamp per la sincronizzazione incrementale. Saranno recuperate solo le righe modificate dopo l\'ultima sincronizzazione.', + "Colonna datetime/timestamp per la sincronizzazione incrementale. Saranno recuperate solo le righe modificate dopo l'ultima sincronizzazione.", rest_apiDescription: 'Connetti qualsiasi endpoint REST API come fonte dati usando un connettore flessibile guidato dalla configurazione.', onedriveDescription: 'Connetti OneDrive o OneDrive for Business per indicizzare file e cartelle tramite query delta Microsoft Graph.', onedriveTenantIdTip: - 'ID tenant Azure Active Directory (Directory ID) dell\'organizzazione Microsoft 365.', + "ID tenant Azure Active Directory (Directory ID) dell'organizzazione Microsoft 365.", onedriveClientIdTip: 'ID applicazione (client) della registrazione app Azure AD con permesso Files.Read.All.', onedriveClientSecretTip: 'Valore del client secret generato nella registrazione app Azure AD.', onedriveFolderPathTip: - 'Percorso sotto-cartella opzionale per limitare l\'indicizzazione (es. /Documents/Reports). Lascia vuoto per indicizzare l\'intero drive.', + "Percorso sotto-cartella opzionale per limitare l'indicizzazione (es. /Documents/Reports). Lascia vuoto per indicizzare l'intero drive.", outlookDescription: 'Connetti le caselle Outlook / Microsoft 365 e indicizza i messaggi tramite query delta Microsoft Graph.', outlookTenantIdTip: - 'ID tenant Azure Active Directory (Directory ID) dell\'organizzazione Microsoft 365.', + "ID tenant Azure Active Directory (Directory ID) dell'organizzazione Microsoft 365.", outlookClientIdTip: 'ID applicazione (client) della registrazione app Azure AD con permesso Mail.Read.', outlookClientSecretTip: @@ -1405,13 +1429,13 @@ Esempio: Virtual Hosted Style`, outlookUserIdsTip: 'UPN o object ID separati da virgola delle caselle da sincronizzare. Lascia vuoto per sincronizzare ogni casella nel tenant (richiede User.Read.All).', salesforceDescription: - 'Connetti un\'org Salesforce e indicizza i record CRM (Account, Contatti, Opportunità, Casi, articoli Knowledge) tramite SOQL con sincronizzazione incrementale.', + "Connetti un'org Salesforce e indicizza i record CRM (Account, Contatti, Opportunità, Casi, articoli Knowledge) tramite SOQL con sincronizzazione incrementale.", salesforceInstanceUrlTip: 'URL org Salesforce, es. https://your-domain.my.salesforce.com (senza barra finale).', salesforceClientIdTip: 'Consumer Key di una Connected App con Client Credentials Flow abilitato e lo scope api.', salesforceClientSecretTip: - 'Consumer Secret della Connected App usata per l\'autenticazione client-credentials.', + "Consumer Secret della Connected App usata per l'autenticazione client-credentials.", salesforceObjectsTip: 'Nomi API SObject separati da virgola da indicizzare. Predefinito Account, Contact, Opportunity, Case, Knowledge__kav.', salesforceApiVersionTip: @@ -1421,27 +1445,27 @@ Esempio: Virtual Hosted Style`, azureBlobAuthModeTip: 'Scegli il metodo di autenticazione. Account Key e Connection String richiedono container_name; SAS Token richiede container_url + sas_token.', azureBlobAccountNameTip: - 'Nome account di storage Azure (es. mystorageaccount). Richiesto per l\'autenticazione account-key.', + "Nome account di storage Azure (es. mystorageaccount). Richiesto per l'autenticazione account-key.", azureBlobAccountKeyTip: - 'Chiave di accesso account di storage (codificata Base64). Richiesta per l\'autenticazione account-key.', + "Chiave di accesso account di storage (codificata Base64). Richiesta per l'autenticazione account-key.", azureBlobConnectionStringTip: - 'Stringa di connessione Azure Storage completa (DefaultEndpointsProtocol=https;AccountName=...;...). Richiesta per l\'autenticazione connection-string.', + "Stringa di connessione Azure Storage completa (DefaultEndpointsProtocol=https;AccountName=...;...). Richiesta per l'autenticazione connection-string.", azureBlobContainerUrlTip: - 'URL HTTPS completo del container (es. https://account.blob.core.windows.net/container). Richiesto per l\'autenticazione SAS-token.', + "URL HTTPS completo del container (es. https://account.blob.core.windows.net/container). Richiesto per l'autenticazione SAS-token.", azureBlobSasTokenTip: 'Stringa di query SAS (senza il "?" iniziale). Richiesta per l\'autenticazione SAS-token.', azureBlobContainerNameTip: - 'Nome del container da indicizzare. Richiesto per l\'autenticazione account-key e connection-string.', + "Nome del container da indicizzare. Richiesto per l'autenticazione account-key e connection-string.", azureBlobPrefixTip: - 'Prefisso nome blob opzionale per limitare l\'indicizzazione a una cartella virtuale (es. documents/reports/). Lascia vuoto per indicizzare l\'intero container.', + "Prefisso nome blob opzionale per limitare l'indicizzazione a una cartella virtuale (es. documents/reports/). Lascia vuoto per indicizzare l'intero container.", restApiQueryParamsTip: - 'Coppie Chiave=valore (una per riga) inviate come parametri di query URL. Usa questo invece di incorporare i parametri nell\'URL.', + "Coppie Chiave=valore (una per riga) inviate come parametri di query URL. Usa questo invece di incorporare i parametri nell'URL.", restApiHeadersTip: 'Oggetto JSON opzionale di header HTTP aggiuntivi da inviare con ogni richiesta.', restApiItemsPathTip: 'Nome del campo o JSONPath all\'array di elementi nella risposta. Lascia vuoto per il rilevamento automatico (prova "items", "results", "data", ecc.).', restApiIdFieldTip: - 'Percorso del campo all\'interno di ogni elemento usato per costruire un ID documento stabile. Lascia vuoto per generare automaticamente dall\'hash del contenuto.', + "Percorso del campo all'interno di ogni elemento usato per costruire un ID documento stabile. Lascia vuoto per generare automaticamente dall'hash del contenuto.", restApiContentFieldsTip: 'Elenco separato da virgola di campi degli elementi da concatenare nel contenuto del documento.', restApiMetadataFieldsTip: @@ -1449,11 +1473,11 @@ Esempio: Virtual Hosted Style`, restApiNextCursorPathTip: 'Espressione JSONPath che si risolve nel cursore della pagina successiva nella risposta API.', restApiPollTimestampFieldTip: - 'Percorso del campo in ogni elemento che rappresenta l\'ora dell\'ultimo aggiornamento, usato per la sincronizzazione incrementale.', + "Percorso del campo in ogni elemento che rappresenta l'ora dell'ultimo aggiornamento, usato per la sincronizzazione incrementale.", restApiRequestBodyTip: 'Body JSON opzionale da inviare per le richieste POST. Usato insieme ai parametri di query e alla paginazione.', restApiRequestDelayTip: - 'Ritardo in secondi tra richieste di pagina consecutive. Aiuta a evitare il rate limiting dall\'API. Imposta a 0 per disabilitare.', + "Ritardo in secondi tra richieste di pagina consecutive. Aiuta a evitare il rate limiting dall'API. Imposta a 0 per disabilitare.", restApiValidationApiKeyRequired: 'La chiave API è richiesta quando il tipo di autenticazione è API Key (Header).', restApiValidationApiKeyHeaderNameRequired: @@ -1506,7 +1530,8 @@ Esempio: Virtual Hosted Style`, timezoneMessage: 'Inserisci il tuo fuso orario!', timezonePlaceholder: 'seleziona il tuo fuso orario', email: 'Email', - emailDescription: "Una volta registrata, l'email non può essere cambiata.", + emailDescription: + "Una volta registrata, l'email non può essere cambiata.", currentPassword: 'Password attuale', currentPasswordMessage: 'Inserisci la tua password!', newPassword: 'Nuova password', @@ -1535,7 +1560,8 @@ Esempio: Virtual Hosted Style`, 'Per utenti cinesi, non è necessario compilare o usare https://dashscope.aliyuncs.com/compatible-mode/v1. Per utenti internazionali, usa https://dashscope-intl.aliyuncs.com/compatible-mode/v1', siliconBaseUrlTip: 'Per utenti cinesi, non è necessario compilare o usare https://api.siliconflow.cn/v1. Per utenti internazionali, usa https://api.siliconflow.com/v1', - tongyiBaseUrlPlaceholder: '(Solo utenti internazionali, vedi suggerimento)', + tongyiBaseUrlPlaceholder: + '(Solo utenti internazionali, vedi suggerimento)', minimaxBaseUrlTip: 'Solo utenti internazionali: usa https://api.minimax.io/v1', minimaxBaseUrlPlaceholder: @@ -1549,7 +1575,7 @@ Esempio: Virtual Hosted Style`, 'Il modello di embedding predefinito per ogni nuovo dataset creato. Se non riesci a trovare un modello di embedding dal menu a discesa, controlla se stai usando la versione slim di RAGFlow (che non include modelli di embedding) o controlla https://ragflow.io/docs/dev/supported_models per vedere se il tuo fornitore di modelli supporta questo modello.', img2txtModel: 'VLM', img2txtModelTip: - 'Il VLM predefinito per ogni nuovo dataset creato. Descrive un\'immagine o un video. Se non riesci a trovare un modello dal menu a discesa, controlla https://ragflow.io/docs/dev/supported_models per vedere se il tuo fornitore di modelli supporta questo modello.', + "Il VLM predefinito per ogni nuovo dataset creato. Descrive un'immagine o un video. Se non riesci a trovare un modello dal menu a discesa, controlla https://ragflow.io/docs/dev/supported_models per vedere se il tuo fornitore di modelli supporta questo modello.", sequence2txtModel: 'ASR', sequence2txtModelTip: 'Il modello ASR predefinito per ogni nuovo dataset creato. Usa questo modello per tradurre le voci in testo corrispondente.', @@ -1564,7 +1590,7 @@ Esempio: Virtual Hosted Style`, editLlmTitle: 'Modifica modello {{name}}', editModel: 'Modifica modello', instanceName: 'Nome istanza', - instanceNameMessage: 'Inserisci il nome dell\'istanza!', + instanceNameMessage: "Inserisci il nome dell'istanza!", instanceNameTip: 'Un nome univoco per identificare questa istanza del fornitore sotto la stessa factory.', modelName: 'Nome modello', @@ -1585,7 +1611,7 @@ Esempio: Virtual Hosted Style`, selectAlgorithm: 'Seleziona algoritmo', modelNamePlaceholder: 'Ad esempio: paddleocr-from-env-1', modelNameRequired: 'Il nome del modello è obbligatorio', - apiUrlRequired: 'L\'URL API di PaddleOCR è obbligatorio', + apiUrlRequired: "L'URL API di PaddleOCR è obbligatorio", }, vision: 'Supporta Vision?', ollamaLink: 'Come integrare {{name}}', @@ -1593,7 +1619,7 @@ Esempio: Virtual Hosted Style`, TencentCloudLink: 'Come usare TencentCloud ASR', volcModelNameMessage: 'Inserisci il nome del tuo modello!', addEndpointID: 'ID Modello', - endpointIDMessage: 'Inserisci l\'ID del tuo modello', + endpointIDMessage: "Inserisci l'ID del tuo modello", addArkApiKey: 'VOLC ARK_API_KEY', ArkApiKeyMessage: 'Inserisci la tua ARK_API_KEY', bedrockModelNameMessage: 'Inserisci il nome del tuo modello!', @@ -1656,8 +1682,7 @@ Esempio: Virtual Hosted Style`, yiyanAKMessage: 'Inserisci la tua API KEY', addyiyanSK: 'yiyan Secret KEY', yiyanSKMessage: 'Inserisci la tua Secret KEY', - FishAudioModelNameMessage: - 'Dai un nome al tuo modello di sintesi vocale', + FishAudioModelNameMessage: 'Dai un nome al tuo modello di sintesi vocale', addFishAudioAK: 'Fish Audio API KEY', addFishAudioAKMessage: 'Inserisci la tua API KEY', addFishAudioRefID: 'FishAudio Reference ID', @@ -1694,21 +1719,22 @@ Esempio: Virtual Hosted Style`, configuration: 'Configurazione', langfuseDescription: 'Tracce, valutazioni, gestione dei prompt e metriche per il debug e il miglioramento della tua applicazione LLM.', - viewLangfuseSDocumentation: "Visualizza la documentazione di Langfuse", + viewLangfuseSDocumentation: 'Visualizza la documentazione di Langfuse', view: 'Visualizza', modelsToBeAddedTooltip: 'Se il tuo fornitore di modelli non è elencato ma dichiara di essere "OpenAI-compatible", seleziona la scheda OpenAI-API-compatible per aggiungere i modelli pertinenti. ', mcp: 'MCP', mineru: { modelNameRequired: 'Il nome del modello è obbligatorio', - apiServerRequired: 'La configurazione del MinerU API Server è richiesta', + apiServerRequired: + 'La configurazione del MinerU API Server è richiesta', serverUrlBackendLimit: - 'L\'indirizzo URL del MinerU Server è disponibile solo per il backend client HTTP', + "L'indirizzo URL del MinerU Server è disponibile solo per il backend client HTTP", apiserver: 'Configurazione MinerU API Server', outputDir: 'Percorso directory di output MinerU', backend: 'Tipo di backend di elaborazione MinerU', serverUrl: 'Indirizzo URL MinerU Server', - deleteOutput: 'Elimina i file di output dopo l\'elaborazione', + deleteOutput: "Elimina i file di output dopo l'elaborazione", selectBackend: 'Seleziona backend di elaborazione', backendOptions: { pipeline: 'Elaborazione pipeline standard', @@ -1787,7 +1813,7 @@ Esempio: Virtual Hosted Style`, directory: 'Directory', uploadTitle: 'Trascina e rilascia il tuo file qui per caricarlo', uploadDescription: - 'Supporta caricamento singolo o multiplo. Per un RAGFlow distribuito localmente: il limite di dimensione totale dei file per caricamento è 1GB, con un limite batch di 32 file. Non c\'è limite al numero totale di file per account. Per cloud.ragflow.io, il limite di dimensione totale dei file per caricamento è 10MB, con ogni file non superiore a 10MB e un massimo di 128 file per account.', + "Supporta caricamento singolo o multiplo. Per un RAGFlow distribuito localmente: il limite di dimensione totale dei file per caricamento è 1GB, con un limite batch di 32 file. Non c'è limite al numero totale di file per account. Per cloud.ragflow.io, il limite di dimensione totale dei file per caricamento è 10MB, con ogni file non superiore a 10MB e un massimo di 128 file per account.", local: 'Caricamenti locali', s3: 'Caricamenti S3', preview: 'Anteprima', @@ -1813,7 +1839,7 @@ Esempio: Virtual Hosted Style`, removeTagAriaLabel: 'Rimuovi {{tag}}', includeHeadingContent: 'Separa contenuto intestazione padre', includeHeadingContentTip: - 'Quando abilitato, i chunk includono solo il loro percorso di intestazione e contenuto; il contenuto immediatamente successivo a un\'intestazione padre è mantenuto come chunk separato.', + "Quando abilitato, i chunk includono solo il loro percorso di intestazione e contenuto; il contenuto immediatamente successivo a un'intestazione padre è mantenuto come chunk separato.", rootAsHeading: 'Imposta il primo chunk come contesto globale', rootAsHeadingTip: 'Tratta la prima divisione come intestazione globale per mantenere un contesto coerente nella gerarchia del documento. Ideale per i curriculum dove la prima sezione identifica il soggetto.', @@ -1826,7 +1852,7 @@ Ideale per: documenti con contenuto fluente e contestualmente connesso — come 'Rileva e analizza i layout di pagina multi-colonna per preservare il corretto ordine di lettura. Attiva questo per PDF o documenti con layout a due colonne o stile giornale.', removeToc: 'Rimuovi indice originale', removeTocTip: - 'Rimuove l\'indice incluso nel PDF originale, così non viene analizzato come contenuto regolare o suddiviso per il recupero.', + "Rimuove l'indice incluso nel PDF originale, così non viene analizzato come contenuto regolare o suddiviso per il recupero.", removeHeaderFooter: 'Rimuovi intestazione e piè di pagina', autoPlay: 'Riproduzione automatica audio', downloadFileTypeTip: 'Il tipo di file da scaricare', @@ -1888,11 +1914,10 @@ Ideale per: documenti con contenuto fluente e contestualmente connesso — come split: 'Dividi', script: 'Script', iterationItemDescription: - 'Rappresenta l\'elemento corrente nell\'iterazione, che può essere referenziato e manipolato nei passaggi successivi.', + "Rappresenta l'elemento corrente nell'iterazione, che può essere referenziato e manipolato nei passaggi successivi.", guidingQuestion: 'Domanda guida', onFailure: 'In caso di fallimento', - userPromptDefaultValue: - 'Questo è l\'ordine che devi inviare all\'agente.', + userPromptDefaultValue: "Questo è l'ordine che devi inviare all'agente.", search: 'Cerca', communication: 'Comunicazione', developer: 'Sviluppatore', @@ -1935,7 +1960,8 @@ Ideale per: documenti con contenuto fluente e contestualmente connesso — come multimodalModels: 'Modelli multimodali', textOnlyModels: 'Modelli solo testo', allModels: 'Tutti i modelli', - codeExecDescription: 'Scrivi la tua logica Python o Javascript personalizzata.', + codeExecDescription: + 'Scrivi la tua logica Python o Javascript personalizzata.', stringTransformDescription: 'Modifica il contenuto del testo. Attualmente supporta: divisione o concatenazione del testo.', foundation: 'Fondamenti', @@ -1961,7 +1987,7 @@ Ideale per: documenti con contenuto fluente e contestualmente connesso — come addMessage: 'Aggiungi messaggio', loop: 'Ciclo', loopDescription: - 'Il ciclo è il limite massimo del numero di cicli del componente corrente; quando il numero di cicli supera il valore del ciclo, significa che il componente non può completare l\'attività corrente, per favore ri-ottimizza l\'agente', + "Il ciclo è il limite massimo del numero di cicli del componente corrente; quando il numero di cicli supera il valore del ciclo, significa che il componente non può completare l'attività corrente, per favore ri-ottimizza l'agente", exitLoop: 'Esci dal ciclo', exitLoopDescription: `Equivalente a "break". Questo nodo non ha elementi di configurazione. Quando il corpo del ciclo raggiunge questo nodo, il ciclo termina.`, loopVariables: 'Variabili ciclo', @@ -2016,7 +2042,7 @@ Ideale per: documenti con contenuto fluente e contestualmente connesso — come 'Un componente che cerca da duckduckgo.com, permettendo di specificare il numero di risultati di ricerca usando TopN. Integra i dataset esistenti.', searXNG: 'SearXNG', searXNGDescription: - 'Un componente che cerca tramite l\'URL dell\'istanza SearXNG fornita. Specifica TopN e l\'URL dell\'istanza.', + "Un componente che cerca tramite l'URL dell'istanza SearXNG fornita. Specifica TopN e l'URL dell'istanza.", docGenerator: 'Generatore Documenti', docGeneratorDescription: `Genera un file da contenuto Markdown.`, browser: 'Browser', @@ -2045,7 +2071,7 @@ Ideale per: documenti con contenuto fluente e contestualmente connesso — come news: 'Notizie', messageHistoryWindowSize: 'Dimensione finestra messaggi', messageHistoryWindowSizeTip: - 'La dimensione della finestra della cronologia conversazione visibile all\'LLM. Più grande è meglio, ma fai attenzione al limite massimo di token dell\'LLM.', + "La dimensione della finestra della cronologia conversazione visibile all'LLM. Più grande è meglio, ma fai attenzione al limite massimo di token dell'LLM.", wikipedia: 'Wikipedia', pubMed: 'PubMed', pubMedDescription: @@ -2097,7 +2123,7 @@ Ideale per: documenti con contenuto fluente e contestualmente connesso — come fieldtranslate: 'Traduzione di settore', }, baiduDomainOptions: { - it: 'Tecnologia dell\'informazione', + it: "Tecnologia dell'informazione", finance: 'Finanza ed economia', machinery: 'Produzione meccanica', senimed: 'Biomedicina', @@ -2142,7 +2168,7 @@ Ideale per: documenti con contenuto fluente e contestualmente connesso — come }, qWeather: 'QWeather', qWeatherDescription: - 'Un componente che recupera informazioni meteorologiche, come temperatura e qualità dell\'aria, da https://www.qweather.com/.', + "Un componente che recupera informazioni meteorologiche, come temperatura e qualità dell'aria, da https://www.qweather.com/.", lang: 'Lingua', type: 'Tipo', webApiKey: 'Web API key', @@ -2184,7 +2210,7 @@ Ideale per: documenti con contenuto fluente e contestualmente connesso — come qWeatherTypeOptions: { weather: 'Previsioni meteo', indices: 'Indice vita meteo', - airquality: 'Qualità dell\'aria', + airquality: "Qualità dell'aria", }, qWeatherUserTypeOptions: { free: 'Abbonato gratuito', @@ -2239,7 +2265,7 @@ Ideale per: documenti con contenuto fluente e contestualmente connesso — come wenCai: 'WenCai', queryType: 'Tipo di query', wenCaiDescription: - 'Un componente che ottiene informazioni finanziarie, inclusi prezzi delle azioni e notizie sui finanziamenti, da un\'ampia gamma di siti web finanziari.', + "Un componente che ottiene informazioni finanziarie, inclusi prezzi delle azioni e notizie sui finanziamenti, da un'ampia gamma di siti web finanziari.", wenCaiQueryTypeOptions: { stock: 'azione', zhishu: 'indice', @@ -2365,7 +2391,7 @@ Ideale per: documenti con contenuto fluente e contestualmente connesso — come testRun: 'Test esecuzione', template: 'Template', templateDescription: - 'Un componente che formatta l\'output di altri componenti. 1. Supporta template Jinja2, prima converte l\'input in un oggetto e poi renderizza il template, 2. Mantiene contemporaneamente il metodo originale di sostituzione stringhe {parameter}', + "Un componente che formatta l'output di altri componenti. 1. Supporta template Jinja2, prima converte l'input in un oggetto e poi renderizza il template, 2. Mantiene contemporaneamente il metodo originale di sostituzione stringhe {parameter}", emailComponent: 'Email', emailDescription: "Invia un'email a un indirizzo specificato.", smtpServer: 'Host SMTP', @@ -2378,11 +2404,11 @@ Ideale per: documenti con contenuto fluente e contestualmente connesso — come ccEmail: 'Email CC', emailSubject: 'Oggetto', emailContent: 'Contenuto', - smtpServerRequired: 'Inserisci l\'indirizzo del server SMTP', - senderEmailRequired: 'Inserisci l\'email del mittente', + smtpServerRequired: "Inserisci l'indirizzo del server SMTP", + senderEmailRequired: "Inserisci l'email del mittente", authCodeRequired: 'Inserisci il codice di autorizzazione', - toEmailRequired: 'Inserisci l\'email del destinatario', - emailContentRequired: 'Inserisci il contenuto dell\'email', + toEmailRequired: "Inserisci l'email del destinatario", + emailContentRequired: "Inserisci il contenuto dell'email", emailSentSuccess: 'Email inviata con successo', emailSentFailed: 'Invio email fallito', dynamicParameters: 'Parametri dinamici', @@ -2462,14 +2488,15 @@ Questo delimitatore è usato per dividere il testo di input in diversi pezzi di }, prompt: 'Prompt', promptTip: - 'Usa il prompt di sistema per descrivere il compito per l\'LLM, specificare come dovrebbe rispondere e delineare altri requisiti vari. Il prompt di sistema è spesso usato insieme a chiavi (variabili), che servono come vari input di dati per l\'LLM. Usa una barra `/` o il pulsante (x) per mostrare le chiavi da usare.', + "Usa il prompt di sistema per descrivere il compito per l'LLM, specificare come dovrebbe rispondere e delineare altri requisiti vari. Il prompt di sistema è spesso usato insieme a chiavi (variabili), che servono come vari input di dati per l'LLM. Usa una barra `/` o il pulsante (x) per mostrare le chiavi da usare.", promptMessage: 'Il prompt è richiesto', infor: 'Esecuzione informativa', knowledgeBasesTip: 'Seleziona i dataset da associare a questo assistente chat, o scegli variabili contenenti ID di dataset qui sotto.', knowledgeBaseVars: 'Variabili dataset', code: 'Codice', - codeDescription: 'Permette agli sviluppatori di scrivere logica Python personalizzata.', + codeDescription: + 'Permette agli sviluppatori di scrivere logica Python personalizzata.', dataOperations: 'Operazioni dati', dataOperationsDescription: 'Esegui varie operazioni su un oggetto Data.', listOperations: 'Operazioni lista', @@ -2485,7 +2512,7 @@ Questo processo aggrega variabili da più rami in una singola variabile per otte openingSwitch: 'Interruttore apertura', openingCopy: 'Saluto di apertura', openingSwitchTip: - 'I tuoi utenti vedranno questo messaggio di benvenuto all\'inizio.', + "I tuoi utenti vedranno questo messaggio di benvenuto all'inizio.", modeTip: 'La modalità definisce come viene avviato il workflow.', mode: 'Modalità', conversational: 'Conversazionale', @@ -2508,7 +2535,8 @@ Questo processo aggrega variabili da più rami in una singola variabile per otte userFillUpDescription: `Mette in pausa il workflow e attende il messaggio dell'utente prima di continuare.`, codeExec: 'Codice', tavilySearch: 'Ricerca Tavily', - tavilySearchDescription: 'Risultati di ricerca tramite il servizio Tavily.', + tavilySearchDescription: + 'Risultati di ricerca tramite il servizio Tavily.', tavilyExtract: 'Estrazione Tavily', tavilyExtractDescription: 'Tavily Extract', log: 'Log', @@ -2529,10 +2557,10 @@ Questo processo aggrega variabili da più rami in una singola variabile per otte logTimeline: { begin: 'Pronto a iniziare', - agent: 'L\'agente sta pensando', + agent: "L'agente sta pensando", userFillUp: 'In attesa di te', retrieval: 'Consultazione della conoscenza', - message: 'L\'agente dice', + message: "L'agente dice", awaitResponse: 'In attesa di te', switch: 'Scelta del percorso migliore', iteration: 'Elaborazione batch', @@ -2547,7 +2575,7 @@ Questo processo aggrega variabili da più rami in una singola variabile per otte googleScholar: 'Ricerca accademica', gitHub: 'Ricerca su GitHub', email: 'Invio email', - httpRequest: 'Chiamata a un\'API', + httpRequest: "Chiamata a un'API", wenCai: 'Interrogazione dati finanziari', }, goto: 'Ramo di fallimento', @@ -2559,7 +2587,7 @@ Questo processo aggrega variabili da più rami in una singola variabile per otte release: 'Pubblica', production: 'Produzione', productionTooltip: - 'Questa versione è pubblicata in produzione. Accedivi tramite l\'API o la pagina incorporata.', + "Questa versione è pubblicata in produzione. Accedivi tramite l'API o la pagina incorporata.", confirmPublish: 'Conferma pubblicazione', publishIngestionPipeline: 'Stai per pubblicare questa ingestion pipeline.', @@ -2574,7 +2602,7 @@ Questo processo aggrega variabili da più rami in una singola variabile per otte chooseAgentType: 'Scegli il tipo di agente', parser: 'Parser', parserDescription: - 'Estrae testo grezzo e struttura dai file per l\'elaborazione a valle.', + "Estrae testo grezzo e struttura dai file per l'elaborazione a valle.", tokenizer: 'Indicizzatore', tokenizerRequired: 'Aggiungi prima il nodo Indicizzatore', tokenizerDescription: @@ -2620,7 +2648,7 @@ L'Indicizzatore memorizzerà il contenuto nelle corrispondenti strutture dati pe tableResultType: 'Tipo risultato tabella', markdownImageResponseType: 'Tipo risposta immagine Markdown', systemPromptPlaceholder: - 'Inserisci il prompt di sistema per l\'analisi dell\'immagine, se vuoto sarà usato il valore predefinito del sistema', + "Inserisci il prompt di sistema per l'analisi dell'immagine, se vuoto sarà usato il valore predefinito del sistema", exportJson: 'Esporta JSON', viewResult: 'Visualizza risultato', running: 'In esecuzione', @@ -2748,7 +2776,7 @@ Le informazioni strutturate importanti possono includere: nomi, date, luoghi, ev response: 'Risposta', executionMode: 'Modalità di esecuzione', executionModeTip: - 'Risposta accettata: il sistema restituisce un riconoscimento immediatamente dopo che la richiesta è validata, mentre il workflow continua a eseguire in modo asincrono in background. /Risposta finale: il sistema restituisce una risposta solo dopo che l\'esecuzione del workflow è completata.', + "Risposta accettata: il sistema restituisce un riconoscimento immediatamente dopo che la richiesta è validata, mentre il workflow continua a eseguire in modo asincrono in background. /Risposta finale: il sistema restituisce una risposta solo dopo che l'esecuzione del workflow è completata.", authMethods: 'Metodi di autenticazione', authType: 'Tipo di autenticazione', limit: 'Limite di frequenza richieste', @@ -2989,10 +3017,10 @@ Le informazioni strutturate importanti possono includere: nomi, date, luoghi, ev sandboxSettingsPage: { description: - 'Configura il tuo provider sandbox per l\'esecuzione del codice. La sandbox è usata dal componente Codice negli agenti.', + "Configura il tuo provider sandbox per l'esecuzione del codice. La sandbox è usata dal componente Codice negli agenti.", providerSelection: 'Selezione provider', providerSelectionDescription: - 'Scegli un provider sandbox per l\'esecuzione del codice', + "Scegli un provider sandbox per l'esecuzione del codice", namedProviderConfiguration: 'Configurazione {{name}}', namedProviderConfigurationDescription: