refactoring

This commit is contained in:
Ettore Di Giacinto
2025-02-26 22:37:48 +01:00
parent 0139b79835
commit 0a18d8409e
22 changed files with 90 additions and 41 deletions

View File

@@ -1,84 +0,0 @@
package main
import (
"context"
"encoding/json"
"github.com/mudler/local-agent-framework/core/action"
"github.com/mudler/local-agent-framework/core/state"
"github.com/mudler/local-agent-framework/pkg/xlog"
"github.com/mudler/local-agent-framework/core/agent"
"github.com/mudler/local-agent-framework/services/actions"
)
const (
// Actions
ActionSearch = "search"
ActionCustom = "custom"
ActionGithubIssueLabeler = "github-issue-labeler"
ActionGithubIssueOpener = "github-issue-opener"
ActionGithubIssueCloser = "github-issue-closer"
ActionGithubIssueSearcher = "github-issue-searcher"
ActionScraper = "scraper"
ActionWikipedia = "wikipedia"
ActionBrowse = "browse"
ActionSendMail = "send_mail"
)
var AvailableActions = []string{
ActionSearch,
ActionCustom,
ActionGithubIssueLabeler,
ActionGithubIssueOpener,
ActionGithubIssueCloser,
ActionGithubIssueSearcher,
ActionScraper,
ActionBrowse,
ActionWikipedia,
ActionSendMail,
}
func Actions(a *state.AgentConfig) func(ctx context.Context) []agent.Action {
return func(ctx context.Context) []agent.Action {
allActions := []agent.Action{}
for _, a := range a.Actions {
var config map[string]string
if err := json.Unmarshal([]byte(a.Config), &config); err != nil {
xlog.Error("Error unmarshalling action config", "error", err)
continue
}
switch a.Name {
case ActionCustom:
customAction, err := action.NewCustom(config, "")
if err != nil {
xlog.Error("Error creating custom action", "error", err)
continue
}
allActions = append(allActions, customAction)
case ActionSearch:
allActions = append(allActions, actions.NewSearch(config))
case ActionGithubIssueLabeler:
allActions = append(allActions, actions.NewGithubIssueLabeler(ctx, config))
case ActionGithubIssueOpener:
allActions = append(allActions, actions.NewGithubIssueOpener(ctx, config))
case ActionGithubIssueCloser:
allActions = append(allActions, actions.NewGithubIssueCloser(ctx, config))
case ActionGithubIssueSearcher:
allActions = append(allActions, actions.NewGithubIssueSearch(ctx, config))
case ActionScraper:
allActions = append(allActions, actions.NewScraper(config))
case ActionWikipedia:
allActions = append(allActions, actions.NewWikipedia(config))
case ActionBrowse:
allActions = append(allActions, actions.NewBrowse(config))
case ActionSendMail:
allActions = append(allActions, actions.NewSendMail(config))
}
}
return allActions
}
}

View File

