Skip to content

Commit

Permalink
chore: add static dir and agents dirs options, move ui to /users path
Browse files Browse the repository at this point in the history
  • Loading branch information
ibuildthecloud committed Jan 22, 2025
1 parent 44d177d commit 1fbd7c8
Show file tree
Hide file tree
Showing 22 changed files with 212 additions and 46 deletions.
3 changes: 2 additions & 1 deletion Dockerfile
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,7 @@ ENV PGDATA=/data/postgresql
COPY --from=build-pgvector /usr/lib/postgresql17/vector.so /usr/lib/postgresql17/
COPY --from=build-pgvector /usr/share/postgresql17/extension/vector* /usr/share/postgresql17/extension/

RUN apk add --no-cache git python-3.13 py3.13-pip npm bash tini procps libreoffice docker
RUN apk add --no-cache git python-3.13 py3.13-pip npm bash tini procps libreoffice docker perl-utils
COPY --chmod=0755 /tools/package-chrome.sh /

RUN /package-chrome.sh && rm /package-chrome.sh
Expand All @@ -58,6 +58,7 @@ EXPOSE 22
ENV PATH=$PATH:/usr/lib/libreoffice/program
ENV HOME=/data
ENV XDG_CACHE_HOME=/data/cache
ENV OBOT_SERVER_AGENTS_DIR=/agents
ENV OBOT_SERVER_ENCRYPTION_CONFIG_FILE=/encryption.yaml
ENV BAAAH_THREADINESS=20
ENV TERM=vt100
Expand Down
2 changes: 1 addition & 1 deletion go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ require (
github.com/gptscript-ai/chat-completion-client v0.0.0-20241219123536-85c44096bc10
github.com/gptscript-ai/cmd v0.0.0-20250122115124-a3d65e9d2432
github.com/gptscript-ai/go-gptscript v0.9.6-0.20241216211344-79a66826cf82
github.com/gptscript-ai/gptscript v0.9.6-0.20250120172457-3f876b2ef42b
github.com/gptscript-ai/gptscript v0.9.6-0.20250122033232-c02c4cbaa1cc
github.com/liggitt/tabwriter v0.0.0-20181228230101-89fcab3d43de
github.com/mhale/smtpd v0.8.3
github.com/obot-platform/kinm v0.0.0-20250116162656-270198b40c6d
Expand Down
4 changes: 2 additions & 2 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -302,8 +302,8 @@ github.com/gptscript-ai/cmd v0.0.0-20250122115124-a3d65e9d2432 h1:cJh/Hl1HFd1qLp
github.com/gptscript-ai/cmd v0.0.0-20250122115124-a3d65e9d2432/go.mod h1:DJAo1xTht1LDkNYFNydVjTHd576TC7MlpsVRl3oloVw=
github.com/gptscript-ai/go-gptscript v0.9.6-0.20241216211344-79a66826cf82 h1:BEN268Z92gqeDc51XVvWdJWdQ47BuuWH3MUysHzilfI=
github.com/gptscript-ai/go-gptscript v0.9.6-0.20241216211344-79a66826cf82/go.mod h1:/FVuLwhz+sIfsWUgUHWKi32qT0i6+IXlUlzs70KKt/Q=
github.com/gptscript-ai/gptscript v0.9.6-0.20250120172457-3f876b2ef42b h1:xOnA8rGg3iGjTpvWUtuw8IzPFey5gsivi1CUNtZp4Gc=
github.com/gptscript-ai/gptscript v0.9.6-0.20250120172457-3f876b2ef42b/go.mod h1:eBrKu1mmZ4tLPoHJJD1xT/Ogm5K7Oue14xk54e+yEZw=
github.com/gptscript-ai/gptscript v0.9.6-0.20250122033232-c02c4cbaa1cc h1:ez6q7fMTmATleVP/d7xKw81oRiymozLNbCxYHmTIstI=
github.com/gptscript-ai/gptscript v0.9.6-0.20250122033232-c02c4cbaa1cc/go.mod h1:eBrKu1mmZ4tLPoHJJD1xT/Ogm5K7Oue14xk54e+yEZw=
github.com/gptscript-ai/tui v0.0.0-20240923192013-172e51ccf1d6 h1:vkgNZVWQgbE33VD3z9WKDwuu7B/eJVVMMPM62ixfCR8=
github.com/gptscript-ai/tui v0.0.0-20240923192013-172e51ccf1d6/go.mod h1:frrl/B+ZH3VSs3Tqk2qxEIIWTONExX3tuUa4JsVnqx4=
github.com/gregjones/httpcache v0.0.0-20180305231024-9cad4c3443a7 h1:pdN6V1QBWetyv/0+wjACpqVH+eVULgEjkurDLq3goeM=
Expand Down
3 changes: 1 addition & 2 deletions pkg/api/authz/authz.go
Original file line number Diff line number Diff line change
Expand Up @@ -28,9 +28,8 @@ var staticRules = map[string][]string{
"/admin/",
"/{$}",
"/{agent}",
"/images/",
"/user/images/",
"/_app/",
"/static/",

// Allow access to the oauth2 endpoints
"/oauth2/",
Expand Down
39 changes: 39 additions & 0 deletions pkg/api/static/static.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
package static

import (
"fmt"
"net/http"
"os"
"strings"
)

func Wrap(next http.Handler, dir string) (http.Handler, error) {
mux := http.NewServeMux()
mux.Handle("/", next)

fs := http.FileServer(http.Dir(dir))

entries, err := os.ReadDir(dir)
if err != nil {
return nil, fmt.Errorf("failed to read directory: %w", err)
}

for _, entry := range entries {
if entry.IsDir() {
mux.Handle("GET /"+entry.Name()+"/", fs)
continue
}

if strings.HasPrefix(entry.Name(), ".") {
continue
}

mux.Handle("GET /"+entry.Name(), fs)

if entry.Name() == "index.html" {
mux.Handle("GET /{$}", fs)
}
}

return mux, nil
}
2 changes: 1 addition & 1 deletion pkg/controller/controller.go
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,7 @@ func New(services *services.Services) (*Controller, error) {
}

func (c *Controller) PreStart(ctx context.Context) error {
if err := data.Data(ctx, c.services.StorageClient); err != nil {
if err := data.Data(ctx, c.services.StorageClient, c.services.AgentsDir); err != nil {
return fmt.Errorf("failed to apply data: %w", err)
}
return nil
Expand Down
98 changes: 97 additions & 1 deletion pkg/controller/data/agent.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,12 +3,22 @@ package data
import (
"context"
_ "embed"
"errors"
"fmt"
"io/fs"
"os"
"path"
"path/filepath"
"slices"
"strings"

"github.com/adrg/xdg"
"github.com/obot-platform/obot/apiclient/types"
"github.com/obot-platform/obot/pkg/alias"
"github.com/obot-platform/obot/pkg/api/handlers"
v1 "github.com/obot-platform/obot/pkg/storage/apis/obot.obot.ai/v1"
"github.com/obot-platform/obot/pkg/system"
"k8s.io/apimachinery/pkg/api/equality"
apierrors "k8s.io/apimachinery/pkg/api/errors"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
kclient "sigs.k8s.io/controller-runtime/pkg/client"
Expand All @@ -18,14 +28,94 @@ import (
//go:embed agent.yaml
var agentBytes []byte

func addAgent(ctx context.Context, k kclient.Client) error {
func addAgents(ctx context.Context, k kclient.Client, agentDir string) error {
if err := addAutoAgents(ctx, k, agentDir); err != nil {
return err
}
return addDefaultAgent(ctx, k, agentDir)
}

func addAutoAgents(ctx context.Context, k kclient.Client, agentDir string) error {
var err error
if agentDir == "" {
agentDir, err = xdg.ConfigFile(path.Join("obot", "agents"))
if err != nil {
return fmt.Errorf("failed to get agent dir: %w", err)
}
}

files, err := os.ReadDir(agentDir)
if errors.Is(err, fs.ErrNotExist) {
return nil
}

for _, file := range files {
if file.IsDir() || !strings.HasSuffix(file.Name(), ".yaml") {
continue
}

data, err := os.ReadFile(filepath.Join(agentDir, file.Name()))
if err != nil {
return fmt.Errorf("failed to read agent file %s: %w", file.Name(), err)
}

var manifest types.AgentManifest
if err := yaml.Unmarshal(data, &manifest); err != nil {
return fmt.Errorf("failed to unmarshal agent file %s: %w", file.Name(), err)
}

if manifest.Alias == "" {
return fmt.Errorf("agent file %s is missing an alias", file.Name())
}

var agent v1.Agent
if err := alias.Get(ctx, k, &agent, system.DefaultNamespace, manifest.Alias); apierrors.IsNotFound(err) {
if err := k.Create(ctx, &v1.Agent{
ObjectMeta: metav1.ObjectMeta{
GenerateName: system.AgentPrefix,
Namespace: system.DefaultNamespace,
},
Spec: v1.AgentSpec{
Manifest: manifest,
},
}); err != nil {
return fmt.Errorf("failed to create agent %s: %w", manifest.Alias, err)
}
continue
} else if err != nil {
return fmt.Errorf("failed to get agent %s: %w", manifest.Alias, err)
}

if !equality.Semantic.DeepEqual(agent.Spec.Manifest, manifest) {
agent.Spec.Manifest = manifest
if err := k.Update(ctx, &agent); err != nil {
return fmt.Errorf("failed to update agent %s: %w", manifest.Alias, err)
}
}
}

return nil
}

func addDefaultAgent(ctx context.Context, k kclient.Client, agentDir string) error {
var agent v1.Agent
if err := yaml.Unmarshal(agentBytes, &agent); err != nil {
return err
}

var existing v1.Agent
if err := k.Get(ctx, kclient.ObjectKey{Namespace: agent.Namespace, Name: agent.Name}, &existing); apierrors.IsNotFound(err) {
if agentDir != "" {
// If the agent dir is set, it's assumed they don't want the default, so only add it if there are zero agents
var agents v1.AgentList
if err := k.List(ctx, &agents); err != nil {
return fmt.Errorf("failed to list agents: %w", err)
}
if len(agents.Items) > 0 {
return nil
}
}

if err := k.Create(ctx, &agent); err != nil {
return err
}
Expand Down Expand Up @@ -53,6 +143,12 @@ func addAgent(ctx context.Context, k kclient.Client) error {
modified, existing.Spec.Manifest.DefaultThreadTools = addTool(modified, &existing, existing.Spec.Manifest.DefaultThreadTools, agent.Spec.Manifest.DefaultThreadTools)
modified, existing.Spec.Manifest.AvailableThreadTools = addTool(modified, &existing, existing.Spec.Manifest.AvailableThreadTools, agent.Spec.Manifest.AvailableThreadTools)

// migrate from the old icon path
if existing.Spec.Manifest.Icons != nil && existing.Spec.Manifest.Icons.Icon == "/images/obot-icon-blue.svg" {
existing.Spec.Manifest.Icons = agent.Spec.Manifest.Icons
modified = true
}

if modified {
return k.Update(ctx, &existing)
}
Expand Down
6 changes: 3 additions & 3 deletions pkg/controller/data/agent.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -8,9 +8,9 @@ spec:
name: Obot
description: Default Assistant
icons:
icon: /images/obot-icon-blue.svg
collapsed: /images/obot-logo-blue-black-text.svg
collapsedDark: /images/obot-logo-blue-white-text.svg
icon: /user/images/obot-icon-blue.svg
collapsed: /user/images/obot-logo-blue-black-text.svg
collapsedDark: /user/images/obot-logo-blue-white-text.svg
prompt: |
You are an AI assistant developed by Acorn Labs named Obot. You are described as follows:
Expand Down
4 changes: 2 additions & 2 deletions pkg/controller/data/data.go
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ var defaultModelsData []byte
//go:embed default-model-aliases.yaml
var defaultModelAliasesData []byte

func Data(ctx context.Context, c kclient.Client) error {
func Data(ctx context.Context, c kclient.Client, agentDir string) error {
var defaultModels v1.ModelList
if err := yaml.Unmarshal(defaultModelsData, &defaultModels); err != nil {
return err
Expand Down Expand Up @@ -45,5 +45,5 @@ func Data(ctx context.Context, c kclient.Client) error {
}
}

return addAgent(ctx, c)
return addAgents(ctx, c, agentDir)
}
2 changes: 1 addition & 1 deletion pkg/credstores/credstore.go
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@ func Init(ctx context.Context, toolRegistries []string, dsn string, opts Options
case strings.HasPrefix(dsn, "postgres://"):
return setupPostgres(toolRegistries, dsn)
default:
return "", nil, fmt.Errorf("unsupported database for credentials %s", dsn)
return "", nil, fmt.Errorf("unsupported database for credentials %s", strings.Split(dsn, "://")[0])
}
}

Expand Down
8 changes: 8 additions & 0 deletions pkg/server/server.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import (

"github.com/obot-platform/obot/logger"
"github.com/obot-platform/obot/pkg/api/router"
"github.com/obot-platform/obot/pkg/api/static"
"github.com/obot-platform/obot/pkg/controller"
"github.com/obot-platform/obot/pkg/services"
"github.com/rs/cors"
Expand Down Expand Up @@ -41,6 +42,13 @@ func Run(ctx context.Context, c services.Config) error {
return err
}

if c.StaticDir != "" {
handler, err = static.Wrap(handler, c.StaticDir)
if err != nil {
return err
}
}

context.AfterFunc(ctx, func() {
log.Fatalf("Interrupted, exiting")
})
Expand Down
4 changes: 4 additions & 0 deletions pkg/services/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -68,6 +68,8 @@ type Config struct {
EnableAuthentication bool `usage:"Enable authentication" default:"false"`
EnableBootstrapUser bool `usage:"Enables the bootstrap user, regardless of configured auth providers" default:"true"`
AuthAdminEmails []string `usage:"Emails of admin users"`
AgentsDir string `usage:"The directory to auto load agents on start (default $XDG_CONFIG_HOME/.obot/agents)"`
StaticDir string `usage:"The directory to serve static files from"`

// Sendgrid webhook
SendgridWebhookUsername string `usage:"The username for the sendgrid webhook to authenticate with"`
Expand Down Expand Up @@ -98,6 +100,7 @@ type Services struct {
Bootstrapper *bootstrap.Bootstrap
KnowledgeSetIngestionLimit int
SupportDocker bool
AgentsDir string

// Use basic auth for sendgrid webhook, if being set
SendgridWebhookUsername string
Expand Down Expand Up @@ -390,6 +393,7 @@ func New(ctx context.Context, config Config) (*Services, error) {
ProxyManager: proxyManager,
ProviderDispatcher: providerDispatcher,
Bootstrapper: bootstrapper,
AgentsDir: config.AgentsDir,
}, nil
}

Expand Down
18 changes: 9 additions & 9 deletions ui/user/src/lib/auth.ts
Original file line number Diff line number Diff line change
@@ -1,13 +1,13 @@
export type AuthProvider = {
configured: boolean
icon?: string
name: string
namespace: string
id: string
}
configured: boolean;
icon?: string;
name: string;
namespace: string;
id: string;
};

export async function listAuthProviders(): Promise<AuthProvider[]> {
const resp = await fetch('/api/auth-providers')
const data = await resp.json()
return data.items.filter((provider: AuthProvider) => provider.configured);
const resp = await fetch('/api/auth-providers');
const data = await resp.json();
return data.items.filter((provider: AuthProvider) => provider.configured);
}
18 changes: 12 additions & 6 deletions ui/user/src/lib/stores/currentassistant.ts
Original file line number Diff line number Diff line change
Expand Up @@ -26,12 +26,18 @@ function assignSelected(currentAssistants: Assistant[], selectedName: string): A
}
const res = currentAssistants.find((value) => value.current);
if (!res && selectedName) {
ChatService.getAssistant(selectedName).then((assistant) => {
if (assistant) {
assistant.current = true;
store.set(assistant);
}
});
ChatService.getAssistant(selectedName)
.then((assistant) => {
if (assistant) {
assistant.current = true;
store.set(assistant);
}
})
.catch((error) => {
if (String(error).includes('404')) {
window.location.href = '/';
}
});
return def;
}
return res ?? def;
Expand Down
Loading

0 comments on commit 1fbd7c8

Please sign in to comment.