Skip to content

Commit

Permalink
wip
Browse files Browse the repository at this point in the history
  • Loading branch information
mudler committed Aug 6, 2024
1 parent a404d82 commit 97ac9c3
Show file tree
Hide file tree
Showing 7 changed files with 275 additions and 8 deletions.
2 changes: 1 addition & 1 deletion core/cli/explorer.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ import (

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

func (e *ExplorerCMD) Run(ctx *cliContext.Context) error {
Expand Down
9 changes: 4 additions & 5 deletions core/explorer/database.go
Original file line number Diff line number Diff line change
Expand Up @@ -45,15 +45,15 @@ func (db *Database) Set(token string, t TokenData) error {
db.data[token] = t
db.Unlock()

return db.save()
return db.Save()
}

// Delete removes a Token from the Database by its token.
func (db *Database) Delete(token string) error {
db.Lock()
delete(db.data, token)
db.Unlock()
return db.save()
return db.Save()
}

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

if _, err := os.Stat(db.path); os.IsNotExist(err) {

return nil
}

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

// save writes the Database to disk.
func (db *Database) save() error {
// Save writes the Database to disk.
func (db *Database) Save() error {
db.RLock()
defer db.RUnlock()

Expand Down
111 changes: 111 additions & 0 deletions core/explorer/discovery.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,111 @@
package explorer

import (
"context"
"fmt"
"time"

"github.com/mudler/LocalAI/core/p2p"
"github.com/mudler/edgevpn/pkg/blockchain"
)

type DiscoveryServer struct {
database *Database
networkState *NetworkState
}

type NetworkState struct {
Nodes map[string]map[string]p2p.NodeData
}

func NewDiscoveryServer(db *Database) *DiscoveryServer {
return &DiscoveryServer{
database: db,
networkState: &NetworkState{
Nodes: map[string]map[string]p2p.NodeData{},
},
}
}

func (s *DiscoveryServer) runBackground() {
for _, token := range s.database.TokenList() {

c, cancel := context.WithTimeout(context.Background(), 50*time.Second)
defer cancel()

// Connect to the network
// Get the number of nodes
// save it in the current state (mutex)
// do not do in parallel
n, err := p2p.NewNode(token)
if err != nil {
fmt.Println(err)
continue
}
err = n.Start(c)
if err != nil {
fmt.Println(err)
continue
}

ledger, err := n.Ledger()
if err != nil {
fmt.Println(err)
continue
}

ledgerKeys := make(chan string)
go s.getLedgerKeys(c, ledger, ledgerKeys)

ledgerK := []string{}

for key := range ledgerKeys {
ledgerK = append(ledgerK, key)
}
fmt.Println("Token network", token)
fmt.Println("Found the following ledger keys in the network", ledgerK)
// get new services, allocate and return to the channel

// TODO:
// a function ensureServices that:
// - starts a service if not started, if the worker is Online
// - checks that workers are Online, if not cancel the context of allocateLocalService
// - discoveryTunnels should return all the nodes and addresses associated with it
// - the caller should take now care of the fact that we are always returning fresh informations
}
}

func (s *DiscoveryServer) getLedgerKeys(c context.Context, ledger *blockchain.Ledger, ledgerKeys chan string) {
keys := map[string]struct{}{}

for {
select {
case <-c.Done():
return
default:
time.Sleep(5 * time.Second)

data := ledger.LastBlock().Storage
for k, _ := range data {
if _, ok := keys[k]; !ok {
keys[k] = struct{}{}
ledgerKeys <- k
}
}
}
}

}

// Start the discovery server. This is meant to be run in to a goroutine.
func (s *DiscoveryServer) Start(ctx context.Context) error {
for {
select {
case <-ctx.Done():
return fmt.Errorf("context cancelled")
default:
// Collect data
s.runBackground()
}
}
}
35 changes: 35 additions & 0 deletions core/http/endpoints/explorer/dashboard.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ package explorer

import (
"github.com/gofiber/fiber/v2"
"github.com/mudler/LocalAI/core/explorer"
"github.com/mudler/LocalAI/internal"
)

Expand All @@ -22,3 +23,37 @@ func Dashboard() func(*fiber.Ctx) error {
}
}
}

type AddNetworkRequest struct {
Token string `json:"token"`
Name string `json:"name"`
Description string `json:"description"`
}

func AddNetwork(db *explorer.Database) func(*fiber.Ctx) error {
return func(c *fiber.Ctx) error {
request := new(AddNetworkRequest)
if err := c.BodyParser(request); err != nil {
return c.Status(fiber.StatusBadRequest).JSON(fiber.Map{"error": "Cannot parse JSON"})
}

if request.Token == "" {
return c.Status(fiber.StatusBadRequest).JSON(fiber.Map{"error": "Token is required"})
}

if request.Name == "" {
return c.Status(fiber.StatusBadRequest).JSON(fiber.Map{"error": "Name is required"})
}

if request.Description == "" {
return c.Status(fiber.StatusBadRequest).JSON(fiber.Map{"error": "Description is required"})
}

err := db.Set(request.Token, explorer.TokenData{Name: request.Name, Description: request.Description})
if err != nil {
return c.Status(fiber.StatusInternalServerError).JSON(fiber.Map{"error": "Cannot add token"})
}

return c.Status(fiber.StatusOK).JSON(fiber.Map{"message": "Token added"})
}
}
2 changes: 1 addition & 1 deletion core/http/explorer.go
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ func Explorer(db *explorer.Database) *fiber.App {

app := fiber.New(fiberCfg)

routes.RegisterExplorerRoutes(app)
routes.RegisterExplorerRoutes(app, db)

return app
}
4 changes: 3 additions & 1 deletion core/http/routes/explorer.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,11 @@ package routes

import (
"github.com/gofiber/fiber/v2"
coreExplorer "github.com/mudler/LocalAI/core/explorer"
"github.com/mudler/LocalAI/core/http/endpoints/explorer"
)

func RegisterExplorerRoutes(app *fiber.App) {
func RegisterExplorerRoutes(app *fiber.App, db *coreExplorer.Database) {
app.Get("/", explorer.Dashboard())
app.Post("/network/add", explorer.AddNetwork(db))
}
120 changes: 120 additions & 0 deletions core/http/views/explorer.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,120 @@
<!DOCTYPE html>
<html lang="en">
{{template "views/partials/head" .}}

<body class="bg-gray-900 text-gray-200">
<div class="flex flex-col min-h-screen">

{{template "views/partials/navbar" .}}

<div class="container mx-auto px-4 flex-grow">
<div class="header text-center py-12">
<h1 class="text-5xl font-bold text-gray-100">Welcome to <i>your</i> LocalAI instance!</h1>
<p class="mt-4 text-lg">The FOSS alternative to OpenAI, Claude, ...</p>
<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">
<i class="fas fa-book-reader pr-2"></i>Documentation
</a>
</div>

<div class="models mt-4">
{{template "views/partials/inprogress" .}}
{{ if eq (len .ModelsConfig) 0 }}
<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>
<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>

{{ if ne (len .Models) 0 }}
<hr class="my-4">
<h3 class="text-center text-xl font-semibold text-gray-100">
However, It seems you have installed some models installed without a configuration file:
</h3>
{{ range .Models }}
<div class="bg-gray-800 border-b border-gray-700 p-4 mt-4">
<h4 class="text-md font-bold text-gray-200">{{.}}</h4>
</div>
{{end}}
{{end}}
{{ else }}
{{ $modelsN := len .ModelsConfig}}
{{ $modelsN = add $modelsN (len .Models)}}
<h2 class="text-center text-3xl font-semibold text-gray-100">{{$modelsN}} Installed model(s)</h2>
<table class="table-auto mt-4 w-full text-left text-gray-200">
<thead class="text-xs text-gray-400 uppercase bg-gray-700">
<tr>
<th class="px-4 py-2"></th>
<th class="px-4 py-2">Model Name</th>
<th class="px-4 py-2">Backend</th>
<th class="px-4 py-2 float-right">Actions</th>
</tr>
</thead>
<tbody>
{{$galleryConfig:=.GalleryConfig}}
{{$noicon:="https://upload.wikimedia.org/wikipedia/commons/6/65/No-Image-Placeholder.svg"}}
{{ range .ModelsConfig }}
{{ $cfg:= index $galleryConfig .Name}}
<tr class="bg-gray-800 border-b border-gray-700">
<td class="px-4 py-3">
{{ with $cfg }}
<img {{ if $cfg.Icon }}
src="{{$cfg.Icon}}"
{{ else }}
src="{{$noicon}}"
{{ end }}
class="rounded-t-lg max-h-24 max-w-24 object-cover mt-3"
>
{{ else}}
<img src="{{$noicon}}" class="rounded-t-lg max-h-24 max-w-24 object-cover mt-3">
{{ end }}
</td>
<td class="px-4 py-3 font-bold">
<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>
</td>
<td class="px-4 py-3 font-bold">
{{ if .Backend }}
<!-- Badge for Backend -->
<span class="inline-block bg-blue-500 text-white py-1 px-3 rounded-full text-xs">
{{.Backend}}
</span>
{{ else }}
<span class="inline-block bg-yellow-500 text-white py-1 px-3 rounded-full text-xs">
auto
</span>
{{ end }}
</td>

<td class="px-4 py-3">
<button
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"
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>
</td>
{{ end }}
{{ range .Models }}
<tr class="bg-gray-800 border-b border-gray-700">
<td class="px-4 py-3">
<img src="{{$noicon}}" class="rounded-t-lg max-h-24 max-w-24 object-cover mt-3">
</td>
<td class="px-4 py-3 font-bold">
<p class="font-bold text-white flex items-center"><i class="fas fa-brain pr-2"></i>{{.}}</p>
</td>
<td class="px-4 py-3 font-bold">
<span class="inline-block bg-yellow-500 text-white py-1 px-3 rounded-full text-xs">
auto
</span>
</td>

<td class="px-4 py-3">
<span class="float-right inline-block bg-red-800 text-white py-1 px-3 rounded-full text-xs">
No Configuration
</span>
</td>
{{end}}
</tbody>
</table>
{{ end }}
</div>
</div>

{{template "views/partials/footer" .}}
</div>

</body>
</html>

0 comments on commit 97ac9c3

Please sign in to comment.