@@ -1,370 +0,0 @@
package main
import (
"bytes"
"encoding/json"
"fmt"
"net/http"
"os"
"strings"
"github.com/mudler/local-agent-framework/pkg/xlog"
"github.com/mudler/local-agent-framework/core/agent"
"github.com/mudler/local-agent-framework/core/sse"
"github.com/mudler/local-agent-framework/core/state"
"github.com/donseba/go-htmx"
"github.com/dslipak/pdf"
fiber "github.com/gofiber/fiber/v2"
)
type (
App struct {
htmx *htmx.HTMX
pool *state.AgentPool
}
)
func (a *App) KnowledgeBaseReset(pool *state.AgentPool) func(c *fiber.Ctx) error {
return func(c *fiber.Ctx) error {
db := pool.GetAgentMemory(c.Params("name"))
db.Reset()
return c.Redirect("/knowledgebase/" + c.Params("name"))
}
}
func (a *App) KnowledgeBaseExport(pool *state.AgentPool) func(c *fiber.Ctx) error {
return func(c *fiber.Ctx) error {
db := pool.GetAgentMemory(c.Params("name"))
knowledgeBase := db.Data()
c.Set("Content-Disposition", fmt.Sprintf("attachment; filename=%s.knowledgebase.json", c.Params("name")))
return c.JSON(knowledgeBase)
}
}
func (a *App) KnowledgeBaseImport(pool *state.AgentPool) func(c *fiber.Ctx) error {
return func(c *fiber.Ctx) error {
file, err := c.FormFile("file")
if err != nil {
// Handle error
return err
}
os.MkdirAll("./uploads", os.ModePerm)
destination := fmt.Sprintf("./uploads/%s", file.Filename)
if err := c.SaveFile(file, destination); err != nil {
// Handle error
return err
}
data, err := os.ReadFile(destination)
if err != nil {
return err
}
knowledge := []string{}
if err := json.Unmarshal(data, &knowledge); err != nil {
return err
}
if len(knowledge) > 0 {
xlog.Info("Importing agent KB")
db := pool.GetAgentMemory(c.Params("name"))
db.Reset()
for _, k := range knowledge {
db.Store(k)
}
} else {
return fmt.Errorf("Empty knowledge base")
}
return c.Redirect("/agents")
}
}
func (a *App) KnowledgeBaseFile(pool *state.AgentPool) func(c *fiber.Ctx) error {
return func(c *fiber.Ctx) error {
agent := pool.GetAgent(c.Params("name"))
db := agent.Memory()
// https://golang.withcodeexample.com/blog/file-upload-handling-golang-fiber-guide/
file, err := c.FormFile("file")
if err != nil {
// Handle error
return err
}
payload := struct {
ChunkSize int `form:"chunk_size"`
}{}
if err := c.BodyParser(&payload); err != nil {
return err
}
os.MkdirAll("./uploads", os.ModePerm)
destination := fmt.Sprintf("./uploads/%s", file.Filename)
if err := c.SaveFile(file, destination); err != nil {
// Handle error
return err
}
xlog.Info("File uploaded to: " + destination)
fmt.Printf("Payload: %+v\n", payload)
content, err := readPdf(destination) // Read local pdf file
if err != nil {
panic(err)
}
xlog.Info("Content is", content)
chunkSize := defaultChunkSize
if payload.ChunkSize > 0 {
chunkSize = payload.ChunkSize
}
go state.StringsToKB(db, chunkSize, content)
_, err = c.WriteString(chatDiv("File uploaded", "gray"))
return err
}
}
func (a *App) KnowledgeBase(pool *state.AgentPool) func(c *fiber.Ctx) error {
return func(c *fiber.Ctx) error {
agent := pool.GetAgent(c.Params("name"))
db := agent.Memory()
payload := struct {
URL string `form:"url"`
ChunkSize int `form:"chunk_size"`
}{}
if err := c.BodyParser(&payload); err != nil {
return err
}
website := payload.URL
if website == "" {
return fmt.Errorf("please enter a URL")
}
chunkSize := defaultChunkSize
if payload.ChunkSize > 0 {
chunkSize = payload.ChunkSize
}
go state.WebsiteToKB(website, chunkSize, db)
return c.Redirect("/knowledgebase/" + c.Params("name"))
}
}
func (a *App) Notify(pool *state.AgentPool) func(c *fiber.Ctx) error {
return func(c *fiber.Ctx) error {
payload := struct {
Message string `form:"message"`
}{}
if err := c.BodyParser(&payload); err != nil {
return err
}
query := payload.Message
if query == "" {
_, _ = c.Write([]byte("Please enter a message."))
return nil
}
a := pool.GetAgent(c.Params("name"))
a.Ask(
agent.WithText(query),
)
_, _ = c.Write([]byte("Message sent"))
return nil
}
}
func (a *App) Delete(pool *state.AgentPool) func(c *fiber.Ctx) error {
return func(c *fiber.Ctx) error {
if err := pool.Remove(c.Params("name")); err != nil {
xlog.Info("Error removing agent", err)
return c.Status(http.StatusInternalServerError).SendString(err.Error())
}
return c.Redirect("/agents")
}
}
func (a *App) Pause(pool *state.AgentPool) func(c *fiber.Ctx) error {
return func(c *fiber.Ctx) error {
xlog.Info("Pausing agent", c.Params("name"))
agent := pool.GetAgent(c.Params("name"))
if agent != nil {
agent.Pause()
}
return c.Redirect("/agents")
}
}
func (a *App) Start(pool *state.AgentPool) func(c *fiber.Ctx) error {
return func(c *fiber.Ctx) error {
agent := pool.GetAgent(c.Params("name"))
if agent != nil {
agent.Resume()
}
return c.Redirect("/agents")
}
}
func (a *App) Create(pool *state.AgentPool) func(c *fiber.Ctx) error {
return func(c *fiber.Ctx) error {
config := state.AgentConfig{}
if err := c.BodyParser(&config); err != nil {
return err
}
fmt.Printf("Agent configuration: %+v\n", config)
if config.Name == "" {
c.Status(http.StatusBadRequest).SendString("Name is required")
return nil
}
if err := pool.CreateAgent(config.Name, &config); err != nil {
c.Status(http.StatusInternalServerError).SendString(err.Error())
return nil
}
return c.Redirect("/agents")
}
}
func (a *App) ExportAgent(pool *state.AgentPool) func(c *fiber.Ctx) error {
return func(c *fiber.Ctx) error {
agent := pool.GetConfig(c.Params("name"))
if agent == nil {
return c.Status(http.StatusNotFound).SendString("Agent not found")
}
c.Set("Content-Disposition", fmt.Sprintf("attachment; filename=%s.json", agent.Name))
return c.JSON(agent)
}
}
func (a *App) ImportAgent(pool *state.AgentPool) func(c *fiber.Ctx) error {
return func(c *fiber.Ctx) error {
file, err := c.FormFile("file")
if err != nil {
// Handle error
return err
}
os.MkdirAll("./uploads", os.ModePerm)
destination := fmt.Sprintf("./uploads/%s", file.Filename)
if err := c.SaveFile(file, destination); err != nil {
// Handle error
return err
}
data, err := os.ReadFile(destination)
if err != nil {
return err
}
config := state.AgentConfig{}
if err := json.Unmarshal(data, &config); err != nil {
return err
}
xlog.Info("Importing agent", config.Name)
if config.Name == "" {
c.Status(http.StatusBadRequest).SendString("Name is required")
return nil
}
if err := pool.CreateAgent(config.Name, &config); err != nil {
c.Status(http.StatusInternalServerError).SendString(err.Error())
return nil
}
return c.Redirect("/agents")
}
}
func (a *App) Chat(pool *state.AgentPool) func(c *fiber.Ctx) error {
return func(c *fiber.Ctx) error {
payload := struct {
Message string `json:"message"`
}{}
if err := c.BodyParser(&payload); err != nil {
return err
}
agentName := c.Params("name")
manager := pool.GetManager(agentName)
query := strings.Clone(payload.Message)
if query == "" {
_, _ = c.Write([]byte("Please enter a message."))
return nil
}
manager.Send(
sse.NewMessage(
chatDiv(query, "gray"),
).WithEvent("messages"))
go func() {
a := pool.GetAgent(agentName)
if a == nil {
xlog.Info("Agent not found in pool", c.Params("name"))
return
}
res := a.Ask(
agent.WithText(query),
)
if res.Error != nil {
xlog.Error("Error asking agent", "agent", agentName, "error", res.Error)
} else {
xlog.Info("we got a response from the agent", "agent", agentName, "response", res.Response)
}
manager.Send(
sse.NewMessage(
chatDiv(res.Response, "blue"),
).WithEvent("messages"))
manager.Send(
sse.NewMessage(
disabledElement("inputMessage", false), // show again the input
).WithEvent("message_status"))
//result := `<i>done</i>`
// _, _ = w.Write([]byte(result))
}()
manager.Send(
sse.NewMessage(
loader() + disabledElement("inputMessage", true),
).WithEvent("message_status"))
return nil
}
}
func readPdf(path string) (string, error) {
r, err := pdf.Open(path)
if err != nil {
return "", err
}
var buf bytes.Buffer
b, err := r.GetPlainText()
if err != nil {
return "", err
}
buf.ReadFrom(b)
return buf.String(), nil
}

View File

@@ -1,58 +0,0 @@
package main
import (
"encoding/json"
"github.com/mudler/local-agent-framework/pkg/xlog"
"github.com/mudler/local-agent-framework/services/connectors"
"github.com/mudler/local-agent-framework/core/state"
)
const (
// Connectors
ConnectorTelegram = "telegram"
ConnectorSlack = "slack"
ConnectorDiscord = "discord"
ConnectorGithubIssues = "github-issues"
ConnectorGithubPRs = "github-prs"
)
var AvailableConnectors = []string{
ConnectorTelegram,
ConnectorSlack,
ConnectorDiscord,
ConnectorGithubIssues,
ConnectorGithubPRs,
}
func Connectors(a *state.AgentConfig) []state.Connector {
conns := []state.Connector{}
for _, c := range a.Connector {
var config map[string]string
if err := json.Unmarshal([]byte(c.Config), &config); err != nil {
xlog.Info("Error unmarshalling connector config", err)
continue
}
switch c.Type {
case ConnectorTelegram:
cc, err := connectors.NewTelegramConnector(config)
if err != nil {
xlog.Info("Error creating telegram connector", err)
continue
}
conns = append(conns, cc)
case ConnectorSlack:
conns = append(conns, connectors.NewSlack(config))
case ConnectorDiscord:
conns = append(conns, connectors.NewDiscord(config))
case ConnectorGithubIssues:
conns = append(conns, connectors.NewGithubIssueWatcher(config))
case ConnectorGithubPRs:
conns = append(conns, connectors.NewGithubPRWatcher(config))
}
}
return conns
}

