mirror of
https://github.com/infiniflow/ragflow.git
synced 2026-06-29 23:41:12 +08:00
@@ -21,6 +21,7 @@ import (
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
"os"
|
||||
//"os/signal"
|
||||
"path/filepath"
|
||||
@@ -177,7 +178,22 @@ func ParseArgs(args []string) (*CommandLineConfig, error) {
|
||||
for i := 0; i < len(args); i++ {
|
||||
arg := args[i]
|
||||
|
||||
// If we've found the command, collect remaining args as subcommand args
|
||||
// Handle known global flags (already parsed in first pass).
|
||||
// Intercept here regardless of position so they are never
|
||||
// mistaken for command args or unknown flags downstream.
|
||||
switch arg {
|
||||
case "-o", "--output":
|
||||
if i+1 < len(args) {
|
||||
i++
|
||||
}
|
||||
continue
|
||||
case "-v", "--verbose", "--help", "-help":
|
||||
continue
|
||||
case "--admin", "-admin":
|
||||
return nil, fmt.Errorf("unexpected parameter: --admin")
|
||||
}
|
||||
|
||||
// If we've found the command, collect remaining args
|
||||
if foundCommand {
|
||||
commandArgs = append(commandArgs, arg)
|
||||
continue
|
||||
@@ -222,17 +238,6 @@ func ParseArgs(args []string) (*CommandLineConfig, error) {
|
||||
}
|
||||
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, "-") {
|
||||
@@ -311,7 +316,22 @@ func ParseArgs(args []string) (*CommandLineConfig, error) {
|
||||
for i := 0; i < len(args); i++ {
|
||||
arg := args[i]
|
||||
|
||||
// If we've found the command, collect remaining args as subcommand args
|
||||
// Handle known global flags regardless of position
|
||||
switch arg {
|
||||
case "-o", "--output":
|
||||
if i+1 < len(args) {
|
||||
i++
|
||||
}
|
||||
continue
|
||||
case "-v", "--verbose", "--admin", "-admin", "--help", "-help":
|
||||
continue
|
||||
case "-t", "--token":
|
||||
return nil, fmt.Errorf("token is invalid in admin mode")
|
||||
case "-f", "--config":
|
||||
return nil, fmt.Errorf("config is invalid in admin mode")
|
||||
}
|
||||
|
||||
// If we've found the command, collect remaining args
|
||||
if foundCommand {
|
||||
commandArgs = append(commandArgs, arg)
|
||||
continue
|
||||
@@ -329,8 +349,6 @@ func ParseArgs(args []string) (*CommandLineConfig, error) {
|
||||
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]
|
||||
@@ -341,17 +359,6 @@ func ParseArgs(args []string) (*CommandLineConfig, error) {
|
||||
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, "-") {
|
||||
@@ -534,8 +541,9 @@ func NewCLIWithConfig(commandLineConfig *CommandLineConfig) (*CLI, error) {
|
||||
line := liner.NewLiner()
|
||||
|
||||
cli := &CLI{
|
||||
line: line,
|
||||
Config: commandLineConfig,
|
||||
line: line,
|
||||
Config: commandLineConfig,
|
||||
outputFormat: commandLineConfig.OutputFormat,
|
||||
}
|
||||
|
||||
if commandLineConfig.CLIMode == APIMode {
|
||||
@@ -789,11 +797,50 @@ func (c *CLI) execute(input string) error {
|
||||
}
|
||||
|
||||
// Filesystem mode: execute filesystem command
|
||||
return c.executeFilesystem(input)
|
||||
cmd := NewCommand("file_system_command")
|
||||
cmd.Params["command"] = input
|
||||
resp, ceErr := c.executeFilesystem(cmd)
|
||||
if resp != nil {
|
||||
resp.PrintOut()
|
||||
}
|
||||
return ceErr
|
||||
}
|
||||
|
||||
// executeFilesystem executes a Filesystem command
|
||||
func (c *CLI) executeFilesystem(input string) error {
|
||||
// executeFilesystem executes a Filesystem command and returns a ResponseIf.
|
||||
func (c *CLI) executeFilesystem(cmd *Command) (ResponseIf, error) {
|
||||
rawInput, _ := cmd.Params["command"].(string)
|
||||
|
||||
r, w, err := os.Pipe()
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("create stdout pipe: %w", err)
|
||||
}
|
||||
old := os.Stdout
|
||||
os.Stdout = w
|
||||
defer func() {
|
||||
os.Stdout = old
|
||||
_ = w.Close()
|
||||
_ = r.Close()
|
||||
}()
|
||||
|
||||
var buf strings.Builder
|
||||
copyErrCh := make(chan error, 1)
|
||||
go func() {
|
||||
_, copyErr := io.Copy(&buf, r)
|
||||
copyErrCh <- copyErr
|
||||
}()
|
||||
|
||||
execErr := c.executeFilesystemInner(rawInput)
|
||||
_ = w.Close() // signal EOF to reader goroutine
|
||||
copyErr := <-copyErrCh
|
||||
if copyErr != nil {
|
||||
return nil, fmt.Errorf("capture filesystem output: %w", copyErr)
|
||||
}
|
||||
return &FileSystemResponse{Output: buf.String()}, execErr
|
||||
}
|
||||
|
||||
// executeFilesystemInner executes a Filesystem command and writes output to stdout.
|
||||
// It is called by executeFilesystem which captures the stdout output.
|
||||
func (c *CLI) executeFilesystemInner(input string) error {
|
||||
// Parse input into arguments
|
||||
var args []string
|
||||
if c.args != nil && len(c.args.CommandArgs) > 0 {
|
||||
@@ -947,36 +994,6 @@ func (c *CLI) executeFilesystem(input string) error {
|
||||
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)
|
||||
if !ok {
|
||||
return fmt.Errorf("file provider not available")
|
||||
}
|
||||
skillProvider := c.ContextEngine.GetProvider("skills")
|
||||
if skillProvider == nil {
|
||||
return fmt.Errorf("skill provider not available")
|
||||
}
|
||||
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")
|
||||
if skillProvider == nil {
|
||||
return fmt.Errorf("skill provider not available")
|
||||
}
|
||||
fileProvider := c.ContextEngine.GetProvider("files")
|
||||
if fileProvider == nil {
|
||||
return fmt.Errorf("file provider not available")
|
||||
}
|
||||
httpAdapter := &httpClientAdapter{client: httpClient}
|
||||
fileProv, _ := fileProvider.(*filesystem.FileProvider)
|
||||
cmd := filesystem.NewUninstallSkillCommand(httpAdapter, skillProvider, fileProv)
|
||||
return cmd.Execute(cmdArgs)
|
||||
|
||||
default:
|
||||
return fmt.Errorf("unknown filesystem command: %s", cmdType)
|
||||
}
|
||||
|
||||
@@ -294,9 +294,8 @@ func (c *CLI) ExecuteUserCommand(cmd *Command) (ResponseIf, error) {
|
||||
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))
|
||||
case "file_system_command":
|
||||
return c.executeFilesystem(cmd)
|
||||
default:
|
||||
return nil, fmt.Errorf("command '%s' would be executed with API", cmd.Type)
|
||||
}
|
||||
|
||||
@@ -1,178 +0,0 @@
|
||||
//
|
||||
// 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"
|
||||
"strings"
|
||||
)
|
||||
|
||||
func (p *Parser) parseContextListCommand() (*Command, error) {
|
||||
p.nextToken() // consume LS
|
||||
|
||||
cmd := NewCommand("ce_ls")
|
||||
|
||||
if p.curToken.Type == TokenEOF {
|
||||
cmd.Params["path"] = "."
|
||||
return cmd, nil
|
||||
}
|
||||
|
||||
for p.curToken.Type != TokenEOF {
|
||||
if p.curToken.Type == TokenDash {
|
||||
p.nextToken() // skip dash
|
||||
if p.curToken.Type != TokenIdentifier {
|
||||
return nil, fmt.Errorf("expect identifier")
|
||||
}
|
||||
if cmd.Params["parameter"] == nil {
|
||||
cmd.Params["parameter"] = p.curToken.Value
|
||||
} else {
|
||||
cmd.Params["parameter"] = fmt.Sprintf("%s%s", cmd.Params["parameter"], p.curToken.Value)
|
||||
}
|
||||
p.nextToken() // skip parameter
|
||||
} else if p.curToken.Type == TokenIdentifier {
|
||||
if cmd.Params["path"] == nil {
|
||||
cmd.Params["path"] = p.curToken.Value
|
||||
} else {
|
||||
err := fmt.Errorf("ls: cannot access '%s': No such file or directory", p.curToken.Value)
|
||||
return nil, err
|
||||
}
|
||||
p.nextToken() // skip path
|
||||
} else {
|
||||
return nil, fmt.Errorf("syntax error")
|
||||
}
|
||||
}
|
||||
|
||||
return cmd, nil
|
||||
}
|
||||
|
||||
func (p *Parser) parseContextCatCommand() (*Command, error) {
|
||||
p.nextToken() // consume CAT
|
||||
|
||||
if p.curToken.Type == TokenEOF {
|
||||
return nil, fmt.Errorf("expect a filename")
|
||||
}
|
||||
|
||||
if p.curToken.Type != TokenIdentifier && p.curToken.Type != TokenQuotedString {
|
||||
return nil, fmt.Errorf("expect a filename")
|
||||
}
|
||||
|
||||
cmd := NewCommand("ce_cat")
|
||||
if p.curToken.Type == TokenIdentifier {
|
||||
for p.curToken.Type != TokenEOF {
|
||||
if p.curToken.Type != TokenIdentifier {
|
||||
return nil, fmt.Errorf("expect a identifier")
|
||||
}
|
||||
|
||||
if cmd.Params["filename"] == nil {
|
||||
cmd.Params["filename"] = p.curToken.Value
|
||||
} else {
|
||||
cmd.Params["filename"] = fmt.Sprintf("%s/%s", cmd.Params["filename"], p.curToken.Value)
|
||||
}
|
||||
p.nextToken()
|
||||
if p.curToken.Type == TokenEOF {
|
||||
break
|
||||
}
|
||||
if p.curToken.Type != TokenSlash {
|
||||
return nil, fmt.Errorf("expect a slash")
|
||||
}
|
||||
p.nextToken()
|
||||
if p.curToken.Type == TokenEOF {
|
||||
return nil, fmt.Errorf("error format")
|
||||
}
|
||||
}
|
||||
|
||||
} else if p.curToken.Type == TokenQuotedString {
|
||||
var err error
|
||||
cmd.Params["filename"], err = p.parseQuotedString()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
p.nextToken()
|
||||
|
||||
if p.curToken.Type != TokenEOF {
|
||||
return nil, fmt.Errorf("syntax error")
|
||||
}
|
||||
|
||||
return cmd, nil
|
||||
}
|
||||
|
||||
func (p *Parser) parseContextSearchCommand() (*Command, error) {
|
||||
p.nextToken() // consume SEARCH
|
||||
|
||||
cmd := NewCommand("ce_search")
|
||||
|
||||
for p.curToken.Type != TokenEOF {
|
||||
if p.curToken.Type == TokenDash {
|
||||
p.nextToken() // skip dash
|
||||
if p.curToken.Type != TokenIdentifier {
|
||||
return nil, fmt.Errorf("expect identifier")
|
||||
}
|
||||
|
||||
if strings.ToLower(p.curToken.Value) == "n" {
|
||||
p.nextToken()
|
||||
var err error
|
||||
if p.curToken.Type != TokenInteger {
|
||||
return nil, fmt.Errorf("expect number")
|
||||
}
|
||||
cmd.Params["number"], err = p.parseNumber()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
p.nextToken()
|
||||
continue
|
||||
}
|
||||
|
||||
if strings.ToLower(p.curToken.Value) == "t" {
|
||||
p.nextToken()
|
||||
var err error
|
||||
if p.curToken.Type != TokenInteger {
|
||||
return nil, fmt.Errorf("expect number")
|
||||
}
|
||||
cmd.Params["threshold"], err = p.parseFloat()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
p.nextToken()
|
||||
continue
|
||||
}
|
||||
|
||||
return nil, fmt.Errorf("unknow parameter: %s", p.curToken.Value)
|
||||
} else if p.curToken.Type == TokenIdentifier {
|
||||
if cmd.Params["path"] == nil {
|
||||
cmd.Params["path"] = p.curToken.Value
|
||||
} else {
|
||||
cmd.Params["path"] = fmt.Sprintf("%s %s", cmd.Params["path"], p.curToken.Value)
|
||||
}
|
||||
p.nextToken() // skip path
|
||||
} else if p.curToken.Type == TokenQuotedString {
|
||||
if cmd.Params["query"] == nil {
|
||||
var err error
|
||||
cmd.Params["query"], err = p.parseQuotedString()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
p.nextToken()
|
||||
} else {
|
||||
return nil, fmt.Errorf("Query phrase exists")
|
||||
}
|
||||
}
|
||||
return nil, fmt.Errorf("syntax error")
|
||||
}
|
||||
|
||||
return cmd, nil
|
||||
}
|
||||
@@ -227,7 +227,7 @@ func (p *Parser) parseUserCommand() (*Command, error) {
|
||||
|
||||
case TokenLS, TokenCat, TokenSearch:
|
||||
// For context engine
|
||||
return p.parseContextEngineCommand()
|
||||
return p.parseFileSystemCommand()
|
||||
default:
|
||||
return nil, fmt.Errorf("unknown command: %s", p.curToken.Value)
|
||||
}
|
||||
@@ -390,241 +390,13 @@ func tokenTypeToString(t int) string {
|
||||
return fmt.Sprintf("token(%d)", t)
|
||||
}
|
||||
|
||||
func (p *Parser) parseContextEngineCommand() (*Command, error) {
|
||||
func (p *Parser) parseFileSystemCommand() (*Command, error) {
|
||||
p.nextToken() // consume COMMAND
|
||||
|
||||
cmd := NewCommand("context_engine_command")
|
||||
cmd := NewCommand("file_system_command")
|
||||
cmd.Params["command"] = p.original
|
||||
|
||||
return cmd, nil
|
||||
}
|
||||
|
||||
// parseCEListCommand parses the ls command
|
||||
// Syntax: ls [path] or ls datasets
|
||||
func (p *Parser) parseCEListCommand() (*Command, error) {
|
||||
p.nextToken() // consume LS/LIST
|
||||
|
||||
cmd := NewCommand("ce_ls")
|
||||
|
||||
// Check if there's a path argument
|
||||
// Also accept TokenDatasets since "datasets" is a keyword but can be a path
|
||||
if p.curToken.Type == TokenIdentifier || p.curToken.Type == TokenQuotedString ||
|
||||
p.curToken.Type == TokenDatasets {
|
||||
path := p.curToken.Value
|
||||
// Remove quotes if present
|
||||
if p.curToken.Type == TokenQuotedString {
|
||||
path = strings.Trim(path, "\"'")
|
||||
}
|
||||
p.nextToken()
|
||||
|
||||
// Handle path components separated by slashes (e.g., "skills/hub1")
|
||||
for p.curToken.Type == TokenSlash {
|
||||
p.nextToken() // consume slash
|
||||
if p.curToken.Type == TokenIdentifier || p.curToken.Type == TokenDatasets ||
|
||||
p.curToken.Type == TokenAgents || p.curToken.Type == TokenChats {
|
||||
path = path + "/" + p.curToken.Value
|
||||
p.nextToken()
|
||||
} else if p.curToken.Type == TokenNumber {
|
||||
// Handle version numbers like 1.0.0 (parsed as number . number . number)
|
||||
// OR filenames starting with numbers like 3_list_compressors.pdf
|
||||
numberPart := p.curToken.Value
|
||||
p.nextToken()
|
||||
// Continue reading .number parts (version number format)
|
||||
if p.curToken.Type == TokenIllegal && p.curToken.Value == "." {
|
||||
versionPart := numberPart
|
||||
for p.curToken.Type == TokenIllegal && p.curToken.Value == "." {
|
||||
p.nextToken() // consume .
|
||||
if p.curToken.Type == TokenNumber {
|
||||
versionPart = versionPart + "." + p.curToken.Value
|
||||
p.nextToken()
|
||||
} else {
|
||||
break
|
||||
}
|
||||
}
|
||||
path = path + "/" + versionPart
|
||||
} else if p.curToken.Type == TokenIdentifier {
|
||||
// Filename starting with number: 3_list_compressors.pdf
|
||||
path = path + "/" + numberPart + p.curToken.Value
|
||||
p.nextToken()
|
||||
} else {
|
||||
// Just a number
|
||||
path = path + "/" + numberPart
|
||||
}
|
||||
} else {
|
||||
// Trailing slash, just append it
|
||||
path = path + "/"
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
cmd.Params["path"] = path
|
||||
} else {
|
||||
// Default to "datasets" root
|
||||
cmd.Params["path"] = "datasets"
|
||||
}
|
||||
|
||||
// Optional semicolon
|
||||
if p.curToken.Type == TokenSemicolon {
|
||||
p.nextToken()
|
||||
}
|
||||
|
||||
return cmd, nil
|
||||
}
|
||||
|
||||
// parseCECatCommand parses the cat command
|
||||
// Syntax: cat <path>
|
||||
func (p *Parser) parseCECatCommand() (*Command, error) {
|
||||
p.nextToken() // consume CAT
|
||||
|
||||
cmd := NewCommand("ce_cat")
|
||||
|
||||
if p.curToken.Type != TokenIdentifier && p.curToken.Type != TokenQuotedString {
|
||||
return nil, fmt.Errorf("expected path after CAT")
|
||||
}
|
||||
|
||||
path := p.curToken.Value
|
||||
if p.curToken.Type == TokenQuotedString {
|
||||
path = strings.Trim(path, "\"'")
|
||||
}
|
||||
p.nextToken()
|
||||
|
||||
// Handle path components separated by slashes (e.g., "skills/hub1/skill/README.md")
|
||||
for p.curToken.Type == TokenSlash {
|
||||
p.nextToken() // consume slash
|
||||
if p.curToken.Type == TokenIdentifier || p.curToken.Type == TokenAgents ||
|
||||
p.curToken.Type == TokenChats || p.curToken.Type == TokenDatasets {
|
||||
path = path + "/" + p.curToken.Value
|
||||
p.nextToken()
|
||||
} else if p.curToken.Type == TokenNumber {
|
||||
// Handle version numbers like 1.0.0 (parsed as number . number . number)
|
||||
// OR filenames starting with numbers like 3_list_compressors.pdf
|
||||
numberPart := p.curToken.Value
|
||||
p.nextToken()
|
||||
// Continue reading .number parts (version number format)
|
||||
if p.curToken.Type == TokenIllegal && p.curToken.Value == "." {
|
||||
versionPart := numberPart
|
||||
for p.curToken.Type == TokenIllegal && p.curToken.Value == "." {
|
||||
p.nextToken() // consume .
|
||||
if p.curToken.Type == TokenNumber {
|
||||
versionPart = versionPart + "." + p.curToken.Value
|
||||
p.nextToken()
|
||||
} else {
|
||||
break
|
||||
}
|
||||
}
|
||||
path = path + "/" + versionPart
|
||||
} else if p.curToken.Type == TokenIdentifier {
|
||||
// Filename starting with number: 3_list_compressors.pdf
|
||||
path = path + "/" + numberPart + p.curToken.Value
|
||||
p.nextToken()
|
||||
} else {
|
||||
// Just a number
|
||||
path = path + "/" + numberPart
|
||||
}
|
||||
} else if p.curToken.Type == TokenQuotedString {
|
||||
path = path + "/" + strings.Trim(p.curToken.Value, "\"'")
|
||||
p.nextToken()
|
||||
} else {
|
||||
// Trailing slash, just append it
|
||||
path = path + "/"
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
cmd.Params["path"] = path
|
||||
|
||||
// Optional semicolon
|
||||
if p.curToken.Type == TokenSemicolon {
|
||||
p.nextToken()
|
||||
}
|
||||
|
||||
return cmd, nil
|
||||
}
|
||||
|
||||
// parseCESearchCommand parses the search command
|
||||
// Syntax: search <query> or search <query> in <path>
|
||||
func (p *Parser) parseCESearchCommand() (*Command, error) {
|
||||
p.nextToken() // consume SEARCH
|
||||
|
||||
cmd := NewCommand("ce_search")
|
||||
|
||||
if p.curToken.Type != TokenIdentifier && p.curToken.Type != TokenQuotedString {
|
||||
return nil, fmt.Errorf("expected query after SEARCH")
|
||||
}
|
||||
|
||||
query := p.curToken.Value
|
||||
if p.curToken.Type == TokenQuotedString {
|
||||
query = strings.Trim(query, "\"'")
|
||||
}
|
||||
cmd.Params["query"] = query
|
||||
p.nextToken()
|
||||
|
||||
// Check for optional "in <path>" clause
|
||||
if p.curToken.Type == TokenIdentifier && strings.ToUpper(p.curToken.Value) == "IN" {
|
||||
p.nextToken() // consume IN
|
||||
|
||||
if p.curToken.Type != TokenIdentifier && p.curToken.Type != TokenQuotedString {
|
||||
return nil, fmt.Errorf("expected path after IN")
|
||||
}
|
||||
|
||||
path := p.curToken.Value
|
||||
if p.curToken.Type == TokenQuotedString {
|
||||
path = strings.Trim(path, "\"'")
|
||||
}
|
||||
p.nextToken()
|
||||
|
||||
// Handle path components separated by slashes (e.g., "skills/hub1")
|
||||
for p.curToken.Type == TokenSlash {
|
||||
p.nextToken() // consume slash
|
||||
if p.curToken.Type == TokenIdentifier || p.curToken.Type == TokenAgents ||
|
||||
p.curToken.Type == TokenChats || p.curToken.Type == TokenDatasets {
|
||||
path = path + "/" + p.curToken.Value
|
||||
p.nextToken()
|
||||
} else if p.curToken.Type == TokenNumber {
|
||||
// Handle version numbers like 1.0.0 (parsed as number . number . number)
|
||||
// OR filenames starting with numbers like 3_list_compressors.pdf
|
||||
numberPart := p.curToken.Value
|
||||
p.nextToken()
|
||||
// Continue reading .number parts (version number format)
|
||||
if p.curToken.Type == TokenIllegal && p.curToken.Value == "." {
|
||||
versionPart := numberPart
|
||||
for p.curToken.Type == TokenIllegal && p.curToken.Value == "." {
|
||||
p.nextToken() // consume .
|
||||
if p.curToken.Type == TokenNumber {
|
||||
versionPart = versionPart + "." + p.curToken.Value
|
||||
p.nextToken()
|
||||
} else {
|
||||
break
|
||||
}
|
||||
}
|
||||
path = path + "/" + versionPart
|
||||
} else if p.curToken.Type == TokenIdentifier {
|
||||
// Filename starting with number: 3_list_compressors.pdf
|
||||
path = path + "/" + numberPart + p.curToken.Value
|
||||
p.nextToken()
|
||||
} else {
|
||||
// Just a number
|
||||
path = path + "/" + numberPart
|
||||
}
|
||||
} else if p.curToken.Type == TokenQuotedString {
|
||||
path = path + "/" + strings.Trim(p.curToken.Value, "\"'")
|
||||
p.nextToken()
|
||||
} else {
|
||||
// Trailing slash, just append it
|
||||
path = path + "/"
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
cmd.Params["path"] = path
|
||||
} else {
|
||||
cmd.Params["path"] = "."
|
||||
}
|
||||
|
||||
// Optional semicolon
|
||||
if p.curToken.Type == TokenSemicolon {
|
||||
p.nextToken()
|
||||
}
|
||||
|
||||
return cmd, nil
|
||||
}
|
||||
|
||||
@@ -529,69 +529,16 @@ func (r *TaskResponse) PrintOut() {
|
||||
}
|
||||
}
|
||||
|
||||
// ==================== ContextEngine Commands ====================
|
||||
|
||||
// ContextListResponse represents the response for ls command
|
||||
type ContextListResponse struct {
|
||||
Code int `json:"code"`
|
||||
Data []map[string]interface{} `json:"data"`
|
||||
Message string `json:"message"`
|
||||
// FileSystemResponse wraps the raw text output from executeFilesystem().
|
||||
type FileSystemResponse struct {
|
||||
Output string
|
||||
Duration float64
|
||||
OutputFormat OutputFormat
|
||||
}
|
||||
|
||||
func (r *ContextListResponse) Type() string { return "ce_ls" }
|
||||
func (r *ContextListResponse) TimeCost() float64 { return r.Duration }
|
||||
func (r *ContextListResponse) SetOutputFormat(format OutputFormat) { r.OutputFormat = format }
|
||||
func (r *ContextListResponse) PrintOut() {
|
||||
if r.Code == 0 {
|
||||
PrintTableSimpleByFormat(r.Data, r.OutputFormat)
|
||||
} else {
|
||||
fmt.Println("ERROR")
|
||||
fmt.Printf("%d, %s\n", r.Code, r.Message)
|
||||
}
|
||||
}
|
||||
|
||||
// ContextSearchResponse represents the response for search command
|
||||
type ContextSearchResponse struct {
|
||||
Code int `json:"code"`
|
||||
Data []map[string]interface{} `json:"data"`
|
||||
Total int `json:"total"`
|
||||
Message string `json:"message"`
|
||||
Duration float64
|
||||
OutputFormat OutputFormat
|
||||
}
|
||||
|
||||
func (r *ContextSearchResponse) Type() string { return "ce_search" }
|
||||
func (r *ContextSearchResponse) TimeCost() float64 { return r.Duration }
|
||||
func (r *ContextSearchResponse) SetOutputFormat(format OutputFormat) { r.OutputFormat = format }
|
||||
func (r *ContextSearchResponse) PrintOut() {
|
||||
if r.Code == 0 {
|
||||
fmt.Printf("Found %d results:\n", r.Total)
|
||||
PrintTableSimpleByFormat(r.Data, r.OutputFormat)
|
||||
} else {
|
||||
fmt.Println("ERROR")
|
||||
fmt.Printf("%d, %s\n", r.Code, r.Message)
|
||||
}
|
||||
}
|
||||
|
||||
// ContextCatResponse represents the response for cat command
|
||||
type ContextCatResponse struct {
|
||||
Code int `json:"code"`
|
||||
Content string `json:"content"`
|
||||
Message string `json:"message"`
|
||||
Duration float64
|
||||
OutputFormat OutputFormat
|
||||
}
|
||||
|
||||
func (r *ContextCatResponse) Type() string { return "ce_cat" }
|
||||
func (r *ContextCatResponse) TimeCost() float64 { return r.Duration }
|
||||
func (r *ContextCatResponse) SetOutputFormat(format OutputFormat) { r.OutputFormat = format }
|
||||
func (r *ContextCatResponse) PrintOut() {
|
||||
if r.Code == 0 {
|
||||
fmt.Println(r.Content)
|
||||
} else {
|
||||
fmt.Println("ERROR")
|
||||
fmt.Printf("%d, %s\n", r.Code, r.Message)
|
||||
}
|
||||
func (r *FileSystemResponse) Type() string { return "filesystem" }
|
||||
func (r *FileSystemResponse) TimeCost() float64 { return r.Duration }
|
||||
func (r *FileSystemResponse) SetOutputFormat(format OutputFormat) { r.OutputFormat = format }
|
||||
func (r *FileSystemResponse) PrintOut() {
|
||||
fmt.Print(r.Output)
|
||||
}
|
||||
|
||||
@@ -18,7 +18,6 @@ package cli
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
"context"
|
||||
"encoding/base64"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
@@ -29,7 +28,6 @@ import (
|
||||
"os"
|
||||
"os/exec"
|
||||
"path/filepath"
|
||||
ce "ragflow/internal/cli/filesystem"
|
||||
"strings"
|
||||
"time"
|
||||
)
|
||||
@@ -2736,113 +2734,6 @@ func (c *CLI) AddCustomModel(cmd *Command) (ResponseIf, error) {
|
||||
|
||||
}
|
||||
|
||||
// Context related commands
|
||||
|
||||
// CECat handles the cat command - shows content using Context Engine
|
||||
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.Config.CLIMode != APIMode {
|
||||
return nil, fmt.Errorf("this command is only allowed in USER mode")
|
||||
}
|
||||
|
||||
path, ok := cmd.Params["path"].(string)
|
||||
if !ok {
|
||||
return nil, fmt.Errorf("fail to convert 'path' to string")
|
||||
}
|
||||
|
||||
// Execute cat command through Filesystem Engine
|
||||
ctx := context.Background()
|
||||
content, err := c.ContextEngine.Cat(ctx, path)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// Convert to response
|
||||
var response ContextCatResponse
|
||||
response.OutputFormat = c.outputFormat
|
||||
response.Code = 0
|
||||
response.Content = string(content)
|
||||
|
||||
return &response, nil
|
||||
}
|
||||
|
||||
// CEList handles the ls command - lists nodes using Context Engine
|
||||
func (c *CLI) CEList(cmd *Command) (ResponseIf, error) {
|
||||
// Get path from command params, default to "datasets"
|
||||
path, _ := cmd.Params["path"].(string)
|
||||
if path == "" {
|
||||
path = "datasets"
|
||||
}
|
||||
|
||||
// Parse options
|
||||
opts := &ce.ListOptions{}
|
||||
if recursive, ok := cmd.Params["recursive"].(bool); ok {
|
||||
opts.Recursive = recursive
|
||||
}
|
||||
if limit, ok := cmd.Params["limit"].(int); ok {
|
||||
opts.Limit = limit
|
||||
}
|
||||
if offset, ok := cmd.Params["offset"].(int); ok {
|
||||
opts.Offset = offset
|
||||
}
|
||||
|
||||
// Execute list command through Filesystem Engine
|
||||
ctx := context.Background()
|
||||
result, err := c.ContextEngine.List(ctx, path, opts)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// Convert to response
|
||||
var response ContextListResponse
|
||||
response.OutputFormat = c.outputFormat
|
||||
response.Code = 0
|
||||
response.Data = ce.FormatNodes(result.Nodes, string(c.outputFormat))
|
||||
|
||||
return &response, nil
|
||||
}
|
||||
|
||||
// CESearch handles the search command using Context Engine
|
||||
func (c *CLI) CESearch(cmd *Command) (ResponseIf, error) {
|
||||
// Get path and query from command params
|
||||
path, _ := cmd.Params["path"].(string)
|
||||
if path == "" {
|
||||
path = "datasets"
|
||||
}
|
||||
query, _ := cmd.Params["query"].(string)
|
||||
|
||||
// Parse options
|
||||
opts := &ce.SearchOptions{
|
||||
Query: query,
|
||||
}
|
||||
if limit, ok := cmd.Params["limit"].(int); ok {
|
||||
opts.Limit = limit
|
||||
}
|
||||
if offset, ok := cmd.Params["offset"].(int); ok {
|
||||
opts.Offset = offset
|
||||
}
|
||||
if recursive, ok := cmd.Params["recursive"].(bool); ok {
|
||||
opts.Recursive = recursive
|
||||
}
|
||||
|
||||
// Execute search command through Filesystem Engine
|
||||
ctx := context.Background()
|
||||
result, err := c.ContextEngine.Search(ctx, path, opts)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// Convert to response
|
||||
var response ContextSearchResponse
|
||||
response.OutputFormat = c.outputFormat
|
||||
response.Code = 0
|
||||
response.Total = result.Total
|
||||
response.Data = ce.FormatNodes(result.Nodes, string(c.outputFormat))
|
||||
|
||||
return &response, nil
|
||||
}
|
||||
|
||||
// InsertChunksFromFile inserts chunks from a JSON file
|
||||
func (c *CLI) InsertChunksFromFile(cmd *Command) (ResponseIf, error) {
|
||||
|
||||
Reference in New Issue
Block a user