Skip to content

Commit 97ac9c3

Browse files
committed
wip
1 parent a404d82 commit 97ac9c3

File tree

7 files changed

+275
-8
lines changed

7 files changed

+275
-8
lines changed

core/cli/explorer.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@ import (
88

99
type ExplorerCMD struct {
1010
Address string `env:"LOCALAI_ADDRESS,ADDRESS" default:":8080" help:"Bind address for the API server" group:"api"`
11-
PoolDatabase string `env:"LOCALAI_POOL_DATABASE,POOL_DATABASE" default:"" help:"Path to the pool database" group:"api"`
11+
PoolDatabase string `env:"LOCALAI_POOL_DATABASE,POOL_DATABASE" default:"explorer.json" help:"Path to the pool database" group:"api"`
1212
}
1313

1414
func (e *ExplorerCMD) Run(ctx *cliContext.Context) error {

core/explorer/database.go

Lines changed: 4 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -45,15 +45,15 @@ func (db *Database) Set(token string, t TokenData) error {
4545
db.data[token] = t
4646
db.Unlock()
4747

48-
return db.save()
48+
return db.Save()
4949
}
5050

5151
// Delete removes a Token from the Database by its token.
5252
func (db *Database) Delete(token string) error {
5353
db.Lock()
5454
delete(db.data, token)
5555
db.Unlock()
56-
return db.save()
56+
return db.Save()
5757
}
5858

5959
func (db *Database) TokenList() []string {
@@ -78,7 +78,6 @@ func (db *Database) load() error {
7878
defer db.Unlock()
7979

8080
if _, err := os.Stat(db.path); os.IsNotExist(err) {
81-
8281
return nil
8382
}
8483

@@ -91,8 +90,8 @@ func (db *Database) load() error {
9190
return json.Unmarshal(f, &db.data)
9291
}
9392

94-
// save writes the Database to disk.
95-
func (db *Database) save() error {
93+
// Save writes the Database to disk.
94+
func (db *Database) Save() error {
9695
db.RLock()
9796
defer db.RUnlock()
9897

core/explorer/discovery.go

Lines changed: 111 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,111 @@
1+
package explorer
2+
3+
import (
4+
"context"
5+
"fmt"
6+
"time"
7+
8+
"github.com/mudler/LocalAI/core/p2p"
9+
"github.com/mudler/edgevpn/pkg/blockchain"
10+
)
11+
12+
type DiscoveryServer struct {
13+
database *Database
14+
networkState *NetworkState
15+
}
16+
17+
type NetworkState struct {
18+
Nodes map[string]map[string]p2p.NodeData
19+
}
20+
21+
func NewDiscoveryServer(db *Database) *DiscoveryServer {
22+
return &DiscoveryServer{
23+
database: db,
24+
networkState: &NetworkState{
25+
Nodes: map[string]map[string]p2p.NodeData{},
26+
},
27+
}
28+
}
29+
30+
func (s *DiscoveryServer) runBackground() {
31+
for _, token := range s.database.TokenList() {
32+
33+
c, cancel := context.WithTimeout(context.Background(), 50*time.Second)
34+
defer cancel()
35+
36+
// Connect to the network
37+
// Get the number of nodes
38+
// save it in the current state (mutex)
39+
// do not do in parallel
40+
n, err := p2p.NewNode(token)
41+
if err != nil {
42+
fmt.Println(err)
43+
continue
44+
}
45+
err = n.Start(c)
46+
if err != nil {
47+
fmt.Println(err)
48+
continue
49+
}
50+
51+
ledger, err := n.Ledger()
52+
if err != nil {
53+
fmt.Println(err)
54+
continue
55+
}
56+
57+
ledgerKeys := make(chan string)
58+
go s.getLedgerKeys(c, ledger, ledgerKeys)
59+
60+
ledgerK := []string{}
61+
62+
for key := range ledgerKeys {
63+
ledgerK = append(ledgerK, key)
64+
}
65+
fmt.Println("Token network", token)
66+
fmt.Println("Found the following ledger keys in the network", ledgerK)
67+
// get new services, allocate and return to the channel
68+
69+
// TODO:
70+
// a function ensureServices that:
71+
// - starts a service if not started, if the worker is Online
72+
// - checks that workers are Online, if not cancel the context of allocateLocalService
73+
// - discoveryTunnels should return all the nodes and addresses associated with it
74+
// - the caller should take now care of the fact that we are always returning fresh informations
75+
}
76+
}
77+
78+
func (s *DiscoveryServer) getLedgerKeys(c context.Context, ledger *blockchain.Ledger, ledgerKeys chan string) {
79+
keys := map[string]struct{}{}
80+
81+
for {
82+
select {
83+
case <-c.Done():
84+
return
85+
default:
86+
time.Sleep(5 * time.Second)
87+
88+
data := ledger.LastBlock().Storage
89+
for k, _ := range data {
90+
if _, ok := keys[k]; !ok {
91+
keys[k] = struct{}{}
92+
ledgerKeys <- k
93+
}
94+
}
95+
}
96+
}
97+
98+
}
99+
100+
// Start the discovery server. This is meant to be run in to a goroutine.
101+
func (s *DiscoveryServer) Start(ctx context.Context) error {
102+
for {
103+
select {
104+
case <-ctx.Done():
105+
return fmt.Errorf("context cancelled")
106+
default:
107+
// Collect data
108+
s.runBackground()
109+
}
110+
}
111+
}

core/http/endpoints/explorer/dashboard.go

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ package explorer
22

33
import (
44
"github.com/gofiber/fiber/v2"
5+
"github.com/mudler/LocalAI/core/explorer"
56
"github.com/mudler/LocalAI/internal"
67
)
78

@@ -22,3 +23,37 @@ func Dashboard() func(*fiber.Ctx) error {
2223
}
2324
}
2425
}
26+
27+
type AddNetworkRequest struct {
28+
Token string `json:"token"`
29+
Name string `json:"name"`
30+
Description string `json:"description"`
31+
}
32+
33+
func AddNetwork(db *explorer.Database) func(*fiber.Ctx) error {
34+
return func(c *fiber.Ctx) error {
35+
request := new(AddNetworkRequest)
36+
if err := c.BodyParser(request); err != nil {
37+
return c.Status(fiber.StatusBadRequest).JSON(fiber.Map{"error": "Cannot parse JSON"})
38+
}
39+
40+
if request.Token == "" {
41+
return c.Status(fiber.StatusBadRequest).JSON(fiber.Map{"error": "Token is required"})
42+
}
43+
44+
if request.Name == "" {
45+
return c.Status(fiber.StatusBadRequest).JSON(fiber.Map{"error": "Name is required"})
46+
}
47+
48+
if request.Description == "" {
49+
return c.Status(fiber.StatusBadRequest).JSON(fiber.Map{"error": "Description is required"})
50+
}
51+
52+
err := db.Set(request.Token, explorer.TokenData{Name: request.Name, Description: request.Description})
53+
if err != nil {
54+
return c.Status(fiber.StatusInternalServerError).JSON(fiber.Map{"error": "Cannot add token"})
55+
}
56+
57+
return c.Status(fiber.StatusOK).JSON(fiber.Map{"message": "Token added"})
58+
}
59+
}

core/http/explorer.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,7 @@ func Explorer(db *explorer.Database) *fiber.App {
1818

1919
app := fiber.New(fiberCfg)
2020

21-
routes.RegisterExplorerRoutes(app)
21+
routes.RegisterExplorerRoutes(app, db)
2222

2323
return app
2424
}

core/http/routes/explorer.go

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,9 +2,11 @@ package routes
22

33
import (
44
"github.com/gofiber/fiber/v2"
5+
coreExplorer "github.com/mudler/LocalAI/core/explorer"
56
"github.com/mudler/LocalAI/core/http/endpoints/explorer"
67
)
78

8-
func RegisterExplorerRoutes(app *fiber.App) {
9+
func RegisterExplorerRoutes(app *fiber.App, db *coreExplorer.Database) {
910
app.Get("/", explorer.Dashboard())
11+
app.Post("/network/add", explorer.AddNetwork(db))
1012
}

core/http/views/explorer.html

Lines changed: 120 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,120 @@
1+
<!DOCTYPE html>
2+
<html lang="en">
3+
{{template "views/partials/head" .}}
4+
5+
<body class="bg-gray-900 text-gray-200">
6+
<div class="flex flex-col min-h-screen">
7+
8+
{{template "views/partials/navbar" .}}
9+
10+
<div class="container mx-auto px-4 flex-grow">
11+
<div class="header text-center py-12">
12+
<h1 class="text-5xl font-bold text-gray-100">Welcome to <i>your</i> LocalAI instance!</h1>
13+
<p class="mt-4 text-lg">The FOSS alternative to OpenAI, Claude, ...</p>
14+
<a href="https://localai.io" target="_blank" class="mt-4 inline-block bg-blue-500 text-white py-2 px-4 rounded-lg shadow transition duration-300 ease-in-out hover:bg-blue-700 hover:shadow-lg">
15+
<i class="fas fa-book-reader pr-2"></i>Documentation
16+
</a>
17+
</div>
18+
19+
<div class="models mt-4">
20+
{{template "views/partials/inprogress" .}}
21+
{{ if eq (len .ModelsConfig) 0 }}
22+
<h2 class="text-center text-3xl font-semibold text-gray-100"> <i class="text-yellow-200 ml-2 fa-solid fa-triangle-exclamation animate-pulse"></i> Ouch! seems you don't have any models installed from the LocalAI gallery!</h2>
23+
<p class="text-center mt-4 text-xl">..install something from the <a class="text-gray-400 hover:text-white ml-1 px-3 py-2 rounded" href="/browse">🖼️ Gallery</a> or check the <a href="https://localai.io/basics/getting_started/" class="text-gray-400 hover:text-white ml-1 px-3 py-2 rounded"> <i class="fa-solid fa-book"></i> Getting started documentation </a></p>
24+
25+
{{ if ne (len .Models) 0 }}
26+
<hr class="my-4">
27+
<h3 class="text-center text-xl font-semibold text-gray-100">
28+
However, It seems you have installed some models installed without a configuration file:
29+
</h3>
30+
{{ range .Models }}
31+
<div class="bg-gray-800 border-b border-gray-700 p-4 mt-4">
32+
<h4 class="text-md font-bold text-gray-200">{{.}}</h4>
33+
</div>
34+
{{end}}
35+
{{end}}
36+
{{ else }}
37+
{{ $modelsN := len .ModelsConfig}}
38+
{{ $modelsN = add $modelsN (len .Models)}}
39+
<h2 class="text-center text-3xl font-semibold text-gray-100">{{$modelsN}} Installed model(s)</h2>
40+
<table class="table-auto mt-4 w-full text-left text-gray-200">
41+
<thead class="text-xs text-gray-400 uppercase bg-gray-700">
42+
<tr>
43+
<th class="px-4 py-2"></th>
44+
<th class="px-4 py-2">Model Name</th>
45+
<th class="px-4 py-2">Backend</th>
46+
<th class="px-4 py-2 float-right">Actions</th>
47+
</tr>
48+
</thead>
49+
<tbody>
50+
{{$galleryConfig:=.GalleryConfig}}
51+
{{$noicon:="https://upload.wikimedia.org/wikipedia/commons/6/65/No-Image-Placeholder.svg"}}
52+
{{ range .ModelsConfig }}
53+
{{ $cfg:= index $galleryConfig .Name}}
54+
<tr class="bg-gray-800 border-b border-gray-700">
55+
<td class="px-4 py-3">
56+
{{ with $cfg }}
57+
<img {{ if $cfg.Icon }}
58+
src="{{$cfg.Icon}}"
59+
{{ else }}
60+
src="{{$noicon}}"
61+
{{ end }}
62+
class="rounded-t-lg max-h-24 max-w-24 object-cover mt-3"
63+
>
64+
{{ else}}
65+
<img src="{{$noicon}}" class="rounded-t-lg max-h-24 max-w-24 object-cover mt-3">
66+
{{ end }}
67+
</td>
68+
<td class="px-4 py-3 font-bold">
69+
<p class="font-bold text-white flex items-center"><i class="fas fa-brain pr-2"></i><a href="/browse?term={{.Name}}">{{.Name}}</a></p>
70+
</td>
71+
<td class="px-4 py-3 font-bold">
72+
{{ if .Backend }}
73+
<!-- Badge for Backend -->
74+
<span class="inline-block bg-blue-500 text-white py-1 px-3 rounded-full text-xs">
75+
{{.Backend}}
76+
</span>
77+
{{ else }}
78+
<span class="inline-block bg-yellow-500 text-white py-1 px-3 rounded-full text-xs">
79+
auto
80+
</span>
81+
{{ end }}
82+
</td>
83+
84+
<td class="px-4 py-3">
85+
<button
86+
class="float-right inline-block rounded bg-red-800 px-6 pb-2.5 mb-3 pt-2.5 text-xs font-medium uppercase leading-normal text-white shadow-primary-3 transition duration-150 ease-in-out hover:bg-red-accent-300 hover:shadow-red-2 focus:bg-red-accent-300 focus:shadow-primary-2 focus:outline-none focus:ring-0 active:bg-red-600 active:shadow-primary-2 dark:shadow-black/30 dark:hover:shadow-dark-strong dark:focus:shadow-dark-strong dark:active:shadow-dark-strong"
87+
data-twe-ripple-color="light" data-twe-ripple-init="" hx-confirm="Are you sure you wish to delete the model?" hx-post="/browse/delete/model/{{.Name}}" hx-swap="outerHTML"><i class="fa-solid fa-cancel pr-2"></i>Delete</button>
88+
</td>
89+
{{ end }}
90+
{{ range .Models }}
91+
<tr class="bg-gray-800 border-b border-gray-700">
92+
<td class="px-4 py-3">
93+
<img src="{{$noicon}}" class="rounded-t-lg max-h-24 max-w-24 object-cover mt-3">
94+
</td>
95+
<td class="px-4 py-3 font-bold">
96+
<p class="font-bold text-white flex items-center"><i class="fas fa-brain pr-2"></i>{{.}}</p>
97+
</td>
98+
<td class="px-4 py-3 font-bold">
99+
<span class="inline-block bg-yellow-500 text-white py-1 px-3 rounded-full text-xs">
100+
auto
101+
</span>
102+
</td>
103+
104+
<td class="px-4 py-3">
105+
<span class="float-right inline-block bg-red-800 text-white py-1 px-3 rounded-full text-xs">
106+
No Configuration
107+
</span>
108+
</td>
109+
{{end}}
110+
</tbody>
111+
</table>
112+
{{ end }}
113+
</div>
114+
</div>
115+
116+
{{template "views/partials/footer" .}}
117+
</div>
118+
119+
</body>
120+
</html>

0 commit comments

Comments
 (0)