View File

@@ -1,39 +0,0 @@
package main
import (
"fmt"
"strings"
elem "github.com/chasefleming/elem-go"
"github.com/chasefleming/elem-go/attrs"
)
func chatDiv(content string, color string) string {
div := elem.Div(attrs.Props{
// attrs.ID: "container",
attrs.Class: fmt.Sprintf("p-2 my-2 rounded bg-%s-600", color),
},
elem.Raw(htmlIfy(content)),
)
return div.Render()
}
func loader() string {
return elem.Div(attrs.Props{
attrs.Class: "loader",
}).Render()
}
func disabledElement(id string, disabled bool) string {
return elem.Script(nil,
elem.If(disabled,
elem.Raw(`document.getElementById('`+id+`').disabled = true`),
elem.Raw(`document.getElementById('`+id+`').disabled = false`),
)).Render()
}
func htmlIfy(s string) string {
s = strings.TrimSpace(s)
s = strings.ReplaceAll(s, "\n", "<br>")
return s
}

View File

@@ -1,94 +0,0 @@
package main
import (
"embed"
"log"
"net/http"
"os"
"github.com/donseba/go-htmx"
fiber "github.com/gofiber/fiber/v2"
"github.com/gofiber/template/html/v2"
"github.com/mudler/local-agent-framework/core/agent"
"github.com/mudler/local-agent-framework/core/state"
"github.com/mudler/local-agent-framework/pkg/llm"
rag "github.com/mudler/local-agent-framework/pkg/vectorstore"
)
var testModel = os.Getenv("TEST_MODEL")
var apiURL = os.Getenv("API_URL")
var apiKey = os.Getenv("API_KEY")
var vectorStore = os.Getenv("VECTOR_STORE")
var timeout = os.Getenv("TIMEOUT")
var embeddingModel = os.Getenv("EMBEDDING_MODEL")
const defaultChunkSize = 4098
func init() {
if testModel == "" {
testModel = "hermes-2-pro-mistral"
}
if apiURL == "" {
apiURL = "http://192.168.68.113:8080"
}
if timeout == "" {
timeout = "5m"
}
}
//go:embed views/*
var viewsfs embed.FS
//go:embed public/*
var embeddedFiles embed.FS
func main() {
// current dir
cwd, err := os.Getwd()
if err != nil {
panic(err)
}
stateDir := cwd + "/pool"
os.MkdirAll(stateDir, 0755)
var ragDB agent.RAGDB
lai := llm.NewClient(apiKey, apiURL+"/v1", timeout)
switch vectorStore {
case "localai":
laiStore := rag.NewStoreClient(apiURL, apiKey)
ragDB = rag.NewLocalAIRAGDB(laiStore, lai)
default:
var err error
ragDB, err = rag.NewChromemDB("local-agent-framework", stateDir, lai, embeddingModel)
if err != nil {
panic(err)
}
}
pool, err := state.NewAgentPool(testModel, apiURL, stateDir, ragDB, Actions, Connectors, timeout)
if err != nil {
panic(err)
}
app := &App{
htmx: htmx.New(),
pool: pool,
}
if err := pool.StartAll(); err != nil {
panic(err)
}
engine := html.NewFileSystem(http.FS(viewsfs), ".html")
// Initialize a new Fiber app
// Pass the engine to the Views
webapp := fiber.New(fiber.Config{
Views: engine,
})
RegisterRoutes(webapp, pool, app)
log.Fatal(webapp.Listen(":3000"))
}

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.1 MiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 118 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 341 KiB

View File

@@ -1,131 +0,0 @@
package main
import (
"math/rand"
"net/http"
fiber "github.com/gofiber/fiber/v2"
"github.com/gofiber/fiber/v2/middleware/filesystem"
"github.com/mudler/local-agent-framework/core/agent"
"github.com/mudler/local-agent-framework/core/sse"
"github.com/mudler/local-agent-framework/core/state"
)
func RegisterRoutes(webapp *fiber.App, pool *state.AgentPool, app *App) {
webapp.Use("/public", filesystem.New(filesystem.Config{
Root: http.FS(embeddedFiles),
PathPrefix: "public",
Browse: true,
}))
webapp.Get("/", func(c *fiber.Ctx) error {
return c.Render("views/index", fiber.Map{
"Agents": pool.List(),
})
})
webapp.Get("/agents", func(c *fiber.Ctx) error {
statuses := map[string]bool{}
for _, a := range pool.List() {
statuses[a] = !pool.GetAgent(a).Paused()
}
return c.Render("views/agents", fiber.Map{
"Agents": pool.List(),
"Status": statuses,
})
})
webapp.Get("/create", func(c *fiber.Ctx) error {
return c.Render("views/create", fiber.Map{
"Actions": AvailableActions,
"Connectors": AvailableConnectors,
})
})
webapp.Get("/knowledgebase/:name", func(c *fiber.Ctx) error {
db := pool.GetAgentMemory(c.Params("name"))
return c.Render(
"views/knowledgebase",
fiber.Map{
"KnowledgebaseItemsCount": db.Count(),
"Name": c.Params("name"),
},
)
})
// Define a route for the GET method on the root path '/'
webapp.Get("/sse/:name", func(c *fiber.Ctx) error {
m := pool.GetManager(c.Params("name"))
if m == nil {
return c.SendStatus(404)
}
m.Handle(c, sse.NewClient(randStringRunes(10)))
return nil
})
webapp.Get("/status/:name", func(c *fiber.Ctx) error {
history := pool.GetStatusHistory(c.Params("name"))
if history == nil {
history = &state.Status{ActionResults: []agent.ActionState{}}
}
// reverse history
return c.Render("views/status", fiber.Map{
"Name": c.Params("name"),
"History": Reverse(history.Results()),
})
})
webapp.Get("/notify/:name", app.Notify(pool))
webapp.Post("/chat/:name", app.Chat(pool))
webapp.Post("/create", app.Create(pool))
webapp.Get("/delete/:name", app.Delete(pool))
webapp.Put("/pause/:name", app.Pause(pool))
webapp.Put("/start/:name", app.Start(pool))
webapp.Post("/knowledgebase/:name", app.KnowledgeBase(pool))
webapp.Post("/knowledgebase/:name/upload", app.KnowledgeBaseFile(pool))
webapp.Delete("/knowledgebase/:name/reset", app.KnowledgeBaseReset(pool))
webapp.Post("/knowledgebase/:name/import", app.KnowledgeBaseImport(pool))
webapp.Get("/knowledgebase/:name/export", app.KnowledgeBaseExport(pool))
webapp.Get("/talk/:name", func(c *fiber.Ctx) error {
return c.Render("views/chat", fiber.Map{
// "Character": agent.Character,
"Name": c.Params("name"),
})
})
webapp.Get("/settings/:name", func(c *fiber.Ctx) error {
return c.Render("views/settings", fiber.Map{
// "Character": agent.Character,
"Name": c.Params("name"),
})
})
webapp.Post("/settings/import", app.ImportAgent(pool))
webapp.Get("/settings/export/:name", app.ExportAgent(pool))
}
var letterRunes = []rune("abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ")
func randStringRunes(n int) string {
b := make([]rune, n)
for i := range b {
b[i] = letterRunes[rand.Intn(len(letterRunes))]
}
return string(b)
}
func Reverse[T any](original []T) (reversed []T) {
reversed = make([]T, len(original))
copy(reversed, original)
for i := len(reversed)/2 - 1; i >= 0; i-- {
tmp := len(reversed) - 1 - i
reversed[i], reversed[tmp] = reversed[tmp], reversed[i]
}
return
}

