starsnatched commited on
Commit
4beb924
·
1 Parent(s): d8668d3

refactor: remove deprecated Go CLI and macOS GUI components

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