Spaces:
Runtime error
Runtime error
starsnatched
commited on
Commit
·
4beb924
1
Parent(s):
d8668d3
refactor: remove deprecated Go CLI and macOS GUI components
Browse files- cli-go/README.md +0 -33
- cli-go/cmd/cat.go +0 -27
- cli-go/cmd/chat.go +0 -83
- cli-go/cmd/ls.go +0 -37
- cli-go/cmd/rm.go +0 -21
- cli-go/cmd/root.go +0 -39
- cli-go/cmd/upload.go +0 -28
- cli-go/cmd/write.go +0 -26
- cli-go/go.mod +0 -16
- cli-go/go.sum +0 -21
- cli-go/internal/client/client.go +0 -218
- cli-go/main.go +0 -7
- mac_gui/__init__.py +0 -0
- mac_gui/__main__.py +0 -4
- mac_gui/api_client.py +0 -70
- mac_gui/app.py +0 -144
- src/cli.py +0 -83
cli-go/README.md
DELETED
|
@@ -1,33 +0,0 @@
|
|
| 1 |
-
# Go CLI for LLM Backend
|
| 2 |
-
|
| 3 |
-
This folder contains a cross-platform command line client written in Go. The CLI can be built as a single executable for Windows, Linux and macOS.
|
| 4 |
-
|
| 5 |
-
## Features
|
| 6 |
-
|
| 7 |
-
- Interactive colour-coded chat sessions
|
| 8 |
-
- Upload documents to the API
|
| 9 |
-
- List, read, write and delete files in the VM
|
| 10 |
-
|
| 11 |
-
## Building
|
| 12 |
-
|
| 13 |
-
Install Go 1.20 or later and run:
|
| 14 |
-
|
| 15 |
-
```bash
|
| 16 |
-
cd cli-go
|
| 17 |
-
go build -o llmcli
|
| 18 |
-
```
|
| 19 |
-
|
| 20 |
-
For other platforms pass `GOOS` and `GOARCH` environment variables:
|
| 21 |
-
|
| 22 |
-
```bash
|
| 23 |
-
GOOS=windows GOARCH=amd64 go build -o llmcli.exe
|
| 24 |
-
```
|
| 25 |
-
|
| 26 |
-
## Usage
|
| 27 |
-
|
| 28 |
-
```
|
| 29 |
-
./llmcli --user yourname --server http://localhost:8000 chat
|
| 30 |
-
```
|
| 31 |
-
|
| 32 |
-
Use `--help` on any subcommand for details.
|
| 33 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
cli-go/cmd/cat.go
DELETED
|
@@ -1,27 +0,0 @@
|
|
| 1 |
-
package cmd
|
| 2 |
-
|
| 3 |
-
import (
|
| 4 |
-
"context"
|
| 5 |
-
"fmt"
|
| 6 |
-
|
| 7 |
-
"github.com/spf13/cobra"
|
| 8 |
-
|
| 9 |
-
"llm-cli/internal/client"
|
| 10 |
-
)
|
| 11 |
-
|
| 12 |
-
func newCatCmd() *cobra.Command {
|
| 13 |
-
return &cobra.Command{
|
| 14 |
-
Use: "cat <path>",
|
| 15 |
-
Short: "Print a file from the VM",
|
| 16 |
-
Args: cobra.ExactArgs(1),
|
| 17 |
-
RunE: func(cmd *cobra.Command, args []string) error {
|
| 18 |
-
c := client.New(server)
|
| 19 |
-
content, err := c.ReadFile(context.Background(), user, args[0])
|
| 20 |
-
if err != nil {
|
| 21 |
-
return err
|
| 22 |
-
}
|
| 23 |
-
fmt.Print(content)
|
| 24 |
-
return nil
|
| 25 |
-
},
|
| 26 |
-
}
|
| 27 |
-
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
cli-go/cmd/chat.go
DELETED
|
@@ -1,83 +0,0 @@
|
|
| 1 |
-
package cmd
|
| 2 |
-
|
| 3 |
-
import (
|
| 4 |
-
"bufio"
|
| 5 |
-
"fmt"
|
| 6 |
-
"os"
|
| 7 |
-
"strconv"
|
| 8 |
-
|
| 9 |
-
"github.com/fatih/color"
|
| 10 |
-
"github.com/spf13/cobra"
|
| 11 |
-
|
| 12 |
-
"llm-cli/internal/client"
|
| 13 |
-
)
|
| 14 |
-
|
| 15 |
-
func newChatCmd() *cobra.Command {
|
| 16 |
-
cmd := &cobra.Command{
|
| 17 |
-
Use: "chat",
|
| 18 |
-
Short: "Start an interactive chat session",
|
| 19 |
-
RunE: runChat,
|
| 20 |
-
}
|
| 21 |
-
return cmd
|
| 22 |
-
}
|
| 23 |
-
|
| 24 |
-
func runChat(cmd *cobra.Command, args []string) error {
|
| 25 |
-
ctx := cmd.Context()
|
| 26 |
-
c := client.New(server)
|
| 27 |
-
|
| 28 |
-
sessions, err := c.ListSessions(ctx, user)
|
| 29 |
-
if err != nil {
|
| 30 |
-
return err
|
| 31 |
-
}
|
| 32 |
-
|
| 33 |
-
session := "default"
|
| 34 |
-
if len(sessions) > 0 {
|
| 35 |
-
fmt.Println("Existing sessions:")
|
| 36 |
-
for i, s := range sessions {
|
| 37 |
-
fmt.Printf(" %d. %s\n", i+1, s)
|
| 38 |
-
}
|
| 39 |
-
fmt.Printf("Select session number or enter new name [%d]: ", len(sessions))
|
| 40 |
-
var choice string
|
| 41 |
-
fmt.Scanln(&choice)
|
| 42 |
-
if n, err := strconv.Atoi(choice); err == nil && n >= 1 && n <= len(sessions) {
|
| 43 |
-
session = sessions[n-1]
|
| 44 |
-
} else if choice != "" {
|
| 45 |
-
session = choice
|
| 46 |
-
}
|
| 47 |
-
}
|
| 48 |
-
|
| 49 |
-
cyan := color.New(color.FgCyan).SprintFunc()
|
| 50 |
-
green := color.New(color.FgGreen).SprintFunc()
|
| 51 |
-
yellow := color.New(color.FgYellow).SprintFunc()
|
| 52 |
-
|
| 53 |
-
fmt.Printf("Chatting as %s in session '%s'\n", green(user), session)
|
| 54 |
-
|
| 55 |
-
scanner := bufio.NewScanner(os.Stdin)
|
| 56 |
-
for {
|
| 57 |
-
fmt.Printf("%s> ", cyan("You"))
|
| 58 |
-
if !scanner.Scan() {
|
| 59 |
-
break
|
| 60 |
-
}
|
| 61 |
-
line := scanner.Text()
|
| 62 |
-
if line == "exit" || line == "quit" {
|
| 63 |
-
break
|
| 64 |
-
}
|
| 65 |
-
stream, err := c.ChatStream(ctx, user, session, line)
|
| 66 |
-
if err != nil {
|
| 67 |
-
fmt.Println("error:", err)
|
| 68 |
-
continue
|
| 69 |
-
}
|
| 70 |
-
r := bufio.NewReader(stream)
|
| 71 |
-
for {
|
| 72 |
-
part, err := r.ReadString('\n')
|
| 73 |
-
if len(part) > 0 {
|
| 74 |
-
fmt.Print(yellow(part))
|
| 75 |
-
}
|
| 76 |
-
if err != nil {
|
| 77 |
-
break
|
| 78 |
-
}
|
| 79 |
-
}
|
| 80 |
-
stream.Close()
|
| 81 |
-
}
|
| 82 |
-
return nil
|
| 83 |
-
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
cli-go/cmd/ls.go
DELETED
|
@@ -1,37 +0,0 @@
|
|
| 1 |
-
package cmd
|
| 2 |
-
|
| 3 |
-
import (
|
| 4 |
-
"context"
|
| 5 |
-
"fmt"
|
| 6 |
-
|
| 7 |
-
"github.com/spf13/cobra"
|
| 8 |
-
|
| 9 |
-
"llm-cli/internal/client"
|
| 10 |
-
)
|
| 11 |
-
|
| 12 |
-
func newLsCmd() *cobra.Command {
|
| 13 |
-
return &cobra.Command{
|
| 14 |
-
Use: "ls [path]",
|
| 15 |
-
Short: "List directory contents in the VM",
|
| 16 |
-
Args: cobra.MaximumNArgs(1),
|
| 17 |
-
RunE: func(cmd *cobra.Command, args []string) error {
|
| 18 |
-
path := "/data"
|
| 19 |
-
if len(args) == 1 {
|
| 20 |
-
path = args[0]
|
| 21 |
-
}
|
| 22 |
-
c := client.New(server)
|
| 23 |
-
entries, err := c.ListDir(context.Background(), user, path)
|
| 24 |
-
if err != nil {
|
| 25 |
-
return err
|
| 26 |
-
}
|
| 27 |
-
for _, e := range entries {
|
| 28 |
-
if e.IsDir {
|
| 29 |
-
fmt.Println(e.Name + "/")
|
| 30 |
-
} else {
|
| 31 |
-
fmt.Println(e.Name)
|
| 32 |
-
}
|
| 33 |
-
}
|
| 34 |
-
return nil
|
| 35 |
-
},
|
| 36 |
-
}
|
| 37 |
-
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
cli-go/cmd/rm.go
DELETED
|
@@ -1,21 +0,0 @@
|
|
| 1 |
-
package cmd
|
| 2 |
-
|
| 3 |
-
import (
|
| 4 |
-
"context"
|
| 5 |
-
|
| 6 |
-
"github.com/spf13/cobra"
|
| 7 |
-
|
| 8 |
-
"llm-cli/internal/client"
|
| 9 |
-
)
|
| 10 |
-
|
| 11 |
-
func newRmCmd() *cobra.Command {
|
| 12 |
-
return &cobra.Command{
|
| 13 |
-
Use: "rm <path>",
|
| 14 |
-
Short: "Remove a file or directory in the VM",
|
| 15 |
-
Args: cobra.ExactArgs(1),
|
| 16 |
-
RunE: func(cmd *cobra.Command, args []string) error {
|
| 17 |
-
c := client.New(server)
|
| 18 |
-
return c.DeleteFile(context.Background(), user, args[0])
|
| 19 |
-
},
|
| 20 |
-
}
|
| 21 |
-
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
cli-go/cmd/root.go
DELETED
|
@@ -1,39 +0,0 @@
|
|
| 1 |
-
package cmd
|
| 2 |
-
|
| 3 |
-
import (
|
| 4 |
-
"fmt"
|
| 5 |
-
"os"
|
| 6 |
-
|
| 7 |
-
"github.com/spf13/cobra"
|
| 8 |
-
)
|
| 9 |
-
|
| 10 |
-
var (
|
| 11 |
-
server string
|
| 12 |
-
user string
|
| 13 |
-
)
|
| 14 |
-
|
| 15 |
-
func NewRootCmd() *cobra.Command {
|
| 16 |
-
cmd := &cobra.Command{
|
| 17 |
-
Use: "llmcli",
|
| 18 |
-
Short: "CLI client for the LLM backend",
|
| 19 |
-
}
|
| 20 |
-
|
| 21 |
-
cmd.PersistentFlags().StringVarP(&server, "server", "s", "http://localhost:8000", "API server URL")
|
| 22 |
-
cmd.PersistentFlags().StringVarP(&user, "user", "u", "default", "User name")
|
| 23 |
-
|
| 24 |
-
cmd.AddCommand(newChatCmd())
|
| 25 |
-
cmd.AddCommand(newUploadCmd())
|
| 26 |
-
cmd.AddCommand(newLsCmd())
|
| 27 |
-
cmd.AddCommand(newCatCmd())
|
| 28 |
-
cmd.AddCommand(newWriteCmd())
|
| 29 |
-
cmd.AddCommand(newRmCmd())
|
| 30 |
-
|
| 31 |
-
return cmd
|
| 32 |
-
}
|
| 33 |
-
|
| 34 |
-
func Execute() {
|
| 35 |
-
if err := NewRootCmd().Execute(); err != nil {
|
| 36 |
-
fmt.Fprintln(os.Stderr, err)
|
| 37 |
-
os.Exit(1)
|
| 38 |
-
}
|
| 39 |
-
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
cli-go/cmd/upload.go
DELETED
|
@@ -1,28 +0,0 @@
|
|
| 1 |
-
package cmd
|
| 2 |
-
|
| 3 |
-
import (
|
| 4 |
-
"context"
|
| 5 |
-
"fmt"
|
| 6 |
-
|
| 7 |
-
"github.com/spf13/cobra"
|
| 8 |
-
|
| 9 |
-
"llm-cli/internal/client"
|
| 10 |
-
)
|
| 11 |
-
|
| 12 |
-
func newUploadCmd() *cobra.Command {
|
| 13 |
-
return &cobra.Command{
|
| 14 |
-
Use: "upload [file]",
|
| 15 |
-
Short: "Upload a document to the VM",
|
| 16 |
-
Args: cobra.ExactArgs(1),
|
| 17 |
-
RunE: func(cmd *cobra.Command, args []string) error {
|
| 18 |
-
ctx := context.Background()
|
| 19 |
-
c := client.New(server)
|
| 20 |
-
path, err := c.UploadDocument(ctx, user, "default", args[0])
|
| 21 |
-
if err != nil {
|
| 22 |
-
return err
|
| 23 |
-
}
|
| 24 |
-
fmt.Println("Uploaded to", path)
|
| 25 |
-
return nil
|
| 26 |
-
},
|
| 27 |
-
}
|
| 28 |
-
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
cli-go/cmd/write.go
DELETED
|
@@ -1,26 +0,0 @@
|
|
| 1 |
-
package cmd
|
| 2 |
-
|
| 3 |
-
import (
|
| 4 |
-
"context"
|
| 5 |
-
"os"
|
| 6 |
-
|
| 7 |
-
"github.com/spf13/cobra"
|
| 8 |
-
|
| 9 |
-
"llm-cli/internal/client"
|
| 10 |
-
)
|
| 11 |
-
|
| 12 |
-
func newWriteCmd() *cobra.Command {
|
| 13 |
-
return &cobra.Command{
|
| 14 |
-
Use: "write <path> <file>",
|
| 15 |
-
Short: "Write a local file to a path in the VM",
|
| 16 |
-
Args: cobra.ExactArgs(2),
|
| 17 |
-
RunE: func(cmd *cobra.Command, args []string) error {
|
| 18 |
-
data, err := os.ReadFile(args[1])
|
| 19 |
-
if err != nil {
|
| 20 |
-
return err
|
| 21 |
-
}
|
| 22 |
-
c := client.New(server)
|
| 23 |
-
return c.WriteFile(context.Background(), user, args[0], string(data))
|
| 24 |
-
},
|
| 25 |
-
}
|
| 26 |
-
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
cli-go/go.mod
DELETED
|
@@ -1,16 +0,0 @@
|
|
| 1 |
-
module llm-cli
|
| 2 |
-
|
| 3 |
-
go 1.23.8
|
| 4 |
-
|
| 5 |
-
require (
|
| 6 |
-
github.com/fatih/color v1.16.0
|
| 7 |
-
github.com/spf13/cobra v1.7.0
|
| 8 |
-
)
|
| 9 |
-
|
| 10 |
-
require (
|
| 11 |
-
github.com/inconshreveable/mousetrap v1.1.0 // indirect
|
| 12 |
-
github.com/mattn/go-colorable v0.1.13 // indirect
|
| 13 |
-
github.com/mattn/go-isatty v0.0.20 // indirect
|
| 14 |
-
github.com/spf13/pflag v1.0.5 // indirect
|
| 15 |
-
golang.org/x/sys v0.14.0 // indirect
|
| 16 |
-
)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
cli-go/go.sum
DELETED
|
@@ -1,21 +0,0 @@
|
|
| 1 |
-
github.com/cpuguy83/go-md2man/v2 v2.0.2/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o=
|
| 2 |
-
github.com/fatih/color v1.16.0 h1:zmkK9Ngbjj+K0yRhTVONQh1p/HknKYSlNT+vZCzyokM=
|
| 3 |
-
github.com/fatih/color v1.16.0/go.mod h1:fL2Sau1YI5c0pdGEVCbKQbLXB6edEj1ZgiY4NijnWvE=
|
| 4 |
-
github.com/inconshreveable/mousetrap v1.1.0 h1:wN+x4NVGpMsO7ErUn/mUI3vEoE6Jt13X2s0bqwp9tc8=
|
| 5 |
-
github.com/inconshreveable/mousetrap v1.1.0/go.mod h1:vpF70FUmC8bwa3OWnCshd2FqLfsEA9PFc4w1p2J65bw=
|
| 6 |
-
github.com/mattn/go-colorable v0.1.13 h1:fFA4WZxdEF4tXPZVKMLwD8oUnCTTo08duU7wxecdEvA=
|
| 7 |
-
github.com/mattn/go-colorable v0.1.13/go.mod h1:7S9/ev0klgBDR4GtXTXX8a3vIGJpMovkB8vQcUbaXHg=
|
| 8 |
-
github.com/mattn/go-isatty v0.0.16/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM=
|
| 9 |
-
github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY=
|
| 10 |
-
github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y=
|
| 11 |
-
github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM=
|
| 12 |
-
github.com/spf13/cobra v1.7.0 h1:hyqWnYt1ZQShIddO5kBpj3vu05/++x6tJ6dg8EC572I=
|
| 13 |
-
github.com/spf13/cobra v1.7.0/go.mod h1:uLxZILRyS/50WlhOIKD7W6V5bgeIt+4sICxh6uRMrb0=
|
| 14 |
-
github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA=
|
| 15 |
-
github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg=
|
| 16 |
-
golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
| 17 |
-
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
| 18 |
-
golang.org/x/sys v0.14.0 h1:Vz7Qs629MkJkGyHxUlRHizWJRG2j8fbQKjELVSNhy7Q=
|
| 19 |
-
golang.org/x/sys v0.14.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
|
| 20 |
-
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
| 21 |
-
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
cli-go/internal/client/client.go
DELETED
|
@@ -1,218 +0,0 @@
|
|
| 1 |
-
package client
|
| 2 |
-
|
| 3 |
-
import (
|
| 4 |
-
"bytes"
|
| 5 |
-
"context"
|
| 6 |
-
"encoding/json"
|
| 7 |
-
"fmt"
|
| 8 |
-
"io"
|
| 9 |
-
"mime/multipart"
|
| 10 |
-
"net/http"
|
| 11 |
-
"os"
|
| 12 |
-
"path/filepath"
|
| 13 |
-
"time"
|
| 14 |
-
)
|
| 15 |
-
|
| 16 |
-
type Client struct {
|
| 17 |
-
baseURL string
|
| 18 |
-
httpClient *http.Client
|
| 19 |
-
}
|
| 20 |
-
|
| 21 |
-
func New(baseURL string) *Client {
|
| 22 |
-
return &Client{
|
| 23 |
-
baseURL: baseURL,
|
| 24 |
-
httpClient: &http.Client{Timeout: 30 * time.Second},
|
| 25 |
-
}
|
| 26 |
-
}
|
| 27 |
-
|
| 28 |
-
type SessionInfo struct {
|
| 29 |
-
Sessions []string `json:"sessions"`
|
| 30 |
-
}
|
| 31 |
-
|
| 32 |
-
func (c *Client) ListSessions(ctx context.Context, user string) ([]string, error) {
|
| 33 |
-
req, err := http.NewRequestWithContext(ctx, http.MethodGet,
|
| 34 |
-
fmt.Sprintf("%s/sessions/%s", c.baseURL, user), nil)
|
| 35 |
-
if err != nil {
|
| 36 |
-
return nil, err
|
| 37 |
-
}
|
| 38 |
-
resp, err := c.httpClient.Do(req)
|
| 39 |
-
if err != nil {
|
| 40 |
-
return nil, err
|
| 41 |
-
}
|
| 42 |
-
defer resp.Body.Close()
|
| 43 |
-
if resp.StatusCode != http.StatusOK {
|
| 44 |
-
return nil, fmt.Errorf("list sessions failed: %s", resp.Status)
|
| 45 |
-
}
|
| 46 |
-
var data SessionInfo
|
| 47 |
-
if err := json.NewDecoder(resp.Body).Decode(&data); err != nil {
|
| 48 |
-
return nil, err
|
| 49 |
-
}
|
| 50 |
-
return data.Sessions, nil
|
| 51 |
-
}
|
| 52 |
-
|
| 53 |
-
type ChatRequest struct {
|
| 54 |
-
User string `json:"user"`
|
| 55 |
-
Session string `json:"session"`
|
| 56 |
-
Prompt string `json:"prompt"`
|
| 57 |
-
}
|
| 58 |
-
|
| 59 |
-
func (c *Client) ChatStream(ctx context.Context, user, session, prompt string) (io.ReadCloser, error) {
|
| 60 |
-
body, err := json.Marshal(ChatRequest{User: user, Session: session, Prompt: prompt})
|
| 61 |
-
if err != nil {
|
| 62 |
-
return nil, err
|
| 63 |
-
}
|
| 64 |
-
req, err := http.NewRequestWithContext(ctx, http.MethodPost, c.baseURL+"/chat/stream", bytes.NewReader(body))
|
| 65 |
-
if err != nil {
|
| 66 |
-
return nil, err
|
| 67 |
-
}
|
| 68 |
-
req.Header.Set("Content-Type", "application/json")
|
| 69 |
-
resp, err := c.httpClient.Do(req)
|
| 70 |
-
if err != nil {
|
| 71 |
-
return nil, err
|
| 72 |
-
}
|
| 73 |
-
if resp.StatusCode != http.StatusOK {
|
| 74 |
-
defer resp.Body.Close()
|
| 75 |
-
b, _ := io.ReadAll(resp.Body)
|
| 76 |
-
return nil, fmt.Errorf("chat failed: %s - %s", resp.Status, string(b))
|
| 77 |
-
}
|
| 78 |
-
return resp.Body, nil
|
| 79 |
-
}
|
| 80 |
-
|
| 81 |
-
type UploadResp struct {
|
| 82 |
-
Path string `json:"path"`
|
| 83 |
-
}
|
| 84 |
-
|
| 85 |
-
func (c *Client) UploadDocument(ctx context.Context, user, session, path string) (string, error) {
|
| 86 |
-
file, err := os.Open(path)
|
| 87 |
-
if err != nil {
|
| 88 |
-
return "", err
|
| 89 |
-
}
|
| 90 |
-
defer file.Close()
|
| 91 |
-
|
| 92 |
-
buf := &bytes.Buffer{}
|
| 93 |
-
writer := multipart.NewWriter(buf)
|
| 94 |
-
_ = writer.WriteField("user", user)
|
| 95 |
-
_ = writer.WriteField("session", session)
|
| 96 |
-
fw, err := writer.CreateFormFile("file", filepath.Base(path))
|
| 97 |
-
if err != nil {
|
| 98 |
-
return "", err
|
| 99 |
-
}
|
| 100 |
-
if _, err = io.Copy(fw, file); err != nil {
|
| 101 |
-
return "", err
|
| 102 |
-
}
|
| 103 |
-
writer.Close()
|
| 104 |
-
|
| 105 |
-
req, err := http.NewRequestWithContext(ctx, http.MethodPost, c.baseURL+"/upload", buf)
|
| 106 |
-
if err != nil {
|
| 107 |
-
return "", err
|
| 108 |
-
}
|
| 109 |
-
req.Header.Set("Content-Type", writer.FormDataContentType())
|
| 110 |
-
|
| 111 |
-
resp, err := c.httpClient.Do(req)
|
| 112 |
-
if err != nil {
|
| 113 |
-
return "", err
|
| 114 |
-
}
|
| 115 |
-
defer resp.Body.Close()
|
| 116 |
-
if resp.StatusCode != http.StatusOK {
|
| 117 |
-
b, _ := io.ReadAll(resp.Body)
|
| 118 |
-
return "", fmt.Errorf("upload failed: %s - %s", resp.Status, string(b))
|
| 119 |
-
}
|
| 120 |
-
var out UploadResp
|
| 121 |
-
if err := json.NewDecoder(resp.Body).Decode(&out); err != nil {
|
| 122 |
-
return "", err
|
| 123 |
-
}
|
| 124 |
-
return out.Path, nil
|
| 125 |
-
}
|
| 126 |
-
|
| 127 |
-
type DirEntry struct {
|
| 128 |
-
Name string `json:"name"`
|
| 129 |
-
IsDir bool `json:"is_dir"`
|
| 130 |
-
}
|
| 131 |
-
|
| 132 |
-
type DirList struct {
|
| 133 |
-
Entries []DirEntry `json:"entries"`
|
| 134 |
-
}
|
| 135 |
-
|
| 136 |
-
func (c *Client) ListDir(ctx context.Context, user, path string) ([]DirEntry, error) {
|
| 137 |
-
req, err := http.NewRequestWithContext(ctx, http.MethodGet,
|
| 138 |
-
fmt.Sprintf("%s/vm/%s/list?path=%s", c.baseURL, user, path), nil)
|
| 139 |
-
if err != nil {
|
| 140 |
-
return nil, err
|
| 141 |
-
}
|
| 142 |
-
resp, err := c.httpClient.Do(req)
|
| 143 |
-
if err != nil {
|
| 144 |
-
return nil, err
|
| 145 |
-
}
|
| 146 |
-
defer resp.Body.Close()
|
| 147 |
-
if resp.StatusCode != http.StatusOK {
|
| 148 |
-
return nil, fmt.Errorf("list dir failed: %s", resp.Status)
|
| 149 |
-
}
|
| 150 |
-
var out DirList
|
| 151 |
-
if err := json.NewDecoder(resp.Body).Decode(&out); err != nil {
|
| 152 |
-
return nil, err
|
| 153 |
-
}
|
| 154 |
-
return out.Entries, nil
|
| 155 |
-
}
|
| 156 |
-
|
| 157 |
-
type FileContent struct {
|
| 158 |
-
Content string `json:"content"`
|
| 159 |
-
}
|
| 160 |
-
|
| 161 |
-
func (c *Client) ReadFile(ctx context.Context, user, path string) (string, error) {
|
| 162 |
-
req, err := http.NewRequestWithContext(ctx, http.MethodGet,
|
| 163 |
-
fmt.Sprintf("%s/vm/%s/file?path=%s", c.baseURL, user, path), nil)
|
| 164 |
-
if err != nil {
|
| 165 |
-
return "", err
|
| 166 |
-
}
|
| 167 |
-
resp, err := c.httpClient.Do(req)
|
| 168 |
-
if err != nil {
|
| 169 |
-
return "", err
|
| 170 |
-
}
|
| 171 |
-
defer resp.Body.Close()
|
| 172 |
-
if resp.StatusCode != http.StatusOK {
|
| 173 |
-
return "", fmt.Errorf("read file failed: %s", resp.Status)
|
| 174 |
-
}
|
| 175 |
-
var out FileContent
|
| 176 |
-
if err := json.NewDecoder(resp.Body).Decode(&out); err != nil {
|
| 177 |
-
return "", err
|
| 178 |
-
}
|
| 179 |
-
return out.Content, nil
|
| 180 |
-
}
|
| 181 |
-
|
| 182 |
-
func (c *Client) WriteFile(ctx context.Context, user, path, content string) error {
|
| 183 |
-
data, _ := json.Marshal(map[string]string{"path": path, "content": content})
|
| 184 |
-
req, err := http.NewRequestWithContext(ctx, http.MethodPost,
|
| 185 |
-
fmt.Sprintf("%s/vm/%s/file", c.baseURL, user), bytes.NewReader(data))
|
| 186 |
-
if err != nil {
|
| 187 |
-
return err
|
| 188 |
-
}
|
| 189 |
-
req.Header.Set("Content-Type", "application/json")
|
| 190 |
-
resp, err := c.httpClient.Do(req)
|
| 191 |
-
if err != nil {
|
| 192 |
-
return err
|
| 193 |
-
}
|
| 194 |
-
defer resp.Body.Close()
|
| 195 |
-
if resp.StatusCode != http.StatusOK {
|
| 196 |
-
b, _ := io.ReadAll(resp.Body)
|
| 197 |
-
return fmt.Errorf("write file failed: %s - %s", resp.Status, string(b))
|
| 198 |
-
}
|
| 199 |
-
return nil
|
| 200 |
-
}
|
| 201 |
-
|
| 202 |
-
func (c *Client) DeleteFile(ctx context.Context, user, path string) error {
|
| 203 |
-
req, err := http.NewRequestWithContext(ctx, http.MethodDelete,
|
| 204 |
-
fmt.Sprintf("%s/vm/%s/file?path=%s", c.baseURL, user, path), nil)
|
| 205 |
-
if err != nil {
|
| 206 |
-
return err
|
| 207 |
-
}
|
| 208 |
-
resp, err := c.httpClient.Do(req)
|
| 209 |
-
if err != nil {
|
| 210 |
-
return err
|
| 211 |
-
}
|
| 212 |
-
defer resp.Body.Close()
|
| 213 |
-
if resp.StatusCode != http.StatusOK {
|
| 214 |
-
b, _ := io.ReadAll(resp.Body)
|
| 215 |
-
return fmt.Errorf("delete file failed: %s - %s", resp.Status, string(b))
|
| 216 |
-
}
|
| 217 |
-
return nil
|
| 218 |
-
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
cli-go/main.go
DELETED
|
@@ -1,7 +0,0 @@
|
|
| 1 |
-
package main
|
| 2 |
-
|
| 3 |
-
import "llm-cli/cmd"
|
| 4 |
-
|
| 5 |
-
func main() {
|
| 6 |
-
cmd.Execute()
|
| 7 |
-
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
mac_gui/__init__.py
DELETED
|
File without changes
|
mac_gui/__main__.py
DELETED
|
@@ -1,4 +0,0 @@
|
|
| 1 |
-
from .app import main
|
| 2 |
-
|
| 3 |
-
if __name__ == "__main__":
|
| 4 |
-
main()
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
mac_gui/api_client.py
DELETED
|
@@ -1,70 +0,0 @@
|
|
| 1 |
-
from __future__ import annotations
|
| 2 |
-
|
| 3 |
-
import os
|
| 4 |
-
from typing import Iterator, List, Dict, Any
|
| 5 |
-
|
| 6 |
-
import httpx
|
| 7 |
-
|
| 8 |
-
|
| 9 |
-
class APIClient:
|
| 10 |
-
"""Simple client for the LLM backend API."""
|
| 11 |
-
|
| 12 |
-
def __init__(self, server: str = "http://localhost:8000", api_key: str | None = None) -> None:
|
| 13 |
-
self._server = server.rstrip("/")
|
| 14 |
-
self._headers = {"X-API-Key": api_key} if api_key else {}
|
| 15 |
-
|
| 16 |
-
# ------------------------------------------------------------------
|
| 17 |
-
# Helper methods
|
| 18 |
-
# ------------------------------------------------------------------
|
| 19 |
-
def _url(self, path: str) -> str:
|
| 20 |
-
return f"{self._server}{path}"
|
| 21 |
-
|
| 22 |
-
# ------------------------------------------------------------------
|
| 23 |
-
# Public API methods
|
| 24 |
-
# ------------------------------------------------------------------
|
| 25 |
-
def list_sessions(self, user: str) -> List[str]:
|
| 26 |
-
resp = httpx.get(self._url(f"/sessions/{user}"), headers=self._headers)
|
| 27 |
-
resp.raise_for_status()
|
| 28 |
-
data = resp.json()
|
| 29 |
-
return data.get("sessions", [])
|
| 30 |
-
|
| 31 |
-
def stream_chat(self, user: str, session: str, prompt: str) -> Iterator[str]:
|
| 32 |
-
with httpx.stream(
|
| 33 |
-
"POST",
|
| 34 |
-
self._url("/chat/stream"),
|
| 35 |
-
json={"user": user, "session": session, "prompt": prompt},
|
| 36 |
-
headers=self._headers,
|
| 37 |
-
timeout=None,
|
| 38 |
-
) as resp:
|
| 39 |
-
resp.raise_for_status()
|
| 40 |
-
for line in resp.iter_lines():
|
| 41 |
-
if line:
|
| 42 |
-
yield line.decode()
|
| 43 |
-
|
| 44 |
-
def upload_document(self, user: str, session: str, path: str) -> str:
|
| 45 |
-
name = os.path.basename(path)
|
| 46 |
-
with open(path, "rb") as f:
|
| 47 |
-
files = {"file": (name, f)}
|
| 48 |
-
data = {"user": user, "session": session}
|
| 49 |
-
resp = httpx.post(self._url("/upload"), data=data, files=files, headers=self._headers)
|
| 50 |
-
resp.raise_for_status()
|
| 51 |
-
return resp.json()["path"]
|
| 52 |
-
|
| 53 |
-
def list_vm_dir(self, user: str, path: str = "/data") -> List[Dict[str, Any]]:
|
| 54 |
-
resp = httpx.get(self._url(f"/vm/{user}/list"), params={"path": path}, headers=self._headers)
|
| 55 |
-
resp.raise_for_status()
|
| 56 |
-
return resp.json().get("entries", [])
|
| 57 |
-
|
| 58 |
-
def read_vm_file(self, user: str, path: str) -> str:
|
| 59 |
-
resp = httpx.get(self._url(f"/vm/{user}/file"), params={"path": path}, headers=self._headers)
|
| 60 |
-
resp.raise_for_status()
|
| 61 |
-
return resp.json().get("content", "")
|
| 62 |
-
|
| 63 |
-
def write_vm_file(self, user: str, path: str, content: str) -> None:
|
| 64 |
-
payload = {"path": path, "content": content}
|
| 65 |
-
resp = httpx.post(self._url(f"/vm/{user}/file"), json=payload, headers=self._headers)
|
| 66 |
-
resp.raise_for_status()
|
| 67 |
-
|
| 68 |
-
def delete_vm_file(self, user: str, path: str) -> None:
|
| 69 |
-
resp = httpx.delete(self._url(f"/vm/{user}/file"), params={"path": path}, headers=self._headers)
|
| 70 |
-
resp.raise_for_status()
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
mac_gui/app.py
DELETED
|
@@ -1,144 +0,0 @@
|
|
| 1 |
-
from __future__ import annotations
|
| 2 |
-
|
| 3 |
-
import threading
|
| 4 |
-
import queue
|
| 5 |
-
from pathlib import Path
|
| 6 |
-
from tkinter import (
|
| 7 |
-
Tk,
|
| 8 |
-
Text,
|
| 9 |
-
Entry,
|
| 10 |
-
Button,
|
| 11 |
-
Scrollbar,
|
| 12 |
-
Frame,
|
| 13 |
-
Label,
|
| 14 |
-
StringVar,
|
| 15 |
-
END,
|
| 16 |
-
filedialog,
|
| 17 |
-
)
|
| 18 |
-
|
| 19 |
-
from .api_client import APIClient
|
| 20 |
-
|
| 21 |
-
|
| 22 |
-
class ChatApp:
|
| 23 |
-
"""Tkinter GUI for interacting with the LLM backend."""
|
| 24 |
-
|
| 25 |
-
def __init__(self, root: Tk) -> None:
|
| 26 |
-
self.root = root
|
| 27 |
-
self.root.title("LLM Backend Chat")
|
| 28 |
-
|
| 29 |
-
self.server_var = StringVar(value="http://localhost:8000")
|
| 30 |
-
self.api_key_var = StringVar(value="")
|
| 31 |
-
self.user_var = StringVar(value="default")
|
| 32 |
-
self.session_var = StringVar(value="default")
|
| 33 |
-
|
| 34 |
-
self._client = APIClient()
|
| 35 |
-
self._queue: queue.Queue[tuple[str, str]] = queue.Queue()
|
| 36 |
-
|
| 37 |
-
self._build_ui()
|
| 38 |
-
self.root.after(100, self._process_queue)
|
| 39 |
-
|
| 40 |
-
# ------------------------------------------------------------------
|
| 41 |
-
# UI construction
|
| 42 |
-
# ------------------------------------------------------------------
|
| 43 |
-
def _build_ui(self) -> None:
|
| 44 |
-
top = Frame(self.root)
|
| 45 |
-
top.pack(fill="x")
|
| 46 |
-
|
| 47 |
-
Label(top, text="Server:").grid(row=0, column=0, sticky="w")
|
| 48 |
-
Entry(top, textvariable=self.server_var, width=30).grid(row=0, column=1, sticky="ew")
|
| 49 |
-
|
| 50 |
-
Label(top, text="API Key:").grid(row=0, column=2, sticky="w")
|
| 51 |
-
Entry(top, textvariable=self.api_key_var, width=20).grid(row=0, column=3, sticky="ew")
|
| 52 |
-
|
| 53 |
-
Label(top, text="User:").grid(row=1, column=0, sticky="w")
|
| 54 |
-
Entry(top, textvariable=self.user_var, width=15).grid(row=1, column=1, sticky="ew")
|
| 55 |
-
|
| 56 |
-
Label(top, text="Session:").grid(row=1, column=2, sticky="w")
|
| 57 |
-
Entry(top, textvariable=self.session_var, width=15).grid(row=1, column=3, sticky="ew")
|
| 58 |
-
|
| 59 |
-
self.chat_display = Text(self.root, wrap="word", height=20)
|
| 60 |
-
self.chat_display.pack(fill="both", expand=True)
|
| 61 |
-
|
| 62 |
-
scroll = Scrollbar(self.chat_display)
|
| 63 |
-
scroll.pack(side="right", fill="y")
|
| 64 |
-
self.chat_display.config(yscrollcommand=scroll.set)
|
| 65 |
-
scroll.config(command=self.chat_display.yview)
|
| 66 |
-
|
| 67 |
-
bottom = Frame(self.root)
|
| 68 |
-
bottom.pack(fill="x")
|
| 69 |
-
|
| 70 |
-
self.msg_entry = Entry(bottom)
|
| 71 |
-
self.msg_entry.pack(side="left", fill="x", expand=True)
|
| 72 |
-
self.msg_entry.bind("<Return>", lambda _: self.send_message())
|
| 73 |
-
|
| 74 |
-
Button(bottom, text="Send", command=self.send_message).pack(side="left")
|
| 75 |
-
Button(bottom, text="Upload", command=self.upload_file).pack(side="left")
|
| 76 |
-
|
| 77 |
-
# ------------------------------------------------------------------
|
| 78 |
-
# Event handlers
|
| 79 |
-
# ------------------------------------------------------------------
|
| 80 |
-
def _update_client(self) -> None:
|
| 81 |
-
self._client = APIClient(self.server_var.get(), self.api_key_var.get() or None)
|
| 82 |
-
|
| 83 |
-
def send_message(self) -> None:
|
| 84 |
-
prompt = self.msg_entry.get().strip()
|
| 85 |
-
if not prompt:
|
| 86 |
-
return
|
| 87 |
-
self.msg_entry.delete(0, END)
|
| 88 |
-
self.chat_display.insert(END, f"You: {prompt}\n")
|
| 89 |
-
self.chat_display.see(END)
|
| 90 |
-
self._update_client()
|
| 91 |
-
thread = threading.Thread(target=self._stream_prompt, args=(prompt,), daemon=True)
|
| 92 |
-
thread.start()
|
| 93 |
-
|
| 94 |
-
def _stream_prompt(self, prompt: str) -> None:
|
| 95 |
-
try:
|
| 96 |
-
for part in self._client.stream_chat(
|
| 97 |
-
self.user_var.get(), self.session_var.get(), prompt
|
| 98 |
-
):
|
| 99 |
-
self._queue.put(("assistant", part))
|
| 100 |
-
except Exception as exc: # pragma: no cover - runtime errors
|
| 101 |
-
self._queue.put(("error", str(exc)))
|
| 102 |
-
|
| 103 |
-
def upload_file(self) -> None:
|
| 104 |
-
path = filedialog.askopenfilename()
|
| 105 |
-
if path:
|
| 106 |
-
self._update_client()
|
| 107 |
-
thread = threading.Thread(target=self._upload_file, args=(Path(path),), daemon=True)
|
| 108 |
-
thread.start()
|
| 109 |
-
|
| 110 |
-
def _upload_file(self, path: Path) -> None:
|
| 111 |
-
try:
|
| 112 |
-
vm_path = self._client.upload_document(
|
| 113 |
-
self.user_var.get(), self.session_var.get(), str(path)
|
| 114 |
-
)
|
| 115 |
-
self._queue.put(("info", f"Uploaded {path.name} -> {vm_path}"))
|
| 116 |
-
except Exception as exc: # pragma: no cover - runtime errors
|
| 117 |
-
self._queue.put(("error", str(exc)))
|
| 118 |
-
|
| 119 |
-
# ------------------------------------------------------------------
|
| 120 |
-
# Queue processing
|
| 121 |
-
# ------------------------------------------------------------------
|
| 122 |
-
def _process_queue(self) -> None:
|
| 123 |
-
while True:
|
| 124 |
-
try:
|
| 125 |
-
kind, msg = self._queue.get_nowait()
|
| 126 |
-
except queue.Empty:
|
| 127 |
-
break
|
| 128 |
-
if kind == "assistant":
|
| 129 |
-
self.chat_display.insert(END, msg)
|
| 130 |
-
else:
|
| 131 |
-
prefix = "INFO" if kind == "info" else "ERROR"
|
| 132 |
-
self.chat_display.insert(END, f"[{prefix}] {msg}\n")
|
| 133 |
-
self.chat_display.see(END)
|
| 134 |
-
self.root.after(100, self._process_queue)
|
| 135 |
-
|
| 136 |
-
|
| 137 |
-
def main() -> None:
|
| 138 |
-
root = Tk()
|
| 139 |
-
ChatApp(root)
|
| 140 |
-
root.mainloop()
|
| 141 |
-
|
| 142 |
-
|
| 143 |
-
if __name__ == "__main__": # pragma: no cover - manual execution
|
| 144 |
-
main()
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
src/cli.py
DELETED
|
@@ -1,83 +0,0 @@
|
|
| 1 |
-
from __future__ import annotations
|
| 2 |
-
|
| 3 |
-
import asyncio
|
| 4 |
-
from typing import AsyncIterator
|
| 5 |
-
|
| 6 |
-
import httpx
|
| 7 |
-
import typer
|
| 8 |
-
from colorama import Fore, Style, init
|
| 9 |
-
|
| 10 |
-
|
| 11 |
-
API_URL = "http://localhost:8000"
|
| 12 |
-
|
| 13 |
-
app = typer.Typer(add_completion=False, help="Interact with the LLM backend API")
|
| 14 |
-
|
| 15 |
-
|
| 16 |
-
async def _get_sessions(user: str, server: str) -> list[str]:
|
| 17 |
-
async with httpx.AsyncClient(base_url=server) as client:
|
| 18 |
-
resp = await client.get(f"/sessions/{user}")
|
| 19 |
-
resp.raise_for_status()
|
| 20 |
-
data = resp.json()
|
| 21 |
-
return data.get("sessions", [])
|
| 22 |
-
|
| 23 |
-
|
| 24 |
-
async def _stream_chat(
|
| 25 |
-
user: str, session: str, prompt: str, server: str
|
| 26 |
-
) -> AsyncIterator[str]:
|
| 27 |
-
async with httpx.AsyncClient(base_url=server, timeout=None) as client:
|
| 28 |
-
async with client.stream(
|
| 29 |
-
"POST",
|
| 30 |
-
"/chat/stream",
|
| 31 |
-
json={"user": user, "session": session, "prompt": prompt},
|
| 32 |
-
) as resp:
|
| 33 |
-
resp.raise_for_status()
|
| 34 |
-
async for line in resp.aiter_lines():
|
| 35 |
-
if line:
|
| 36 |
-
yield line
|
| 37 |
-
|
| 38 |
-
|
| 39 |
-
async def _chat_loop(user: str, server: str) -> None:
|
| 40 |
-
init(autoreset=True)
|
| 41 |
-
sessions = await _get_sessions(user, server)
|
| 42 |
-
session = "default"
|
| 43 |
-
if sessions:
|
| 44 |
-
typer.echo("Existing sessions:")
|
| 45 |
-
for idx, name in enumerate(sessions, 1):
|
| 46 |
-
typer.echo(f" {idx}. {name}")
|
| 47 |
-
choice = typer.prompt(
|
| 48 |
-
"Select session number or enter new name", default=str(len(sessions))
|
| 49 |
-
)
|
| 50 |
-
if choice.isdigit() and 1 <= int(choice) <= len(sessions):
|
| 51 |
-
session = sessions[int(choice) - 1]
|
| 52 |
-
else:
|
| 53 |
-
session = choice.strip() or session
|
| 54 |
-
else:
|
| 55 |
-
session = typer.prompt("Session name", default=session)
|
| 56 |
-
|
| 57 |
-
typer.echo(
|
| 58 |
-
f"Chatting as {Fore.GREEN}{user}{Style.RESET_ALL} in session '{session}'"
|
| 59 |
-
)
|
| 60 |
-
|
| 61 |
-
while True:
|
| 62 |
-
try:
|
| 63 |
-
msg = typer.prompt(f"{Fore.CYAN}You{Style.RESET_ALL}")
|
| 64 |
-
except EOFError:
|
| 65 |
-
break
|
| 66 |
-
if msg.strip().lower() in {"exit", "quit"}:
|
| 67 |
-
break
|
| 68 |
-
async for part in _stream_chat(user, session, msg, server):
|
| 69 |
-
typer.echo(f"{Fore.YELLOW}{part}{Style.RESET_ALL}")
|
| 70 |
-
|
| 71 |
-
|
| 72 |
-
@app.callback(invoke_without_command=True)
|
| 73 |
-
def main(
|
| 74 |
-
user: str = typer.Option("default", "--user", "-u"),
|
| 75 |
-
server: str = typer.Option(API_URL, "--server", "-s"),
|
| 76 |
-
) -> None:
|
| 77 |
-
"""Start an interactive chat session."""
|
| 78 |
-
|
| 79 |
-
asyncio.run(_chat_loop(user, server))
|
| 80 |
-
|
| 81 |
-
|
| 82 |
-
if __name__ == "__main__": # pragma: no cover - manual execution
|
| 83 |
-
app()
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|