View File

@@ -1,111 +0,0 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Agent List</title>
{{template "views/partials/header"}}
<style>
.button-container {
display: flex;
justify-content: flex-end;
margin-bottom: 8px; /* Adjusts the spacing to your liking */
}
</style>
</head>
<body class="bg-gray-900 p-4 text-white font-sans">
{{template "views/partials/menu"}}
<div class="max-w-6xl mx-auto">
<header class="text-center mb-8">
<h1 class="text-3xl md:text-5xl font-bold">Smart Agent List</h1>
</header>
<div class="button-container">
<a href="/create" class="bg-green-500 hover:bg-green-700 text-white font-bold py-2 px-4 rounded transition duration-300 ease-in-out">
Add New Agent
</a>
</div>
<section class="flex flex-col">
<div class="overflow-x-auto">
<div class="py-2 align-middle inline-block min-w-full">
<div class="shadow overflow-hidden border-b border-gray-700 rounded-lg">
<table class="min-w-full divide-y divide-gray-700">
<thead class="bg-gray-700">
<tr>
<th scope="col" class="px-6 py-3 text-left text-xs font-medium text-gray-400 uppercase tracking-wider">
Name
</th>
<th scope="col" class="px-6 py-3 text-left text-xs font-medium text-gray-400 uppercase tracking-wider">
Status
</th>
<th scope="col" class="px-6 py-3 text-center">
<span class="sr-only">Talk</span>
</th>
<th scope="col" class="px-6 py-3 text-center">
<span class="sr-only">Start</span>
</th>
<th scope="col" class="px-6 py-3 text-center">
<span class="sr-only">Stop</span>
</th>
<th scope="col" class="px-6 py-3 text-center">
<span class="sr-only">Settings</span>
</th>
</tr>
</thead>
<tbody class="bg-gray-800 divide-y divide-gray-700">
<!-- Dynamic agent rows go here -->
{{ $status := .Status }}
{{ range .Agents }}
<tr hx-ext="sse">
<td class="px-6 py-4 whitespace-nowrap text-sm font-medium text-gray-300">{{.}}</td>
<td class="px-6 py-4 whitespace-nowrap text-sm text-gray-300">
Online: {{ index $status . }}
<a href="/status/{{.}}" class="text-indigo-500 hover:text-indigo-400">
<i class="fas fa-info"></i> Status
</a>
</td>
<td class="px-6 py-4 whitespace-nowrap text-center text-sm font-medium">
<a href="/talk/{{.}}" class="text-indigo-500 hover:text-indigo-400">
<i class="fas fa-comments"></i> Talk
</a>
</td>
<td class="px-6 py-4 whitespace-nowrap text-center text-sm font-medium">
<button hx-put="/start/{{.}}">
Start
</button>
</td>
<td class="px-6 py-4 whitespace-nowrap text-center text-sm font-medium">
<button hx-put="/pause/{{.}}">
Pause
</button>
</td>
<td class="px-6 py-4 whitespace-nowrap text-center text-sm font-medium">
<a href="/settings/{{.}}" class="text-indigo-500 hover:text-indigo-400">
<i class="fas fa-cog"></i> Settings
</a>
</td>
</tr>
{{ end }}
<!-- Repeat for each agent -->
</tbody>
</table>
</div>
</div>
</section>
<footer class="mt-8 text-center">
<!-- File Upload Form -->
<div class="section-box">
<form id='form' hx-encoding='multipart/form-data' hx-post='/settings/import'>
<h2>Import</h2>
<label for="file">File:</label>
<input type='file' name='file' id='file'>
<button type="submit">Upload File</button>
<progress id='progress' value='0' max='100'></progress>
</form>
</div>
</footer>
</div>
</body>
</html>

View File

