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()
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|