@@ -1,91 +0,0 @@
<!DOCTYPE html>
<html lang="en">
<head>
<title>Smart Agent Interface</title>
{{template "views/partials/header"}}
<style>
body { overflow: hidden; }
.chat-container { height: 90vh; display: flex; flex-direction: column; }
.chat-messages { overflow-y: auto; flex-grow: 1; }
.htmx-indicator{
opacity:0;
transition: opacity 10ms ease-in;
}
.htmx-request .htmx-indicator{
opacity:1
}
/* Loader (https://cssloaders.github.io/) */
.loader {
width: 12px;
height: 12px;
border-radius: 50%;
display: block;
margin:15px auto;
position: relative;
color: #FFF;
box-sizing: border-box;
animation: animloader 2s linear infinite;
}
@keyframes animloader {
0% { box-shadow: 14px 0 0 -2px, 38px 0 0 -2px, -14px 0 0 -2px, -38px 0 0 -2px; }
25% { box-shadow: 14px 0 0 -2px, 38px 0 0 -2px, -14px 0 0 -2px, -38px 0 0 2px; }
50% { box-shadow: 14px 0 0 -2px, 38px 0 0 -2px, -14px 0 0 2px, -38px 0 0 -2px; }
75% { box-shadow: 14px 0 0 2px, 38px 0 0 -2px, -14px 0 0 -2px, -38px 0 0 -2px; }
100% { box-shadow: 14px 0 0 -2px, 38px 0 0 2px, -14px 0 0 -2px, -38px 0 0 -2px; }
}
</style>
</head>
<body class="bg-gray-900 p-4 text-white font-sans" hx-ext="sse" sse-connect="/sse/{{.Name}}">
{{template "views/partials/menu"}}
<div class="chat-container bg-gray-800 shadow-lg rounded-lg" >
<!-- Chat Header -->
<div class="border-b border-gray-700 p-4">
<h1 class="text-lg font-semibold">Talk to '{{.Name}}'</h1>
</div>
<!-- Chat Messages -->
<div class="chat-messages p-4">
<!-- Client Box -->
<div class="bg-gray-700 p-4">
<h2 class="text-sm font-semibold">Clients:</h2>
<div id="clients" class="text-sm text-gray-300">
<!-- Status updates dynamically here -->
<div sse-swap="clients"></div>
</div>
</div>
<!-- HUD Box -->
<div class="bg-gray-700 p-4">
<h2 class="text-sm font-semibold">Status:</h2>
<div id="hud" class="text-sm text-gray-300">
<!-- Status updates dynamically here -->
<div sse-swap="hud"></div>
</div>
</div>
<div sse-swap="messages" hx-swap="beforeend" id="messages" hx-on:htmx:after-settle="document.getElementById('messages').scrollIntoView(false)"></div>
</div>
<!-- Agent Status Box -->
<div class="bg-gray-700 p-4">
<h2 class="text-sm font-semibold">Agent:</h2>
<div id="agentStatus" class="text-sm text-gray-300">
<!-- Status updates dynamically here -->
<div sse-swap="status" ></div>
</div>
</div>
<!-- Message Input -->
<div class="p-4 border-t border-gray-700">
<div sse-swap="message_status"></div>
<input id="inputMessage" name="message" type="text" hx-post="/chat/{{.Name}}" hx-target="#results" hx-indicator=".htmx-indicator"
class="p-2 border rounded w-full bg-gray-600 text-white placeholder-gray-300" placeholder="Type a message..." _="on htmx:afterRequest set my value to ''">
<div class="my-2 htmx-indicator" ></div>
<div id="results" class="flex justify-center"></div>
</div>
</div>
</body>
</html>

View File

@@ -1,138 +0,0 @@
<!DOCTYPE html>
<html lang="en">
<head>
<title>Create New Agent</title>
{{template "views/partials/header"}}
<script src="https://unpkg.com/htmx.org"></script>
<script src="https://unpkg.com/htmx.org/dist/ext/sse.js"></script>
<script src="https://unpkg.com/hyperscript.org@0.9.12"></script>
</head>
<body class="bg-gray-900 p-4 text-white font-sans">
{{template "views/partials/menu"}}
<div class="max-w-2xl mx-auto my-12 bg-gray-800 p-8 rounded-lg shadow-lg">
<h1 class="text-3xl font-bold text-center mb-10 text-blue-400">Create New Agent</h1>
<form action="/create" method="POST" class="space-y-6">
<div class="mb-6">
<label for="name" class="block text-lg font-medium text-gray-400">Name</label>
<input type="text" name="name" id="name" class="mt-1 focus:ring-indigo-500 focus:border-indigo-500 block w-full shadow-sm sm:text-lg border-gray-300 rounded-md bg-gray-700 text-white" placeholder="Name">
</div>
<div id="connectorsSection">
</div>
<button type="button" id="addConnectorButton" class="w-full flex justify-center py-2 px-4 border border-transparent rounded-md shadow-sm text-sm font-medium text-white bg-blue-500 hover:bg-blue-700 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-blue-500">
Add Connector
</button>
<script>
const actions = `{{ range .Actions }}<option value="{{.}}">{{.}}</option>{{ end }}`;
const connectors = `{{ range .Connectors }}<option value="{{.}}">{{.}}</option>{{ end }}`;
document.getElementById('addConnectorButton').addEventListener('click', function() {
const connectorsSection = document.getElementById('connectorsSection');
const newConnectorIndex = connectorsSection.getElementsByClassName('connector').length;
const newConnectorHTML = `
<div class="connector mb-4">
<div class="mb-4">
<label for="connectorType${newConnectorIndex}" class="block text-lg font-medium text-gray-400">Connector Type</label>
<select name="connectors[${newConnectorIndex}].type" id="connectorType${newConnectorIndex}" class="mt-1 focus:ring-blue-500 focus:border-blue-500 block w-full shadow-sm sm:text-sm border-gray-300 rounded-md bg-gray-700 text-white">
`+connectors+` </select>
</div>
<div class="mb-4">
<label for="connectorConfig${newConnectorIndex}" class="block text-lg font-medium text-gray-400">Connector Config (JSON)</label>
<textarea id="connectorConfig${newConnectorIndex}" name="connectors[${newConnectorIndex}].config" class="mt-1 focus:ring-blue-500 focus:border-blue-500 block w-full shadow-sm sm:text-sm border-gray-300 rounded-md bg-gray-700 text-white" placeholder='{"token":"sk-mg3.."}'>{}</textarea>
</div>
</div>
`;
connectorsSection.insertAdjacentHTML('beforeend', newConnectorHTML);
});
</script>
<div class="mb-4" id="action_box">
</div>
<button id="action_button" type="button" class="w-full flex justify-center py-2 px-4 border border-transparent rounded-md shadow-sm text-sm font-medium text-white bg-blue-500 hover:bg-blue-700 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-blue-500">Add action</button>
<script>
document.getElementById('action_button').addEventListener('click', function() {
const actionsSection = document.getElementById('action_box');
const ii = actionsSection.getElementsByClassName('action').length;
const newActionHTML = `
<div class="action mb-4">
<label for="actionsName${ii}" class="block text-lg font-medium text-gray-400">Action</label>
<select name="actions[${ii}].name" id="actionsName${ii}" class="mt-1 focus:ring-blue-500 focus:border-blue-500 block w-full shadow-sm sm:text-sm border-gray-300 rounded-md bg-gray-700 text-white">
`+actions+`</select>
<div class="mb-4">
<label for="actionsConfig${ii}" class="block text-lg font-medium text-gray-400">Action Config (JSON)</label>
<textarea id="actionsConfig${ii}" name="actions[${ii}].config" class="mt-1 focus:ring-blue-500 focus:border-blue-500 block w-full shadow-sm sm:text-sm border-gray-300 rounded-md bg-gray-700 text-white" placeholder='{"results":"5"}'>{}</textarea>
</div>
</div>
`;
actionsSection.insertAdjacentHTML('beforeend', newActionHTML);
});
</script>
<div class="mb-4">
<label for="hud" class="block text-lg font-medium text-gray-400">HUD</label>
<input type="checkbox" name="hud" id="hud" class="mt-1 focus:ring-indigo-500 focus:border-indigo-500 block w-full shadow-sm sm:text-lg border-gray-300 rounded-md bg-gray-700 text-white">
</div>
<div class="mb-4">
</div>
<div class="mb-4">
<label for="enable_kb" class="block text-lg font-medium text-gray-400">Enable Knowledge Base</label>
<input type="checkbox" name="enable_kb" id="enable_kb" class="mt-1 focus:ring-indigo-500 focus:border-indigo-500 block w-full shadow-sm sm:text-lg border-gray-300 rounded-md bg-gray-700 text-white">
<label for="enable_reasoning" class="block text-lg font-medium text-gray-400">Enable Reasoning</label>
<input type="checkbox" name="enable_reasoning" id="enable_reasoning" class="mt-1 focus:ring-indigo-500 focus:border-indigo-500 block w-full shadow-sm sm:text-lg border-gray-300 rounded-md bg-gray-700 text-white">
<div class="mb-6">
<label for="kb_results" class="block text-lg font-medium text-gray-400">Knowledge base results</label>
<input type="text" name="kb_results" id="kb_results" class="mt-1 focus:ring-indigo-500 focus:border-indigo-500 block w-full shadow-sm sm:text-lg border-gray-300 rounded-md bg-gray-700 text-white" placeholder="3">
</div>
<label for="standalone_job" class="block text-lg font-medium text-gray-400">Standalone Job</label>
<input type="checkbox" name="standalone_job" id="standalone_job" class="mt-1 focus:ring-indigo-500 focus:border-indigo-500 block w-full shadow-sm sm:text-lg border-gray-300 rounded-md bg-gray-700 text-white">
<label for="initiate_conversations" class="block text-lg font-medium text-gray-400">Initiate conversations</label>
<input type="checkbox" name="random_identity" id="initiate_conversations" class="mt-1 focus:ring-indigo-500 focus:border-indigo-500 block w-full shadow-sm sm:text-lg border-gray-300 rounded-md bg-gray-700 text-white">
<label for="can_stop_itself" class="block text-lg font-medium text-gray-400">Can stop itself</label>
<input type="checkbox" name="can_stop_itself" id="can_stop_itself" class="mt-1 focus:ring-indigo-500 focus:border-indigo-500 block w-full shadow-sm sm:text-lg border-gray-300 rounded-md bg-gray-700 text-white">
<label for="random_identity" class="block text-lg font-medium text-gray-400">Random Identity</label>
<input type="checkbox" name="random_identity" id="random_identity" class="mt-1 focus:ring-indigo-500 focus:border-indigo-500 block w-full shadow-sm sm:text-lg border-gray-300 rounded-md bg-gray-700 text-white">
<label for="long_term_memory" class="block text-lg font-medium text-gray-400">Long term memory</label>
<input type="checkbox" name="long_term_memory" id="long_term_memory" class="mt-1 focus:ring-indigo-500 focus:border-indigo-500 block w-full shadow-sm sm:text-lg border-gray-300 rounded-md bg-gray-700 text-white">
<label for="summary_long_term_memory" class="block text-lg font-medium text-gray-400">Long term memory (summarize!)</label>
<input type="checkbox" name="summary_long_term_memory" id="summary_long_term_memory" class="mt-1 focus:ring-indigo-500 focus:border-indigo-500 block w-full shadow-sm sm:text-lg border-gray-300 rounded-md bg-gray-700 text-white">
</div>
<div class="mb-4">
<label for="identity_guidance" class="block text-lg font-medium text-gray-400">Identity Guidance</label>
<textarea name="identity_guidance" id="identity_guidance" class="mt-1 focus:ring-indigo-500 focus:border-indigo-500 block w-full shadow-sm sm:text-lg border-gray-300 rounded-md bg-gray-700 text-white" placeholder="Identity Guidance"></textarea>
</div>
<div class="mb-4">
<label for="periodic_runs" class="block text-lg font-medium text-gray-400">Periodic Runs</label>
<input type="text" name="periodic_runs" id="periodic_runs" class="mt-1 focus:ring-blue-500 focus:border-blue-500 block w-full shadow-sm sm:text-sm border-gray-300 rounded-md bg-gray-700 text-white" placeholder="Periodic Runs">
</div>
<div class="mb-4">
<label for="permanent_goal" class="block text-lg font-medium text-gray-400">Permanent goal</label>
<textarea name="permanent_goal" id="permanent_goal" class="mt-1 focus:ring-blue-500 focus:border-blue-500 block w-full shadow-sm sm:text-sm border-gray-300 rounded-md bg-gray-700 text-white" placeholder="Permanent goal"></textarea>
</div>
<div class="mb-4">
<label for="system_prompt" class="block text-lg font-medium text-gray-400">System prompt</label>
<textarea name="system_prompt" id="system_prompt" class="mt-1 focus:ring-blue-500 focus:border-blue-500 block w-full shadow-sm sm:text-sm border-gray-300 rounded-md bg-gray-700 text-white" placeholder="System prompt"></textarea>
</div>
<div class="flex items-center justify-between">
<button type="submit" class="w-full flex justify-center py-2 px-4 border border-transparent rounded-md shadow-sm text-sm font-medium text-white bg-blue-500 hover:bg-blue-700 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-blue-500">
Create Agent
</button>
</div>
</form>
</div>
</body>
</html>

View File

@@ -1,31 +0,0 @@
<!DOCTYPE html>
<html lang="en">
<head>
<title>Smart Assistant Dashboard</title>
{{template "views/partials/header"}}
<style>
</style>
</head>
<body class="bg-gray-900 p-4 text-white font-sans">
{{template "views/partials/menu"}}
<div class="container">
<div class="image-container">
<img src="/public/logo_1.png" width="250" alt="Company Logo">
</div>
<!-- Card for Agent List Page -->
<a href="/agents" class="card-link">
<div class="card">
<h2>Agent List</h2>
<p>View and manage your list of agents, including detailed profiles and statistics.</p>
</div>
</a>
<a href="/create" class="card-link">
<div class="card">
<h2>Create</h2>
<p>Create a new agent.</p>
</div>
</a>
</div>
</body>
</html>

View File

@@ -1,64 +0,0 @@
<!DOCTYPE html>
<html lang="en">
<head>
<title>Knowledgebase for {{.Name}}</title>
{{template "views/partials/header"}}
</head>
<body class="bg-gray-900 p-4 text-white font-sans">
{{template "views/partials/menu"}}
<header class="text-center mb-8">
<h1 class="text-3xl md:text-5xl font-bold">Knowledgebase (items: {{.KnowledgebaseItemsCount}})</h1>
</header>
<div class="max-w-4xl mx-auto">
<!-- Add Site Form -->
<div class="section-box">
<form action="/knowledgebase/{{.Name}}" method="POST">
<h2>Add sites to KB</h2>
<label for="url">URL:</label>
<input type="text" name="url" id="url" placeholder="Enter URL here">
<label for="chunk_size">Chunk size:</label>
<input type="text" name="chunk_size" id="chunk_size" placeholder="Enter chunk size here">
<button type="submit">Add Site</button>
</form>
</div>
<!-- File Upload Form -->
<div class="section-box">
<form id='form' hx-encoding='multipart/form-data' hx-post='/knowledgebase/{{.Name}}/upload'>
<h2>Upload File</h2>
<label for="file">File:</label>
<input type='file' name='file' id='file'>
<label for="chunk_size">Chunk size:</label>
<input type="text" name="chunk_size" id="chunk_size" placeholder="Enter chunk size here">
<button type="submit">Upload File</button>
<progress id='progress' value='0' max='100'></progress>
</form>
</div>
<!-- Reset Knowledge Base -->
<div class="section-box">
<button hx-swap="none" hx-trigger="click" hx-delete="/knowledgebase/{{.Name}}/reset">Reset Knowledge Base</button>
</div>
<div class="section-box">
<h2>Export</h2>
<a href="/knowledgebase/{{.Name}}/export" >Export</a>
</div>
<div class="section-box">
<form id='form' hx-encoding='multipart/form-data' hx-post='/knowledgebase/{{.Name}}/import'>
<h2>Import</h2>
<label for="file">File:</label>
<input type='file' name='file' id='file'>
<button type="submit">Upload File</button>
<progress id='progress' value='0' max='100'></progress>
</form>
</div>
</div>
<script>
htmx.on('#form', 'htmx:xhr:progress', function(evt) {
htmx.find('#progress').setAttribute('value', evt.detail.loaded / evt.detail.total * 100);
});
</script>
</body>
</html>

View File

@@ -1,223 +0,0 @@
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<link href="https://cdn.jsdelivr.net/npm/tailwindcss@2.2.19/dist/tailwind.min.css" rel="stylesheet">
<link href="https://fonts.googleapis.com/css2?family=Roboto:wght@400;500;700&display=swap" rel="stylesheet">
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.0.0-beta3/css/all.min.css">
<script src="https://unpkg.com/htmx.org"></script>
<script src="https://unpkg.com/htmx.org/dist/ext/sse.js"></script>
<script src="https://unpkg.com/hyperscript.org@0.9.12"></script>
<script defer src="https://unpkg.com/@alpinejs/collapse@3.x.x/dist/cdn.min.js"></script>
<script src="https://unpkg.com/alpinejs" defer></script>
<style>
body {
font-family: 'Roboto', sans-serif;
}
.section-box {
background-color: #2a2a2a; /* Darker background for the form */
padding: 20px;
margin-bottom: 20px;
border-radius: 10px;
box-shadow: 0 4px 6px rgba(0, 0, 0, 0.1);
}
body {
background-color: #1a1a1a; /* Lighter overall background */
color: #ffffff;
font-family: sans-serif;
padding: 20px;
}
input, button {
width: 100%;
padding: 10px;
margin-top: 5px;
border-radius: 5px;
border: none;
}
input[type="text"], input[type="file"] {
background-color: #333333;
color: white;
}
button {
background-color: #4a76a8; /* Blue color for buttons */
color: white;
cursor: pointer;
}
button:hover {
background-color: #5a86b8;
}
textarea {
height: 200px; /* Increased height for better JSON visibility */
}
/* Enhancing select box styles */
select {
appearance: none; /* Remove default system appearance */
background-color: #333; /* Darker background for the dark theme */
border: 2px solid #555; /* Slightly lighter border for contrast */
color: white; /* Text color */
padding: 10px; /* Padding inside the select box */
border-radius: 5px; /* Rounded corners */
background-image: url('data:image/svg+xml;utf8,<svg fill="%23ffffff" height="24" viewBox="0 0 24 24" width="24" xmlns="http://www.w3.org/2000/svg"><path d="M7 10l5 5 5-5z"/></svg>'); /* Custom dropdown arrow using SVG */
background-repeat: no-repeat;
background-position: right 10px center; /* Positioning the arrow nicely */
background-size: 12px; /* Size of the arrow */
cursor: pointer; /* Cursor indicates it's clickable */
}
select:hover {
border-color: #777; /* Lighter border on hover for visibility */
}
select:focus {
outline: none; /* Remove default focus outline */
border-color: #1e90ff; /* Focus color similar to the hover background of buttons */
box-shadow: 0 0 3px #1e90ff; /* Adding a slight glow effect */
}
select {
/* Previous styles */
overflow-y: auto; /* Ensures that a scrollbar is available when needed */
}
option {
background-color: #333; /* Dark background for each option */
color: white; /* Light color for the text */
padding: 8px 10px; /* Padding for each option */
}
/* Adding a hover effect for options is not consistently supported across all browsers */
select:hover option {
background-color: #444; /* Slightly lighter background on hover */
}
/* Custom Scrollbars for the dropdown */
select {
scrollbar-width: thin; /* Firefox */
scrollbar-color: #666 #333; /* Firefox: thumb and track color */
}
select::-webkit-scrollbar {
width: 8px; /* For WebKit browsers */
}
select::-webkit-scrollbar-track {
background: #333;
}
select::-webkit-scrollbar-thumb {
background-color: #666;
border-radius: 10px;
border: 2px solid #333;
}
/* Basic setup for the checkbox */
.checkbox-custom {
position: relative;
display: inline-block;
width: 20px; /* Width of the checkbox */
height: 20px; /* Height of the checkbox */
margin: 5px;
cursor: pointer;
vertical-align: middle;
}
/* Hide the default checkbox input */
.checkbox-custom input {
opacity: 0;
width: 0;
height: 0;
}
/* Create a custom box */
.checkbox-custom .checkmark {
position: absolute;
top: 0;
left: 0;
height: 20px;
width: 20px;
background-color: #444; /* Dark grey box */
border-radius: 4px; /* Rounded corners for the box */
border: 1px solid #777; /* Slightly lighter border */
}
/* On mouse-over, add a different border color */
.checkbox-custom:hover .checkmark {
border-color: #aaa;
}
/* When the checkbox is checked, change the background and insert a checkmark */
.checkbox-custom input:checked ~ .checkmark {
background-color: #1e90ff; /* Blue background for checked state */
border-color: #1e90ff;
}
/* Create the checkmark using a pseudo element */
.checkbox-custom .checkmark:after {
content: "";
position: absolute;
display: none;
}
/* Show the checkmark when checked */
.checkbox-custom input:checked ~ .checkmark:after {
display: block;
}
/* Style the checkmark */
.checkbox-custom .checkmark:after {
left: 7px;
top: 3px;
width: 5px;
height: 10px;
border: solid white;
border-width: 0 3px 3px 0;
transform: rotate(45deg);
}
.container {
max-width: 100%;
margin: 0 auto;
padding: 20px;
text-align: center;
}
.card-link {
text-decoration: none; /* Removes underline from links */
}
.card {
background: rgba(255, 255, 255, 0.1);
border-radius: 8px;
padding: 20px;
margin: 20px auto;
text-align: left;
width: 90%;
transition: transform 0.3s ease, box-shadow 0.3s ease; /* Smooth transition for hover effects */
display: block; /* Ensures the link fills the card */
}
.card:hover {
transform: translateY(-5px); /* Slight lift effect */
box-shadow: 0 10px 20px rgba(0, 0, 0, 0.2); /* Shadow for depth */
}
.card h2 {
font-size: 1.5em; /* Larger and more prominent */
font-weight: bold; /* Ensures boldness */
color: white; /* Ensures visibility against the card's background */
margin-bottom: 0.5em; /* Space below the heading */
}
.card a,
.card p {
color: white; /* Ensures text color is consistent */
}
.card p {
font-size: 1em;
}
.image-container {
display: flex;
justify-content: center;
margin: 20px 0;
}
</style>

View File

@@ -1,22 +0,0 @@
<nav class="bg-gray-800 w-full">
<div class="px-4 sm:px-6 lg:px-8">
<div class="flex justify-between h-16">
<div class="flex">
<!-- Logo container -->
<div class="flex-shrink-0 flex items-center">
<!-- Replace 'logo.png' with the actual path to your logo image -->
<a href="/" >
<img src="/public/logo_1.png" alt="Logo" class="h-8 w-auto mr-3">
</a>
<a href="/" class="px-3 py-2 rounded-md text-sm font-medium text-white bg-gray-900 focus:outline-none focus:text-white focus:bg-gray-700">
<i class="fas fa-home"></i> LocalAgent
</a>
<a href="/agents" class="px-3 py-2 rounded-md text-sm font-medium text-gray-300 hover:text-white hover:bg-gray-700 focus:outline-none focus:text-white focus:bg-gray-700">
<i class="fas fa-users"></i> Agent list
</a>
</div>
</div>
</div>
</div>
</nav>
<br>

View File

@@ -1,86 +0,0 @@
<!DOCTYPE html>
<html lang="en">
<head>
<title>Agent settings {{.Name}}</title>
{{template "views/partials/header"}}
<style>
.section-box {
background-color: #2a2a2a; /* Darker background for the form */
padding: 20px;
margin-bottom: 20px;
border-radius: 10px;
box-shadow: 0 4px 6px rgba(0, 0, 0, 0.1);
}
body {
background-color: #1a1a1a; /* Lighter overall background */
color: #ffffff;
font-family: sans-serif;
padding: 20px;
}
input, button {
width: 100%;
padding: 10px;
margin-top: 5px;
border-radius: 5px;
border: none;
}
input[type="text"], input[type="file"] {
background-color: #333333;
color: white;
}
button {
background-color: #4a76a8; /* Blue color for buttons */
color: white;
cursor: pointer;
}
button:hover {
background-color: #5a86b8;
}
</style>
</head>
<body>
{{template "views/partials/menu"}}
<header class="text-center mb-8">
<h1 class="text-3xl md:text-5xl font-bold">Agent settings - {{.Name}}</h1>
</header>
<div class="max-w-4xl mx-auto">
<a href="/knowledgebase/{{.Name}}" class="card-link">
<div class="card">
<h2>Manage Knowledgebase</h2>
<p>Access and update your knowledgebase to improve agent responses and efficiency.</p>
</div>
</a>
<div class="section-box">
<button hx-put="/start/{{.Name}}" class="text-indigo-500 hover:text-indigo-400">
Start
</button>
<button hx-put="/pause/{{.Name}}" class="text-indigo-500 hover:text-indigo-400">
Pause
</button>
</div>
<div class="section-box">
<h2>Export</h2>
<a href="/settings/export/{{.Name}}" >Export</a>
</div>
<div class="section-box">
<h2>Danger section</h2>
<a href="/delete/{{.Name}}" class="text-red-500 hover:text-red-400">
<i class="fas fa-trash-alt"></i> Delete
</a>
</div>
</div>
<script>
htmx.on('#form', 'htmx:xhr:progress', function(evt) {
htmx.find('#progress').setAttribute('value', evt.detail.loaded / evt.detail.total * 100);
});
</script>
</body>
</html>

View File

@@ -1,61 +0,0 @@
<!DOCTYPE html>
<html lang="en">
<head>
<title>Smart Agent status</title>
{{template "views/partials/header"}}
<style>
body { overflow: hidden; }
.chat-container { height: 90vh; display: flex; flex-direction: column; }
.chat-messages { overflow-y: auto; flex-grow: 1; }
.htmx-indicator{
opacity:0;
transition: opacity 10ms ease-in;
}
.htmx-request .htmx-indicator{
opacity:1
}
/* Loader (https://cssloaders.github.io/) */
.loader {
width: 12px;
height: 12px;
border-radius: 50%;
display: block;
margin:15px auto;
position: relative;
color: #FFF;
box-sizing: border-box;
animation: animloader 2s linear infinite;
}
@keyframes animloader {
0% { box-shadow: 14px 0 0 -2px, 38px 0 0 -2px, -14px 0 0 -2px, -38px 0 0 -2px; }
25% { box-shadow: 14px 0 0 -2px, 38px 0 0 -2px, -14px 0 0 -2px, -38px 0 0 2px; }
50% { box-shadow: 14px 0 0 -2px, 38px 0 0 -2px, -14px 0 0 2px, -38px 0 0 -2px; }
75% { box-shadow: 14px 0 0 2px, 38px 0 0 -2px, -14px 0 0 -2px, -38px 0 0 -2px; }
100% { box-shadow: 14px 0 0 -2px, 38px 0 0 2px, -14px 0 0 -2px, -38px 0 0 -2px; }
}
</style>
</head>
<body class="bg-gray-900 p-4 text-white font-sans" hx-ext="sse" sse-connect="/sse/{{.Name}}">
{{template "views/partials/menu"}}
<div class="chat-container bg-gray-800 shadow-lg rounded-lg" >
<!-- Chat Header -->
<div class="border-b border-gray-700 p-4">
<h1 class="text-lg font-semibold">{{.Name}}</h1>
</div>
<!-- Chat Messages -->
<div class="chat-messages p-4">
<div sse-swap="status" hx-swap="afterbegin" id="status"></div>
{{ range .History }}
<!-- Agent Status Box -->
<div class="bg-gray-700 p-4">
<h2 class="text-sm font-semibold">Agent:</h2>
<div id="agentStatus" class="text-sm text-gray-300">
Result: {{.Result}} Action: {{.Action}} Params: {{.Params}} Reasoning: {{.Reasoning}}
</div>
</div>
{{end}}
</div>
</div>
</body>
</html>