mirror of
https://github.com/mudler/LocalAGI.git
synced 2026-08-06 16:29:39 -04:00
Compare commits
68 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 3a5ea024eb | |||
| fbb098e996 | |||
| b124244b0c | |||
| c7d1b58340 | |||
| 9971819350 | |||
| e3769d3e1f | |||
| f777ce2992 | |||
| 02eda4efb3 | |||
| 1b3420c857 | |||
| 2f537e72bb | |||
| 4afdb6f813 | |||
| c610c2c6ca | |||
| 047a324e0e | |||
| e37283cb11 | |||
| d6f10b28c1 | |||
| c082b42395 | |||
| 252ee2aff6 | |||
| fffc1f8647 | |||
| dc1653294d | |||
| 3862c3c0eb | |||
| 756c46b526 | |||
| 73a6be8264 | |||
| 84f21c6ab4 | |||
| a0ab067bf7 | |||
| c719cbd544 | |||
| 6fa73262fc | |||
| 558fa396a8 | |||
| 33ffe06510 | |||
| a5a602c5b0 | |||
| 500862b768 | |||
| b06522d1ec | |||
| 0c312c9caa | |||
| d456315f19 | |||
| 983f5599d5 | |||
| 471c937d57 | |||
| ad61551db5 | |||
| 2bad9c399f | |||
| 580ad7d46f | |||
| 47f013f3e1 | |||
| d5219a9379 | |||
| 00460b1538 | |||
| 98e62edf12 | |||
| 592969f976 | |||
| 6be849e409 | |||
| d42c2a5749 | |||
| 8d2eafbc35 | |||
| 848f688ae9 | |||
| b813842f85 | |||
| 684ce43eb8 | |||
| 39d13d155f | |||
| 2d5523a129 | |||
| 7fe3ed2169 | |||
| 6db3b5f31f | |||
| f6ac820a3f | |||
| d955284278 | |||
| 3fd6134bcd | |||
| 4d875e9e2a | |||
| 79b72ec311 | |||
| 8487459d90 | |||
| be336adf02 | |||
| be1acb06fd | |||
| 26ec479596 | |||
| efee3c462d | |||
| 4cf52ec698 | |||
| ba8ea0117e | |||
| 36770b70ae | |||
| 5784be2853 | |||
| 5b93e5c21c |
@@ -20,7 +20,7 @@ jobs:
|
||||
with:
|
||||
fetch-depth: 0
|
||||
- name: Set up Go
|
||||
uses: actions/setup-go@v5
|
||||
uses: actions/setup-go@v6
|
||||
with:
|
||||
go-version: 1.24
|
||||
- name: Run GoReleaser
|
||||
|
||||
@@ -83,79 +83,7 @@ jobs:
|
||||
#tags: ${{ steps.prep.outputs.tags }}
|
||||
tags: ${{ steps.meta.outputs.tags }}
|
||||
labels: ${{ steps.meta.outputs.labels }}
|
||||
mcpbox-build:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- name: Prepare
|
||||
id: prep
|
||||
run: |
|
||||
DOCKER_IMAGE=quay.io/mudler/localagi-mcpbox
|
||||
# Use branch name as default
|
||||
VERSION=${GITHUB_REF#refs/heads/}
|
||||
BINARY_VERSION=$(git describe --always --tags --dirty)
|
||||
SHORTREF=${GITHUB_SHA::8}
|
||||
# If this is git tag, use the tag name as a docker tag
|
||||
if [[ $GITHUB_REF == refs/tags/* ]]; then
|
||||
VERSION=${GITHUB_REF#refs/tags/}
|
||||
fi
|
||||
TAGS="${DOCKER_IMAGE}:${VERSION},${DOCKER_IMAGE}:${SHORTREF}"
|
||||
# If the VERSION looks like a version number, assume that
|
||||
# this is the most recent version of the image and also
|
||||
# tag it 'latest'.
|
||||
if [[ $VERSION =~ ^[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}$ ]]; then
|
||||
TAGS="$TAGS,${DOCKER_IMAGE}:latest"
|
||||
fi
|
||||
# Set output parameters.
|
||||
echo ::set-output name=binary_version::${BINARY_VERSION}
|
||||
echo ::set-output name=tags::${TAGS}
|
||||
echo ::set-output name=docker_image::${DOCKER_IMAGE}
|
||||
- name: Set up QEMU
|
||||
uses: docker/setup-qemu-action@master
|
||||
with:
|
||||
platforms: all
|
||||
|
||||
- name: Set up Docker Buildx
|
||||
id: buildx
|
||||
uses: docker/setup-buildx-action@master
|
||||
|
||||
- name: Login to DockerHub
|
||||
if: github.event_name != 'pull_request'
|
||||
uses: docker/login-action@v3
|
||||
with:
|
||||
registry: quay.io
|
||||
username: ${{ secrets.DOCKER_USERNAME }}
|
||||
password: ${{ secrets.DOCKER_PASSWORD }}
|
||||
- name: Extract metadata (tags, labels) for Docker
|
||||
id: meta
|
||||
uses: docker/metadata-action@902fa8ec7d6ecbf8d84d538b9b233a880e428804
|
||||
with:
|
||||
images: quay.io/mudler/localagi-mcpbox
|
||||
tags: |
|
||||
type=ref,event=branch,suffix=-{{date 'YYYYMMDDHHmmss'}}
|
||||
type=semver,pattern={{raw}}
|
||||
type=sha,suffix=-{{date 'YYYYMMDDHHmmss'}}
|
||||
type=ref,event=branch
|
||||
flavor: |
|
||||
latest=auto
|
||||
prefix=
|
||||
suffix=
|
||||
|
||||
- name: Build
|
||||
uses: docker/build-push-action@v6
|
||||
with:
|
||||
builder: ${{ steps.buildx.outputs.name }}
|
||||
build-args: |
|
||||
VERSION=${{ steps.prep.outputs.binary_version }}
|
||||
context: ./
|
||||
file: ./Dockerfile.mcpbox
|
||||
platforms: linux/amd64,linux/arm64
|
||||
push: true
|
||||
#tags: ${{ steps.prep.outputs.tags }}
|
||||
tags: ${{ steps.meta.outputs.tags }}
|
||||
labels: ${{ steps.meta.outputs.labels }}
|
||||
|
||||
sshbox-build:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
|
||||
+12
-36
@@ -1,4 +1,4 @@
|
||||
name: Run Go Tests
|
||||
name: Run Tests
|
||||
|
||||
on:
|
||||
push:
|
||||
@@ -16,43 +16,19 @@ jobs:
|
||||
steps:
|
||||
- name: Checkout code
|
||||
uses: actions/checkout@v4
|
||||
- run: |
|
||||
# Add Docker's official GPG key:
|
||||
sudo apt-get update
|
||||
sudo apt-get install ca-certificates curl
|
||||
sudo install -m 0755 -d /etc/apt/keyrings
|
||||
sudo curl -fsSL https://download.docker.com/linux/ubuntu/gpg -o /etc/apt/keyrings/docker.asc
|
||||
sudo chmod a+r /etc/apt/keyrings/docker.asc
|
||||
|
||||
# Add the repository to Apt sources:
|
||||
echo \
|
||||
"deb [arch=$(dpkg --print-architecture) signed-by=/etc/apt/keyrings/docker.asc] https://download.docker.com/linux/ubuntu \
|
||||
$(. /etc/os-release && echo "${UBUNTU_CODENAME:-$VERSION_CODENAME}") stable" | \
|
||||
sudo tee /etc/apt/sources.list.d/docker.list > /dev/null
|
||||
sudo apt-get update
|
||||
sudo apt-get install -y docker-ce docker-ce-cli containerd.io docker-buildx-plugin docker-compose-plugin make
|
||||
docker version
|
||||
|
||||
docker run --rm hello-world
|
||||
- uses: actions/setup-go@v5
|
||||
- uses: actions/setup-go@v6
|
||||
with:
|
||||
go-version: '>=1.17.0'
|
||||
- name: Free up disk space
|
||||
run: |
|
||||
sudo rm -rf /usr/share/dotnet
|
||||
sudo rm -rf /usr/local/lib/android
|
||||
sudo rm -rf /opt/ghc
|
||||
sudo apt-get clean
|
||||
docker system prune -af || true
|
||||
df -h
|
||||
|
||||
- name: Run tests
|
||||
run: |
|
||||
make tests
|
||||
#sudo mv coverage/coverage.txt coverage.txt
|
||||
#sudo chmod 777 coverage.txt
|
||||
|
||||
# - name: Upload coverage to Codecov
|
||||
# uses: codecov/codecov-action@v4
|
||||
# with:
|
||||
# token: ${{ secrets.CODECOV_TOKEN }}
|
||||
test-e2e:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Checkout code
|
||||
uses: actions/checkout@v4
|
||||
- uses: actions/setup-go@v6
|
||||
with:
|
||||
go-version: '>=1.17.0'
|
||||
- run: |
|
||||
make tests-e2e
|
||||
@@ -1,49 +0,0 @@
|
||||
# Build stage
|
||||
FROM golang:1.24-alpine AS builder
|
||||
|
||||
# Install build dependencies
|
||||
RUN apk add --no-cache git
|
||||
|
||||
# Set working directory
|
||||
WORKDIR /app
|
||||
|
||||
# Copy go mod files
|
||||
COPY go.mod go.sum ./
|
||||
|
||||
# Download dependencies
|
||||
RUN go mod download
|
||||
|
||||
# Copy source code
|
||||
COPY . .
|
||||
|
||||
# Build the application
|
||||
RUN CGO_ENABLED=0 GOOS=linux go build -o mcpbox ./cmd/mcpbox
|
||||
|
||||
# Final stage
|
||||
FROM ubuntu:24.04
|
||||
|
||||
ENV DEBIAN_FRONTEND=noninteractive
|
||||
|
||||
# Install runtime dependencies
|
||||
RUN apt-get update && apt-get install -y ca-certificates tzdata docker.io bash wget curl
|
||||
|
||||
# Create non-root user
|
||||
#RUN adduser -D -g '' appuser
|
||||
|
||||
# Set working directory
|
||||
WORKDIR /app
|
||||
|
||||
# Copy binary from builder
|
||||
COPY --from=builder /app/mcpbox .
|
||||
|
||||
# Use non-root user
|
||||
#USER appuser
|
||||
|
||||
# Expose port
|
||||
EXPOSE 8080
|
||||
|
||||
# Set entrypoint
|
||||
ENTRYPOINT ["/app/mcpbox"]
|
||||
|
||||
# Default command
|
||||
CMD ["-addr", ":8080"]
|
||||
@@ -1,5 +1,5 @@
|
||||
# python
|
||||
FROM python:3.13-slim
|
||||
FROM python:3.14-slim
|
||||
|
||||
ENV DEBIAN_FRONTEND=noninteractive
|
||||
RUN apt-get update && apt-get install -y python3-dev portaudio19-dev ffmpeg build-essential
|
||||
|
||||
+12
-3
@@ -44,12 +44,21 @@ COPY --from=ui-builder /app/dist /work/webui/react-ui/dist
|
||||
# Build the application
|
||||
RUN CGO_ENABLED=0 go build -ldflags="$LDFLAGS" -o localagi ./
|
||||
|
||||
FROM scratch
|
||||
FROM ubuntu:24.04
|
||||
|
||||
ENV DEBIAN_FRONTEND=noninteractive
|
||||
|
||||
# Install runtime dependencies
|
||||
RUN apt-get update && apt-get install -y \
|
||||
ca-certificates \
|
||||
tzdata \
|
||||
docker.io \
|
||||
bash \
|
||||
wget \
|
||||
curl
|
||||
|
||||
# Copy the webui binary from the builder stage to the final image
|
||||
COPY --from=builder /work/localagi /localagi
|
||||
COPY --from=builder /etc/ssl/ /etc/ssl/
|
||||
COPY --from=builder /tmp /tmp
|
||||
|
||||
# Define the command that will be run when the container is started
|
||||
ENTRYPOINT ["/localagi"]
|
||||
|
||||
@@ -1,17 +1,15 @@
|
||||
GOCMD?=go
|
||||
IMAGE_NAME?=webui
|
||||
MCPBOX_IMAGE_NAME?=mcpbox
|
||||
ROOT_DIR:=$(shell dirname $(realpath $(lastword $(MAKEFILE_LIST))))
|
||||
|
||||
prepare-tests: build-mcpbox
|
||||
prepare-tests:
|
||||
docker compose up -d --build
|
||||
docker run -d -v /var/run/docker.sock:/var/run/docker.sock --privileged -p 9090:8080 --rm -ti $(MCPBOX_IMAGE_NAME)
|
||||
|
||||
cleanup-tests:
|
||||
docker compose down
|
||||
|
||||
tests: prepare-tests
|
||||
LOCALAGI_MCPBOX_URL="http://localhost:9090" LOCALAGI_MODEL="gemma-3-4b-it-qat" LOCALAI_API_URL="http://localhost:8081" LOCALAGI_API_URL="http://localhost:8080" $(GOCMD) run github.com/onsi/ginkgo/v2/ginkgo --fail-fast -v -r ./...
|
||||
LOCALAGI_MODEL="gemma-3-4b-it-qat" LOCALAI_API_URL="http://localhost:8081" LOCALAGI_API_URL="http://localhost:8080" $(GOCMD) run github.com/onsi/ginkgo/v2/ginkgo --label-filter="!E2E" --flake-attempts=5 --fail-fast -v -r ./...
|
||||
|
||||
run-nokb:
|
||||
$(MAKE) run KBDISABLEINDEX=true
|
||||
@@ -25,7 +23,7 @@ build: webui/react-ui/dist
|
||||
|
||||
.PHONY: run
|
||||
run: webui/react-ui/dist
|
||||
LOCALAGI_MCPBOX_URL="http://localhost:9090" $(GOCMD) run ./
|
||||
$(GOCMD) run ./
|
||||
|
||||
build-image:
|
||||
docker build -t $(IMAGE_NAME) -f Dockerfile.webui .
|
||||
@@ -33,8 +31,5 @@ build-image:
|
||||
image-push:
|
||||
docker push $(IMAGE_NAME)
|
||||
|
||||
build-mcpbox:
|
||||
docker build -t $(MCPBOX_IMAGE_NAME) -f Dockerfile.mcpbox .
|
||||
|
||||
run-mcpbox:
|
||||
docker run -v /var/run/docker.sock:/var/run/docker.sock --privileged -p 9090:8080 -ti mcpbox
|
||||
tests-e2e: prepare-tests
|
||||
LOCALAGI_MODEL="gemma-3-4b-it-qat" LOCALAI_API_URL="http://localhost:8081" LOCALAGI_API_URL="http://localhost:8080" $(GOCMD) run github.com/onsi/ginkgo/v2/ginkgo --label-filter="E2E" --flake-attempts=5 --fail-fast -v -r ./tests/e2e/...
|
||||
|
||||
@@ -31,7 +31,7 @@ LocalAGI ensures your data stays exactly where you want it—on your hardware. N
|
||||
- 🎛 **No-Code Agents**: Easy-to-configure multiple agents via Web UI.
|
||||
- 🖥 **Web-Based Interface**: Simple and intuitive agent management.
|
||||
- 🤖 **Advanced Agent Teaming**: Instantly create cooperative agent teams from a single prompt.
|
||||
- 📡 **Connectors Galore**: Built-in integrations with Discord, Slack, Telegram, GitHub Issues, and IRC.
|
||||
- 📡 **Connectors**: Built-in integrations with Discord, Slack, Telegram, GitHub Issues, and IRC.
|
||||
- 🛠 **Comprehensive REST API**: Seamless integration into your workflows. Every agent created will support OpenAI Responses API out of the box.
|
||||
- 📚 **Short & Long-Term Memory**: Powered by [LocalRecall](https://github.com/mudler/LocalRecall).
|
||||
- 🧠 **Planning & Reasoning**: Agents intelligently plan, reason, and adapt.
|
||||
@@ -173,6 +173,9 @@ MODEL_NAME=gemma-3-12b-it \
|
||||
MULTIMODAL_MODEL=moondream2-20250414 \
|
||||
IMAGE_MODEL=sd-1.5-ggml \
|
||||
docker compose -f docker-compose.intel.yaml up
|
||||
|
||||
# With custom actions directory
|
||||
LOCALAGI_CUSTOM_ACTIONS_DIR=/app/custom-actions docker compose up
|
||||
```
|
||||
|
||||
If no models are specified, it will use the defaults:
|
||||
@@ -237,6 +240,7 @@ LocalAGI supports environment configurations. Note that these environment variab
|
||||
| `LOCALAGI_LOCALRAG_URL` | LocalRecall connection |
|
||||
| `LOCALAGI_ENABLE_CONVERSATIONS_LOGGING` | Toggle conversation logs |
|
||||
| `LOCALAGI_API_KEYS` | A comma separated list of api keys used for authentication |
|
||||
| `LOCALAGI_CUSTOM_ACTIONS_DIR` | Directory containing custom Go action files to be automatically loaded |
|
||||
|
||||
## Installation Options
|
||||
|
||||
@@ -339,7 +343,6 @@ pool, err := state.NewAgentPool(
|
||||
"http://localhost:8080", // API URL
|
||||
"your-api-key", // API key
|
||||
"./state", // state directory
|
||||
"", // MCP box URL (optional)
|
||||
"http://localhost:8081", // LocalRAG API URL
|
||||
func(config *AgentConfig) func(ctx context.Context, pool *AgentPool) []types.Action {
|
||||
// Define available actions for agents
|
||||
@@ -425,6 +428,30 @@ LocalAGI provides two powerful ways to extend its functionality with custom acti
|
||||
|
||||
LocalAGI supports custom actions written in Go that can be defined inline when creating an agent. These actions are interpreted at runtime, so no compilation is required.
|
||||
|
||||
#### Automatic Custom Actions Loading
|
||||
|
||||
You can also place custom Go action files in a directory and have LocalAGI automatically load them. Set the `LOCALAGI_CUSTOM_ACTIONS_DIR` environment variable to point to a directory containing your custom action files. Each `.go` file in this directory will be automatically loaded and made available to all agents.
|
||||
|
||||
**Example setup:**
|
||||
```bash
|
||||
# Set the environment variable
|
||||
export LOCALAGI_CUSTOM_ACTIONS_DIR="/path/to/custom/actions"
|
||||
|
||||
# Or in docker-compose.yaml
|
||||
environment:
|
||||
- LOCALAGI_CUSTOM_ACTIONS_DIR=/app/custom-actions
|
||||
```
|
||||
|
||||
**Directory structure:**
|
||||
```
|
||||
custom-actions/
|
||||
├── weather_action.go
|
||||
├── file_processor.go
|
||||
└── database_query.go
|
||||
```
|
||||
|
||||
Each file should contain the three required functions (`Run`, `Definition`, `RequiredFields`) as described below.
|
||||
|
||||
#### How Custom Actions Work
|
||||
|
||||
When creating a new Agent, in the action sections select the "custom" action, you can add the Golang code directly there.
|
||||
@@ -675,15 +702,39 @@ The development workflow is similar to the source build, but with additional ste
|
||||
git clone https://github.com/mudler/LocalAGI.git
|
||||
cd LocalAGI
|
||||
|
||||
# Install dependencies and start frontend development server
|
||||
cd webui/react-ui && bun i && bun run dev
|
||||
cd webui/react-ui
|
||||
|
||||
# Install dependencies
|
||||
bun i
|
||||
|
||||
# Compile frontend (the build directory needs to exist for the backend to start)
|
||||
bun run build
|
||||
|
||||
# Start frontend development server
|
||||
bun run dev
|
||||
```
|
||||
|
||||
Then in separate terminal:
|
||||
|
||||
```bash
|
||||
cd LocalAGI
|
||||
|
||||
# Create a "pool" directory for agent state
|
||||
mkdir pool
|
||||
|
||||
# Set required environment variables
|
||||
export LOCALAGI_MODEL=gemma-3-4b-it-qat
|
||||
export LOCALAGI_MULTIMODAL_MODEL=moondream2-20250414
|
||||
export LOCALAGI_IMAGE_MODEL=sd-1.5-ggml
|
||||
export LOCALAGI_LLM_API_URL=http://localai:8080
|
||||
export LOCALAGI_LOCALRAG_URL=http://localrecall:8080
|
||||
export LOCALAGI_STATE_DIR=./pool
|
||||
export LOCALAGI_TIMEOUT=5m
|
||||
export LOCALAGI_ENABLE_CONVERSATIONS_LOGGING=false
|
||||
export LOCALAGI_SSHBOX_URL=root:root@sshbox:22
|
||||
|
||||
# Start development server
|
||||
cd ../.. && go run main.go
|
||||
go run main.go
|
||||
```
|
||||
|
||||
> Note: see webui/react-ui/.vite.config.js for env vars that can be used to configure the backend URL
|
||||
@@ -982,9 +1033,9 @@ LocalAGI supports environment configurations. Note that these environment variab
|
||||
| `LOCALAGI_STATE_DIR` | Where state gets stored |
|
||||
| `LOCALAGI_LOCALRAG_URL` | LocalRecall connection |
|
||||
| `LOCALAGI_SSHBOX_URL` | LocalAGI SSHBox URL, e.g. user:pass@ip:port |
|
||||
| `LOCALAGI_MCPBOX_URL` | LocalAGI MCPBox URL, e.g. http://mcpbox:8080 |
|
||||
| `LOCALAGI_ENABLE_CONVERSATIONS_LOGGING` | Toggle conversation logs |
|
||||
| `LOCALAGI_API_KEYS` | A comma separated list of api keys used for authentication |
|
||||
| `LOCALAGI_CUSTOM_ACTIONS_DIR` | Directory containing custom Go action files to be automatically loaded |
|
||||
</details>
|
||||
|
||||
## LICENSE
|
||||
|
||||
@@ -1,38 +0,0 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"flag"
|
||||
"log"
|
||||
"os"
|
||||
"os/signal"
|
||||
"syscall"
|
||||
|
||||
"github.com/mudler/LocalAGI/pkg/stdio"
|
||||
)
|
||||
|
||||
func main() {
|
||||
// Parse command line flags
|
||||
addr := flag.String("addr", ":8080", "HTTP server address")
|
||||
flag.Parse()
|
||||
|
||||
// Create and start the server
|
||||
server := stdio.NewServer()
|
||||
|
||||
// Handle graceful shutdown
|
||||
sigChan := make(chan os.Signal, 1)
|
||||
signal.Notify(sigChan, syscall.SIGINT, syscall.SIGTERM)
|
||||
|
||||
go func() {
|
||||
log.Printf("Starting server on %s", *addr)
|
||||
if err := server.Start(*addr); err != nil {
|
||||
log.Fatalf("Failed to start server: %v", err)
|
||||
}
|
||||
}()
|
||||
|
||||
// Wait for shutdown signal
|
||||
<-sigChan
|
||||
log.Println("Shutting down server...")
|
||||
|
||||
// TODO: Implement graceful shutdown if needed
|
||||
os.Exit(0)
|
||||
}
|
||||
+42
-5
@@ -3,6 +3,8 @@ package action
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"os"
|
||||
"regexp"
|
||||
"strings"
|
||||
|
||||
"github.com/mudler/LocalAGI/core/types"
|
||||
@@ -24,7 +26,7 @@ func NewCustom(config map[string]string, goPkgPath string) (*CustomAction, error
|
||||
}
|
||||
|
||||
if err := a.callInit(); err != nil {
|
||||
xlog.Error("Error calling custom action init", "error", err)
|
||||
xlog.Warn("No init function found for custom action", "error", err, "action", a.config["name"])
|
||||
}
|
||||
|
||||
return a, nil
|
||||
@@ -43,18 +45,23 @@ func (a *CustomAction) callInit() error {
|
||||
|
||||
v, err := a.i.Eval(fmt.Sprintf("%s.Init", a.config["name"]))
|
||||
if err != nil {
|
||||
return err
|
||||
xlog.Warn("No init function found for custom action", "error", err, "action", a.config["name"])
|
||||
return nil
|
||||
}
|
||||
|
||||
run := v.Interface().(func() error)
|
||||
run, ok := v.Interface().(func(string) error)
|
||||
if !ok {
|
||||
return nil
|
||||
}
|
||||
|
||||
return run()
|
||||
return run(a.config["configuration"])
|
||||
}
|
||||
|
||||
func (a *CustomAction) initializeInterpreter() error {
|
||||
if _, exists := a.config["code"]; exists && a.i == nil {
|
||||
unsafe := strings.ToLower(a.config["unsafe"]) == "true"
|
||||
i := interp.New(interp.Options{
|
||||
Env: os.Environ(),
|
||||
GoPath: a.goPkgPath,
|
||||
Unrestricted: unsafe,
|
||||
})
|
||||
@@ -66,6 +73,15 @@ func (a *CustomAction) initializeInterpreter() error {
|
||||
a.config["name"] = "custom"
|
||||
}
|
||||
|
||||
// let's find first if there is already a package declarated in the code
|
||||
// the user might want to specify it to not break syntax with IDEs
|
||||
re := regexp.MustCompile("package (\\w+)")
|
||||
packageName := re.FindStringSubmatch(a.config["code"])
|
||||
if len(packageName) > 1 {
|
||||
// remove it from the code, normalize to `name`
|
||||
a.config["code"] = re.ReplaceAllString(a.config["code"], "")
|
||||
}
|
||||
|
||||
_, err := i.Eval(fmt.Sprintf("package %s\n%s", a.config["name"], a.config["code"]))
|
||||
if err != nil {
|
||||
return err
|
||||
@@ -106,6 +122,21 @@ func (a *CustomAction) Definition() types.ActionDefinition {
|
||||
return types.ActionDefinition{}
|
||||
}
|
||||
|
||||
description := ""
|
||||
desc, err := a.i.Eval(fmt.Sprintf("%s.Description", a.config["name"]))
|
||||
if err != nil {
|
||||
xlog.Warn("No description found for custom action", "error", err, "action", a.config["name"])
|
||||
} else {
|
||||
d, ok := desc.Interface().(func() string)
|
||||
if ok {
|
||||
description = d()
|
||||
}
|
||||
}
|
||||
|
||||
if a.config["description"] != "" {
|
||||
description = a.config["description"]
|
||||
}
|
||||
|
||||
properties := v.Interface().(func() map[string][]string)
|
||||
|
||||
v, err = a.i.Eval(fmt.Sprintf("%s.RequiredFields", a.config["name"]))
|
||||
@@ -130,7 +161,7 @@ func (a *CustomAction) Definition() types.ActionDefinition {
|
||||
}
|
||||
return types.ActionDefinition{
|
||||
Name: types.ActionDefinitionName(a.config["name"]),
|
||||
Description: a.config["description"],
|
||||
Description: description,
|
||||
Properties: prop,
|
||||
Required: requiredFields(),
|
||||
}
|
||||
@@ -164,5 +195,11 @@ func CustomConfigMeta() []config.Field {
|
||||
Type: config.FieldTypeCheckbox,
|
||||
HelpText: "Allow unsafe code execution",
|
||||
},
|
||||
{
|
||||
Name: "configuration",
|
||||
Label: "Configuration",
|
||||
Type: config.FieldTypeTextarea,
|
||||
HelpText: "Configuration for the custom action",
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,48 +0,0 @@
|
||||
package action
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
"github.com/mudler/LocalAGI/core/types"
|
||||
"github.com/sashabaranov/go-openai/jsonschema"
|
||||
)
|
||||
|
||||
// NewGoal creates a new intention action
|
||||
// The inention action is special as it tries to identify
|
||||
// a tool to use and a reasoning over to use it
|
||||
func NewGoal() *GoalAction {
|
||||
return &GoalAction{}
|
||||
}
|
||||
|
||||
type GoalAction struct {
|
||||
}
|
||||
type GoalResponse struct {
|
||||
Goal string `json:"goal"`
|
||||
Achieved bool `json:"achieved"`
|
||||
}
|
||||
|
||||
func (a *GoalAction) Run(ctx context.Context, sharedState *types.AgentSharedState, params types.ActionParams) (types.ActionResult, error) {
|
||||
return types.ActionResult{}, nil
|
||||
}
|
||||
|
||||
func (a *GoalAction) Plannable() bool {
|
||||
return false
|
||||
}
|
||||
|
||||
func (a *GoalAction) Definition() types.ActionDefinition {
|
||||
return types.ActionDefinition{
|
||||
Name: "goal",
|
||||
Description: "Check if the goal is achieved",
|
||||
Properties: map[string]jsonschema.Definition{
|
||||
"goal": {
|
||||
Type: jsonschema.String,
|
||||
Description: "The goal to check if it is achieved.",
|
||||
},
|
||||
"achieved": {
|
||||
Type: jsonschema.Boolean,
|
||||
Description: "Whether the goal is achieved",
|
||||
},
|
||||
},
|
||||
Required: []string{"goal", "achieved"},
|
||||
}
|
||||
}
|
||||
@@ -1,50 +0,0 @@
|
||||
package action
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
"github.com/mudler/LocalAGI/core/types"
|
||||
"github.com/sashabaranov/go-openai/jsonschema"
|
||||
)
|
||||
|
||||
// NewIntention creates a new intention action
|
||||
// The inention action is special as it tries to identify
|
||||
// a tool to use and a reasoning over to use it
|
||||
func NewIntention(s ...string) *IntentAction {
|
||||
return &IntentAction{tools: s}
|
||||
}
|
||||
|
||||
type IntentAction struct {
|
||||
tools []string
|
||||
}
|
||||
type IntentResponse struct {
|
||||
Tool string `json:"tool"`
|
||||
Reasoning string `json:"reasoning"`
|
||||
}
|
||||
|
||||
func (a *IntentAction) Run(ctx context.Context, sharedState *types.AgentSharedState, params types.ActionParams) (types.ActionResult, error) {
|
||||
return types.ActionResult{}, nil
|
||||
}
|
||||
|
||||
func (a *IntentAction) Plannable() bool {
|
||||
return false
|
||||
}
|
||||
|
||||
func (a *IntentAction) Definition() types.ActionDefinition {
|
||||
return types.ActionDefinition{
|
||||
Name: "pick_tool",
|
||||
Description: "Pick a tool",
|
||||
Properties: map[string]jsonschema.Definition{
|
||||
"reasoning": {
|
||||
Type: jsonschema.String,
|
||||
Description: "A detailed reasoning on why you want to call this tool.",
|
||||
},
|
||||
"tool": {
|
||||
Type: jsonschema.String,
|
||||
Description: "The tool you want to use",
|
||||
Enum: a.tools,
|
||||
},
|
||||
},
|
||||
Required: []string{"tool", "reasoning"},
|
||||
}
|
||||
}
|
||||
@@ -1,71 +0,0 @@
|
||||
package action
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
"github.com/mudler/LocalAGI/core/types"
|
||||
"github.com/sashabaranov/go-openai/jsonschema"
|
||||
)
|
||||
|
||||
// PlanActionName is the name of the plan action
|
||||
// used by the LLM to schedule more actions
|
||||
const PlanActionName = "plan"
|
||||
|
||||
func NewPlan(plannableActions []string) *PlanAction {
|
||||
return &PlanAction{
|
||||
plannables: plannableActions,
|
||||
}
|
||||
}
|
||||
|
||||
type PlanAction struct {
|
||||
plannables []string
|
||||
}
|
||||
|
||||
type PlanResult struct {
|
||||
Subtasks []PlanSubtask `json:"subtasks"`
|
||||
Goal string `json:"goal"`
|
||||
}
|
||||
type PlanSubtask struct {
|
||||
Action string `json:"action"`
|
||||
Reasoning string `json:"reasoning"`
|
||||
}
|
||||
|
||||
func (a *PlanAction) Run(ctx context.Context, sharedState *types.AgentSharedState, params types.ActionParams) (types.ActionResult, error) {
|
||||
return types.ActionResult{}, nil
|
||||
}
|
||||
|
||||
func (a *PlanAction) Plannable() bool {
|
||||
return false
|
||||
}
|
||||
|
||||
func (a *PlanAction) Definition() types.ActionDefinition {
|
||||
return types.ActionDefinition{
|
||||
Name: PlanActionName,
|
||||
Description: "Use it for situations that involves doing more actions in sequence.",
|
||||
Properties: map[string]jsonschema.Definition{
|
||||
"subtasks": {
|
||||
Type: jsonschema.Array,
|
||||
Description: "The subtasks to be executed",
|
||||
Items: &jsonschema.Definition{
|
||||
Type: jsonschema.Object,
|
||||
Properties: map[string]jsonschema.Definition{
|
||||
"action": {
|
||||
Type: jsonschema.String,
|
||||
Description: "The action to call",
|
||||
Enum: a.plannables,
|
||||
},
|
||||
"reasoning": {
|
||||
Type: jsonschema.String,
|
||||
Description: "The reasoning for calling this action",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
"goal": {
|
||||
Type: jsonschema.String,
|
||||
Description: "The goal of this plan",
|
||||
},
|
||||
},
|
||||
Required: []string{"subtasks", "goal"},
|
||||
}
|
||||
}
|
||||
@@ -1,43 +0,0 @@
|
||||
package action
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
"github.com/mudler/LocalAGI/core/types"
|
||||
"github.com/sashabaranov/go-openai/jsonschema"
|
||||
)
|
||||
|
||||
// NewReasoning creates a new reasoning action
|
||||
// The reasoning action is special as it tries to force the LLM
|
||||
// to think about what to do next
|
||||
func NewReasoning() *ReasoningAction {
|
||||
return &ReasoningAction{}
|
||||
}
|
||||
|
||||
type ReasoningAction struct{}
|
||||
|
||||
type ReasoningResponse struct {
|
||||
Reasoning string `json:"reasoning"`
|
||||
}
|
||||
|
||||
func (a *ReasoningAction) Run(ctx context.Context, sharedState *types.AgentSharedState, params types.ActionParams) (types.ActionResult, error) {
|
||||
return types.ActionResult{}, nil
|
||||
}
|
||||
|
||||
func (a *ReasoningAction) Plannable() bool {
|
||||
return false
|
||||
}
|
||||
|
||||
func (a *ReasoningAction) Definition() types.ActionDefinition {
|
||||
return types.ActionDefinition{
|
||||
Name: "pick_action",
|
||||
Description: "try to understand what's the best thing to do and pick an action with a reasoning",
|
||||
Properties: map[string]jsonschema.Definition{
|
||||
"reasoning": {
|
||||
Type: jsonschema.String,
|
||||
Description: "A detailed reasoning on what would you do in this situation.",
|
||||
},
|
||||
},
|
||||
Required: []string{"reasoning"},
|
||||
}
|
||||
}
|
||||
@@ -1,45 +0,0 @@
|
||||
package action
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
"github.com/mudler/LocalAGI/core/types"
|
||||
"github.com/sashabaranov/go-openai/jsonschema"
|
||||
)
|
||||
|
||||
// ReplyActionName is the name of the reply action
|
||||
// used by the LLM to reply to the user without
|
||||
// any additional processing
|
||||
const ReplyActionName = "reply"
|
||||
|
||||
func NewReply() *ReplyAction {
|
||||
return &ReplyAction{}
|
||||
}
|
||||
|
||||
type ReplyAction struct{}
|
||||
|
||||
type ReplyResponse struct {
|
||||
Message string `json:"message"`
|
||||
}
|
||||
|
||||
func (a *ReplyAction) Run(ctx context.Context, sharedState *types.AgentSharedState, params types.ActionParams) (string, error) {
|
||||
return "no-op", nil
|
||||
}
|
||||
|
||||
func (a *ReplyAction) Plannable() bool {
|
||||
return false
|
||||
}
|
||||
|
||||
func (a *ReplyAction) Definition() types.ActionDefinition {
|
||||
return types.ActionDefinition{
|
||||
Name: ReplyActionName,
|
||||
Description: "Use this tool to reply to the user once we have all the informations we need.",
|
||||
Properties: map[string]jsonschema.Definition{
|
||||
"message": {
|
||||
Type: jsonschema.String,
|
||||
Description: "The message to reply with",
|
||||
},
|
||||
},
|
||||
Required: []string{"message"},
|
||||
}
|
||||
}
|
||||
+8
-506
@@ -1,155 +1,18 @@
|
||||
package agent
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"os"
|
||||
"strings"
|
||||
|
||||
"github.com/mudler/LocalAGI/core/action"
|
||||
"github.com/mudler/LocalAGI/core/types"
|
||||
"golang.org/x/exp/slices"
|
||||
|
||||
"github.com/mudler/LocalAGI/pkg/xlog"
|
||||
|
||||
"github.com/sashabaranov/go-openai"
|
||||
"github.com/sashabaranov/go-openai/jsonschema"
|
||||
)
|
||||
|
||||
const parameterReasoningPrompt = `You are tasked with generating the optimal parameters for the action "%s". The action requires the following parameters:
|
||||
%s
|
||||
|
||||
Your task is to:
|
||||
1. Generate the best possible values for each required parameter
|
||||
2. If the parameter requires code, provide complete, working code
|
||||
3. If the parameter requires text or documentation, provide comprehensive, well-structured content
|
||||
4. Ensure all parameters are complete and ready to be used
|
||||
|
||||
Focus on quality and completeness. Do not explain your reasoning or analyze the action's purpose - just provide the best possible parameter values.`
|
||||
|
||||
type decisionResult struct {
|
||||
actionParams types.ActionParams
|
||||
message string
|
||||
actionName string
|
||||
}
|
||||
|
||||
// decision forces the agent to take one of the available actions
|
||||
func (a *Agent) decision(
|
||||
job *types.Job,
|
||||
conversation []openai.ChatCompletionMessage,
|
||||
tools []openai.Tool, toolchoice string, maxRetries int) (*decisionResult, error) {
|
||||
|
||||
var choice *openai.ToolChoice
|
||||
|
||||
if toolchoice != "" {
|
||||
choice = &openai.ToolChoice{
|
||||
Type: openai.ToolTypeFunction,
|
||||
Function: openai.ToolFunction{Name: toolchoice},
|
||||
}
|
||||
}
|
||||
|
||||
decision := openai.ChatCompletionRequest{
|
||||
Model: a.options.LLMAPI.Model,
|
||||
Messages: conversation,
|
||||
Tools: tools,
|
||||
}
|
||||
|
||||
if choice != nil {
|
||||
decision.ToolChoice = *choice
|
||||
}
|
||||
|
||||
var obs *types.Observable
|
||||
if job.Obs != nil {
|
||||
obs = a.observer.NewObservable()
|
||||
obs.Name = "decision"
|
||||
obs.ParentID = job.Obs.ID
|
||||
obs.Icon = "brain"
|
||||
obs.Creation = &types.Creation{
|
||||
ChatCompletionRequest: &decision,
|
||||
}
|
||||
a.observer.Update(*obs)
|
||||
}
|
||||
|
||||
var lastErr error
|
||||
for attempts := 0; attempts < maxRetries; attempts++ {
|
||||
resp, err := a.client.CreateChatCompletion(job.GetContext(), decision)
|
||||
if err != nil {
|
||||
lastErr = err
|
||||
xlog.Warn("Attempt to make a decision failed", "attempt", attempts+1, "error", err)
|
||||
|
||||
if obs != nil {
|
||||
obs.Progress = append(obs.Progress, types.Progress{
|
||||
Error: err.Error(),
|
||||
})
|
||||
a.observer.Update(*obs)
|
||||
}
|
||||
|
||||
continue
|
||||
}
|
||||
|
||||
jsonResp, _ := json.Marshal(resp)
|
||||
xlog.Debug("Decision response", "response", string(jsonResp))
|
||||
|
||||
if obs != nil {
|
||||
obs.AddProgress(types.Progress{
|
||||
ChatCompletionResponse: &resp,
|
||||
})
|
||||
}
|
||||
|
||||
if len(resp.Choices) != 1 {
|
||||
lastErr = fmt.Errorf("no choices: %d", len(resp.Choices))
|
||||
xlog.Warn("Attempt to make a decision failed", "attempt", attempts+1, "error", lastErr)
|
||||
|
||||
if obs != nil {
|
||||
obs.Progress[len(obs.Progress)-1].Error = lastErr.Error()
|
||||
a.observer.Update(*obs)
|
||||
}
|
||||
|
||||
continue
|
||||
}
|
||||
|
||||
msg := resp.Choices[0].Message
|
||||
if len(msg.ToolCalls) != 1 {
|
||||
if err := a.saveConversation(append(conversation, msg), "decision"); err != nil {
|
||||
xlog.Error("Error saving conversation", "error", err)
|
||||
}
|
||||
|
||||
if obs != nil {
|
||||
obs.MakeLastProgressCompletion()
|
||||
a.observer.Update(*obs)
|
||||
}
|
||||
|
||||
return &decisionResult{message: msg.Content}, nil
|
||||
}
|
||||
|
||||
params := types.ActionParams{}
|
||||
if err := params.Read(msg.ToolCalls[0].Function.Arguments); err != nil {
|
||||
lastErr = err
|
||||
xlog.Warn("Attempt to parse action parameters failed", "attempt", attempts+1, "error", err)
|
||||
|
||||
if obs != nil {
|
||||
obs.Progress[len(obs.Progress)-1].Error = lastErr.Error()
|
||||
a.observer.Update(*obs)
|
||||
}
|
||||
|
||||
continue
|
||||
}
|
||||
|
||||
if err := a.saveConversation(append(conversation, msg), "decision"); err != nil {
|
||||
xlog.Error("Error saving conversation", "error", err)
|
||||
}
|
||||
|
||||
if obs != nil {
|
||||
obs.MakeLastProgressCompletion()
|
||||
a.observer.Update(*obs)
|
||||
}
|
||||
|
||||
return &decisionResult{actionParams: params, actionName: msg.ToolCalls[0].Function.Name, message: msg.Content}, nil
|
||||
}
|
||||
|
||||
return nil, fmt.Errorf("failed to make a decision after %d attempts: %w", maxRetries, lastErr)
|
||||
}
|
||||
|
||||
type Messages []openai.ChatCompletionMessage
|
||||
|
||||
func (m Messages) ToOpenAI() []openai.ChatCompletionMessage {
|
||||
@@ -228,210 +91,11 @@ func (m Messages) GetLatestUserMessage() *openai.ChatCompletionMessage {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (m Messages) IsLastMessageFromRole(role string) bool {
|
||||
if len(m) == 0 {
|
||||
return false
|
||||
}
|
||||
|
||||
return m[len(m)-1].Role == role
|
||||
}
|
||||
|
||||
func (a *Agent) generateParameters(job *types.Job, pickTemplate string, act types.Action, c []openai.ChatCompletionMessage, reasoning string, maxAttempts int) (*decisionResult, error) {
|
||||
if act == nil {
|
||||
return nil, fmt.Errorf("action is nil")
|
||||
}
|
||||
if len(act.Definition().Properties) > 0 {
|
||||
xlog.Debug("Action has properties", "action", act.Definition().Name, "properties", act.Definition().Properties)
|
||||
} else {
|
||||
xlog.Debug("Action has no properties", "action", act.Definition().Name)
|
||||
return &decisionResult{actionParams: types.ActionParams{}}, nil
|
||||
}
|
||||
|
||||
stateHUD, err := renderTemplate(pickTemplate, a.prepareHUD(), a.availableActions(), reasoning)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
conversation := c
|
||||
if !Messages(c).Exist(stateHUD) && a.options.enableHUD {
|
||||
conversation = append([]openai.ChatCompletionMessage{
|
||||
{
|
||||
Role: "system",
|
||||
Content: stateHUD,
|
||||
},
|
||||
}, conversation...)
|
||||
}
|
||||
|
||||
cc := conversation
|
||||
if a.options.forceReasoning {
|
||||
// First, get the LLM to reason about optimal parameter usage
|
||||
parameterReasoningPrompt := fmt.Sprintf(parameterReasoningPrompt,
|
||||
act.Definition().Name,
|
||||
formatProperties(act.Definition().Properties))
|
||||
|
||||
// Get initial reasoning about parameters using askLLM
|
||||
paramReasoningMsg, err := a.askLLM(job.GetContext(),
|
||||
append(conversation, openai.ChatCompletionMessage{
|
||||
Role: "system",
|
||||
Content: parameterReasoningPrompt,
|
||||
}),
|
||||
maxAttempts,
|
||||
)
|
||||
if err != nil {
|
||||
xlog.Warn("Failed to get parameter reasoning", "error", err)
|
||||
}
|
||||
|
||||
// Combine original reasoning with parameter-specific reasoning
|
||||
enhancedReasoning := reasoning
|
||||
if paramReasoningMsg.Content != "" {
|
||||
enhancedReasoning = fmt.Sprintf("%s\n\nParameter Analysis:\n%s", reasoning, paramReasoningMsg.Content)
|
||||
}
|
||||
|
||||
cc = append(conversation, openai.ChatCompletionMessage{
|
||||
Role: "system",
|
||||
Content: fmt.Sprintf("The agent decided to use the tool %s with the following reasoning: %s", act.Definition().Name, enhancedReasoning),
|
||||
})
|
||||
}
|
||||
|
||||
var result *decisionResult
|
||||
var attemptErr error
|
||||
|
||||
for attempts := 0; attempts < maxAttempts; attempts++ {
|
||||
result, attemptErr = a.decision(job,
|
||||
cc,
|
||||
a.availableActions().ToTools(),
|
||||
act.Definition().Name.String(),
|
||||
maxAttempts,
|
||||
)
|
||||
if attemptErr == nil && result.actionParams != nil {
|
||||
return result, nil
|
||||
}
|
||||
xlog.Warn("Attempt to generate parameters failed", "attempt", attempts+1, "error", attemptErr)
|
||||
}
|
||||
|
||||
return nil, fmt.Errorf("failed to generate parameters after %d attempts: %w", maxAttempts, attemptErr)
|
||||
}
|
||||
|
||||
// Helper function to format properties for the prompt
|
||||
func formatProperties(props map[string]jsonschema.Definition) string {
|
||||
var result strings.Builder
|
||||
for name, prop := range props {
|
||||
result.WriteString(fmt.Sprintf("- %s: %s\n", name, prop.Description))
|
||||
}
|
||||
return result.String()
|
||||
}
|
||||
|
||||
func (a *Agent) handlePlanning(ctx context.Context, job *types.Job, chosenAction types.Action, actionParams types.ActionParams, reasoning string, pickTemplate string, conv Messages) (Messages, error) {
|
||||
// Planning: run all the actions in sequence
|
||||
if !chosenAction.Definition().Name.Is(action.PlanActionName) {
|
||||
xlog.Debug("no plan action")
|
||||
return conv, nil
|
||||
}
|
||||
|
||||
xlog.Debug("[planning]...")
|
||||
planResult := action.PlanResult{}
|
||||
if err := actionParams.Unmarshal(&planResult); err != nil {
|
||||
return conv, fmt.Errorf("error unmarshalling plan result: %w", err)
|
||||
}
|
||||
|
||||
stateResult := types.ActionState{
|
||||
ActionCurrentState: types.ActionCurrentState{
|
||||
Job: job,
|
||||
Action: chosenAction,
|
||||
Params: actionParams,
|
||||
Reasoning: reasoning,
|
||||
},
|
||||
ActionResult: types.ActionResult{
|
||||
Result: fmt.Sprintf("planning %s, subtasks: %+v", planResult.Goal, planResult.Subtasks),
|
||||
},
|
||||
}
|
||||
job.Result.SetResult(stateResult)
|
||||
job.CallbackWithResult(stateResult)
|
||||
|
||||
xlog.Info("[Planning] starts", "agent", a.Character.Name, "goal", planResult.Goal)
|
||||
for _, s := range planResult.Subtasks {
|
||||
xlog.Info("[Planning] subtask", "agent", a.Character.Name, "action", s.Action, "reasoning", s.Reasoning)
|
||||
}
|
||||
|
||||
if len(planResult.Subtasks) == 0 {
|
||||
return conv, fmt.Errorf("no subtasks")
|
||||
}
|
||||
|
||||
// Execute all subtasks in sequence
|
||||
for _, subtask := range planResult.Subtasks {
|
||||
xlog.Info("[subtask] Generating parameters",
|
||||
"agent", a.Character.Name,
|
||||
"action", subtask.Action,
|
||||
"reasoning", reasoning,
|
||||
)
|
||||
|
||||
subTaskAction := a.availableActions().Find(subtask.Action)
|
||||
if subTaskAction == nil {
|
||||
xlog.Error("Action not found: %s", subtask.Action)
|
||||
return conv, fmt.Errorf("action %s not found", subtask.Action)
|
||||
}
|
||||
|
||||
subTaskReasoning := fmt.Sprintf("%s Overall goal is: %s", subtask.Reasoning, planResult.Goal)
|
||||
|
||||
params, err := a.generateParameters(job, pickTemplate, subTaskAction, conv, subTaskReasoning, maxRetries)
|
||||
if err != nil {
|
||||
xlog.Error("error generating action's parameters", "error", err)
|
||||
return conv, fmt.Errorf("error generating action's parameters: %w", err)
|
||||
|
||||
}
|
||||
actionParams = params.actionParams
|
||||
|
||||
if !job.Callback(types.ActionCurrentState{
|
||||
Job: job,
|
||||
Action: subTaskAction,
|
||||
Params: actionParams,
|
||||
Reasoning: subTaskReasoning,
|
||||
}) {
|
||||
job.Result.SetResult(types.ActionState{
|
||||
ActionCurrentState: types.ActionCurrentState{
|
||||
Job: job,
|
||||
Action: chosenAction,
|
||||
Params: actionParams,
|
||||
Reasoning: subTaskReasoning,
|
||||
},
|
||||
ActionResult: types.ActionResult{
|
||||
Result: "stopped by callback",
|
||||
},
|
||||
})
|
||||
job.Result.Conversation = conv
|
||||
job.Result.Finish(nil)
|
||||
break
|
||||
}
|
||||
|
||||
result, err := a.runAction(job, subTaskAction, actionParams)
|
||||
if err != nil {
|
||||
xlog.Error("error running action", "error", err)
|
||||
return conv, fmt.Errorf("error running action: %w", err)
|
||||
}
|
||||
|
||||
stateResult := types.ActionState{
|
||||
ActionCurrentState: types.ActionCurrentState{
|
||||
Job: job,
|
||||
Action: subTaskAction,
|
||||
Params: actionParams,
|
||||
Reasoning: subTaskReasoning,
|
||||
},
|
||||
ActionResult: result,
|
||||
}
|
||||
job.Result.SetResult(stateResult)
|
||||
job.CallbackWithResult(stateResult)
|
||||
xlog.Debug("[subtask] Action executed", "agent", a.Character.Name, "action", subTaskAction.Definition().Name, "result", result)
|
||||
conv = a.addFunctionResultToConversation(subTaskAction, actionParams, result, conv)
|
||||
}
|
||||
|
||||
return conv, nil
|
||||
}
|
||||
|
||||
// getAvailableActionsForJob returns available actions including user-defined ones for a specific job
|
||||
func (a *Agent) getAvailableActionsForJob(job *types.Job) types.Actions {
|
||||
// Start with regular available actions
|
||||
baseActions := a.availableActions()
|
||||
|
||||
|
||||
// Add user-defined actions from the job
|
||||
userTools := job.GetUserTools()
|
||||
if len(userTools) > 0 {
|
||||
@@ -439,29 +103,14 @@ func (a *Agent) getAvailableActionsForJob(job *types.Job) types.Actions {
|
||||
baseActions = append(baseActions, userDefinedActions...)
|
||||
xlog.Debug("Added user-defined actions", "definitions", userTools)
|
||||
}
|
||||
|
||||
|
||||
return baseActions
|
||||
}
|
||||
|
||||
func (a *Agent) availableActions() types.Actions {
|
||||
// defaultActions := append(a.options.userActions, action.NewReply())
|
||||
|
||||
addPlanAction := func(actions types.Actions) types.Actions {
|
||||
if !a.options.canPlan {
|
||||
return actions
|
||||
}
|
||||
plannablesActions := []string{}
|
||||
for _, a := range actions {
|
||||
if a.Plannable() {
|
||||
plannablesActions = append(plannablesActions, a.Definition().Name.String())
|
||||
}
|
||||
}
|
||||
planAction := action.NewPlan(plannablesActions)
|
||||
actions = append(actions, planAction)
|
||||
return actions
|
||||
}
|
||||
|
||||
defaultActions := append(a.mcpActions, a.options.userActions...)
|
||||
defaultActions := slices.Clone(a.options.userActions)
|
||||
|
||||
if a.options.initiateConversations && a.selfEvaluationInProgress { // && self-evaluation..
|
||||
acts := append(defaultActions, action.NewConversation())
|
||||
@@ -472,7 +121,7 @@ func (a *Agent) availableActions() types.Actions {
|
||||
// acts = append(acts, action.NewStop())
|
||||
// }
|
||||
|
||||
return addPlanAction(acts)
|
||||
return acts
|
||||
}
|
||||
|
||||
if a.options.canStopItself {
|
||||
@@ -480,14 +129,14 @@ func (a *Agent) availableActions() types.Actions {
|
||||
if a.options.enableHUD {
|
||||
acts = append(acts, action.NewState())
|
||||
}
|
||||
return addPlanAction(acts)
|
||||
return acts
|
||||
}
|
||||
|
||||
if a.options.enableHUD {
|
||||
return addPlanAction(append(defaultActions, action.NewState()))
|
||||
return append(defaultActions, action.NewState())
|
||||
}
|
||||
|
||||
return addPlanAction(defaultActions)
|
||||
return defaultActions
|
||||
}
|
||||
|
||||
func (a *Agent) prepareHUD() (promptHUD *PromptHUD) {
|
||||
@@ -502,150 +151,3 @@ func (a *Agent) prepareHUD() (promptHUD *PromptHUD) {
|
||||
ShowCharacter: a.options.showCharacter,
|
||||
}
|
||||
}
|
||||
|
||||
// pickAction picks an action based on the conversation
|
||||
func (a *Agent) pickAction(job *types.Job, templ string, messages []openai.ChatCompletionMessage, maxRetries int) (types.Action, types.ActionParams, string, error) {
|
||||
c := messages
|
||||
|
||||
xlog.Debug("[pickAction] picking action starts", "messages", messages)
|
||||
|
||||
// Get available actions including user-defined ones
|
||||
availableActions := a.getAvailableActionsForJob(job)
|
||||
|
||||
// Identify the goal of this conversation
|
||||
|
||||
if !a.options.forceReasoning || job.ToolChoice != "" {
|
||||
xlog.Debug("not forcing reasoning", "forceReasoning", a.options.forceReasoning, "ToolChoice", job.ToolChoice)
|
||||
// We also could avoid to use functions here and get just a reply from the LLM
|
||||
// and then use the reply to get the action
|
||||
thought, err := a.decision(job,
|
||||
messages,
|
||||
availableActions.ToTools(),
|
||||
job.ToolChoice,
|
||||
maxRetries)
|
||||
if err != nil {
|
||||
return nil, nil, "", err
|
||||
}
|
||||
|
||||
xlog.Debug("thought action Name", "actionName", thought.actionName)
|
||||
xlog.Debug("thought message", "message", thought.message)
|
||||
|
||||
// Find the action
|
||||
chosenAction := availableActions.Find(thought.actionName)
|
||||
if chosenAction == nil || thought.actionName == "" {
|
||||
xlog.Debug("no answer")
|
||||
|
||||
// LLM replied with an answer?
|
||||
//fmt.Errorf("no action found for intent:" + thought.actioName)
|
||||
return nil, nil, thought.message, nil
|
||||
}
|
||||
xlog.Debug(fmt.Sprintf("chosenAction: %v", chosenAction.Definition().Name))
|
||||
return chosenAction, thought.actionParams, thought.message, nil
|
||||
}
|
||||
|
||||
// Force the LLM to think and we extract a "reasoning" to pick a specific action and with which parameters
|
||||
xlog.Debug("[pickAction] forcing reasoning")
|
||||
|
||||
prompt, err := renderTemplate(templ, a.prepareHUD(), a.availableActions(), "")
|
||||
if err != nil {
|
||||
return nil, nil, "", err
|
||||
}
|
||||
// Get the LLM to think on what to do
|
||||
// and have a thought
|
||||
if !Messages(c).Exist(prompt) {
|
||||
c = append([]openai.ChatCompletionMessage{
|
||||
{
|
||||
Role: "system",
|
||||
Content: prompt,
|
||||
},
|
||||
}, c...)
|
||||
}
|
||||
|
||||
// Create a detailed prompt for reasoning that includes available actions and their properties
|
||||
reasoningPrompt := "Analyze the current situation and determine the best course of action. Consider the following:\n\n"
|
||||
reasoningPrompt += "Available Actions:\n"
|
||||
for _, act := range a.availableActions() {
|
||||
reasoningPrompt += fmt.Sprintf("- %s: %s\n", act.Definition().Name, act.Definition().Description)
|
||||
if len(act.Definition().Properties) > 0 {
|
||||
reasoningPrompt += " Properties:\n"
|
||||
for name, prop := range act.Definition().Properties {
|
||||
reasoningPrompt += fmt.Sprintf(" - %s: %s\n", name, prop.Description)
|
||||
}
|
||||
}
|
||||
reasoningPrompt += "\n"
|
||||
}
|
||||
reasoningPrompt += "\nProvide a detailed reasoning about what action would be most appropriate in this situation and why. You can also just reply with a simple message by choosing the 'reply' or 'answer' action."
|
||||
|
||||
// Get reasoning using askLLM
|
||||
reasoningMsg, err := a.askLLM(job.GetContext(),
|
||||
append(c, openai.ChatCompletionMessage{
|
||||
Role: "system",
|
||||
Content: reasoningPrompt,
|
||||
}),
|
||||
maxRetries)
|
||||
if err != nil {
|
||||
return nil, nil, "", fmt.Errorf("failed to get reasoning: %w", err)
|
||||
}
|
||||
|
||||
originalReasoning := reasoningMsg.Content
|
||||
|
||||
xlog.Debug("[pickAction] picking action", "messages", c)
|
||||
|
||||
actionsID := []string{"reply"}
|
||||
for _, m := range a.availableActions() {
|
||||
actionsID = append(actionsID, m.Definition().Name.String())
|
||||
}
|
||||
|
||||
xlog.Debug("[pickAction] actionsID", "actionsID", actionsID)
|
||||
|
||||
intentionsTools := action.NewIntention(actionsID...)
|
||||
// TODO: FORCE to select ana ction here
|
||||
// NOTE: we do not give the full conversation here to pick the action
|
||||
// to avoid hallucinations
|
||||
|
||||
// Extract an action
|
||||
params, err := a.decision(job,
|
||||
append(c, openai.ChatCompletionMessage{
|
||||
Role: "system",
|
||||
Content: "Pick the relevant action given the following reasoning: " + originalReasoning,
|
||||
}),
|
||||
types.Actions{intentionsTools}.ToTools(),
|
||||
intentionsTools.Definition().Name.String(), maxRetries)
|
||||
if err != nil {
|
||||
return nil, nil, "", fmt.Errorf("failed to get the action tool parameters: %v", err)
|
||||
}
|
||||
|
||||
if params.actionParams == nil {
|
||||
xlog.Debug("[pickAction] no action params found")
|
||||
return nil, nil, params.message, nil
|
||||
}
|
||||
|
||||
actionChoice := action.IntentResponse{}
|
||||
err = params.actionParams.Unmarshal(&actionChoice)
|
||||
if err != nil {
|
||||
return nil, nil, "", err
|
||||
}
|
||||
|
||||
if actionChoice.Tool == "" || actionChoice.Tool == "reply" {
|
||||
xlog.Debug("[pickAction] no action found, replying")
|
||||
return nil, nil, "", nil
|
||||
}
|
||||
|
||||
chosenAction := a.availableActions().Find(actionChoice.Tool)
|
||||
|
||||
xlog.Debug("[pickAction] chosenAction", "chosenAction", chosenAction, "actionName", actionChoice.Tool)
|
||||
|
||||
// // Let's double check if the action is correct by asking the LLM to judge it
|
||||
|
||||
// if chosenAction!= nil {
|
||||
// promptString:= "Given the following goal and thoughts, is the action correct? \n\n"
|
||||
// promptString+= fmt.Sprintf("Goal: %s\n", goalResponse.Goal)
|
||||
// promptString+= fmt.Sprintf("Thoughts: %s\n", originalReasoning)
|
||||
// promptString+= fmt.Sprintf("Action: %s\n", chosenAction.Definition().Name.String())
|
||||
// promptString+= fmt.Sprintf("Action description: %s\n", chosenAction.Definition().Description)
|
||||
// promptString+= fmt.Sprintf("Action parameters: %s\n", params.actionParams)
|
||||
|
||||
// }
|
||||
|
||||
return chosenAction, nil, originalReasoning, nil
|
||||
}
|
||||
|
||||
+486
-516
File diff suppressed because it is too large
Load Diff
+44
-15
@@ -37,7 +37,8 @@ var debugOptions = []types.JobOption{
|
||||
}
|
||||
|
||||
type TestAction struct {
|
||||
response map[string]string
|
||||
response map[string]string
|
||||
definition *types.ActionDefinition
|
||||
}
|
||||
|
||||
func (a *TestAction) Plannable() bool {
|
||||
@@ -55,7 +56,7 @@ func (a *TestAction) Run(c context.Context, sharedState *types.AgentSharedState,
|
||||
}
|
||||
|
||||
func (a *TestAction) Definition() types.ActionDefinition {
|
||||
return types.ActionDefinition{
|
||||
def := types.ActionDefinition{
|
||||
Name: "get_weather",
|
||||
Description: "get current weather",
|
||||
Properties: map[string]jsonschema.Definition{
|
||||
@@ -71,6 +72,11 @@ func (a *TestAction) Definition() types.ActionDefinition {
|
||||
|
||||
Required: []string{"location"},
|
||||
}
|
||||
|
||||
if a.definition != nil {
|
||||
def = *a.definition
|
||||
}
|
||||
return def
|
||||
}
|
||||
|
||||
type FakeStoreResultAction struct {
|
||||
@@ -128,7 +134,6 @@ var _ = Describe("Agent test", func() {
|
||||
WithModel(testModel),
|
||||
EnableForceReasoning,
|
||||
WithTimeout("10m"),
|
||||
WithLoopDetectionSteps(3),
|
||||
// WithRandomIdentity(),
|
||||
WithActions(&TestAction{response: map[string]string{
|
||||
"boston": testActionResult,
|
||||
@@ -153,6 +158,9 @@ var _ = Describe("Agent test", func() {
|
||||
}
|
||||
Expect(reasons).To(ContainElement(testActionResult), fmt.Sprint(res))
|
||||
Expect(reasons).To(ContainElement(testActionResult2), fmt.Sprint(res))
|
||||
|
||||
Expect(len(res.Conversation)).To(BeNumerically(">", 1), fmt.Sprint(res.Conversation))
|
||||
|
||||
reasons = []string{}
|
||||
|
||||
res = agent.Ask(
|
||||
@@ -225,11 +233,33 @@ var _ = Describe("Agent test", func() {
|
||||
WithModel(testModel),
|
||||
WithLLMAPIKey(apiKeyURL),
|
||||
WithTimeout("10m"),
|
||||
WithMaxEvaluationLoops(1),
|
||||
WithActions(
|
||||
&TestAction{response: map[string]string{
|
||||
"boston": testActionResult,
|
||||
"milan": testActionResult2,
|
||||
}},
|
||||
&TestAction{
|
||||
response: map[string]string{
|
||||
"boston": testActionResult,
|
||||
"milan": testActionResult2,
|
||||
},
|
||||
},
|
||||
&TestAction{
|
||||
response: map[string]string{
|
||||
"flight": "Flight options from Boston to Milan (April 22-26, 2025):\n• Outbound: Boston Logan (BOS) → Milan Malpensa (MXP), April 22, 2025\n - Economy: $450-650 (Alitalia, Delta, Lufthansa)\n - Business: $1,200-1,800\n - Flight time: 8h 15m (1 stop) or 9h 45m (direct)\n• Return: Milan Malpensa (MXP) → Boston Logan (BOS), April 26, 2025\n - Economy: $420-580\n - Business: $1,100-1,600\n• Total estimated cost: $870-1,230 per person\n• Best booking window: 2-3 months in advance for optimal prices",
|
||||
"hotel": "Hotel recommendations in Milan for April 22-26, 2025:\n• Luxury (4-5 stars): $200-400/night\n - Hotel Principe di Savoia: $380/night (central location)\n - Mandarin Oriental: $420/night (luxury amenities)\n• Mid-range (3-4 stars): $120-200/night\n - Hotel Spadari al Duomo: $160/night (near cathedral)\n - Hotel Milano Scala: $140/night (theater district)\n• Budget (2-3 stars): $80-120/night\n - Hotel Bernina: $95/night (near train station)\n• Total 4-night stay: $320-1,680 depending on category\n• Booking tip: Reserve early for spring season discounts",
|
||||
"car": "Car rental options in Milan for April 22-26, 2025:\n• Economy cars: $35-50/day (Fiat 500, VW Polo)\n• Compact cars: $45-65/day (Ford Focus, Opel Astra)\n• Mid-size cars: $60-85/day (BMW 3 Series, Audi A4)\n• SUV/Luxury: $90-150/day (BMW X3, Mercedes E-Class)\n• Total 4-day rental: $140-600\n• Pickup locations: Milan Malpensa Airport, Milan Central Station, city center\n• Insurance: $15-25/day additional\n• Fuel: ~$60-80 for 4 days of city driving\n• Parking: $20-40/day in city center hotels",
|
||||
"food": "Dining budget and recommendations for Milan (April 22-26, 2025):\n• Fine dining: $80-150/person (Michelin-starred restaurants)\n - Cracco: $120/person (2 Michelin stars)\n - Trussardi alla Scala: $100/person\n• Mid-range restaurants: $40-80/person\n - Luini: $15/person (famous panzerotti)\n - Piz: $25/person (authentic pizza)\n• Casual dining: $20-40/person\n - Aperitivo bars: $15-25/person\n - Street food: $8-15/person\n• Daily food budget: $60-120/person\n• Total 4-day food cost: $240-480/person\n• Must-try: Risotto alla Milanese, Osso Buco, Panettone",
|
||||
},
|
||||
definition: &types.ActionDefinition{
|
||||
Name: "search_internet",
|
||||
Description: "search the internet for information",
|
||||
Properties: map[string]jsonschema.Definition{
|
||||
"query": {
|
||||
Type: jsonschema.String,
|
||||
Description: "The query to search for",
|
||||
},
|
||||
},
|
||||
Required: []string{"query"},
|
||||
},
|
||||
},
|
||||
),
|
||||
EnablePlanning,
|
||||
EnableForceReasoning,
|
||||
@@ -241,21 +271,20 @@ var _ = Describe("Agent test", func() {
|
||||
defer agent.Stop()
|
||||
|
||||
result := agent.Ask(
|
||||
types.WithText("Use the plan tool to do two actions in sequence: search for the weather in boston and search for the weather in milan"),
|
||||
types.WithText("Create a plan for my 4-day trip from Boston to milan in April of this year (2025). I'm not sure about the dates yet, I want you to find out the best dates also according to what you find."),
|
||||
)
|
||||
Expect(len(result.State)).To(BeNumerically(">", 1))
|
||||
|
||||
Expect(len(result.Conversation)).To(BeNumerically(">", 1), fmt.Sprint(result.Conversation))
|
||||
|
||||
Expect(len(result.Plans)).To(BeNumerically(">=", 1), fmt.Sprintf("%+v", result))
|
||||
Expect(len(result.State)).To(BeNumerically(">=", 1))
|
||||
|
||||
actionsExecuted := []string{}
|
||||
actionResults := []string{}
|
||||
for _, r := range result.State {
|
||||
xlog.Info(r.Result)
|
||||
actionsExecuted = append(actionsExecuted, r.Action.Definition().Name.String())
|
||||
actionResults = append(actionResults, r.ActionResult.Result)
|
||||
}
|
||||
Expect(actionsExecuted).To(ContainElement("get_weather"), fmt.Sprint(result))
|
||||
Expect(actionsExecuted).To(ContainElement("plan"), fmt.Sprint(result))
|
||||
Expect(actionResults).To(ContainElement(testActionResult), fmt.Sprint(result))
|
||||
Expect(actionResults).To(ContainElement(testActionResult2), fmt.Sprint(result))
|
||||
Expect(actionsExecuted).To(Or(ContainElement("search_internet"), ContainElement("get_weather")), fmt.Sprint(result))
|
||||
})
|
||||
|
||||
It("Can initiate conversations", func() {
|
||||
|
||||
@@ -1,162 +0,0 @@
|
||||
package agent
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"github.com/mudler/LocalAGI/core/types"
|
||||
"github.com/mudler/LocalAGI/pkg/llm"
|
||||
"github.com/mudler/LocalAGI/pkg/xlog"
|
||||
"github.com/sashabaranov/go-openai"
|
||||
"github.com/sashabaranov/go-openai/jsonschema"
|
||||
)
|
||||
|
||||
type EvaluationResult struct {
|
||||
Satisfied bool `json:"satisfied"`
|
||||
Gaps []string `json:"gaps"`
|
||||
Reasoning string `json:"reasoning"`
|
||||
}
|
||||
|
||||
type GoalExtraction struct {
|
||||
Goal string `json:"goal"`
|
||||
Constraints []string `json:"constraints"`
|
||||
Context string `json:"context"`
|
||||
}
|
||||
|
||||
func (a *Agent) extractGoal(job *types.Job, conv []openai.ChatCompletionMessage) (*GoalExtraction, error) {
|
||||
// Create the goal extraction schema
|
||||
schema := jsonschema.Definition{
|
||||
Type: jsonschema.Object,
|
||||
Properties: map[string]jsonschema.Definition{
|
||||
"goal": {
|
||||
Type: jsonschema.String,
|
||||
Description: "The main goal or request from the user",
|
||||
},
|
||||
"constraints": {
|
||||
Type: jsonschema.Array,
|
||||
Items: &jsonschema.Definition{
|
||||
Type: jsonschema.String,
|
||||
},
|
||||
Description: "Any constraints or requirements specified by the user",
|
||||
},
|
||||
"context": {
|
||||
Type: jsonschema.String,
|
||||
Description: "Additional context that might be relevant for understanding the goal",
|
||||
},
|
||||
},
|
||||
Required: []string{"goal", "constraints", "context"},
|
||||
}
|
||||
|
||||
// Create the goal extraction prompt
|
||||
prompt := `Analyze the conversation and extract the user's main goal, any constraints, and relevant context.
|
||||
Consider the entire conversation history to understand the complete context and requirements.
|
||||
Focus on identifying the primary objective and any specific requirements or limitations mentioned.`
|
||||
|
||||
var result GoalExtraction
|
||||
err := llm.GenerateTypedJSONWithConversation(job.GetContext(), a.client,
|
||||
append(
|
||||
[]openai.ChatCompletionMessage{
|
||||
{
|
||||
Role: "system",
|
||||
Content: prompt,
|
||||
},
|
||||
},
|
||||
conv...), a.options.LLMAPI.Model, schema, &result)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("error extracting goal: %w", err)
|
||||
}
|
||||
|
||||
return &result, nil
|
||||
}
|
||||
|
||||
func (a *Agent) evaluateJob(job *types.Job, conv []openai.ChatCompletionMessage) (*EvaluationResult, error) {
|
||||
if !a.options.enableEvaluation {
|
||||
return &EvaluationResult{Satisfied: true}, nil
|
||||
}
|
||||
|
||||
// Extract the goal first
|
||||
goal, err := a.extractGoal(job, conv)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("error extracting goal: %w", err)
|
||||
}
|
||||
|
||||
// Create the evaluation schema
|
||||
schema := jsonschema.Definition{
|
||||
Type: jsonschema.Object,
|
||||
Properties: map[string]jsonschema.Definition{
|
||||
"satisfied": {
|
||||
Type: jsonschema.Boolean,
|
||||
},
|
||||
"gaps": {
|
||||
Type: jsonschema.Array,
|
||||
Items: &jsonschema.Definition{
|
||||
Type: jsonschema.String,
|
||||
},
|
||||
},
|
||||
"reasoning": {
|
||||
Type: jsonschema.String,
|
||||
},
|
||||
},
|
||||
Required: []string{"satisfied", "gaps", "reasoning"},
|
||||
}
|
||||
|
||||
// Create the evaluation prompt
|
||||
prompt := fmt.Sprintf(`Evaluate if the assistant has satisfied the user's request. Consider:
|
||||
1. The identified goal: %s
|
||||
2. Constraints and requirements: %v
|
||||
3. Context: %s
|
||||
4. The conversation history
|
||||
5. Any gaps or missing information
|
||||
6. Whether the response fully addresses the user's needs
|
||||
|
||||
Provide a detailed evaluation with specific gaps if any are found.`,
|
||||
goal.Goal,
|
||||
goal.Constraints,
|
||||
goal.Context)
|
||||
|
||||
var result EvaluationResult
|
||||
err = llm.GenerateTypedJSONWithConversation(job.GetContext(), a.client,
|
||||
append(
|
||||
[]openai.ChatCompletionMessage{
|
||||
{
|
||||
Role: "system",
|
||||
Content: prompt,
|
||||
},
|
||||
},
|
||||
conv...),
|
||||
a.options.LLMAPI.Model, schema, &result)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("error generating evaluation: %w", err)
|
||||
}
|
||||
|
||||
return &result, nil
|
||||
}
|
||||
|
||||
func (a *Agent) handleEvaluation(job *types.Job, conv []openai.ChatCompletionMessage, currentLoop int) (bool, []openai.ChatCompletionMessage, error) {
|
||||
if !a.options.enableEvaluation || currentLoop >= a.options.maxEvaluationLoops {
|
||||
return true, conv, nil
|
||||
}
|
||||
|
||||
result, err := a.evaluateJob(job, conv)
|
||||
if err != nil {
|
||||
return false, conv, err
|
||||
}
|
||||
|
||||
if result.Satisfied {
|
||||
return true, conv, nil
|
||||
}
|
||||
|
||||
// If there are gaps, we need to address them
|
||||
if len(result.Gaps) > 0 {
|
||||
// Add the evaluation result to the conversation
|
||||
conv = append(conv, openai.ChatCompletionMessage{
|
||||
Role: "system",
|
||||
Content: fmt.Sprintf("Evaluation found gaps that need to be addressed:\n%s\nReasoning: %s",
|
||||
result.Gaps, result.Reasoning),
|
||||
})
|
||||
|
||||
xlog.Debug("Evaluation found gaps, incrementing loop count", "loop", currentLoop+1)
|
||||
return false, conv, nil
|
||||
}
|
||||
|
||||
return true, conv, nil
|
||||
}
|
||||
@@ -8,6 +8,7 @@ import (
|
||||
|
||||
"github.com/mudler/LocalAGI/core/types"
|
||||
"github.com/mudler/LocalAGI/pkg/xlog"
|
||||
"github.com/mudler/cogito"
|
||||
"github.com/sashabaranov/go-openai"
|
||||
)
|
||||
|
||||
@@ -17,7 +18,7 @@ func (a *Agent) knowledgeBaseLookup(job *types.Job, conv Messages) Messages {
|
||||
xlog.Debug("[Knowledge Base Lookup] Disabled, skipping", "agent", a.Character.Name)
|
||||
return conv
|
||||
}
|
||||
|
||||
|
||||
var obs *types.Observable
|
||||
if job != nil && job.Obs != nil && a.observer != nil {
|
||||
obs = a.observer.NewObservable()
|
||||
@@ -90,7 +91,7 @@ func (a *Agent) knowledgeBaseLookup(job *types.Job, conv Messages) Messages {
|
||||
|
||||
if obs != nil {
|
||||
obs.Completion = &types.Completion{
|
||||
Conversation: []openai.ChatCompletionMessage{systemMessage},
|
||||
Conversation: []openai.ChatCompletionMessage{systemMessage},
|
||||
}
|
||||
a.observer.Update(*obs)
|
||||
}
|
||||
@@ -125,13 +126,12 @@ func (a *Agent) saveCurrentConversation(conv Messages) {
|
||||
xlog.Info("Saving conversation", "agent", a.Character.Name, "conversation size", len(conv))
|
||||
|
||||
if a.options.enableSummaryMemory && len(conv) > 0 {
|
||||
msg, err := a.askLLM(a.context.Context, []openai.ChatCompletionMessage{{
|
||||
Role: "user",
|
||||
Content: "Summarize the conversation below, keep the highlights as a bullet list:\n" + Messages(conv).String(),
|
||||
}}, maxRetries)
|
||||
fragment := cogito.NewEmptyFragment().AddStartMessage("user", "Summarize the conversation below, keep the highlights as a bullet list:\n"+Messages(conv).String())
|
||||
fragment, err := a.llm.Ask(a.context.Context, fragment)
|
||||
if err != nil {
|
||||
xlog.Error("Error summarizing conversation", "error", err)
|
||||
}
|
||||
msg := fragment.LastMessage()
|
||||
|
||||
if err := a.options.ragdb.Store(msg.Content); err != nil {
|
||||
xlog.Error("Error storing into memory", "error", err)
|
||||
|
||||
+110
-114
@@ -3,18 +3,21 @@ package agent
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"os"
|
||||
"os/exec"
|
||||
"time"
|
||||
|
||||
mcp "github.com/metoro-io/mcp-golang"
|
||||
"github.com/metoro-io/mcp-golang/transport/http"
|
||||
stdioTransport "github.com/metoro-io/mcp-golang/transport/stdio"
|
||||
"net/http"
|
||||
|
||||
"github.com/modelcontextprotocol/go-sdk/mcp"
|
||||
"github.com/mudler/LocalAGI/core/types"
|
||||
"github.com/mudler/LocalAGI/pkg/stdio"
|
||||
"github.com/mudler/LocalAGI/pkg/xlog"
|
||||
|
||||
"github.com/sashabaranov/go-openai/jsonschema"
|
||||
)
|
||||
|
||||
var _ types.Action = &mcpAction{}
|
||||
var _ types.Action = &mcpWrapperAction{}
|
||||
|
||||
type MCPServer struct {
|
||||
URL string `json:"url"`
|
||||
@@ -27,44 +30,20 @@ type MCPSTDIOServer struct {
|
||||
Cmd string `json:"cmd"`
|
||||
}
|
||||
|
||||
type mcpAction struct {
|
||||
mcpClient *mcp.Client
|
||||
type mcpWrapperAction struct {
|
||||
mcpClient *mcp.ClientSession
|
||||
inputSchema ToolInputSchema
|
||||
toolName string
|
||||
toolDescription string
|
||||
}
|
||||
|
||||
func (a *mcpAction) Plannable() bool {
|
||||
return true
|
||||
func (m *mcpWrapperAction) Run(ctx context.Context, sharedState *types.AgentSharedState, params types.ActionParams) (types.ActionResult, error) {
|
||||
// We don't call the method here, it is used by cogito.
|
||||
// We will just use these to have a list of actions that MCP server provides for resolving internal states
|
||||
return types.ActionResult{Result: "MCP action called"}, fmt.Errorf("not implemented")
|
||||
}
|
||||
|
||||
func (m *mcpAction) Run(ctx context.Context, sharedState *types.AgentSharedState, params types.ActionParams) (types.ActionResult, error) {
|
||||
resp, err := m.mcpClient.CallTool(ctx, m.toolName, params)
|
||||
if err != nil {
|
||||
xlog.Error("Failed to call tool", "error", err.Error())
|
||||
return types.ActionResult{}, err
|
||||
}
|
||||
|
||||
xlog.Debug("MCP response", "response", resp)
|
||||
|
||||
textResult := ""
|
||||
for _, c := range resp.Content {
|
||||
switch c.Type {
|
||||
case mcp.ContentTypeText:
|
||||
textResult += c.TextContent.Text + "\n"
|
||||
case mcp.ContentTypeImage:
|
||||
xlog.Error("Image content not supported yet")
|
||||
case mcp.ContentTypeEmbeddedResource:
|
||||
xlog.Error("Embedded resource content not supported yet")
|
||||
}
|
||||
}
|
||||
|
||||
return types.ActionResult{
|
||||
Result: textResult,
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (m *mcpAction) Definition() types.ActionDefinition {
|
||||
func (m *mcpWrapperAction) Definition() types.ActionDefinition {
|
||||
props := map[string]jsonschema.Definition{}
|
||||
dat, err := json.Marshal(m.inputSchema.Properties)
|
||||
if err != nil {
|
||||
@@ -87,87 +66,106 @@ type ToolInputSchema struct {
|
||||
Required []string `json:"required,omitempty"`
|
||||
}
|
||||
|
||||
func (a *Agent) addTools(client *mcp.Client) (types.Actions, error) {
|
||||
|
||||
func (a *Agent) addTools(client *mcp.ClientSession) (types.Actions, error) {
|
||||
var generatedActions types.Actions
|
||||
xlog.Debug("Initializing client")
|
||||
// Initialize the client
|
||||
response, e := client.Initialize(a.context)
|
||||
if e != nil {
|
||||
xlog.Error("Failed to initialize client", "error", e.Error())
|
||||
return nil, e
|
||||
|
||||
tools, err := client.ListTools(a.context, nil)
|
||||
if err != nil {
|
||||
xlog.Error("Failed to list tools", "error", err.Error())
|
||||
return nil, err
|
||||
}
|
||||
|
||||
xlog.Debug("Client initialized: %v", response.Instructions)
|
||||
for _, t := range tools.Tools {
|
||||
desc := ""
|
||||
if t.Description != "" {
|
||||
desc = t.Description
|
||||
}
|
||||
|
||||
var cursor *string
|
||||
for {
|
||||
tools, err := client.ListTools(a.context, cursor)
|
||||
xlog.Debug("Tool", "name", t.Name, "description", desc)
|
||||
|
||||
dat, err := json.Marshal(t.InputSchema)
|
||||
if err != nil {
|
||||
xlog.Error("Failed to list tools", "error", err.Error())
|
||||
return nil, err
|
||||
xlog.Error("Failed to marshal input schema", "error", err.Error())
|
||||
}
|
||||
|
||||
for _, t := range tools.Tools {
|
||||
desc := ""
|
||||
if t.Description != nil {
|
||||
desc = *t.Description
|
||||
}
|
||||
xlog.Debug("Input schema", "tool", t.Name, "schema", string(dat))
|
||||
|
||||
xlog.Debug("Tool", "name", t.Name, "description", desc)
|
||||
|
||||
dat, err := json.Marshal(t.InputSchema)
|
||||
if err != nil {
|
||||
xlog.Error("Failed to marshal input schema", "error", err.Error())
|
||||
}
|
||||
|
||||
xlog.Debug("Input schema", "tool", t.Name, "schema", string(dat))
|
||||
|
||||
// XXX: This is a wild guess, to verify (data types might be incompatible)
|
||||
var inputSchema ToolInputSchema
|
||||
err = json.Unmarshal(dat, &inputSchema)
|
||||
if err != nil {
|
||||
xlog.Error("Failed to unmarshal input schema", "error", err.Error())
|
||||
}
|
||||
|
||||
// Create a new action with Client + tool
|
||||
generatedActions = append(generatedActions, &mcpAction{
|
||||
mcpClient: client,
|
||||
toolName: t.Name,
|
||||
inputSchema: inputSchema,
|
||||
toolDescription: desc,
|
||||
})
|
||||
// XXX: This is a wild guess, to verify (data types might be incompatible)
|
||||
var inputSchema ToolInputSchema
|
||||
err = json.Unmarshal(dat, &inputSchema)
|
||||
if err != nil {
|
||||
xlog.Error("Failed to unmarshal input schema", "error", err.Error())
|
||||
}
|
||||
|
||||
if tools.NextCursor == nil {
|
||||
break // No more pages
|
||||
}
|
||||
cursor = tools.NextCursor
|
||||
// Create a new action with Client + tool
|
||||
generatedActions = append(generatedActions, &mcpWrapperAction{
|
||||
mcpClient: client,
|
||||
toolName: t.Name,
|
||||
inputSchema: inputSchema,
|
||||
toolDescription: desc,
|
||||
})
|
||||
}
|
||||
|
||||
return generatedActions, nil
|
||||
}
|
||||
|
||||
// bearerTokenRoundTripper is a custom roundtripper that injects a bearer token
|
||||
// into HTTP requests
|
||||
type bearerTokenRoundTripper struct {
|
||||
token string
|
||||
base http.RoundTripper
|
||||
}
|
||||
|
||||
// RoundTrip implements the http.RoundTripper interface
|
||||
func (rt *bearerTokenRoundTripper) RoundTrip(req *http.Request) (*http.Response, error) {
|
||||
if rt.token != "" {
|
||||
req.Header.Set("Authorization", "Bearer "+rt.token)
|
||||
}
|
||||
return rt.base.RoundTrip(req)
|
||||
}
|
||||
|
||||
// newBearerTokenRoundTripper creates a new roundtripper that injects the given token
|
||||
func newBearerTokenRoundTripper(token string, base http.RoundTripper) http.RoundTripper {
|
||||
if base == nil {
|
||||
base = http.DefaultTransport
|
||||
}
|
||||
return &bearerTokenRoundTripper{
|
||||
token: token,
|
||||
base: base,
|
||||
}
|
||||
}
|
||||
|
||||
func (a *Agent) initMCPActions() error {
|
||||
a.closeMCPSTDIOServers() // Make sure we stop all previous servers if any is active
|
||||
|
||||
a.mcpActions = nil
|
||||
a.mcpActionDefinitions = nil
|
||||
var err error
|
||||
|
||||
generatedActions := types.Actions{}
|
||||
client := mcp.NewClient(&mcp.Implementation{Name: "LocalAI", Version: "v1.0.0"}, nil)
|
||||
|
||||
// Connect to a server over stdin/stdout.
|
||||
|
||||
// MCP HTTP Servers
|
||||
for _, mcpServer := range a.options.mcpServers {
|
||||
transport := http.NewHTTPClientTransport("/mcp")
|
||||
transport.WithBaseURL(mcpServer.URL)
|
||||
if mcpServer.Token != "" {
|
||||
transport.WithHeader("Authorization", "Bearer "+mcpServer.Token)
|
||||
// Create HTTP client with custom roundtripper for bearer token injection
|
||||
httpclient := &http.Client{
|
||||
Timeout: 360 * time.Second,
|
||||
Transport: newBearerTokenRoundTripper(mcpServer.Token, http.DefaultTransport),
|
||||
}
|
||||
|
||||
transport := &mcp.SSEClientTransport{HTTPClient: httpclient, Endpoint: mcpServer.URL}
|
||||
|
||||
// Create a new client
|
||||
client := mcp.NewClient(transport)
|
||||
session, err := client.Connect(a.context, transport, nil)
|
||||
if err != nil {
|
||||
xlog.Error("Failed to connect to MCP server", "server", mcpServer, "error", err.Error())
|
||||
continue
|
||||
}
|
||||
a.mcpSessions = append(a.mcpSessions, session)
|
||||
|
||||
xlog.Debug("Adding tools for MCP server", "server", mcpServer)
|
||||
actions, err := a.addTools(client)
|
||||
actions, err := a.addTools(session)
|
||||
if err != nil {
|
||||
xlog.Error("Failed to add tools for MCP server", "server", mcpServer, "error", err.Error())
|
||||
}
|
||||
@@ -179,47 +177,45 @@ func (a *Agent) initMCPActions() error {
|
||||
a.closeMCPSTDIOServers() // Make sure we stop all previous servers if any is active
|
||||
|
||||
if a.options.mcpPrepareScript != "" {
|
||||
xlog.Debug("Preparing MCP box", "script", a.options.mcpPrepareScript)
|
||||
client := stdio.NewClient(a.options.mcpBoxURL)
|
||||
client.RunProcess(a.context, "/bin/bash", []string{"-c", a.options.mcpPrepareScript}, []string{})
|
||||
xlog.Debug("Preparing MCP", "script", a.options.mcpPrepareScript)
|
||||
|
||||
prepareCmd := exec.Command("/bin/bash", "-c", a.options.mcpPrepareScript)
|
||||
output, err := prepareCmd.CombinedOutput()
|
||||
if err != nil {
|
||||
xlog.Error("Failed with error: '%s' - %s", err.Error(), output)
|
||||
}
|
||||
xlog.Debug("Prepared MCP: \n%s", output)
|
||||
}
|
||||
|
||||
for _, mcpStdioServer := range a.options.mcpStdioServers {
|
||||
client := stdio.NewClient(a.options.mcpBoxURL)
|
||||
p, err := client.CreateProcess(a.context,
|
||||
mcpStdioServer.Cmd,
|
||||
mcpStdioServer.Args,
|
||||
mcpStdioServer.Env,
|
||||
a.Character.Name)
|
||||
if err != nil {
|
||||
xlog.Error("Failed to create process", "error", err.Error())
|
||||
continue
|
||||
}
|
||||
read, writer, err := client.GetProcessIO(p.ID)
|
||||
if err != nil {
|
||||
xlog.Error("Failed to get process IO", "error", err.Error())
|
||||
continue
|
||||
}
|
||||
|
||||
transport := stdioTransport.NewStdioServerTransportWithIO(read, writer)
|
||||
command := exec.Command(mcpStdioServer.Cmd, mcpStdioServer.Args...)
|
||||
command.Env = os.Environ()
|
||||
command.Env = append(command.Env, mcpStdioServer.Env...)
|
||||
|
||||
// Create a new client
|
||||
mcpClient := mcp.NewClient(transport)
|
||||
session, err := client.Connect(a.context, &mcp.CommandTransport{
|
||||
Command: command}, nil)
|
||||
if err != nil {
|
||||
xlog.Error("Failed to connect to MCP server", "server", mcpStdioServer, "error", err.Error())
|
||||
continue
|
||||
}
|
||||
a.mcpSessions = append(a.mcpSessions, session)
|
||||
|
||||
xlog.Debug("Adding tools for MCP server (stdio)", "server", mcpStdioServer)
|
||||
actions, err := a.addTools(mcpClient)
|
||||
actions, err := a.addTools(session)
|
||||
if err != nil {
|
||||
xlog.Error("Failed to add tools for MCP server", "server", mcpStdioServer, "error", err.Error())
|
||||
}
|
||||
generatedActions = append(generatedActions, actions...)
|
||||
}
|
||||
|
||||
a.mcpActions = generatedActions
|
||||
a.mcpActionDefinitions = generatedActions
|
||||
|
||||
return err
|
||||
}
|
||||
|
||||
func (a *Agent) closeMCPSTDIOServers() {
|
||||
client := stdio.NewClient(a.options.mcpBoxURL)
|
||||
client.StopGroup(a.Character.Name)
|
||||
for _, s := range a.mcpSessions {
|
||||
s.Close()
|
||||
}
|
||||
}
|
||||
|
||||
+13
-4
@@ -14,6 +14,7 @@ type Observer interface {
|
||||
NewObservable() *types.Observable
|
||||
Update(types.Observable)
|
||||
History() []types.Observable
|
||||
ClearHistory()
|
||||
}
|
||||
|
||||
type SSEObserver struct {
|
||||
@@ -21,8 +22,8 @@ type SSEObserver struct {
|
||||
maxID int32
|
||||
manager sse.Manager
|
||||
|
||||
mutex sync.Mutex
|
||||
history []types.Observable
|
||||
mutex sync.Mutex
|
||||
history []types.Observable
|
||||
historyLast int
|
||||
}
|
||||
|
||||
@@ -36,8 +37,8 @@ func NewSSEObserver(agent string, manager sse.Manager) *SSEObserver {
|
||||
}
|
||||
|
||||
func (s *SSEObserver) NewObservable() *types.Observable {
|
||||
id := atomic.AddInt32(&s.maxID, 1)
|
||||
|
||||
id := atomic.AddInt32(&s.maxID, 1)
|
||||
|
||||
return &types.Observable{
|
||||
ID: id - 1,
|
||||
Agent: s.agent,
|
||||
@@ -86,3 +87,11 @@ func (s *SSEObserver) History() []types.Observable {
|
||||
|
||||
return h
|
||||
}
|
||||
|
||||
func (s *SSEObserver) ClearHistory() {
|
||||
s.mutex.Lock()
|
||||
defer s.mutex.Unlock()
|
||||
|
||||
s.history = make([]types.Observable, 100)
|
||||
s.historyLast = 0
|
||||
}
|
||||
|
||||
+33
-23
@@ -12,10 +12,13 @@ import (
|
||||
type Option func(*options) error
|
||||
|
||||
type llmOptions struct {
|
||||
APIURL string
|
||||
APIKey string
|
||||
Model string
|
||||
MultimodalModel string
|
||||
APIURL string
|
||||
APIKey string
|
||||
Model string
|
||||
MultimodalModel string
|
||||
TranscriptionModel string
|
||||
TranscriptionLanguage string
|
||||
TTSModel string
|
||||
}
|
||||
|
||||
type options struct {
|
||||
@@ -30,7 +33,6 @@ type options struct {
|
||||
|
||||
canStopItself bool
|
||||
initiateConversations bool
|
||||
loopDetectionSteps int
|
||||
forceReasoning bool
|
||||
canPlan bool
|
||||
characterfile string
|
||||
@@ -58,7 +60,6 @@ type options struct {
|
||||
|
||||
mcpServers []MCPServer
|
||||
mcpStdioServers []MCPSTDIOServer
|
||||
mcpBoxURL string
|
||||
mcpPrepareScript string
|
||||
newConversationsSubscribers []func(openai.ChatCompletionMessage)
|
||||
|
||||
@@ -76,12 +77,14 @@ func defaultOptions() *options {
|
||||
return &options{
|
||||
parallelJobs: 1,
|
||||
periodicRuns: 15 * time.Minute,
|
||||
loopDetectionSteps: 10,
|
||||
maxEvaluationLoops: 2,
|
||||
enableEvaluation: false,
|
||||
LLMAPI: llmOptions{
|
||||
APIURL: "http://localhost:8080",
|
||||
Model: "gpt-4",
|
||||
APIURL: "http://localhost:8080",
|
||||
Model: "gpt-4",
|
||||
TranscriptionModel: "whisper-1",
|
||||
TranscriptionLanguage: "",
|
||||
TTSModel: "tts-1",
|
||||
},
|
||||
character: Character{
|
||||
Name: "",
|
||||
@@ -131,13 +134,6 @@ func WithTimeout(timeout string) Option {
|
||||
}
|
||||
}
|
||||
|
||||
func WithLoopDetectionSteps(steps int) Option {
|
||||
return func(o *options) error {
|
||||
o.loopDetectionSteps = steps
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
func WithConversationsPath(path string) Option {
|
||||
return func(o *options) error {
|
||||
o.conversationsPath = path
|
||||
@@ -238,13 +234,6 @@ func WithMCPSTDIOServers(servers ...MCPSTDIOServer) Option {
|
||||
}
|
||||
}
|
||||
|
||||
func WithMCPBoxURL(url string) Option {
|
||||
return func(o *options) error {
|
||||
o.mcpBoxURL = url
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
func WithMCPPrepareScript(script string) Option {
|
||||
return func(o *options) error {
|
||||
o.mcpPrepareScript = script
|
||||
@@ -425,3 +414,24 @@ func EnableEvaluation() Option {
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
func WithTranscriptionModel(model string) Option {
|
||||
return func(o *options) error {
|
||||
o.LLMAPI.TranscriptionModel = model
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
func WithTranscriptionLanguage(language string) Option {
|
||||
return func(o *options) error {
|
||||
o.LLMAPI.TranscriptionLanguage = language
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
func WithTTSModel(model string) Option {
|
||||
return func(o *options) error {
|
||||
o.LLMAPI.TTSModel = model
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,6 +1,8 @@
|
||||
package agent
|
||||
|
||||
import "github.com/mudler/LocalAGI/core/types"
|
||||
|
||||
type DynamicPrompt interface {
|
||||
Render(a *Agent) (string, error)
|
||||
Render(a *Agent) (types.PromptResult, error)
|
||||
Role() string
|
||||
}
|
||||
|
||||
+19
-44
@@ -2,18 +2,32 @@ package agent
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"html/template"
|
||||
"text/template"
|
||||
"time"
|
||||
|
||||
"github.com/Masterminds/sprig/v3"
|
||||
"github.com/mudler/LocalAGI/core/types"
|
||||
"github.com/sashabaranov/go-openai"
|
||||
)
|
||||
|
||||
func templateBase(templateName, templatetext string) (*template.Template, error) {
|
||||
return template.New(templateName).Funcs(sprig.FuncMap()).Parse(templatetext)
|
||||
}
|
||||
|
||||
func templateExecute(template *template.Template, data interface{}) (string, error) {
|
||||
prompt := bytes.NewBuffer([]byte{})
|
||||
err := template.Execute(prompt, data)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
return prompt.String(), nil
|
||||
}
|
||||
|
||||
func renderTemplate(templ string, hud *PromptHUD, actions types.Actions, reasoning string) (string, error) {
|
||||
// prepare the prompt
|
||||
prompt := bytes.NewBuffer([]byte{})
|
||||
|
||||
promptTemplate, err := template.New("pickAction").Parse(templ)
|
||||
promptTemplate, err := templateBase("pickAction", templ)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
@@ -34,7 +48,7 @@ func renderTemplate(templ string, hud *PromptHUD, actions types.Actions, reasoni
|
||||
Actions: definitions,
|
||||
HUD: hud,
|
||||
Reasoning: reasoning,
|
||||
Time: time.Now().Format(time.RFC3339),
|
||||
Time: time.Now().UTC().Format(time.RFC1123),
|
||||
})
|
||||
if err != nil {
|
||||
return "", err
|
||||
@@ -80,7 +94,8 @@ Current State:
|
||||
- Current Goal: {{if .CurrentState.Goal}}{{.CurrentState.Goal}}{{else}}None{{end}}
|
||||
- Action History: {{range .CurrentState.DoneHistory}}{{.}} {{end}}
|
||||
- Short-term Memory: {{range .CurrentState.Memories}}{{.}} {{end}}{{end}}
|
||||
Current Time: {{.Time}}`
|
||||
|
||||
Current Time and Date: {{.Time}}`
|
||||
|
||||
const pickSelfTemplate = `
|
||||
You are an autonomous AI agent with a defined character and state (as shown above).
|
||||
@@ -93,7 +108,6 @@ Guidelines:
|
||||
4. Update your state appropriately
|
||||
|
||||
When making decisions:
|
||||
- Use the "reply" tool to provide final responses
|
||||
- Update your state using appropriate tools
|
||||
- Plan complex tasks using the planning tool
|
||||
- Consider both immediate and long-term goals
|
||||
@@ -104,43 +118,4 @@ Remember:
|
||||
- Keep track of your progress and state
|
||||
- Be proactive in addressing potential issues
|
||||
|
||||
Available Tools:
|
||||
{{range .Actions -}}
|
||||
- {{.Name}}: {{.Description }}
|
||||
{{ end }}
|
||||
|
||||
{{if .Reasoning}}Previous Reasoning: {{.Reasoning}}{{end}}
|
||||
` + hudTemplate
|
||||
|
||||
const reSelfEvalTemplate = pickSelfTemplate
|
||||
|
||||
const pickActionTemplate = hudTemplate + `
|
||||
Your only task is to analyze the conversation and determine a goal and the best tool to use, or just a final response if we have fullfilled the goal.
|
||||
|
||||
Guidelines:
|
||||
1. Review the current state, what was done already and context
|
||||
2. Consider available tools and their purposes
|
||||
3. Plan your approach carefully
|
||||
4. Explain your reasoning clearly
|
||||
|
||||
When choosing actions:
|
||||
- Use "reply" or "answer" tools for direct responses
|
||||
- Select appropriate tools for specific tasks
|
||||
- Consider the impact of each action
|
||||
- Plan for potential challenges
|
||||
|
||||
Decision Process:
|
||||
1. Analyze the situation
|
||||
2. Consider available options
|
||||
3. Choose the best course of action
|
||||
4. Explain your reasoning
|
||||
5. Execute the chosen action
|
||||
|
||||
Available Tools:
|
||||
{{range .Actions -}}
|
||||
- {{.Name}}: {{.Description }}
|
||||
{{ end }}
|
||||
|
||||
{{if .Reasoning}}Previous Reasoning: {{.Reasoning}}{{end}}`
|
||||
|
||||
const reEvalTemplate = pickActionTemplate
|
||||
|
||||
+32
-19
@@ -43,18 +43,20 @@ type AgentConfig struct {
|
||||
MCPServers []agent.MCPServer `json:"mcp_servers" form:"mcp_servers"`
|
||||
MCPSTDIOServers []agent.MCPSTDIOServer `json:"mcp_stdio_servers" form:"mcp_stdio_servers"`
|
||||
MCPPrepareScript string `json:"mcp_prepare_script" form:"mcp_prepare_script"`
|
||||
MCPBoxURL string `json:"mcp_box_url" form:"mcp_box_url"`
|
||||
Filters []FiltersConfig `json:"filters" form:"filters"`
|
||||
|
||||
Description string `json:"description" form:"description"`
|
||||
|
||||
Model string `json:"model" form:"model"`
|
||||
MultimodalModel string `json:"multimodal_model" form:"multimodal_model"`
|
||||
APIURL string `json:"api_url" form:"api_url"`
|
||||
APIKey string `json:"api_key" form:"api_key"`
|
||||
LocalRAGURL string `json:"local_rag_url" form:"local_rag_url"`
|
||||
LocalRAGAPIKey string `json:"local_rag_api_key" form:"local_rag_api_key"`
|
||||
LastMessageDuration string `json:"last_message_duration" form:"last_message_duration"`
|
||||
Model string `json:"model" form:"model"`
|
||||
MultimodalModel string `json:"multimodal_model" form:"multimodal_model"`
|
||||
TranscriptionModel string `json:"transcription_model" form:"transcription_model"`
|
||||
TranscriptionLanguage string `json:"transcription_language" form:"transcription_language"`
|
||||
TTSModel string `json:"tts_model" form:"tts_model"`
|
||||
APIURL string `json:"api_url" form:"api_url"`
|
||||
APIKey string `json:"api_key" form:"api_key"`
|
||||
LocalRAGURL string `json:"local_rag_url" form:"local_rag_url"`
|
||||
LocalRAGAPIKey string `json:"local_rag_api_key" form:"local_rag_api_key"`
|
||||
LastMessageDuration string `json:"last_message_duration" form:"last_message_duration"`
|
||||
|
||||
Name string `json:"name" form:"name"`
|
||||
HUD bool `json:"hud" form:"hud"`
|
||||
@@ -68,7 +70,6 @@ type AgentConfig struct {
|
||||
EnableKnowledgeBase bool `json:"enable_kb" form:"enable_kb"`
|
||||
EnableReasoning bool `json:"enable_reasoning" form:"enable_reasoning"`
|
||||
KnowledgeBaseResults int `json:"kb_results" form:"kb_results"`
|
||||
LoopDetectionSteps int `json:"loop_detection_steps" form:"loop_detection_steps"`
|
||||
CanStopItself bool `json:"can_stop_itself" form:"can_stop_itself"`
|
||||
SystemPrompt string `json:"system_prompt" form:"system_prompt"`
|
||||
LongTermMemory bool `json:"long_term_memory" form:"long_term_memory"`
|
||||
@@ -146,6 +147,27 @@ func NewAgentConfigMeta(
|
||||
DefaultValue: "",
|
||||
Tags: config.Tags{Section: "ModelSettings"},
|
||||
},
|
||||
{
|
||||
Name: "transcription_model",
|
||||
Label: "Transcription Model",
|
||||
Type: "text",
|
||||
DefaultValue: "",
|
||||
Tags: config.Tags{Section: "ModelSettings"},
|
||||
},
|
||||
{
|
||||
Name: "transcription_language",
|
||||
Label: "Transcription Language",
|
||||
Type: "text",
|
||||
DefaultValue: "",
|
||||
Tags: config.Tags{Section: "ModelSettings"},
|
||||
},
|
||||
{
|
||||
Name: "tts_model",
|
||||
Label: "TTS Model",
|
||||
Type: "text",
|
||||
DefaultValue: "",
|
||||
Tags: config.Tags{Section: "ModelSettings"},
|
||||
},
|
||||
{
|
||||
Name: "api_url",
|
||||
Label: "API URL",
|
||||
@@ -269,15 +291,6 @@ func NewAgentConfigMeta(
|
||||
HelpText: "Enable agent to explain its reasoning process",
|
||||
Tags: config.Tags{Section: "AdvancedSettings"},
|
||||
},
|
||||
{
|
||||
Name: "loop_detection_steps",
|
||||
Label: "Max Loop Detection Steps",
|
||||
Type: "number",
|
||||
DefaultValue: 5,
|
||||
Min: 1,
|
||||
Step: 1,
|
||||
Tags: config.Tags{Section: "AdvancedSettings"},
|
||||
},
|
||||
{
|
||||
Name: "parallel_jobs",
|
||||
Label: "Parallel Jobs",
|
||||
@@ -301,7 +314,7 @@ func NewAgentConfigMeta(
|
||||
Label: "MCP Prepare Script",
|
||||
Type: "textarea",
|
||||
DefaultValue: "",
|
||||
HelpText: "Script to prepare the MCP box",
|
||||
HelpText: "Script to prepare for running MCP servers",
|
||||
Tags: config.Tags{Section: "AdvancedSettings"},
|
||||
},
|
||||
{
|
||||
|
||||
+81
-68
@@ -26,21 +26,21 @@ import (
|
||||
|
||||
type AgentPool struct {
|
||||
sync.Mutex
|
||||
file string
|
||||
pooldir string
|
||||
pool AgentPoolData
|
||||
agents map[string]*Agent
|
||||
managers map[string]sse.Manager
|
||||
agentStatus map[string]*Status
|
||||
apiURL, defaultModel, defaultMultimodalModel string
|
||||
mcpBoxURL string
|
||||
imageModel, localRAGAPI, localRAGKey, apiKey string
|
||||
availableActions func(*AgentConfig) func(ctx context.Context, pool *AgentPool) []types.Action
|
||||
connectors func(*AgentConfig) []Connector
|
||||
dynamicPrompt func(*AgentConfig) []DynamicPrompt
|
||||
filters func(*AgentConfig) types.JobFilters
|
||||
timeout string
|
||||
conversationLogs string
|
||||
file string
|
||||
pooldir string
|
||||
pool AgentPoolData
|
||||
agents map[string]*Agent
|
||||
managers map[string]sse.Manager
|
||||
agentStatus map[string]*Status
|
||||
apiURL, defaultModel, defaultMultimodalModel, defaultTTSModel string
|
||||
defaultTranscriptionModel, defaultTranscriptionLanguage string
|
||||
imageModel, localRAGAPI, localRAGKey, apiKey string
|
||||
availableActions func(*AgentConfig) func(ctx context.Context, pool *AgentPool) []types.Action
|
||||
connectors func(*AgentConfig) []Connector
|
||||
dynamicPrompt func(*AgentConfig) func(ctx context.Context, pool *AgentPool) []DynamicPrompt
|
||||
filters func(*AgentConfig) types.JobFilters
|
||||
timeout string
|
||||
conversationLogs string
|
||||
}
|
||||
|
||||
type Status struct {
|
||||
@@ -74,11 +74,11 @@ func loadPoolFromFile(path string) (*AgentPoolData, error) {
|
||||
}
|
||||
|
||||
func NewAgentPool(
|
||||
defaultModel, defaultMultimodalModel, imageModel, apiURL, apiKey, directory, mcpBoxURL string,
|
||||
defaultModel, defaultMultimodalModel, defaultTranscriptionModel, defaultTranscriptionLanguage, defaultTTSModel, imageModel, apiURL, apiKey, directory string,
|
||||
LocalRAGAPI string,
|
||||
availableActions func(*AgentConfig) func(ctx context.Context, pool *AgentPool) []types.Action,
|
||||
connectors func(*AgentConfig) []Connector,
|
||||
promptBlocks func(*AgentConfig) []DynamicPrompt,
|
||||
promptBlocks func(*AgentConfig) func(ctx context.Context, pool *AgentPool) []DynamicPrompt,
|
||||
filters func(*AgentConfig) types.JobFilters,
|
||||
timeout string,
|
||||
withLogs bool,
|
||||
@@ -96,25 +96,27 @@ func NewAgentPool(
|
||||
if _, err := os.Stat(poolfile); err != nil {
|
||||
// file does not exist, create a new pool
|
||||
return &AgentPool{
|
||||
file: poolfile,
|
||||
pooldir: directory,
|
||||
apiURL: apiURL,
|
||||
defaultModel: defaultModel,
|
||||
defaultMultimodalModel: defaultMultimodalModel,
|
||||
mcpBoxURL: mcpBoxURL,
|
||||
imageModel: imageModel,
|
||||
localRAGAPI: LocalRAGAPI,
|
||||
apiKey: apiKey,
|
||||
agents: make(map[string]*Agent),
|
||||
pool: make(map[string]AgentConfig),
|
||||
agentStatus: make(map[string]*Status),
|
||||
managers: make(map[string]sse.Manager),
|
||||
connectors: connectors,
|
||||
availableActions: availableActions,
|
||||
dynamicPrompt: promptBlocks,
|
||||
filters: filters,
|
||||
timeout: timeout,
|
||||
conversationLogs: conversationPath,
|
||||
file: poolfile,
|
||||
pooldir: directory,
|
||||
apiURL: apiURL,
|
||||
defaultModel: defaultModel,
|
||||
defaultMultimodalModel: defaultMultimodalModel,
|
||||
defaultTranscriptionModel: defaultTranscriptionModel,
|
||||
defaultTranscriptionLanguage: defaultTranscriptionLanguage,
|
||||
defaultTTSModel: defaultTTSModel,
|
||||
imageModel: imageModel,
|
||||
localRAGAPI: LocalRAGAPI,
|
||||
apiKey: apiKey,
|
||||
agents: make(map[string]*Agent),
|
||||
pool: make(map[string]AgentConfig),
|
||||
agentStatus: make(map[string]*Status),
|
||||
managers: make(map[string]sse.Manager),
|
||||
connectors: connectors,
|
||||
availableActions: availableActions,
|
||||
dynamicPrompt: promptBlocks,
|
||||
filters: filters,
|
||||
timeout: timeout,
|
||||
conversationLogs: conversationPath,
|
||||
}, nil
|
||||
}
|
||||
|
||||
@@ -123,25 +125,27 @@ func NewAgentPool(
|
||||
return nil, err
|
||||
}
|
||||
return &AgentPool{
|
||||
file: poolfile,
|
||||
apiURL: apiURL,
|
||||
pooldir: directory,
|
||||
defaultModel: defaultModel,
|
||||
defaultMultimodalModel: defaultMultimodalModel,
|
||||
mcpBoxURL: mcpBoxURL,
|
||||
imageModel: imageModel,
|
||||
apiKey: apiKey,
|
||||
agents: make(map[string]*Agent),
|
||||
managers: make(map[string]sse.Manager),
|
||||
agentStatus: map[string]*Status{},
|
||||
pool: *poolData,
|
||||
connectors: connectors,
|
||||
localRAGAPI: LocalRAGAPI,
|
||||
dynamicPrompt: promptBlocks,
|
||||
filters: filters,
|
||||
availableActions: availableActions,
|
||||
timeout: timeout,
|
||||
conversationLogs: conversationPath,
|
||||
file: poolfile,
|
||||
apiURL: apiURL,
|
||||
pooldir: directory,
|
||||
defaultModel: defaultModel,
|
||||
defaultMultimodalModel: defaultMultimodalModel,
|
||||
defaultTranscriptionModel: defaultTranscriptionModel,
|
||||
defaultTranscriptionLanguage: defaultTranscriptionLanguage,
|
||||
defaultTTSModel: defaultTTSModel,
|
||||
imageModel: imageModel,
|
||||
apiKey: apiKey,
|
||||
agents: make(map[string]*Agent),
|
||||
managers: make(map[string]sse.Manager),
|
||||
agentStatus: map[string]*Status{},
|
||||
pool: *poolData,
|
||||
connectors: connectors,
|
||||
localRAGAPI: LocalRAGAPI,
|
||||
dynamicPrompt: promptBlocks,
|
||||
filters: filters,
|
||||
availableActions: availableActions,
|
||||
timeout: timeout,
|
||||
conversationLogs: conversationPath,
|
||||
}, nil
|
||||
}
|
||||
|
||||
@@ -334,21 +338,31 @@ func (a *AgentPool) startAgentWithConfig(name string, config *AgentConfig, obs O
|
||||
ctx := context.Background()
|
||||
model := a.defaultModel
|
||||
multimodalModel := a.defaultMultimodalModel
|
||||
transcriptionModel := a.defaultTranscriptionModel
|
||||
transcriptionLanguage := a.defaultTranscriptionLanguage
|
||||
ttsModel := a.defaultTTSModel
|
||||
|
||||
if config.MultimodalModel != "" {
|
||||
multimodalModel = config.MultimodalModel
|
||||
}
|
||||
|
||||
if config.TranscriptionModel != "" {
|
||||
transcriptionModel = config.TranscriptionModel
|
||||
}
|
||||
|
||||
if config.TranscriptionLanguage != "" {
|
||||
transcriptionLanguage = config.TranscriptionLanguage
|
||||
}
|
||||
if config.TTSModel != "" {
|
||||
ttsModel = config.TTSModel
|
||||
}
|
||||
|
||||
if config.Model != "" {
|
||||
model = config.Model
|
||||
} else {
|
||||
config.Model = model
|
||||
}
|
||||
|
||||
if config.MCPBoxURL != "" {
|
||||
a.mcpBoxURL = config.MCPBoxURL
|
||||
}
|
||||
|
||||
if config.PeriodicRuns == "" {
|
||||
config.PeriodicRuns = "10m"
|
||||
}
|
||||
@@ -375,7 +389,7 @@ func (a *AgentPool) startAgentWithConfig(name string, config *AgentConfig, obs O
|
||||
}
|
||||
|
||||
connectors := a.connectors(config)
|
||||
promptBlocks := a.dynamicPrompt(config)
|
||||
promptBlocks := a.dynamicPrompt(config)(ctx, a)
|
||||
actions := a.availableActions(config)(ctx, a)
|
||||
filters := a.filters(config)
|
||||
stateFile, characterFile := a.stateFiles(name)
|
||||
@@ -419,10 +433,12 @@ func (a *AgentPool) startAgentWithConfig(name string, config *AgentConfig, obs O
|
||||
WithLLMAPIURL(a.apiURL),
|
||||
WithContext(ctx),
|
||||
WithMCPServers(config.MCPServers...),
|
||||
WithTranscriptionModel(transcriptionModel),
|
||||
WithTranscriptionLanguage(transcriptionLanguage),
|
||||
WithTTSModel(ttsModel),
|
||||
WithPeriodicRuns(config.PeriodicRuns),
|
||||
WithPermanentGoal(config.PermanentGoal),
|
||||
WithMCPSTDIOServers(config.MCPSTDIOServers...),
|
||||
WithMCPBoxURL(a.mcpBoxURL),
|
||||
WithPrompts(promptBlocks...),
|
||||
WithJobFilters(filters...),
|
||||
WithMCPPrepareScript(config.MCPPrepareScript),
|
||||
@@ -554,19 +570,16 @@ func (a *AgentPool) startAgentWithConfig(name string, config *AgentConfig, obs O
|
||||
opts = append(opts, EnableKnowledgeBaseWithResults(config.KnowledgeBaseResults))
|
||||
}
|
||||
|
||||
if config.LoopDetectionSteps > 0 {
|
||||
opts = append(opts, WithLoopDetectionSteps(config.LoopDetectionSteps))
|
||||
}
|
||||
|
||||
if config.ParallelJobs > 0 {
|
||||
opts = append(opts, WithParallelJobs(config.ParallelJobs))
|
||||
}
|
||||
|
||||
if config.EnableEvaluation {
|
||||
opts = append(opts, EnableEvaluation())
|
||||
if config.MaxEvaluationLoops > 0 {
|
||||
opts = append(opts, WithMaxEvaluationLoops(config.MaxEvaluationLoops))
|
||||
}
|
||||
}
|
||||
|
||||
if config.MaxEvaluationLoops > 0 {
|
||||
opts = append(opts, WithMaxEvaluationLoops(config.MaxEvaluationLoops))
|
||||
}
|
||||
|
||||
xlog.Info("Starting agent", "name", name, "config", config)
|
||||
|
||||
+41
-7
@@ -5,6 +5,7 @@ import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
|
||||
"github.com/mudler/cogito"
|
||||
"github.com/sashabaranov/go-openai"
|
||||
"github.com/sashabaranov/go-openai/jsonschema"
|
||||
)
|
||||
@@ -30,9 +31,10 @@ func NewActionContext(ctx context.Context, cancel context.CancelFunc) *ActionCon
|
||||
type ActionParams map[string]interface{}
|
||||
|
||||
type ActionResult struct {
|
||||
Job *Job
|
||||
Result string
|
||||
Metadata map[string]interface{}
|
||||
Job *Job
|
||||
Result string
|
||||
ImageBase64Result string
|
||||
Metadata map[string]interface{}
|
||||
}
|
||||
|
||||
func (ap ActionParams) Read(s string) error {
|
||||
@@ -87,11 +89,35 @@ func (a ActionDefinition) ToFunctionDefinition() *openai.FunctionDefinition {
|
||||
}
|
||||
}
|
||||
|
||||
type cogitoWrapper struct {
|
||||
action Action
|
||||
ctx context.Context
|
||||
sharedState *AgentSharedState
|
||||
}
|
||||
|
||||
func (c *cogitoWrapper) Tool() openai.Tool {
|
||||
return openai.Tool{
|
||||
Type: openai.ToolTypeFunction,
|
||||
Function: c.action.Definition().ToFunctionDefinition(),
|
||||
}
|
||||
}
|
||||
|
||||
func (c *cogitoWrapper) Run(args map[string]any) (string, error) {
|
||||
ctx := c.ctx
|
||||
if ctx == nil {
|
||||
ctx = context.Background()
|
||||
}
|
||||
result, err := c.action.Run(ctx, c.sharedState, ActionParams(args))
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
return result.Result, nil
|
||||
}
|
||||
|
||||
// Actions is something the agent can do
|
||||
type Action interface {
|
||||
Run(ctx context.Context, sharedState *AgentSharedState, action ActionParams) (ActionResult, error)
|
||||
Definition() ActionDefinition
|
||||
Plannable() bool
|
||||
}
|
||||
|
||||
// UserDefinedChecker interface to identify user-defined actions
|
||||
@@ -141,9 +167,9 @@ func (u *UserDefinedAction) IsUserDefined() bool {
|
||||
func CreateUserDefinedActions(userTools []ActionDefinition) []Action {
|
||||
var actions []Action
|
||||
for _, tool := range userTools {
|
||||
actions = append(actions, &UserDefinedAction{
|
||||
ActionDef: &tool,
|
||||
})
|
||||
actions = append(actions, &UserDefinedAction{
|
||||
ActionDef: &tool,
|
||||
})
|
||||
}
|
||||
return actions
|
||||
}
|
||||
@@ -161,6 +187,14 @@ func (a Actions) ToTools() []openai.Tool {
|
||||
return tools
|
||||
}
|
||||
|
||||
func (a Actions) ToCogitoTools(ctx context.Context, sharedState *AgentSharedState) []cogito.Tool {
|
||||
tools := []cogito.Tool{}
|
||||
for _, action := range a {
|
||||
tools = append(tools, &cogitoWrapper{action: action, ctx: ctx, sharedState: sharedState})
|
||||
}
|
||||
return tools
|
||||
}
|
||||
|
||||
func (a Actions) Find(name string) Action {
|
||||
for _, action := range a {
|
||||
if action.Definition().Name.Is(name) {
|
||||
|
||||
+5
-39
@@ -5,6 +5,7 @@ import (
|
||||
"log"
|
||||
|
||||
"github.com/google/uuid"
|
||||
"github.com/mudler/cogito"
|
||||
"github.com/sashabaranov/go-openai"
|
||||
)
|
||||
|
||||
@@ -20,19 +21,15 @@ type Job struct {
|
||||
UUID string
|
||||
Metadata map[string]interface{}
|
||||
DoneFilter bool
|
||||
|
||||
|
||||
// Tools available for this job
|
||||
BuiltinTools []ActionDefinition // Built-in tools like web search
|
||||
UserTools []ActionDefinition // User-defined function tools
|
||||
ToolChoice string
|
||||
|
||||
pastActions []*ActionRequest
|
||||
nextAction *Action
|
||||
nextActionParams *ActionParams
|
||||
nextActionReasoning string
|
||||
|
||||
context context.Context
|
||||
cancel context.CancelFunc
|
||||
context context.Context
|
||||
fragment *cogito.Fragment
|
||||
cancel context.CancelFunc
|
||||
|
||||
Obs *Observable
|
||||
}
|
||||
@@ -108,37 +105,6 @@ func (j *Job) CallbackWithResult(stateResult ActionState) {
|
||||
j.ResultCallback(stateResult)
|
||||
}
|
||||
|
||||
func (j *Job) SetNextAction(action *Action, params *ActionParams, reasoning string) {
|
||||
j.nextAction = action
|
||||
j.nextActionParams = params
|
||||
j.nextActionReasoning = reasoning
|
||||
}
|
||||
|
||||
func (j *Job) AddPastAction(action Action, params *ActionParams) {
|
||||
j.pastActions = append(j.pastActions, &ActionRequest{
|
||||
Action: action,
|
||||
Params: params,
|
||||
})
|
||||
}
|
||||
|
||||
func (j *Job) GetPastActions() []*ActionRequest {
|
||||
return j.pastActions
|
||||
}
|
||||
|
||||
func (j *Job) GetNextAction() (*Action, *ActionParams, string) {
|
||||
return j.nextAction, j.nextActionParams, j.nextActionReasoning
|
||||
}
|
||||
|
||||
func (j *Job) HasNextAction() bool {
|
||||
return j.nextAction != nil
|
||||
}
|
||||
|
||||
func (j *Job) ResetNextAction() {
|
||||
j.nextAction = nil
|
||||
j.nextActionParams = nil
|
||||
j.nextActionReasoning = ""
|
||||
}
|
||||
|
||||
func WithTextImage(text, image string) JobOption {
|
||||
return func(j *Job) {
|
||||
j.ConversationHistory = append(j.ConversationHistory, openai.ChatCompletionMessage{
|
||||
|
||||
@@ -0,0 +1,6 @@
|
||||
package types
|
||||
|
||||
type PromptResult struct {
|
||||
Content string
|
||||
ImageBase64 string
|
||||
}
|
||||
@@ -3,6 +3,7 @@ package types
|
||||
import (
|
||||
"sync"
|
||||
|
||||
"github.com/mudler/cogito"
|
||||
"github.com/sashabaranov/go-openai"
|
||||
)
|
||||
|
||||
@@ -11,6 +12,7 @@ type JobResult struct {
|
||||
sync.Mutex
|
||||
// The result of a job
|
||||
State []ActionState
|
||||
Plans []cogito.PlanStatus
|
||||
Conversation []openai.ChatCompletionMessage
|
||||
|
||||
Finalizers []func([]openai.ChatCompletionMessage)
|
||||
|
||||
@@ -11,11 +11,6 @@ services:
|
||||
- /dev/dri
|
||||
- /dev/kfd
|
||||
|
||||
mcpbox:
|
||||
extends:
|
||||
file: docker-compose.yaml
|
||||
service: mcpbox
|
||||
|
||||
dind:
|
||||
extends:
|
||||
file: docker-compose.yaml
|
||||
|
||||
@@ -6,17 +6,12 @@ services:
|
||||
environment:
|
||||
- LOCALAI_SINGLE_ACTIVE_BACKEND=true
|
||||
- DEBUG=true
|
||||
image: localai/localai:master-gpu-intel-f32
|
||||
image: localai/localai:master-gpu-intel
|
||||
devices:
|
||||
# On a system with integrated GPU and an Arc 770, this is the Arc 770
|
||||
- /dev/dri/card1
|
||||
- /dev/dri/renderD129
|
||||
|
||||
mcpbox:
|
||||
extends:
|
||||
file: docker-compose.yaml
|
||||
service: mcpbox
|
||||
|
||||
dind:
|
||||
extends:
|
||||
file: docker-compose.yaml
|
||||
|
||||
@@ -6,7 +6,7 @@ services:
|
||||
environment:
|
||||
- LOCALAI_SINGLE_ACTIVE_BACKEND=true
|
||||
- DEBUG=true
|
||||
image: localai/localai:master-gpu-nvidia-cuda12
|
||||
image: localai/localai:master-gpu-nvidia-cuda-12
|
||||
# For images with python backends, use:
|
||||
# image: localai/localai:master-cublas-cuda12-ffmpeg
|
||||
deploy:
|
||||
@@ -17,11 +17,6 @@ services:
|
||||
count: 1
|
||||
capabilities: [gpu]
|
||||
|
||||
mcpbox:
|
||||
extends:
|
||||
file: docker-compose.yaml
|
||||
service: mcpbox
|
||||
|
||||
dind:
|
||||
extends:
|
||||
file: docker-compose.yaml
|
||||
|
||||
+2
-21
@@ -61,25 +61,6 @@ services:
|
||||
dind:
|
||||
condition: service_healthy
|
||||
|
||||
mcpbox:
|
||||
build:
|
||||
context: .
|
||||
dockerfile: Dockerfile.mcpbox
|
||||
ports:
|
||||
- "8080"
|
||||
volumes:
|
||||
- ./volumes/mcpbox:/app/data
|
||||
environment:
|
||||
- DOCKER_HOST=tcp://dind:2375
|
||||
depends_on:
|
||||
dind:
|
||||
condition: service_healthy
|
||||
healthcheck:
|
||||
test: ["CMD", "wget", "-q", "-O", "-", "http://localhost:8080/processes"]
|
||||
interval: 30s
|
||||
timeout: 10s
|
||||
retries: 3
|
||||
|
||||
dind:
|
||||
image: docker:dind
|
||||
privileged: true
|
||||
@@ -97,7 +78,7 @@ services:
|
||||
condition: service_healthy
|
||||
localrecall-healthcheck:
|
||||
condition: service_completed_successfully
|
||||
mcpbox:
|
||||
dind:
|
||||
condition: service_healthy
|
||||
build:
|
||||
context: .
|
||||
@@ -115,8 +96,8 @@ services:
|
||||
- LOCALAGI_STATE_DIR=/pool
|
||||
- LOCALAGI_TIMEOUT=5m
|
||||
- LOCALAGI_ENABLE_CONVERSATIONS_LOGGING=false
|
||||
- LOCALAGI_MCPBOX_URL=http://mcpbox:8080
|
||||
- LOCALAGI_SSHBOX_URL=root:root@sshbox:22
|
||||
- DOCKER_HOST=tcp://dind:2375
|
||||
extra_hosts:
|
||||
- "host.docker.internal:host-gateway"
|
||||
volumes:
|
||||
|
||||
@@ -0,0 +1,56 @@
|
||||
package custom_actions
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
)
|
||||
|
||||
type Params struct {
|
||||
Message string `json:"message"` // field name
|
||||
}
|
||||
|
||||
func Run(config map[string]interface{}) (string, map[string]interface{}, error) {
|
||||
p := Params{}
|
||||
b, err := json.Marshal(config)
|
||||
if err != nil {
|
||||
return "", map[string]interface{}{}, err
|
||||
}
|
||||
if err := json.Unmarshal(b, &p); err != nil {
|
||||
return "", map[string]interface{}{}, err
|
||||
}
|
||||
|
||||
return "Hello, " + p.Message + "!", map[string]interface{}{}, nil
|
||||
}
|
||||
|
||||
func Description() string {
|
||||
return "Send a message to the user"
|
||||
}
|
||||
|
||||
func Definition() map[string][]string {
|
||||
return map[string][]string{
|
||||
"message": { // field name
|
||||
"string", // type
|
||||
"The message to send", // description
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
func RequiredFields() []string {
|
||||
return []string{"message"} // field name
|
||||
}
|
||||
|
||||
var config string
|
||||
|
||||
func Init(configuration string) error {
|
||||
// Do something with the configuration that was passed-by
|
||||
config = configuration
|
||||
return nil
|
||||
}
|
||||
|
||||
// DynamicPrompt
|
||||
func Render() (string, string, error) {
|
||||
return "Hello, " + config + "!", "", nil
|
||||
}
|
||||
|
||||
func Role() string {
|
||||
return "system" // Role for the dynamic prompt
|
||||
}
|
||||
@@ -1,12 +0,0 @@
|
||||
#!/usr/bin/env python
|
||||
# -*- coding: utf-8 -*-
|
||||
from RealtimeSTT import AudioToTextRecorder
|
||||
|
||||
def process_text(text):
|
||||
print(text)
|
||||
|
||||
if __name__ == '__main__':
|
||||
recorder = AudioToTextRecorder(wake_words="jarvis")
|
||||
|
||||
while True:
|
||||
recorder.text(process_text)
|
||||
@@ -1,92 +1,81 @@
|
||||
module github.com/mudler/LocalAGI
|
||||
|
||||
go 1.24
|
||||
|
||||
toolchain go1.24.2
|
||||
go 1.24.4
|
||||
|
||||
require (
|
||||
github.com/Masterminds/sprig/v3 v3.3.0
|
||||
github.com/bwmarrin/discordgo v0.29.0
|
||||
github.com/chasefleming/elem-go v0.30.0
|
||||
github.com/dave-gray101/v2keyauth v0.0.0-20240624150259-c45d584d25e2
|
||||
github.com/donseba/go-htmx v1.12.0
|
||||
github.com/eritikass/githubmarkdownconvertergo v0.1.10
|
||||
github.com/go-telegram/bot v1.15.0
|
||||
github.com/gofiber/fiber/v2 v2.52.8
|
||||
github.com/gofiber/template/html/v2 v2.1.3
|
||||
github.com/go-telegram/bot v1.17.0
|
||||
github.com/gofiber/fiber/v2 v2.52.9
|
||||
github.com/google/go-github/v69 v69.2.0
|
||||
github.com/google/uuid v1.6.0
|
||||
github.com/gorilla/websocket v1.5.3
|
||||
github.com/metoro-io/mcp-golang v0.13.0
|
||||
github.com/onsi/ginkgo/v2 v2.23.4
|
||||
github.com/onsi/gomega v1.37.0
|
||||
github.com/modelcontextprotocol/go-sdk v1.1.0
|
||||
github.com/mudler/cogito v0.5.1
|
||||
github.com/onsi/ginkgo/v2 v2.25.3
|
||||
github.com/onsi/gomega v1.38.2
|
||||
github.com/philippgille/chromem-go v0.7.0
|
||||
github.com/robfig/cron/v3 v3.0.1
|
||||
github.com/sashabaranov/go-openai v1.40.3
|
||||
github.com/sashabaranov/go-openai v1.41.2
|
||||
github.com/slack-go/slack v0.17.3
|
||||
github.com/thoj/go-ircevent v0.0.0-20210723090443-73e444401d64
|
||||
github.com/tmc/langchaingo v0.1.13
|
||||
github.com/tmc/langchaingo v0.1.14
|
||||
github.com/traefik/yaegi v0.16.1
|
||||
github.com/valyala/fasthttp v1.62.0
|
||||
golang.org/x/crypto v0.40.0
|
||||
github.com/valyala/fasthttp v1.68.0
|
||||
golang.org/x/crypto v0.43.0
|
||||
jaytaylor.com/html2text v0.0.0-20230321000545-74c2419ad056
|
||||
maunium.net/go/mautrix v0.17.0
|
||||
mvdan.cc/xurls/v2 v2.6.0
|
||||
)
|
||||
|
||||
require github.com/JohannesKaufmann/dom v0.2.0 // indirect
|
||||
require (
|
||||
dario.cat/mergo v1.0.2 // indirect
|
||||
github.com/JohannesKaufmann/dom v0.2.0 // indirect
|
||||
github.com/Masterminds/goutils v1.1.1 // indirect
|
||||
github.com/Masterminds/semver/v3 v3.4.0 // indirect
|
||||
github.com/google/jsonschema-go v0.3.0 // indirect
|
||||
github.com/gorilla/websocket v1.5.3 // indirect
|
||||
github.com/huandu/xstrings v1.5.0 // indirect
|
||||
github.com/mitchellh/copystructure v1.2.0 // indirect
|
||||
github.com/mitchellh/reflectwalk v1.0.2 // indirect
|
||||
github.com/shopspring/decimal v1.4.0 // indirect
|
||||
github.com/spf13/cast v1.7.0 // indirect
|
||||
github.com/yosida95/uritemplate/v3 v3.0.2 // indirect
|
||||
go.yaml.in/yaml/v3 v3.0.4 // indirect
|
||||
golang.org/x/oauth2 v0.30.0 // indirect
|
||||
)
|
||||
|
||||
require (
|
||||
github.com/JohannesKaufmann/html-to-markdown/v2 v2.3.3
|
||||
github.com/JohannesKaufmann/html-to-markdown/v2 v2.4.0
|
||||
github.com/PuerkitoBio/goquery v1.10.3 // indirect
|
||||
github.com/andybalholm/brotli v1.1.1 // indirect
|
||||
github.com/andybalholm/brotli v1.2.0 // indirect
|
||||
github.com/andybalholm/cascadia v1.3.3 // indirect
|
||||
github.com/antchfx/htmlquery v1.3.4 // indirect
|
||||
github.com/antchfx/xmlquery v1.4.4 // indirect
|
||||
github.com/antchfx/xpath v1.3.4 // indirect
|
||||
github.com/bahlo/generic-list-go v0.2.0 // indirect
|
||||
github.com/buger/jsonparser v1.1.1 // indirect
|
||||
github.com/bytedance/sonic v1.13.2 // indirect
|
||||
github.com/bytedance/sonic/loader v0.2.4 // indirect
|
||||
github.com/cloudwego/base64x v0.1.5 // indirect
|
||||
github.com/dlclark/regexp2 v1.11.5 // indirect
|
||||
github.com/emersion/go-imap/v2 v2.0.0-beta.5
|
||||
github.com/emersion/go-message v0.18.2
|
||||
github.com/emersion/go-sasl v0.0.0-20241020182733-b788ff22d5a6
|
||||
github.com/emersion/go-smtp v0.22.0
|
||||
github.com/gabriel-vasile/mimetype v1.4.9 // indirect
|
||||
github.com/gin-contrib/sse v1.1.0 // indirect
|
||||
github.com/gin-gonic/gin v1.10.0 // indirect
|
||||
github.com/go-logr/logr v1.4.2 // indirect
|
||||
github.com/go-playground/locales v0.14.1 // indirect
|
||||
github.com/go-playground/universal-translator v0.18.1 // indirect
|
||||
github.com/go-playground/validator/v10 v10.26.0 // indirect
|
||||
github.com/emersion/go-smtp v0.24.0
|
||||
github.com/go-logr/logr v1.4.3 // indirect
|
||||
github.com/go-task/slim-sprig/v3 v3.0.0 // indirect
|
||||
github.com/gobwas/glob v0.2.3 // indirect
|
||||
github.com/goccy/go-json v0.10.5 // indirect
|
||||
github.com/gocolly/colly v1.2.0 // indirect
|
||||
github.com/gofiber/template v1.8.3 // indirect
|
||||
github.com/gofiber/utils v1.1.0 // indirect
|
||||
github.com/golang/groupcache v0.0.0-20241129210726-2c02b8208cf8 // indirect
|
||||
github.com/golang/protobuf v1.5.4 // indirect
|
||||
github.com/gomarkdown/markdown v0.0.0-20250311123330-531bef5e742b
|
||||
github.com/google/go-cmp v0.7.0 // indirect
|
||||
github.com/google/go-querystring v1.1.0 // indirect
|
||||
github.com/google/pprof v0.0.0-20250423184734-337e5dd93bb4 // indirect
|
||||
github.com/invopop/jsonschema v0.13.0 // indirect
|
||||
github.com/json-iterator/go v1.1.12 // indirect
|
||||
github.com/kennygrant/sanitize v1.2.4 // indirect
|
||||
github.com/klauspost/compress v1.18.0 // indirect
|
||||
github.com/klauspost/cpuid/v2 v2.2.10 // indirect
|
||||
github.com/leodido/go-urn v1.4.0 // indirect
|
||||
github.com/mailru/easyjson v0.9.0 // indirect
|
||||
github.com/klauspost/compress v1.18.1 // indirect
|
||||
github.com/mattn/go-colorable v0.1.14 // indirect
|
||||
github.com/mattn/go-isatty v0.0.20 // indirect
|
||||
github.com/mattn/go-runewidth v0.0.16 // indirect
|
||||
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect
|
||||
github.com/modern-go/reflect2 v1.0.2 // indirect
|
||||
github.com/olekukonko/tablewriter v0.0.5 // indirect
|
||||
github.com/pelletier/go-toml/v2 v2.2.4 // indirect
|
||||
github.com/pkg/errors v0.9.1 // indirect
|
||||
github.com/pkoukk/tiktoken-go v0.1.7 // indirect
|
||||
github.com/rivo/uniseg v0.4.7 // indirect
|
||||
github.com/rs/zerolog v1.31.0 // indirect
|
||||
@@ -97,21 +86,16 @@ require (
|
||||
github.com/tidwall/match v1.1.1 // indirect
|
||||
github.com/tidwall/pretty v1.2.1 // indirect
|
||||
github.com/tidwall/sjson v1.2.5 // indirect
|
||||
github.com/twitchyliquid64/golang-asm v0.15.1 // indirect
|
||||
github.com/ugorji/go/codec v1.2.12 // indirect
|
||||
github.com/valyala/bytebufferpool v1.0.0 // indirect
|
||||
github.com/wk8/go-ordered-map/v2 v2.1.8 // indirect
|
||||
go.mau.fi/util v0.3.0 // indirect
|
||||
go.starlark.net v0.0.0-20250417143717-f57e51f710eb // indirect
|
||||
go.uber.org/automaxprocs v1.6.0 // indirect
|
||||
golang.org/x/arch v0.16.0 // indirect
|
||||
golang.org/x/exp v0.0.0-20240112132812-db7319d0e0e3 // indirect
|
||||
golang.org/x/net v0.41.0 // indirect
|
||||
golang.org/x/sys v0.34.0 // indirect
|
||||
golang.org/x/text v0.27.0 // indirect
|
||||
golang.org/x/tools v0.34.0 // indirect
|
||||
golang.org/x/exp v0.0.0-20240808152545-0cdaa3abc0fa
|
||||
golang.org/x/net v0.46.0 // indirect
|
||||
golang.org/x/sys v0.37.0 // indirect
|
||||
golang.org/x/text v0.30.0 // indirect
|
||||
golang.org/x/tools v0.37.0 // indirect
|
||||
google.golang.org/appengine v1.6.8 // indirect
|
||||
google.golang.org/protobuf v1.36.6 // indirect
|
||||
gopkg.in/yaml.v3 v3.0.1 // indirect
|
||||
google.golang.org/protobuf v1.36.8 // indirect
|
||||
maunium.net/go/maulogger/v2 v2.4.1 // indirect
|
||||
)
|
||||
|
||||
@@ -1,11 +1,23 @@
|
||||
dario.cat/mergo v1.0.2 h1:85+piFYR1tMbRrLcDwR18y4UKJ3aH1Tbzi24VRW1TK8=
|
||||
dario.cat/mergo v1.0.2/go.mod h1:E/hbnu0NxMFBjpMIE34DRGLWqDy0g5FuKDhCb31ngxA=
|
||||
github.com/Azure/go-ansiterm v0.0.0-20250102033503-faa5f7b0171c h1:udKWzYgxTojEKWjV8V+WSxDXJ4NFATAsZjh8iIbsQIg=
|
||||
github.com/Azure/go-ansiterm v0.0.0-20250102033503-faa5f7b0171c/go.mod h1:xomTg63KZ2rFqZQzSB4Vz2SUXa1BpHTVz9L5PTmPC4E=
|
||||
github.com/JohannesKaufmann/dom v0.2.0 h1:1bragmEb19K8lHAqgFgqCpiPCFEZMTXzOIEjuxkUfLQ=
|
||||
github.com/JohannesKaufmann/dom v0.2.0/go.mod h1:57iSUl5RKric4bUkgos4zu6Xt5LMHUnw3TF1l5CbGZo=
|
||||
github.com/JohannesKaufmann/html-to-markdown/v2 v2.3.3 h1:r3fokGFRDk/8pHmwLwJ8zsX4qiqfS1/1TZm2BH8ueY8=
|
||||
github.com/JohannesKaufmann/html-to-markdown/v2 v2.3.3/go.mod h1:HtsP+1Fchp4dVvaiIsLHAl/yqL3H1YLwqLC9kNwqQEg=
|
||||
github.com/JohannesKaufmann/html-to-markdown/v2 v2.4.0 h1:C0/TerKdQX9Y9pbYi1EsLr5LDNANsqunyI/btpyfCg8=
|
||||
github.com/JohannesKaufmann/html-to-markdown/v2 v2.4.0/go.mod h1:OLaKh+giepO8j7teevrNwiy/fwf8LXgoc9g7rwaE1jk=
|
||||
github.com/Masterminds/goutils v1.1.1 h1:5nUrii3FMTL5diU80unEVvNevw1nH4+ZV4DSLVJLSYI=
|
||||
github.com/Masterminds/goutils v1.1.1/go.mod h1:8cTjp+g8YejhMuvIA5y2vz3BpJxksy863GQaJW2MFNU=
|
||||
github.com/Masterminds/semver/v3 v3.4.0 h1:Zog+i5UMtVoCU8oKka5P7i9q9HgrJeGzI9SA1Xbatp0=
|
||||
github.com/Masterminds/semver/v3 v3.4.0/go.mod h1:4V+yj/TJE1HU9XfppCwVMZq3I84lprf4nC11bSS5beM=
|
||||
github.com/Masterminds/sprig/v3 v3.3.0 h1:mQh0Yrg1XPo6vjYXgtf5OtijNAKJRNcTdOOGZe3tPhs=
|
||||
github.com/Masterminds/sprig/v3 v3.3.0/go.mod h1:Zy1iXRYNqNLUolqCpL4uhk6SHUMAOSCzdgBfDb35Lz0=
|
||||
github.com/Microsoft/go-winio v0.6.2 h1:F2VQgta7ecxGYO8k3ZZz3RS8fVIXVxONVUPlNERoyfY=
|
||||
github.com/Microsoft/go-winio v0.6.2/go.mod h1:yd8OoFMLzJbo9gZq8j5qaps8bJ9aShtEA8Ipt1oGCvU=
|
||||
github.com/PuerkitoBio/goquery v1.10.3 h1:pFYcNSqHxBD06Fpj/KsbStFRsgRATgnf3LeXiUkhzPo=
|
||||
github.com/PuerkitoBio/goquery v1.10.3/go.mod h1:tMUX0zDMHXYlAQk6p35XxQMqMweEKB7iK7iLNd4RH4Y=
|
||||
github.com/andybalholm/brotli v1.1.1 h1:PR2pgnyFznKEugtsUo0xLdDop5SKXd5Qf5ysW+7XdTA=
|
||||
github.com/andybalholm/brotli v1.1.1/go.mod h1:05ib4cKhjx3OQYUY22hTVd34Bc8upXjOLL2rKwwZBoA=
|
||||
github.com/andybalholm/brotli v1.2.0 h1:ukwgCxwYrmACq68yiUqwIWnGY0cTPox/M94sVwToPjQ=
|
||||
github.com/andybalholm/brotli v1.2.0/go.mod h1:rzTDkvFWvIrjDXZHkuS16NPggd91W3kUSvPlQ1pLaKY=
|
||||
github.com/andybalholm/cascadia v1.3.3 h1:AG2YHrzJIm4BZ19iwJ/DAua6Btl3IwJX+VI4kktS1LM=
|
||||
github.com/andybalholm/cascadia v1.3.3/go.mod h1:xNd9bqTn98Ln4DwST8/nG+H0yuB8Hmgu1YHNnWw0GeA=
|
||||
github.com/antchfx/htmlquery v1.3.4 h1:Isd0srPkni2iNTWCwVj/72t7uCphFeor5Q8nCzj1jdQ=
|
||||
@@ -15,79 +27,76 @@ github.com/antchfx/xmlquery v1.4.4/go.mod h1:AEPEEPYE9GnA2mj5Ur2L5Q5/2PycJ0N9Fus
|
||||
github.com/antchfx/xpath v1.3.3/go.mod h1:i54GszH55fYfBmoZXapTHN8T8tkcHfRgLyVwwqzXNcs=
|
||||
github.com/antchfx/xpath v1.3.4 h1:1ixrW1VnXd4HurCj7qnqnR0jo14g8JMe20Fshg1Vgz4=
|
||||
github.com/antchfx/xpath v1.3.4/go.mod h1:i54GszH55fYfBmoZXapTHN8T8tkcHfRgLyVwwqzXNcs=
|
||||
github.com/bahlo/generic-list-go v0.2.0 h1:5sz/EEAK+ls5wF+NeqDpk5+iNdMDXrh3z3nPnH1Wvgk=
|
||||
github.com/bahlo/generic-list-go v0.2.0/go.mod h1:2KvAjgMlE5NNynlg/5iLrrCCZ2+5xWbdbCW3pNTGyYg=
|
||||
github.com/buger/jsonparser v1.1.1 h1:2PnMjfWD7wBILjqQbt530v576A/cAbQvEW9gGIpYMUs=
|
||||
github.com/buger/jsonparser v1.1.1/go.mod h1:6RYKKt7H4d4+iWqouImQ9R2FZql3VbhNgx27UK13J/0=
|
||||
github.com/bwmarrin/discordgo v0.29.0 h1:FmWeXFaKUwrcL3Cx65c20bTRW+vOb6k8AnaP+EgjDno=
|
||||
github.com/bwmarrin/discordgo v0.29.0/go.mod h1:NJZpH+1AfhIcyQsPeuBKsUtYrRnjkyu0kIVMCHkZtRY=
|
||||
github.com/bytedance/sonic v1.13.2 h1:8/H1FempDZqC4VqjptGo14QQlJx8VdZJegxs6wwfqpQ=
|
||||
github.com/bytedance/sonic v1.13.2/go.mod h1:o68xyaF9u2gvVBuGHPlUVCy+ZfmNNO5ETf1+KgkJhz4=
|
||||
github.com/bytedance/sonic/loader v0.1.1/go.mod h1:ncP89zfokxS5LZrJxl5z0UJcsk4M4yY2JpfqGeCtNLU=
|
||||
github.com/bytedance/sonic/loader v0.2.4 h1:ZWCw4stuXUsn1/+zQDqeE7JKP+QO47tz7QCNan80NzY=
|
||||
github.com/bytedance/sonic/loader v0.2.4/go.mod h1:N8A3vUdtUebEY2/VQC0MyhYeKUFosQU6FxH2JmUe6VI=
|
||||
github.com/cenkalti/backoff v2.2.1+incompatible h1:tNowT99t7UNflLxfYYSlKYsBpXdEet03Pg2g16Swow4=
|
||||
github.com/cenkalti/backoff/v4 v4.3.0 h1:MyRJ/UdXutAwSAT+s3wNd7MfTIcy71VQueUuFK343L8=
|
||||
github.com/cenkalti/backoff/v4 v4.3.0/go.mod h1:Y3VNntkOUPxTVeUxJ/G5vcM//AlwfmyYozVcomhLiZE=
|
||||
github.com/chasefleming/elem-go v0.30.0 h1:BlhV1ekv1RbFiM8XZUQeln1Ikb4D+bu2eDO4agREvok=
|
||||
github.com/chasefleming/elem-go v0.30.0/go.mod h1:hz73qILBIKnTgOujnSMtEj20/epI+f6vg71RUilJAA4=
|
||||
github.com/cloudwego/base64x v0.1.5 h1:XPciSp1xaq2VCSt6lF0phncD4koWyULpl5bUxbfCyP4=
|
||||
github.com/cloudwego/base64x v0.1.5/go.mod h1:0zlkT4Wn5C6NdauXdJRhSKRlJvmclQ1hhJgA0rcu/8w=
|
||||
github.com/cloudwego/iasm v0.2.0/go.mod h1:8rXZaNYT2n95jn+zTI1sDr+IgcD2GVs0nlbbQPiEFhY=
|
||||
github.com/containerd/errdefs v1.0.0 h1:tg5yIfIlQIrxYtu9ajqY42W3lpS19XqdxRQeEwYG8PI=
|
||||
github.com/containerd/errdefs v1.0.0/go.mod h1:+YBYIdtsnF4Iw6nWZhJcqGSg/dwvV7tyJ/kCkyJ2k+M=
|
||||
github.com/containerd/errdefs/pkg v0.3.0 h1:9IKJ06FvyNlexW690DXuQNx2KA2cUJXx151Xdx3ZPPE=
|
||||
github.com/containerd/errdefs/pkg v0.3.0/go.mod h1:NJw6s9HwNuRhnjJhM7pylWwMyAkmCQvQ4GpJHEqRLVk=
|
||||
github.com/containerd/log v0.1.0 h1:TCJt7ioM2cr/tfR8GPbGf9/VRAX8D2B4PjzCpfX540I=
|
||||
github.com/containerd/log v0.1.0/go.mod h1:VRRf09a7mHDIRezVKTRCrOq78v577GXq3bSa3EhrzVo=
|
||||
github.com/containerd/platforms v0.2.1 h1:zvwtM3rz2YHPQsF2CHYM8+KtB5dvhISiXh5ZpSBQv6A=
|
||||
github.com/containerd/platforms v0.2.1/go.mod h1:XHCb+2/hzowdiut9rkudds9bE5yJ7npe7dG/wG+uFPw=
|
||||
github.com/coreos/go-systemd/v22 v22.5.0/go.mod h1:Y58oyj3AT4RCenI/lSvhwexgC+NSVTIJ3seZv2GcEnc=
|
||||
github.com/cpuguy83/dockercfg v0.3.2 h1:DlJTyZGBDlXqUZ2Dk2Q3xHs/FtnooJJVaad2S9GKorA=
|
||||
github.com/cpuguy83/dockercfg v0.3.2/go.mod h1:sugsbF4//dDlL/i+S+rtpIWp+5h0BHJHfjj5/jFyUJc=
|
||||
github.com/dave-gray101/v2keyauth v0.0.0-20240624150259-c45d584d25e2 h1:flLYmnQFZNo04x2NPehMbf30m7Pli57xwZ0NFqR/hb0=
|
||||
github.com/dave-gray101/v2keyauth v0.0.0-20240624150259-c45d584d25e2/go.mod h1:NtWqRzAp/1tw+twkW8uuBenEVVYndEAZACWU3F3xdoQ=
|
||||
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
|
||||
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||
github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc h1:U9qPSI2PIWSS1VwoXQT9A3Wy9MM3WgvqSxFWenqJduM=
|
||||
github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||
github.com/distribution/reference v0.6.0 h1:0IXCQ5g4/QMHHkarYzh5l+u8T3t73zM5QvfrDyIgxBk=
|
||||
github.com/distribution/reference v0.6.0/go.mod h1:BbU0aIcezP1/5jX/8MP0YiH4SdvB5Y4f/wlDRiLyi3E=
|
||||
github.com/dlclark/regexp2 v1.11.5 h1:Q/sSnsKerHeCkc/jSTNq1oCm7KiVgUMZRDUoRu0JQZQ=
|
||||
github.com/dlclark/regexp2 v1.11.5/go.mod h1:DHkYz0B9wPfa6wondMfaivmHpzrQ3v9q8cnmRbL6yW8=
|
||||
github.com/donseba/go-htmx v1.12.0 h1:7tESER0uxaqsuGMv3yP3pK1drfBUXM6apG4H7/3+IgE=
|
||||
github.com/donseba/go-htmx v1.12.0/go.mod h1:8PTAYvNKf8+QYis+DpAsggKz+sa2qljtMgvdAeNBh5s=
|
||||
github.com/docker/docker v28.2.2+incompatible h1:CjwRSksz8Yo4+RmQ339Dp/D2tGO5JxwYeqtMOEe0LDw=
|
||||
github.com/docker/docker v28.2.2+incompatible/go.mod h1:eEKB0N0r5NX/I1kEveEz05bcu8tLC/8azJZsviup8Sk=
|
||||
github.com/docker/go-connections v0.5.0 h1:USnMq7hx7gwdVZq1L49hLXaFtUdTADjXGp+uj1Br63c=
|
||||
github.com/docker/go-connections v0.5.0/go.mod h1:ov60Kzw0kKElRwhNs9UlUHAE/F9Fe6GLaXnqyDdmEXc=
|
||||
github.com/docker/go-units v0.5.0 h1:69rxXcBk27SvSaaxTtLh/8llcHD8vYHT7WSdRZ/jvr4=
|
||||
github.com/docker/go-units v0.5.0/go.mod h1:fgPhTUdO+D/Jk86RDLlptpiXQzgHJF7gydDDbaIK4Dk=
|
||||
github.com/ebitengine/purego v0.8.4 h1:CF7LEKg5FFOsASUj0+QwaXf8Ht6TlFxg09+S9wz0omw=
|
||||
github.com/ebitengine/purego v0.8.4/go.mod h1:iIjxzd6CiRiOG0UyXP+V1+jWqUXVjPKLAI0mRfJZTmQ=
|
||||
github.com/emersion/go-imap/v2 v2.0.0-beta.5 h1:H3858DNmBuXyMK1++YrQIRdpKE1MwBc+ywBtg3n+0wA=
|
||||
github.com/emersion/go-imap/v2 v2.0.0-beta.5/go.mod h1:BZTFHsS1hmgBkFlHqbxGLXk2hnRqTItUgwjSSCsYNAk=
|
||||
github.com/emersion/go-message v0.18.2 h1:rl55SQdjd9oJcIoQNhubD2Acs1E6IzlZISRTK7x/Lpg=
|
||||
github.com/emersion/go-message v0.18.2/go.mod h1:XpJyL70LwRvq2a8rVbHXikPgKj8+aI0kGdHlg16ibYA=
|
||||
github.com/emersion/go-sasl v0.0.0-20241020182733-b788ff22d5a6 h1:oP4q0fw+fOSWn3DfFi4EXdT+B+gTtzx8GC9xsc26Znk=
|
||||
github.com/emersion/go-sasl v0.0.0-20241020182733-b788ff22d5a6/go.mod h1:iL2twTeMvZnrg54ZoPDNfJaJaqy0xIQFuBdrLsmspwQ=
|
||||
github.com/emersion/go-smtp v0.22.0 h1:/d3HWxkZZ4riB+0kzfoODh9X+xyCrLEezMnAAa1LEMU=
|
||||
github.com/emersion/go-smtp v0.22.0/go.mod h1:ZtRRkbTyp2XTHCA+BmyTFTrj8xY4I+b4McvHxCU2gsQ=
|
||||
github.com/emersion/go-smtp v0.24.0 h1:g6AfoF140mvW0vLNPD/LuCBLEAdlxOjIXqbIkJIS6Wk=
|
||||
github.com/emersion/go-smtp v0.24.0/go.mod h1:ZtRRkbTyp2XTHCA+BmyTFTrj8xY4I+b4McvHxCU2gsQ=
|
||||
github.com/eritikass/githubmarkdownconvertergo v0.1.10 h1:mL93ADvYMOeT15DcGtK9AaFFc+RcWcy6kQBC6yS/5f4=
|
||||
github.com/eritikass/githubmarkdownconvertergo v0.1.10/go.mod h1:BdpHs6imOtzE5KorbUtKa6bZ0ZBh1yFcrTTAL8FwDKY=
|
||||
github.com/gabriel-vasile/mimetype v1.4.9 h1:5k+WDwEsD9eTLL8Tz3L0VnmVh9QxGjRmjBvAG7U/oYY=
|
||||
github.com/gabriel-vasile/mimetype v1.4.9/go.mod h1:WnSQhFKJuBlRyLiKohA/2DtIlPFAbguNaG7QCHcyGok=
|
||||
github.com/gin-contrib/sse v1.1.0 h1:n0w2GMuUpWDVp7qSpvze6fAu9iRxJY4Hmj6AmBOU05w=
|
||||
github.com/gin-contrib/sse v1.1.0/go.mod h1:hxRZ5gVpWMT7Z0B0gSNYqqsSCNIJMjzvm6fqCz9vjwM=
|
||||
github.com/gin-gonic/gin v1.10.0 h1:nTuyha1TYqgedzytsKYqna+DfLos46nTv2ygFy86HFU=
|
||||
github.com/gin-gonic/gin v1.10.0/go.mod h1:4PMNQiOhvDRa013RKVbsiNwoyezlm2rm0uX/T7kzp5Y=
|
||||
github.com/go-logr/logr v1.4.2 h1:6pFjapn8bFcIbiKo3XT4j/BhANplGihG6tvd+8rYgrY=
|
||||
github.com/go-logr/logr v1.4.2/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY=
|
||||
github.com/go-playground/assert/v2 v2.2.0 h1:JvknZsQTYeFEAhQwI4qEt9cyV5ONwRHC+lYKSsYSR8s=
|
||||
github.com/go-playground/assert/v2 v2.2.0/go.mod h1:VDjEfimB/XKnb+ZQfWdccd7VUvScMdVu0Titje2rxJ4=
|
||||
github.com/go-playground/locales v0.14.1 h1:EWaQ/wswjilfKLTECiXz7Rh+3BjFhfDFKv/oXslEjJA=
|
||||
github.com/go-playground/locales v0.14.1/go.mod h1:hxrqLVvrK65+Rwrd5Fc6F2O76J/NuW9t0sjnWqG1slY=
|
||||
github.com/go-playground/universal-translator v0.18.1 h1:Bcnm0ZwsGyWbCzImXv+pAJnYK9S473LQFuzCbDbfSFY=
|
||||
github.com/go-playground/universal-translator v0.18.1/go.mod h1:xekY+UJKNuX9WP91TpwSH2VMlDf28Uj24BCp08ZFTUY=
|
||||
github.com/go-playground/validator/v10 v10.26.0 h1:SP05Nqhjcvz81uJaRfEV0YBSSSGMc/iMaVtFbr3Sw2k=
|
||||
github.com/go-playground/validator/v10 v10.26.0/go.mod h1:I5QpIEbmr8On7W0TktmJAumgzX4CA1XNl4ZmDuVHKKo=
|
||||
github.com/felixge/httpsnoop v1.0.4 h1:NFTV2Zj1bL4mc9sqWACXbQFVBBg2W3GPvqp8/ESS2Wg=
|
||||
github.com/felixge/httpsnoop v1.0.4/go.mod h1:m8KPJKqk1gH5J9DgRY2ASl2lWCfGKXixSwevea8zH2U=
|
||||
github.com/frankban/quicktest v1.14.6 h1:7Xjx+VpznH+oBnejlPUj8oUpdxnVs4f8XU8WnHkI4W8=
|
||||
github.com/frankban/quicktest v1.14.6/go.mod h1:4ptaffx2x8+WTWXmUCuVU6aPUX1/Mz7zb5vbUoiM6w0=
|
||||
github.com/go-logr/logr v1.4.3 h1:CjnDlHq8ikf6E492q6eKboGOC0T8CDaOvkHCIg8idEI=
|
||||
github.com/go-logr/logr v1.4.3/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY=
|
||||
github.com/go-logr/stdr v1.2.2 h1:hSWxHoqTgW2S2qGc0LTAI563KZ5YKYRhT3MFKZMbjag=
|
||||
github.com/go-logr/stdr v1.2.2/go.mod h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre4VKE=
|
||||
github.com/go-ole/go-ole v1.3.0 h1:Dt6ye7+vXGIKZ7Xtk4s6/xVdGDQynvom7xCFEdWr6uE=
|
||||
github.com/go-ole/go-ole v1.3.0/go.mod h1:5LS6F96DhAwUc7C+1HLexzMXY1xGRSryjyPPKW6zv78=
|
||||
github.com/go-task/slim-sprig/v3 v3.0.0 h1:sUs3vkvUymDpBKi3qH1YSqBQk9+9D/8M2mN1vB6EwHI=
|
||||
github.com/go-task/slim-sprig/v3 v3.0.0/go.mod h1:W848ghGpv3Qj3dhTPRyJypKRiqCdHZiAzKg9hl15HA8=
|
||||
github.com/go-telegram/bot v1.15.0 h1:/ba5pp084MUhjR5sQDymQ7JNZ001CQa7QjtxLWcuGpg=
|
||||
github.com/go-telegram/bot v1.15.0/go.mod h1:i2TRs7fXWIeaceF3z7KzsMt/he0TwkVC680mvdTFYeM=
|
||||
github.com/go-telegram/bot v1.17.0 h1:Hs0kGxSj97QFqOQP0zxduY/4tSx8QDzvNI9uVRS+zmY=
|
||||
github.com/go-telegram/bot v1.17.0/go.mod h1:i2TRs7fXWIeaceF3z7KzsMt/he0TwkVC680mvdTFYeM=
|
||||
github.com/go-test/deep v1.1.1 h1:0r/53hagsehfO4bzD2Pgr/+RgHqhmf+k1Bpse2cTu1U=
|
||||
github.com/go-test/deep v1.1.1/go.mod h1:5C2ZWiW0ErCdrYzpqxLbTX7MG14M9iiw8DgHncVwcsE=
|
||||
github.com/gobwas/glob v0.2.3 h1:A4xDbljILXROh+kObIiy5kIaPYD8e96x1tgBhUI5J+Y=
|
||||
github.com/gobwas/glob v0.2.3/go.mod h1:d3Ez4x06l9bZtSvzIay5+Yzi0fmZzPgnTbPcKjJAkT8=
|
||||
github.com/goccy/go-json v0.10.5 h1:Fq85nIqj+gXn/S5ahsiTlK3TmC85qgirsdTP/+DeaC4=
|
||||
github.com/goccy/go-json v0.10.5/go.mod h1:oq7eo15ShAhp70Anwd5lgX2pLfOS3QCiwU/PULtXL6M=
|
||||
github.com/gocolly/colly v1.2.0 h1:qRz9YAn8FIH0qzgNUw+HT9UN7wm1oF9OBAilwEWpyrI=
|
||||
github.com/gocolly/colly v1.2.0/go.mod h1:Hof5T3ZswNVsOHYmba1u03W65HDWgpV5HifSuueE0EA=
|
||||
github.com/godbus/dbus/v5 v5.0.4/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA=
|
||||
github.com/gofiber/fiber/v2 v2.52.8 h1:xl4jJQ0BV5EJTA2aWiKw/VddRpHrKeZLF0QPUxqn0x4=
|
||||
github.com/gofiber/fiber/v2 v2.52.8/go.mod h1:YEcBbO/FB+5M1IZNBP9FO3J9281zgPAreiI1oqg8nDw=
|
||||
github.com/gofiber/template v1.8.3 h1:hzHdvMwMo/T2kouz2pPCA0zGiLCeMnoGsQZBTSYgZxc=
|
||||
github.com/gofiber/template v1.8.3/go.mod h1:bs/2n0pSNPOkRa5VJ8zTIvedcI/lEYxzV3+YPXdBvq8=
|
||||
github.com/gofiber/template/html/v2 v2.1.3 h1:n1LYBtmr9C0V/k/3qBblXyMxV5B0o/gpb6dFLp8ea+o=
|
||||
github.com/gofiber/template/html/v2 v2.1.3/go.mod h1:U5Fxgc5KpyujU9OqKzy6Kn6Qup6Tm7zdsISR+VpnHRE=
|
||||
github.com/gofiber/utils v1.1.0 h1:vdEBpn7AzIUJRhe+CiTOJdUcTg4Q9RK+pEa0KPbLdrM=
|
||||
github.com/gofiber/utils v1.1.0/go.mod h1:poZpsnhBykfnY1Mc0KeEa6mSHrS3dV0+oBWyeQmb2e0=
|
||||
github.com/gofiber/fiber/v2 v2.52.9 h1:YjKl5DOiyP3j0mO61u3NTmK7or8GzzWzCFzkboyP5cw=
|
||||
github.com/gofiber/fiber/v2 v2.52.9/go.mod h1:YEcBbO/FB+5M1IZNBP9FO3J9281zgPAreiI1oqg8nDw=
|
||||
github.com/gogo/protobuf v1.3.2 h1:Ov1cvc58UF3b5XjBnZv7+opcTcQFZebYjWzi34vdm4Q=
|
||||
github.com/gogo/protobuf v1.3.2/go.mod h1:P1XiOD3dCwIKUDQYPy72D8LYyHL2YPYrpS2s69NZV8Q=
|
||||
github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
|
||||
github.com/golang/groupcache v0.0.0-20241129210726-2c02b8208cf8 h1:f+oWsMOmNPc8JmEHVZIycC7hBoQxHH9pNKQORJNozsQ=
|
||||
github.com/golang/groupcache v0.0.0-20241129210726-2c02b8208cf8/go.mod h1:wcDNUvekVysuuOpQKo3191zZyTpiI6se1N1ULghS0sw=
|
||||
@@ -106,7 +115,8 @@ github.com/google/go-github/v69 v69.2.0 h1:wR+Wi/fN2zdUx9YxSmYE0ktiX9IAR/BeePzea
|
||||
github.com/google/go-github/v69 v69.2.0/go.mod h1:xne4jymxLR6Uj9b7J7PyTpkMYstEMMwGZa0Aehh1azM=
|
||||
github.com/google/go-querystring v1.1.0 h1:AnCroh3fv4ZBgVIf1Iwtovgjaw/GiKJo8M8yD/fhyJ8=
|
||||
github.com/google/go-querystring v1.1.0/go.mod h1:Kcdr2DB4koayq7X8pmAG4sNG59So17icRSOU623lUBU=
|
||||
github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
|
||||
github.com/google/jsonschema-go v0.3.0 h1:6AH2TxVNtk3IlvkkhjrtbUc4S8AvO0Xii0DxIygDg+Q=
|
||||
github.com/google/jsonschema-go v0.3.0/go.mod h1:r5quNTdLOYEz95Ru18zA0ydNbBuYoo9tgaYcxEYhJVE=
|
||||
github.com/google/pprof v0.0.0-20250423184734-337e5dd93bb4 h1:gD0vax+4I+mAj+jEChEf25Ia07Jq7kYOFO5PPhAxFl4=
|
||||
github.com/google/pprof v0.0.0-20250423184734-337e5dd93bb4/go.mod h1:5hDyRhoBCxViHszMt12TnOpEI4VVi+U8Gm9iphldiMA=
|
||||
github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0=
|
||||
@@ -114,26 +124,20 @@ github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+
|
||||
github.com/gorilla/websocket v1.4.2/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE=
|
||||
github.com/gorilla/websocket v1.5.3 h1:saDtZ6Pbx/0u+bgYQ3q96pZgCzfhKXGPqt7kZ72aNNg=
|
||||
github.com/gorilla/websocket v1.5.3/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE=
|
||||
github.com/invopop/jsonschema v0.13.0 h1:KvpoAJWEjR3uD9Kbm2HWJmqsEaHt8lBUpd0qHcIi21E=
|
||||
github.com/invopop/jsonschema v0.13.0/go.mod h1:ffZ5Km5SWWRAIN6wbDXItl95euhFz2uON45H2qjYt+0=
|
||||
github.com/json-iterator/go v1.1.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnrnM=
|
||||
github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo=
|
||||
github.com/huandu/xstrings v1.5.0 h1:2ag3IFq9ZDANvthTwTiqSSZLjDc+BedvHPAp5tJy2TI=
|
||||
github.com/huandu/xstrings v1.5.0/go.mod h1:y5/lhBue+AyNmUVz9RLU9xbLR0o4KIIExikq4ovT0aE=
|
||||
github.com/kennygrant/sanitize v1.2.4 h1:gN25/otpP5vAsO2djbMhF/LQX6R7+O1TB4yv8NzpJ3o=
|
||||
github.com/kennygrant/sanitize v1.2.4/go.mod h1:LGsjYYtgxbetdg5owWB2mpgUL6e2nfw2eObZ0u0qvak=
|
||||
github.com/klauspost/compress v1.18.0 h1:c/Cqfb0r+Yi+JtIEq73FWXVkRonBlf0CRNYc8Zttxdo=
|
||||
github.com/klauspost/compress v1.18.0/go.mod h1:2Pp+KzxcywXVXMr50+X0Q/Lsb43OQHYWRCY2AiWywWQ=
|
||||
github.com/klauspost/cpuid/v2 v2.0.9/go.mod h1:FInQzS24/EEf25PyTYn52gqo7WaD8xa0213Md/qVLRg=
|
||||
github.com/klauspost/cpuid/v2 v2.2.10 h1:tBs3QSyvjDyFTq3uoc/9xFpCuOsJQFNPiAhYdw2skhE=
|
||||
github.com/klauspost/cpuid/v2 v2.2.10/go.mod h1:hqwkgyIinND0mEev00jJYCxPNVRVXFQeu1XKlok6oO0=
|
||||
github.com/knz/go-libedit v1.10.1/go.mod h1:MZTVkCWyz0oBc7JOWP3wNAzd002ZbM/5hgShxwh4x8M=
|
||||
github.com/klauspost/compress v1.18.1 h1:bcSGx7UbpBqMChDtsF28Lw6v/G94LPrrbMbdC3JH2co=
|
||||
github.com/klauspost/compress v1.18.1/go.mod h1:ZQFFVG+MdnR0P+l6wpXgIL4NTtwiKIdBnrBd8Nrxr+0=
|
||||
github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE=
|
||||
github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk=
|
||||
github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
|
||||
github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
|
||||
github.com/leodido/go-urn v1.4.0 h1:WT9HwE9SGECu3lg4d/dIA+jxlljEa1/ffXKmRjqdmIQ=
|
||||
github.com/leodido/go-urn v1.4.0/go.mod h1:bvxc+MVxLKB4z00jd1z+Dvzr47oO32F/QSNjSBOlFxI=
|
||||
github.com/mailru/easyjson v0.9.0 h1:PrnmzHw7262yW8sTBwxi1PdJA3Iw/EKBa8psRf7d9a4=
|
||||
github.com/mailru/easyjson v0.9.0/go.mod h1:1+xMtQp2MRNVL/V1bOzuP3aP8VNwRW55fQUto+XFtTU=
|
||||
github.com/lufia/plan9stats v0.0.0-20250317134145-8bc96cf8fc35 h1:PpXWgLPs+Fqr325bN2FD2ISlRRztXibcX6e8f5FR5Dc=
|
||||
github.com/lufia/plan9stats v0.0.0-20250317134145-8bc96cf8fc35/go.mod h1:autxFIvghDt3jPTLoqZ9OZ7s9qTGNAWmYCjVFWPX/zg=
|
||||
github.com/magiconair/properties v1.8.10 h1:s31yESBquKXCV9a/ScB3ESkOjUYYv+X0rg8SYxI99mE=
|
||||
github.com/magiconair/properties v1.8.10/go.mod h1:Dhd985XPs7jluiymwWYZ0G4Z61jb3vdS329zhj2hYo0=
|
||||
github.com/mattn/go-colorable v0.1.13/go.mod h1:7S9/ev0klgBDR4GtXTXX8a3vIGJpMovkB8vQcUbaXHg=
|
||||
github.com/mattn/go-colorable v0.1.14 h1:9A9LHSqF/7dyVVX6g0U9cwm9pG3kP9gSzcuIPHPsaIE=
|
||||
github.com/mattn/go-colorable v0.1.14/go.mod h1:6LmQG8QLFO4G5z1gPvYEzlUgJ2wF+stgPZH1UqBm1s8=
|
||||
@@ -144,29 +148,51 @@ github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D
|
||||
github.com/mattn/go-runewidth v0.0.9/go.mod h1:H031xJmbD/WCDINGzjvQ9THkh0rPKHF+m2gUSrubnMI=
|
||||
github.com/mattn/go-runewidth v0.0.16 h1:E5ScNMtiwvlvB5paMFdw9p4kSQzbXFikJ5SQO6TULQc=
|
||||
github.com/mattn/go-runewidth v0.0.16/go.mod h1:Jdepj2loyihRzMpdS35Xk/zdY8IAYHsh153qUoGf23w=
|
||||
github.com/metoro-io/mcp-golang v0.13.0 h1:54TFBJIW76VRB55CJovQQje9x4GnXg0BQQwGRtXrbCE=
|
||||
github.com/metoro-io/mcp-golang v0.13.0/go.mod h1:ifLP9ZzKpN1UqFWNTpAHOqSvNkMK6b7d1FSZ5Lu0lN0=
|
||||
github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
|
||||
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd h1:TRLaZ9cD/w8PVh93nsPXa1VrQ6jlwL5oN8l14QlcNfg=
|
||||
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
|
||||
github.com/modern-go/reflect2 v1.0.2 h1:xBagoLtFs94CBntxluKeaWgTMpvLxC4ur3nMaC9Gz0M=
|
||||
github.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk=
|
||||
github.com/mitchellh/copystructure v1.2.0 h1:vpKXTN4ewci03Vljg/q9QvCGUDttBOGBIa15WveJJGw=
|
||||
github.com/mitchellh/copystructure v1.2.0/go.mod h1:qLl+cE2AmVv+CoeAwDPye/v+N2HKCj9FbZEVFJRxO9s=
|
||||
github.com/mitchellh/reflectwalk v1.0.2 h1:G2LzWKi524PWgd3mLHV8Y5k7s6XUvT0Gef6zxSIeXaQ=
|
||||
github.com/mitchellh/reflectwalk v1.0.2/go.mod h1:mSTlrgnPZtwu0c4WaC2kGObEpuNDbx0jmZXqmk4esnw=
|
||||
github.com/moby/docker-image-spec v1.3.1 h1:jMKff3w6PgbfSa69GfNg+zN/XLhfXJGnEx3Nl2EsFP0=
|
||||
github.com/moby/docker-image-spec v1.3.1/go.mod h1:eKmb5VW8vQEh/BAr2yvVNvuiJuY6UIocYsFu/DxxRpo=
|
||||
github.com/moby/go-archive v0.1.0 h1:Kk/5rdW/g+H8NHdJW2gsXyZ7UnzvJNOy6VKJqueWdcQ=
|
||||
github.com/moby/go-archive v0.1.0/go.mod h1:G9B+YoujNohJmrIYFBpSd54GTUB4lt9S+xVQvsJyFuo=
|
||||
github.com/moby/patternmatcher v0.6.0 h1:GmP9lR19aU5GqSSFko+5pRqHi+Ohk1O69aFiKkVGiPk=
|
||||
github.com/moby/patternmatcher v0.6.0/go.mod h1:hDPoyOpDY7OrrMDLaYoY3hf52gNCR/YOUYxkhApJIxc=
|
||||
github.com/moby/sys/sequential v0.6.0 h1:qrx7XFUd/5DxtqcoH1h438hF5TmOvzC/lspjy7zgvCU=
|
||||
github.com/moby/sys/sequential v0.6.0/go.mod h1:uyv8EUTrca5PnDsdMGXhZe6CCe8U/UiTWd+lL+7b/Ko=
|
||||
github.com/moby/sys/user v0.4.0 h1:jhcMKit7SA80hivmFJcbB1vqmw//wU61Zdui2eQXuMs=
|
||||
github.com/moby/sys/user v0.4.0/go.mod h1:bG+tYYYJgaMtRKgEmuueC0hJEAZWwtIbZTB+85uoHjs=
|
||||
github.com/moby/sys/userns v0.1.0 h1:tVLXkFOxVu9A64/yh59slHVv9ahO9UIev4JZusOLG/g=
|
||||
github.com/moby/sys/userns v0.1.0/go.mod h1:IHUYgu/kao6N8YZlp9Cf444ySSvCmDlmzUcYfDHOl28=
|
||||
github.com/moby/term v0.5.2 h1:6qk3FJAFDs6i/q3W/pQ97SX192qKfZgGjCQqfCJkgzQ=
|
||||
github.com/moby/term v0.5.2/go.mod h1:d3djjFCrjnB+fl8NJux+EJzu0msscUP+f8it8hPkFLc=
|
||||
github.com/modelcontextprotocol/go-sdk v1.1.0 h1:Qjayg53dnKC4UZ+792W21e4BpwEZBzwgRW6LrjLWSwA=
|
||||
github.com/modelcontextprotocol/go-sdk v1.1.0/go.mod h1:6fM3LCm3yV7pAs8isnKLn07oKtB0MP9LHd3DfAcKw10=
|
||||
github.com/morikuni/aec v1.0.0 h1:nP9CBfwrvYnBRgY6qfDQkygYDmYwOilePFkwzv4dU8A=
|
||||
github.com/morikuni/aec v1.0.0/go.mod h1:BbKIizmSmc5MMPqRYbxO4ZU0S0+P200+tUnFx7PXmsc=
|
||||
github.com/mudler/cogito v0.5.1 h1:KK9F7pNJUopewiZRsKnDEGu49+22fPrxWYxpoWQWN7s=
|
||||
github.com/mudler/cogito v0.5.1/go.mod h1:2uhEElCTq8eXSsqJ1JF01oA5h9niXSELVKqCF1PqjEw=
|
||||
github.com/olekukonko/tablewriter v0.0.5 h1:P2Ga83D34wi1o9J6Wh1mRuqd4mF/x/lgBS7N7AbDhec=
|
||||
github.com/olekukonko/tablewriter v0.0.5/go.mod h1:hPp6KlRPjbx+hW8ykQs1w3UBbZlj6HuIJcUGPhkA7kY=
|
||||
github.com/onsi/ginkgo/v2 v2.23.4 h1:ktYTpKJAVZnDT4VjxSbiBenUjmlL/5QkBEocaWXiQus=
|
||||
github.com/onsi/ginkgo/v2 v2.23.4/go.mod h1:Bt66ApGPBFzHyR+JO10Zbt0Gsp4uWxu5mIOTusL46e8=
|
||||
github.com/onsi/gomega v1.37.0 h1:CdEG8g0S133B4OswTDC/5XPSzE1OeP29QOioj2PID2Y=
|
||||
github.com/onsi/gomega v1.37.0/go.mod h1:8D9+Txp43QWKhM24yyOBEdpkzN8FvJyAwecBgsU4KU0=
|
||||
github.com/pelletier/go-toml/v2 v2.2.4 h1:mye9XuhQ6gvn5h28+VilKrrPoQVanw5PMw/TB0t5Ec4=
|
||||
github.com/pelletier/go-toml/v2 v2.2.4/go.mod h1:2gIqNv+qfxSVS7cM2xJQKtLSTLUE9V8t9Stt+h56mCY=
|
||||
github.com/onsi/ginkgo/v2 v2.25.3 h1:Ty8+Yi/ayDAGtk4XxmmfUy4GabvM+MegeB4cDLRi6nw=
|
||||
github.com/onsi/ginkgo/v2 v2.25.3/go.mod h1:43uiyQC4Ed2tkOzLsEYm7hnrb7UJTWHYNsuy3bG/snE=
|
||||
github.com/onsi/gomega v1.38.2 h1:eZCjf2xjZAqe+LeWvKb5weQ+NcPwX84kqJ0cZNxok2A=
|
||||
github.com/onsi/gomega v1.38.2/go.mod h1:W2MJcYxRGV63b418Ai34Ud0hEdTVXq9NW9+Sx6uXf3k=
|
||||
github.com/opencontainers/go-digest v1.0.0 h1:apOUWs51W5PlhuyGyz9FCeeBIOUDA/6nW8Oi/yOhh5U=
|
||||
github.com/opencontainers/go-digest v1.0.0/go.mod h1:0JzlMkj0TRzQZfJkVvzbP0HBR3IKzErnv2BNG4W4MAM=
|
||||
github.com/opencontainers/image-spec v1.1.1 h1:y0fUlFfIZhPF1W537XOLg0/fcx6zcHCJwooC2xJA040=
|
||||
github.com/opencontainers/image-spec v1.1.1/go.mod h1:qpqAh3Dmcf36wStyyWU+kCeDgrGnAve2nCC8+7h8Q0M=
|
||||
github.com/philippgille/chromem-go v0.7.0 h1:4jfvfyKymjKNfGxBUhHUcj1kp7B17NL/I1P+vGh1RvY=
|
||||
github.com/philippgille/chromem-go v0.7.0/go.mod h1:hTd+wGEm/fFPQl7ilfCwQXkgEUxceYh86iIdoKMolPo=
|
||||
github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4=
|
||||
github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
|
||||
github.com/pkoukk/tiktoken-go v0.1.7 h1:qOBHXX4PHtvIvmOtyg1EeKlwFRiMKAcoMp4Q+bLQDmw=
|
||||
github.com/pkoukk/tiktoken-go v0.1.7/go.mod h1:9NiV+i9mJKGj1rYOT+njbv+ZwA/zJxYdewGl6qVatpg=
|
||||
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
|
||||
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
||||
github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 h1:Jamvg5psRIccs7FGNTlIRMkT8wgtp5eCXdBlqhYGL6U=
|
||||
github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
||||
github.com/power-devops/perfstat v0.0.0-20240221224432-82ca36839d55 h1:o4JXh1EVt9k/+g42oCprj/FisM4qX9L3sZB3upGN2ZU=
|
||||
github.com/power-devops/perfstat v0.0.0-20240221224432-82ca36839d55/go.mod h1:OmDBASR4679mdNQnz2pUhc2G8CO2JrUAVFDRBDP/hJE=
|
||||
github.com/prashantv/gostub v1.1.0 h1:BTyx3RfQjRHnUWaGF9oQos79AlQ5k8WNktv7VGvVH4g=
|
||||
github.com/prashantv/gostub v1.1.0/go.mod h1:A5zLQHz7ieHGG7is6LLXLz7I8+3LZzsrV0P1IAHhP5U=
|
||||
github.com/rivo/uniseg v0.2.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc=
|
||||
@@ -181,28 +207,34 @@ github.com/rs/zerolog v1.31.0 h1:FcTR3NnLWW+NnTwwhFWiJSZr4ECLpqCm6QsEnyvbV4A=
|
||||
github.com/rs/zerolog v1.31.0/go.mod h1:/7mN4D5sKwJLZQ2b/znpjC3/GQWY/xaDXUM0kKWRHss=
|
||||
github.com/saintfish/chardet v0.0.0-20230101081208-5e3ef4b5456d h1:hrujxIzL1woJ7AwssoOcM/tq5JjjG2yYOc8odClEiXA=
|
||||
github.com/saintfish/chardet v0.0.0-20230101081208-5e3ef4b5456d/go.mod h1:uugorj2VCxiV1x+LzaIdVa9b4S4qGAcH6cbhh4qVxOU=
|
||||
github.com/sashabaranov/go-openai v1.40.3 h1:PkOw0SK34wrvYVOuXF1HZzuTBRh992qRZHil4kG3eYE=
|
||||
github.com/sashabaranov/go-openai v1.40.3/go.mod h1:lj5b/K+zjTSFxVLijLSTDZuP7adOgerWeFyZLUhAKRg=
|
||||
github.com/sebdah/goldie/v2 v2.5.5 h1:rx1mwF95RxZ3/83sdS4Yp7t2C5TCokvWP4TBRbAyEWY=
|
||||
github.com/sebdah/goldie/v2 v2.5.5/go.mod h1:oZ9fp0+se1eapSRjfYbsV/0Hqhbuu3bJVvKI/NNtssI=
|
||||
github.com/sergi/go-diff v1.3.1 h1:xkr+Oxo4BOQKmkn/B9eMK0g5Kg/983T9DqqPHwYqD+8=
|
||||
github.com/sergi/go-diff v1.3.1/go.mod h1:aMJSSKb2lpPvRNec0+w3fl7LP9IOFzdc9Pa4NFbPK1I=
|
||||
github.com/sashabaranov/go-openai v1.41.2 h1:vfPRBZNMpnqu8ELsclWcAvF19lDNgh1t6TVfFFOPiSM=
|
||||
github.com/sashabaranov/go-openai v1.41.2/go.mod h1:lj5b/K+zjTSFxVLijLSTDZuP7adOgerWeFyZLUhAKRg=
|
||||
github.com/sebdah/goldie/v2 v2.7.1 h1:PkBHymaYdtvEkZV7TmyqKxdmn5/Vcj+8TpATWZjnG5E=
|
||||
github.com/sebdah/goldie/v2 v2.7.1/go.mod h1:oZ9fp0+se1eapSRjfYbsV/0Hqhbuu3bJVvKI/NNtssI=
|
||||
github.com/sergi/go-diff v1.4.0 h1:n/SP9D5ad1fORl+llWyN+D6qoUETXNZARKjyY2/KVCw=
|
||||
github.com/sergi/go-diff v1.4.0/go.mod h1:A0bzQcvG0E7Rwjx0REVgAGH58e96+X0MeOfepqsbeW4=
|
||||
github.com/shirou/gopsutil/v4 v4.25.5 h1:rtd9piuSMGeU8g1RMXjZs9y9luK5BwtnG7dZaQUJAsc=
|
||||
github.com/shirou/gopsutil/v4 v4.25.5/go.mod h1:PfybzyydfZcN+JMMjkF6Zb8Mq1A/VcogFFg7hj50W9c=
|
||||
github.com/shopspring/decimal v1.4.0 h1:bxl37RwXBklmTi0C79JfXCEBD1cqqHt0bbgBAGFp81k=
|
||||
github.com/shopspring/decimal v1.4.0/go.mod h1:gawqmDU56v4yIKSwfBSFip1HdCCXN8/+DMd9qYNcwME=
|
||||
github.com/sirupsen/logrus v1.9.3 h1:dueUQJ1C2q9oE3F7wvmSGAaVtTmUizReu6fjN8uqzbQ=
|
||||
github.com/sirupsen/logrus v1.9.3/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVsIT4qYEQ=
|
||||
github.com/slack-go/slack v0.17.3 h1:zV5qO3Q+WJAQ/XwbGfNFrRMaJ5T/naqaonyPV/1TP4g=
|
||||
github.com/slack-go/slack v0.17.3/go.mod h1:X+UqOufi3LYQHDnMG1vxf0J8asC6+WllXrVrhl8/Prk=
|
||||
github.com/spf13/cast v1.7.0 h1:ntdiHjuueXFgm5nzDRdOS4yfT43P5Fnud6DH50rz/7w=
|
||||
github.com/spf13/cast v1.7.0/go.mod h1:ancEpBxwJDODSW/UG4rDrAqiKolqNNh2DX3mk86cAdo=
|
||||
github.com/ssor/bom v0.0.0-20170718123548-6386211fdfcf h1:pvbZ0lM0XWPBqUKqFU8cmavspvIl9nulOYwdy6IFRRo=
|
||||
github.com/ssor/bom v0.0.0-20170718123548-6386211fdfcf/go.mod h1:RJID2RhlZKId02nZ62WenDCkgHFerpIOmW0iT7GKmXM=
|
||||
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
|
||||
github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw=
|
||||
github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo=
|
||||
github.com/stretchr/objx v0.5.2 h1:xuMeJ0Sdp5ZMRXx/aWO6RZxdr3beISkG5/G/aIRr3pY=
|
||||
github.com/stretchr/objx v0.5.2/go.mod h1:FRsXN1f5AsAjCGJKqEizvkpNtU+EGNCLh3NxZ/8L+MA=
|
||||
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
|
||||
github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
||||
github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
||||
github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU=
|
||||
github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4=
|
||||
github.com/stretchr/testify v1.10.0 h1:Xv5erBjTwe/5IxqUQTdXv5kgmIvbHo3QQyRwhJsOfJA=
|
||||
github.com/stretchr/testify v1.10.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=
|
||||
github.com/stretchr/testify v1.11.1 h1:7s2iGBzp5EwR7/aIZr8ao5+dra3wiQyKjjFuvgVKu7U=
|
||||
github.com/stretchr/testify v1.11.1/go.mod h1:wZwfW3scLgRK+23gO65QZefKpKQRnfz6sD981Nm4B6U=
|
||||
github.com/temoto/robotstxt v1.1.2 h1:W2pOjSJ6SWvldyEuiFXNxz3xZ8aiWX5LbfDiOFd7Fxg=
|
||||
github.com/temoto/robotstxt v1.1.2/go.mod h1:+1AmkuG3IYkh1kv0d2qEB9Le88ehNO0zwOr3ujewlOo=
|
||||
github.com/testcontainers/testcontainers-go v0.38.0 h1:d7uEapLcv2P8AvH8ahLqDMMxda2W9gQN1nRbHS28HBw=
|
||||
github.com/testcontainers/testcontainers-go v0.38.0/go.mod h1:C52c9MoHpWO+C4aqmgSU+hxlR5jlEayWtgYrb8Pzz1w=
|
||||
github.com/thoj/go-ircevent v0.0.0-20210723090443-73e444401d64 h1:l/T7dYuJEQZOwVOpjIXr1180aM9PZL/d1MnMVIxefX4=
|
||||
github.com/thoj/go-ircevent v0.0.0-20210723090443-73e444401d64/go.mod h1:Q1NAJOuRdQCqN/VIWdnaaEhV8LpeO2rtlBP7/iDJNII=
|
||||
github.com/tidwall/gjson v1.14.2/go.mod h1:/wbyibRr2FHMks5tjHJ5F8dMZh3AcwJEMf5vlfC0lxk=
|
||||
@@ -215,33 +247,45 @@ github.com/tidwall/pretty v1.2.1 h1:qjsOFOWWQl+N3RsoF5/ssm1pHmJJwhjlSbZ51I6wMl4=
|
||||
github.com/tidwall/pretty v1.2.1/go.mod h1:ITEVvHYasfjBbM0u2Pg8T2nJnzm8xPwvNhhsoaGGjNU=
|
||||
github.com/tidwall/sjson v1.2.5 h1:kLy8mja+1c9jlljvWTlSazM7cKDRfJuR/bOJhcY5NcY=
|
||||
github.com/tidwall/sjson v1.2.5/go.mod h1:Fvgq9kS/6ociJEDnK0Fk1cpYF4FIW6ZF7LAe+6jwd28=
|
||||
github.com/tmc/langchaingo v0.1.13 h1:rcpMWBIi2y3B90XxfE4Ao8dhCQPVDMaNPnN5cGB1CaA=
|
||||
github.com/tmc/langchaingo v0.1.13/go.mod h1:vpQ5NOIhpzxDfTZK9B6tf2GM/MoaHewPWM5KXXGh7hg=
|
||||
github.com/tklauser/go-sysconf v0.3.15 h1:VE89k0criAymJ/Os65CSn1IXaol+1wrsFHEB8Ol49K4=
|
||||
github.com/tklauser/go-sysconf v0.3.15/go.mod h1:Dmjwr6tYFIseJw7a3dRLJfsHAMXZ3nEnL/aZY+0IuI4=
|
||||
github.com/tklauser/numcpus v0.10.0 h1:18njr6LDBk1zuna922MgdjQuJFjrdppsZG60sHGfjso=
|
||||
github.com/tklauser/numcpus v0.10.0/go.mod h1:BiTKazU708GQTYF4mB+cmlpT2Is1gLk7XVuEeem8LsQ=
|
||||
github.com/tmc/langchaingo v0.1.14 h1:o1qWBPigAIuFvrG6cjTFo0cZPFEZ47ZqpOYMjM15yZc=
|
||||
github.com/tmc/langchaingo v0.1.14/go.mod h1:aKKYXYoqhIDEv7WKdpnnCLRaqXic69cX9MnDUk72378=
|
||||
github.com/traefik/yaegi v0.16.1 h1:f1De3DVJqIDKmnasUF6MwmWv1dSEEat0wcpXhD2On3E=
|
||||
github.com/traefik/yaegi v0.16.1/go.mod h1:4eVhbPb3LnD2VigQjhYbEJ69vDRFdT2HQNrXx8eEwUY=
|
||||
github.com/twitchyliquid64/golang-asm v0.15.1 h1:SU5vSMR7hnwNxj24w34ZyCi/FmDZTkS4MhqMhdFk5YI=
|
||||
github.com/twitchyliquid64/golang-asm v0.15.1/go.mod h1:a1lVb/DtPvCB8fslRZhAngC2+aY1QWCk3Cedj/Gdt08=
|
||||
github.com/ugorji/go/codec v1.2.12 h1:9LC83zGrHhuUA9l16C9AHXAqEV/2wBQ4nkvumAE65EE=
|
||||
github.com/ugorji/go/codec v1.2.12/go.mod h1:UNopzCgEMSXjBc6AOMqYvWC1ktqTAfzJZUZgYf6w6lg=
|
||||
github.com/valyala/bytebufferpool v1.0.0 h1:GqA5TC/0021Y/b9FG4Oi9Mr3q7XYx6KllzawFIhcdPw=
|
||||
github.com/valyala/bytebufferpool v1.0.0/go.mod h1:6bBcMArwyJ5K/AmCkWv1jt77kVWyCJ6HpOuEn7z0Csc=
|
||||
github.com/valyala/fasthttp v1.62.0 h1:8dKRBX/y2rCzyc6903Zu1+3qN0H/d2MsxPPmVNamiH0=
|
||||
github.com/valyala/fasthttp v1.62.0/go.mod h1:FCINgr4GKdKqV8Q0xv8b+UxPV+H/O5nNFo3D+r54Htg=
|
||||
github.com/wk8/go-ordered-map/v2 v2.1.8 h1:5h/BUHu93oj4gIdvHHHGsScSTMijfx5PeYkE/fJgbpc=
|
||||
github.com/wk8/go-ordered-map/v2 v2.1.8/go.mod h1:5nJHM5DyteebpVlHnWMV0rPz6Zp7+xBAnxjb1X5vnTw=
|
||||
github.com/valyala/fasthttp v1.68.0 h1:v12Nx16iepr8r9ySOwqI+5RBJ/DqTxhOy1HrHoDFnok=
|
||||
github.com/valyala/fasthttp v1.68.0/go.mod h1:5EXiRfYQAoiO/khu4oU9VISC/eVY6JqmSpPJoHCKsz4=
|
||||
github.com/xyproto/randomstring v1.0.5 h1:YtlWPoRdgMu3NZtP45drfy1GKoojuR7hmRcnhZqKjWU=
|
||||
github.com/xyproto/randomstring v1.0.5/go.mod h1:rgmS5DeNXLivK7YprL0pY+lTuhNQW3iGxZ18UQApw/E=
|
||||
github.com/yosida95/uritemplate/v3 v3.0.2 h1:Ed3Oyj9yrmi9087+NczuL5BwkIc4wvTb5zIM+UJPGz4=
|
||||
github.com/yosida95/uritemplate/v3 v3.0.2/go.mod h1:ILOh0sOhIJR3+L/8afwt/kE++YT040gmv5BQTMR2HP4=
|
||||
github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY=
|
||||
github.com/yuin/goldmark v1.7.11 h1:ZCxLyDMtz0nT2HFfsYG8WZ47Trip2+JyLysKcMYE5bo=
|
||||
github.com/yuin/goldmark v1.7.11/go.mod h1:ip/1k0VRfGynBgxOz0yCqHrbZXhcjxyuS66Brc7iBKg=
|
||||
github.com/yuin/goldmark v1.7.13 h1:GPddIs617DnBLFFVJFgpo1aBfe/4xcvMc3SB5t/D0pA=
|
||||
github.com/yuin/goldmark v1.7.13/go.mod h1:ip/1k0VRfGynBgxOz0yCqHrbZXhcjxyuS66Brc7iBKg=
|
||||
github.com/yusufpapurcu/wmi v1.2.4 h1:zFUKzehAFReQwLys1b/iSMl+JQGSCSjtVqQn9bBrPo0=
|
||||
github.com/yusufpapurcu/wmi v1.2.4/go.mod h1:SBZ9tNy3G9/m5Oi98Zks0QjeHVDvuK0qfxQmPyzfmi0=
|
||||
go.mau.fi/util v0.3.0 h1:Lt3lbRXP6ZBqTINK0EieRWor3zEwwwrDT14Z5N8RUCs=
|
||||
go.mau.fi/util v0.3.0/go.mod h1:9dGsBCCbZJstx16YgnVMVi3O2bOizELoKpugLD4FoGs=
|
||||
go.opentelemetry.io/auto/sdk v1.1.0 h1:cH53jehLUN6UFLY71z+NDOiNJqDdPRaXzTel0sJySYA=
|
||||
go.opentelemetry.io/auto/sdk v1.1.0/go.mod h1:3wSPjt5PWp2RhlCcmmOial7AvC4DQqZb7a7wCow3W8A=
|
||||
go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.61.0 h1:F7Jx+6hwnZ41NSFTO5q4LYDtJRXBf2PD0rNBkeB/lus=
|
||||
go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.61.0/go.mod h1:UHB22Z8QsdRDrnAtX4PntOl36ajSxcdUMt1sF7Y6E7Q=
|
||||
go.opentelemetry.io/otel v1.38.0 h1:RkfdswUDRimDg0m2Az18RKOsnI8UDzppJAtj01/Ymk8=
|
||||
go.opentelemetry.io/otel v1.38.0/go.mod h1:zcmtmQ1+YmQM9wrNsTGV/q/uyusom3P8RxwExxkZhjM=
|
||||
go.opentelemetry.io/otel/metric v1.38.0 h1:Kl6lzIYGAh5M159u9NgiRkmoMKjvbsKtYRwgfrA6WpA=
|
||||
go.opentelemetry.io/otel/metric v1.38.0/go.mod h1:kB5n/QoRM8YwmUahxvI3bO34eVtQf2i4utNVLr9gEmI=
|
||||
go.opentelemetry.io/otel/trace v1.38.0 h1:Fxk5bKrDZJUH+AMyyIXGcFAPah0oRcT+LuNtJrmcNLE=
|
||||
go.opentelemetry.io/otel/trace v1.38.0/go.mod h1:j1P9ivuFsTceSWe1oY+EeW3sc+Pp42sO++GHkg4wwhs=
|
||||
go.starlark.net v0.0.0-20250417143717-f57e51f710eb h1:zOg9DxxrorEmgGUr5UPdCEwKqiqG0MlZciuCuA3XiDE=
|
||||
go.starlark.net v0.0.0-20250417143717-f57e51f710eb/go.mod h1:YKMCv9b1WrfWmeqdV5MAuEHWsu5iC+fe6kYl2sQjdI8=
|
||||
go.uber.org/automaxprocs v1.6.0 h1:O3y2/QNTOdbF+e/dpXNNW7Rx2hZ4sTIPyybbxyNqTUs=
|
||||
go.uber.org/automaxprocs v1.6.0/go.mod h1:ifeIMSnPZuznNm6jmdzmU3/bfk01Fe2fotchwEFJ8r8=
|
||||
golang.org/x/arch v0.16.0 h1:foMtLTdyOmIniqWCHjY6+JxuC54XP1fDwx4N0ASyW+U=
|
||||
golang.org/x/arch v0.16.0/go.mod h1:JmwW7aLIoRUKgaTzhkiEFxvcEiQGyOg9BMonBJUS7EE=
|
||||
go.yaml.in/yaml/v3 v3.0.4 h1:tfq32ie2Jv2UxXFdLJdh3jXuOzWiL1fo0bu/FbuKpbc=
|
||||
go.yaml.in/yaml/v3 v3.0.4/go.mod h1:DhzuOOF2ATzADvBadXxruRBLzYTpT36CKvDb3+aBEFg=
|
||||
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
|
||||
golang.org/x/crypto v0.0.0-20210421170649-83a5a9bb288b/go.mod h1:T9bdIzuCu7OtxOm1hfPfRQxPLYneinmdGuTeoZ9dtd4=
|
||||
golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
|
||||
@@ -249,10 +293,10 @@ golang.org/x/crypto v0.13.0/go.mod h1:y6Z2r+Rw4iayiXXAIxJIDAJ1zMW4yaTpebo8fPOliY
|
||||
golang.org/x/crypto v0.19.0/go.mod h1:Iy9bg/ha4yyC70EfRS8jz+B6ybOBKMaSxLj6P6oBDfU=
|
||||
golang.org/x/crypto v0.23.0/go.mod h1:CKFgDieR+mRhux2Lsu27y0fO304Db0wZe70UKqHu0v8=
|
||||
golang.org/x/crypto v0.31.0/go.mod h1:kDsLvtWBEx7MV9tJOj9bnXsPbxwJQ6csT/x4KIN4Ssk=
|
||||
golang.org/x/crypto v0.40.0 h1:r4x+VvoG5Fm+eJcxMaY8CQM7Lb0l1lsmjGBQ6s8BfKM=
|
||||
golang.org/x/crypto v0.40.0/go.mod h1:Qr1vMER5WyS2dfPHAlsOj01wgLbsyWtFn/aY+5+ZdxY=
|
||||
golang.org/x/exp v0.0.0-20240112132812-db7319d0e0e3 h1:hNQpMuAJe5CtcUqCXaWga3FHu+kQvCqcsoVaQgSV60o=
|
||||
golang.org/x/exp v0.0.0-20240112132812-db7319d0e0e3/go.mod h1:idGWGoKP1toJGkd5/ig9ZLuPcZBC3ewk7SzmH0uou08=
|
||||
golang.org/x/crypto v0.43.0 h1:dduJYIi3A3KOfdGOHX8AVZ/jGiyPa3IbBozJ5kNuE04=
|
||||
golang.org/x/crypto v0.43.0/go.mod h1:BFbav4mRNlXJL4wNeejLpWxB7wMbc79PdRGhWKncxR0=
|
||||
golang.org/x/exp v0.0.0-20240808152545-0cdaa3abc0fa h1:ELnwvuAXPNtPk1TJRuGkI9fDTwym6AYBu0qzT8AcHdI=
|
||||
golang.org/x/exp v0.0.0-20240808152545-0cdaa3abc0fa/go.mod h1:akd2r19cwCdwSwWeIdzYQGa/EZZyqcOdwWiwj5L5eKQ=
|
||||
golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4=
|
||||
golang.org/x/mod v0.8.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs=
|
||||
golang.org/x/mod v0.12.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs=
|
||||
@@ -268,8 +312,10 @@ golang.org/x/net v0.15.0/go.mod h1:idbUs1IY1+zTqbi8yxTbhexhEEk5ur9LInksu6HrEpk=
|
||||
golang.org/x/net v0.21.0/go.mod h1:bIjVDfnllIU7BJ2DNgfnXvpSvtn8VRwhlsaeUTyUS44=
|
||||
golang.org/x/net v0.25.0/go.mod h1:JkAGAh7GEvH74S6FOH42FLoXpXbE/aqXSrIQjXgsiwM=
|
||||
golang.org/x/net v0.33.0/go.mod h1:HXLR5J+9DxmrqMwG9qjGCxZ+zKXxBru04zlTvWlWuN4=
|
||||
golang.org/x/net v0.41.0 h1:vBTly1HeNPEn3wtREYfy4GZ/NECgw2Cnl+nK6Nz3uvw=
|
||||
golang.org/x/net v0.41.0/go.mod h1:B/K4NNqkfmg07DQYrbwvSluqCJOOXwUjeb/5lOisjbA=
|
||||
golang.org/x/net v0.46.0 h1:giFlY12I07fugqwPuWJi68oOnpfqFnJIJzaIIm2JVV4=
|
||||
golang.org/x/net v0.46.0/go.mod h1:Q9BGdFy1y4nkUwiLvT5qtyhAnEHgnQ/zd8PfU6nc210=
|
||||
golang.org/x/oauth2 v0.30.0 h1:dnDm7JmhM45NNpd8FDDeLhK6FwqbOf4MLCM9zb1BOHI=
|
||||
golang.org/x/oauth2 v0.30.0/go.mod h1:B++QgG3ZKulg6sRPGD/mqlHQs5rB3Ml9erfeDY7xKlU=
|
||||
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
@@ -291,8 +337,8 @@ golang.org/x/sys v0.12.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.17.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
|
||||
golang.org/x/sys v0.20.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
|
||||
golang.org/x/sys v0.28.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
|
||||
golang.org/x/sys v0.34.0 h1:H5Y5sJ2L2JRdyv7ROF1he/lPdvFsd0mJHFw2ThKHxLA=
|
||||
golang.org/x/sys v0.34.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k=
|
||||
golang.org/x/sys v0.37.0 h1:fdNQudmxPjkdUTPnLn5mdQv7Zwvbvpaxqs831goi9kQ=
|
||||
golang.org/x/sys v0.37.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks=
|
||||
golang.org/x/telemetry v0.0.0-20240228155512-f48c80bd79b2/go.mod h1:TeRTkGYfJXctD9OcfyVLyj2J3IxLnKwHJR8f4D8a3YE=
|
||||
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
|
||||
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
|
||||
@@ -302,8 +348,8 @@ golang.org/x/term v0.12.0/go.mod h1:owVbMEjm3cBLCHdkQu9b1opXd4ETQWc3BhuQGKgXgvU=
|
||||
golang.org/x/term v0.17.0/go.mod h1:lLRBjIVuehSbZlaOtGMbcMncT+aqLLLmKrsjNrUguwk=
|
||||
golang.org/x/term v0.20.0/go.mod h1:8UkIAJTvZgivsXaD6/pH6U9ecQzZ45awqEOzuCvwpFY=
|
||||
golang.org/x/term v0.27.0/go.mod h1:iMsnZpn0cago0GOrHO2+Y7u7JPn5AylBrcoWkElMTSM=
|
||||
golang.org/x/term v0.33.0 h1:NuFncQrRcaRvVmgRkvM3j/F00gWIAlcmlB8ACEKmGIg=
|
||||
golang.org/x/term v0.33.0/go.mod h1:s18+ql9tYWp1IfpV9DmCtQDDSRBUjKaw9M1eAv5UeF0=
|
||||
golang.org/x/term v0.36.0 h1:zMPR+aF8gfksFprF/Nc/rd1wRS1EI6nDBGyWAvDzx2Q=
|
||||
golang.org/x/term v0.36.0/go.mod h1:Qu394IJq6V6dCBRgwqshf3mPF85AqzYEzofzRdZkWss=
|
||||
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
||||
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
||||
golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
||||
@@ -315,30 +361,29 @@ golang.org/x/text v0.13.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE=
|
||||
golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU=
|
||||
golang.org/x/text v0.15.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU=
|
||||
golang.org/x/text v0.21.0/go.mod h1:4IBbMaMmOPCJ8SecivzSH54+73PCFmPWxNTLm+vZkEQ=
|
||||
golang.org/x/text v0.27.0 h1:4fGWRpyh641NLlecmyl4LOe6yDdfaYNrGb2zdfo4JV4=
|
||||
golang.org/x/text v0.27.0/go.mod h1:1D28KMCvyooCX9hBiosv5Tz/+YLxj0j7XhWjpSUF7CU=
|
||||
golang.org/x/text v0.30.0 h1:yznKA/E9zq54KzlzBEAWn1NXSQ8DIp/NYMy88xJjl4k=
|
||||
golang.org/x/text v0.30.0/go.mod h1:yDdHFIX9t+tORqspjENWgzaCVXgk0yYnYuSZ8UzzBVM=
|
||||
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
||||
golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
|
||||
golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc=
|
||||
golang.org/x/tools v0.6.0/go.mod h1:Xwgl3UAJ/d3gWutnCtw505GrjyAbvKui8lOU390QaIU=
|
||||
golang.org/x/tools v0.13.0/go.mod h1:HvlwmtVNQAhOuCjW7xxvovg8wbNq7LwfXh/k7wXUl58=
|
||||
golang.org/x/tools v0.21.1-0.20240508182429-e35e4ccd0d2d/go.mod h1:aiJjzUbINMkxbQROHiO6hDPo2LHcIPhhQsa9DLh0yGk=
|
||||
golang.org/x/tools v0.34.0 h1:qIpSLOxeCYGg9TrcJokLBG4KFA6d795g0xkBkiESGlo=
|
||||
golang.org/x/tools v0.34.0/go.mod h1:pAP9OwEaY1CAW3HOmg3hLZC5Z0CCmzjAF2UQMSqNARg=
|
||||
golang.org/x/tools v0.37.0 h1:DVSRzp7FwePZW356yEAChSdNcQo6Nsp+fex1SUW09lE=
|
||||
golang.org/x/tools v0.37.0/go.mod h1:MBN5QPQtLMHVdvsbtarmTNukZDdgwdwlO5qGacAzF0w=
|
||||
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||
google.golang.org/appengine v1.6.8 h1:IhEN5q69dyKagZPYMSdIjS2HqprW324FRQZJcGqPAsM=
|
||||
google.golang.org/appengine v1.6.8/go.mod h1:1jJ3jBArFh5pcgW8gCtRJnepW8FzD1V44FJffLiz/Ds=
|
||||
google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw=
|
||||
google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc=
|
||||
google.golang.org/protobuf v1.36.6 h1:z1NpPI8ku2WgiWnf+t9wTPsn6eP1L7ksHUlkfLvd9xY=
|
||||
google.golang.org/protobuf v1.36.6/go.mod h1:jduwjTPXsFjZGTmRluh+L6NjiWu7pchiJ2/5YcXBHnY=
|
||||
google.golang.org/protobuf v1.36.8 h1:xHScyCOEuuwZEc6UtSOvPbAT4zRh0xcNRYekJwfqyMc=
|
||||
google.golang.org/protobuf v1.36.8/go.mod h1:fuxRtAxBytpl4zzqUh6/eyUujkJdNiuEkXntxiD/uRU=
|
||||
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||
gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127 h1:qIbj1fsPNlZgppZ+VLlY7N33q108Sa+fhmuc+sWQYwY=
|
||||
gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||
gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY=
|
||||
gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ=
|
||||
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
|
||||
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||
jaytaylor.com/html2text v0.0.0-20230321000545-74c2419ad056 h1:6YFJoB+0fUH6X3xU/G2tQqCYg+PkGtnZ5nMR5rpw72g=
|
||||
@@ -349,6 +394,5 @@ maunium.net/go/mautrix v0.17.0 h1:scc1qlUbzPn+wc+3eAPquyD+3gZwwy/hBANBm+iGKK8=
|
||||
maunium.net/go/mautrix v0.17.0/go.mod h1:j+puTEQCEydlVxhJ/dQP5chfa26TdvBO7X6F3Ataav8=
|
||||
mvdan.cc/xurls/v2 v2.6.0 h1:3NTZpeTxYVWNSokW3MKeyVkz/j7uYXYiMtXRUfmjbgI=
|
||||
mvdan.cc/xurls/v2 v2.6.0/go.mod h1:bCvEZ1XvdA6wDnxY7jPPjEmigDtvtvPXAD/Exa9IMSk=
|
||||
nullprogram.com/x/optparse v1.0.0/go.mod h1:KdyPE+Igbe0jQUrVfMqDMeJQIJZEuyV7pjYmp6pbG50=
|
||||
sigs.k8s.io/yaml v1.3.0 h1:a2VclLzOGrwOHDiV8EfBGhvjHvP46CtW5j6POvhYGGo=
|
||||
sigs.k8s.io/yaml v1.3.0/go.mod h1:GeOyir5tyXNByN85N/dRIT9es5UQNerPYEKK56eTBm8=
|
||||
|
||||
@@ -13,6 +13,9 @@ import (
|
||||
|
||||
var baseModel = os.Getenv("LOCALAGI_MODEL")
|
||||
var multimodalModel = os.Getenv("LOCALAGI_MULTIMODAL_MODEL")
|
||||
var transcriptionModel = os.Getenv("LOCALAGI_TRANSCRIPTION_MODEL")
|
||||
var transcriptionLanguage = os.Getenv("LOCALAGI_TRANSCRIPTION_LANGUAGE")
|
||||
var ttsModel = os.Getenv("LOCALAGI_TTS_MODEL")
|
||||
var apiURL = os.Getenv("LOCALAGI_LLM_API_URL")
|
||||
var apiKey = os.Getenv("LOCALAGI_LLM_API_KEY")
|
||||
var timeout = os.Getenv("LOCALAGI_TIMEOUT")
|
||||
@@ -23,7 +26,7 @@ var apiKeysEnv = os.Getenv("LOCALAGI_API_KEYS")
|
||||
var imageModel = os.Getenv("LOCALAGI_IMAGE_MODEL")
|
||||
var conversationDuration = os.Getenv("LOCALAGI_CONVERSATION_DURATION")
|
||||
var localOperatorBaseURL = os.Getenv("LOCALOPERATOR_BASE_URL")
|
||||
var mcpboxURL = os.Getenv("LOCALAGI_MCPBOX_URL")
|
||||
var customActionsDir = os.Getenv("LOCALAGI_CUSTOM_ACTIONS_DIR")
|
||||
var sshBoxURL = os.Getenv("LOCALAGI_SSHBOX_URL")
|
||||
|
||||
func init() {
|
||||
@@ -59,19 +62,26 @@ func main() {
|
||||
pool, err := state.NewAgentPool(
|
||||
baseModel,
|
||||
multimodalModel,
|
||||
transcriptionModel,
|
||||
transcriptionLanguage,
|
||||
ttsModel,
|
||||
imageModel,
|
||||
apiURL,
|
||||
apiKey,
|
||||
stateDir,
|
||||
mcpboxURL,
|
||||
localRAG,
|
||||
services.Actions(map[string]string{
|
||||
services.ActionConfigBrowserAgentRunner: localOperatorBaseURL,
|
||||
services.ActionConfigDeepResearchRunner: localOperatorBaseURL,
|
||||
services.ActionConfigSSHBoxURL: sshBoxURL,
|
||||
services.ConfigStateDir: stateDir,
|
||||
services.CustomActionsDir: customActionsDir,
|
||||
}),
|
||||
services.Connectors,
|
||||
services.DynamicPrompts,
|
||||
services.DynamicPrompts(map[string]string{
|
||||
services.ConfigStateDir: stateDir,
|
||||
services.CustomActionsDir: customActionsDir,
|
||||
}),
|
||||
services.Filters,
|
||||
timeout,
|
||||
withLogs,
|
||||
@@ -88,6 +98,7 @@ func main() {
|
||||
webui.WithLLMAPIUrl(apiURL),
|
||||
webui.WithLLMAPIKey(apiKey),
|
||||
webui.WithLLMModel(baseModel),
|
||||
webui.WithCustomActionsDir(customActionsDir),
|
||||
webui.WithStateDir(stateDir),
|
||||
)
|
||||
|
||||
|
||||
@@ -8,13 +8,13 @@ import (
|
||||
|
||||
// AgentConfig represents the configuration for an agent
|
||||
type AgentConfig struct {
|
||||
Name string `json:"name"`
|
||||
Actions []string `json:"actions,omitempty"`
|
||||
Connectors []string `json:"connectors,omitempty"`
|
||||
PromptBlocks []string `json:"prompt_blocks,omitempty"`
|
||||
InitialPrompt string `json:"initial_prompt,omitempty"`
|
||||
Parallel bool `json:"parallel,omitempty"`
|
||||
Config map[string]interface{} `json:"config,omitempty"`
|
||||
Name string `json:"name"`
|
||||
Actions []string `json:"actions,omitempty"`
|
||||
Connectors []string `json:"connectors,omitempty"`
|
||||
PromptBlocks []string `json:"prompt_blocks,omitempty"`
|
||||
InitialPrompt string `json:"initial_prompt,omitempty"`
|
||||
Parallel bool `json:"parallel,omitempty"`
|
||||
EnableReasoning bool `json:"enable_reasoning,omitempty"`
|
||||
}
|
||||
|
||||
// AgentStatus represents the status of an agent
|
||||
|
||||
@@ -1,325 +0,0 @@
|
||||
package stdio
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"context"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"io"
|
||||
"log"
|
||||
"net/http"
|
||||
"net/url"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"github.com/gorilla/websocket"
|
||||
)
|
||||
|
||||
// Client implements the transport.Interface for stdio processes
|
||||
type Client struct {
|
||||
baseURL string
|
||||
processes map[string]*Process
|
||||
groups map[string][]string
|
||||
mu sync.RWMutex
|
||||
}
|
||||
|
||||
// NewClient creates a new stdio transport client
|
||||
func NewClient(baseURL string) *Client {
|
||||
return &Client{
|
||||
baseURL: baseURL,
|
||||
processes: make(map[string]*Process),
|
||||
groups: make(map[string][]string),
|
||||
}
|
||||
}
|
||||
|
||||
// CreateProcess starts a new process in a group
|
||||
func (c *Client) CreateProcess(ctx context.Context, command string, args []string, env []string, groupID string) (*Process, error) {
|
||||
log.Printf("Creating process: command=%s, args=%v, groupID=%s", command, args, groupID)
|
||||
|
||||
req := struct {
|
||||
Command string `json:"command"`
|
||||
Args []string `json:"args"`
|
||||
Env []string `json:"env"`
|
||||
GroupID string `json:"group_id"`
|
||||
}{
|
||||
Command: command,
|
||||
Args: args,
|
||||
Env: env,
|
||||
GroupID: groupID,
|
||||
}
|
||||
|
||||
reqBody, err := json.Marshal(req)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to marshal request: %w", err)
|
||||
}
|
||||
|
||||
url := fmt.Sprintf("%s/processes", c.baseURL)
|
||||
log.Printf("Sending POST request to %s", url)
|
||||
|
||||
resp, err := http.Post(url, "application/json", bytes.NewReader(reqBody))
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to start process: %w", err)
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
|
||||
log.Printf("Received response with status: %d", resp.StatusCode)
|
||||
|
||||
var result struct {
|
||||
ID string `json:"id"`
|
||||
}
|
||||
|
||||
body, _ := io.ReadAll(resp.Body)
|
||||
|
||||
if err := json.Unmarshal(body, &result); err != nil {
|
||||
return nil, fmt.Errorf("failed to decode response: %w. body: %s", err, string(body))
|
||||
}
|
||||
|
||||
log.Printf("Successfully created process with ID: %s", result.ID)
|
||||
|
||||
process := &Process{
|
||||
ID: result.ID,
|
||||
GroupID: groupID,
|
||||
CreatedAt: time.Now(),
|
||||
}
|
||||
|
||||
c.mu.Lock()
|
||||
c.processes[process.ID] = process
|
||||
if groupID != "" {
|
||||
c.groups[groupID] = append(c.groups[groupID], process.ID)
|
||||
}
|
||||
c.mu.Unlock()
|
||||
|
||||
return process, nil
|
||||
}
|
||||
|
||||
// GetProcess returns a process by ID
|
||||
func (c *Client) GetProcess(id string) (*Process, error) {
|
||||
c.mu.RLock()
|
||||
process, exists := c.processes[id]
|
||||
c.mu.RUnlock()
|
||||
|
||||
if !exists {
|
||||
return nil, fmt.Errorf("process not found: %s", id)
|
||||
}
|
||||
|
||||
return process, nil
|
||||
}
|
||||
|
||||
// GetGroupProcesses returns all processes in a group
|
||||
func (c *Client) GetGroupProcesses(groupID string) ([]*Process, error) {
|
||||
c.mu.RLock()
|
||||
processIDs, exists := c.groups[groupID]
|
||||
if !exists {
|
||||
c.mu.RUnlock()
|
||||
return nil, fmt.Errorf("group not found: %s", groupID)
|
||||
}
|
||||
|
||||
processes := make([]*Process, 0, len(processIDs))
|
||||
for _, pid := range processIDs {
|
||||
if process, exists := c.processes[pid]; exists {
|
||||
processes = append(processes, process)
|
||||
}
|
||||
}
|
||||
c.mu.RUnlock()
|
||||
|
||||
return processes, nil
|
||||
}
|
||||
|
||||
// StopProcess stops a single process
|
||||
func (c *Client) StopProcess(id string) error {
|
||||
c.mu.Lock()
|
||||
process, exists := c.processes[id]
|
||||
if !exists {
|
||||
c.mu.Unlock()
|
||||
return fmt.Errorf("process not found: %s", id)
|
||||
}
|
||||
|
||||
// Remove from group if it exists
|
||||
if process.GroupID != "" {
|
||||
groupProcesses := c.groups[process.GroupID]
|
||||
for i, pid := range groupProcesses {
|
||||
if pid == id {
|
||||
c.groups[process.GroupID] = append(groupProcesses[:i], groupProcesses[i+1:]...)
|
||||
break
|
||||
}
|
||||
}
|
||||
if len(c.groups[process.GroupID]) == 0 {
|
||||
delete(c.groups, process.GroupID)
|
||||
}
|
||||
}
|
||||
|
||||
delete(c.processes, id)
|
||||
c.mu.Unlock()
|
||||
|
||||
req, err := http.NewRequest(
|
||||
"DELETE",
|
||||
fmt.Sprintf("%s/processes/%s", c.baseURL, id),
|
||||
nil,
|
||||
)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to create request: %w", err)
|
||||
}
|
||||
|
||||
resp, err := http.DefaultClient.Do(req)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to stop process: %w", err)
|
||||
}
|
||||
resp.Body.Close()
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// StopGroup stops all processes in a group
|
||||
func (c *Client) StopGroup(groupID string) error {
|
||||
c.mu.Lock()
|
||||
processIDs, exists := c.groups[groupID]
|
||||
if !exists {
|
||||
c.mu.Unlock()
|
||||
return fmt.Errorf("group not found: %s", groupID)
|
||||
}
|
||||
c.mu.Unlock()
|
||||
|
||||
for _, pid := range processIDs {
|
||||
if err := c.StopProcess(pid); err != nil {
|
||||
return fmt.Errorf("failed to stop process %s in group %s: %w", pid, groupID, err)
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// ListGroups returns all group IDs
|
||||
func (c *Client) ListGroups() []string {
|
||||
c.mu.RLock()
|
||||
defer c.mu.RUnlock()
|
||||
|
||||
groups := make([]string, 0, len(c.groups))
|
||||
for groupID := range c.groups {
|
||||
groups = append(groups, groupID)
|
||||
}
|
||||
return groups
|
||||
}
|
||||
|
||||
// GetProcessIO returns io.Reader and io.Writer for a process
|
||||
func (c *Client) GetProcessIO(id string) (io.Reader, io.Writer, error) {
|
||||
log.Printf("Getting IO for process: %s", id)
|
||||
|
||||
process, err := c.GetProcess(id)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
|
||||
// Parse the base URL to get the host
|
||||
baseURL, err := url.Parse(c.baseURL)
|
||||
if err != nil {
|
||||
return nil, nil, fmt.Errorf("failed to parse base URL: %w", err)
|
||||
}
|
||||
|
||||
// Connect to WebSocket
|
||||
u := url.URL{
|
||||
Scheme: "ws",
|
||||
Host: baseURL.Host,
|
||||
Path: fmt.Sprintf("/ws/%s", process.ID),
|
||||
}
|
||||
|
||||
log.Printf("Connecting to WebSocket at: %s", u.String())
|
||||
|
||||
conn, _, err := websocket.DefaultDialer.Dial(u.String(), nil)
|
||||
if err != nil {
|
||||
return nil, nil, fmt.Errorf("failed to connect to WebSocket: %w", err)
|
||||
}
|
||||
|
||||
log.Printf("Successfully connected to WebSocket for process: %s", id)
|
||||
|
||||
// Create reader and writer
|
||||
reader := &websocketReader{conn: conn}
|
||||
writer := &websocketWriter{conn: conn}
|
||||
|
||||
return reader, writer, nil
|
||||
}
|
||||
|
||||
// websocketReader implements io.Reader for WebSocket
|
||||
type websocketReader struct {
|
||||
conn *websocket.Conn
|
||||
}
|
||||
|
||||
func (r *websocketReader) Read(p []byte) (n int, err error) {
|
||||
_, message, err := r.conn.ReadMessage()
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
n = copy(p, message)
|
||||
return n, nil
|
||||
}
|
||||
|
||||
// websocketWriter implements io.Writer for WebSocket
|
||||
type websocketWriter struct {
|
||||
conn *websocket.Conn
|
||||
}
|
||||
|
||||
func (w *websocketWriter) Write(p []byte) (n int, err error) {
|
||||
// Use BinaryMessage type for better compatibility
|
||||
err = w.conn.WriteMessage(websocket.BinaryMessage, p)
|
||||
if err != nil {
|
||||
return 0, fmt.Errorf("failed to write WebSocket message: %w", err)
|
||||
}
|
||||
return len(p), nil
|
||||
}
|
||||
|
||||
// Close closes all connections and stops all processes
|
||||
func (c *Client) Close() error {
|
||||
c.mu.Lock()
|
||||
defer c.mu.Unlock()
|
||||
|
||||
// Stop all processes
|
||||
for id := range c.processes {
|
||||
if err := c.StopProcess(id); err != nil {
|
||||
return fmt.Errorf("failed to stop process %s: %w", id, err)
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// RunProcess executes a command and returns its output
|
||||
func (c *Client) RunProcess(ctx context.Context, command string, args []string, env []string) (string, error) {
|
||||
log.Printf("Running one-time process: command=%s, args=%v", command, args)
|
||||
|
||||
req := struct {
|
||||
Command string `json:"command"`
|
||||
Args []string `json:"args"`
|
||||
Env []string `json:"env"`
|
||||
}{
|
||||
Command: command,
|
||||
Args: args,
|
||||
Env: env,
|
||||
}
|
||||
|
||||
reqBody, err := json.Marshal(req)
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("failed to marshal request: %w", err)
|
||||
}
|
||||
|
||||
url := fmt.Sprintf("%s/run", c.baseURL)
|
||||
log.Printf("Sending POST request to %s", url)
|
||||
|
||||
resp, err := http.Post(url, "application/json", bytes.NewReader(reqBody))
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("failed to execute process: %w", err)
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
|
||||
log.Printf("Received response with status: %d", resp.StatusCode)
|
||||
|
||||
var result struct {
|
||||
Output string `json:"output"`
|
||||
}
|
||||
|
||||
if err := json.NewDecoder(resp.Body).Decode(&result); err != nil {
|
||||
body, _ := io.ReadAll(resp.Body)
|
||||
return "", fmt.Errorf("failed to decode response: %w. body: %s", err, string(body))
|
||||
}
|
||||
|
||||
log.Printf("Successfully executed process with output length: %d", len(result.Output))
|
||||
return result.Output, nil
|
||||
}
|
||||
@@ -1,28 +0,0 @@
|
||||
package stdio
|
||||
|
||||
import (
|
||||
"os"
|
||||
"testing"
|
||||
|
||||
. "github.com/onsi/ginkgo/v2"
|
||||
. "github.com/onsi/gomega"
|
||||
)
|
||||
|
||||
func TestSTDIOTransport(t *testing.T) {
|
||||
RegisterFailHandler(Fail)
|
||||
RunSpecs(t, "STDIOTransport test suite")
|
||||
}
|
||||
|
||||
var baseURL string
|
||||
|
||||
func init() {
|
||||
baseURL = os.Getenv("LOCALAGI_MCPBOX_URL")
|
||||
if baseURL == "" {
|
||||
baseURL = "http://localhost:8080"
|
||||
}
|
||||
}
|
||||
|
||||
var _ = AfterSuite(func() {
|
||||
client := NewClient(baseURL)
|
||||
client.StopGroup("test-group")
|
||||
})
|
||||
@@ -1,235 +0,0 @@
|
||||
package stdio
|
||||
|
||||
import (
|
||||
"context"
|
||||
"time"
|
||||
|
||||
mcp "github.com/metoro-io/mcp-golang"
|
||||
"github.com/metoro-io/mcp-golang/transport/stdio"
|
||||
"github.com/mudler/LocalAGI/pkg/xlog"
|
||||
. "github.com/onsi/ginkgo/v2"
|
||||
. "github.com/onsi/gomega"
|
||||
)
|
||||
|
||||
var _ = Describe("Client", func() {
|
||||
var (
|
||||
client *Client
|
||||
)
|
||||
|
||||
BeforeEach(func() {
|
||||
client = NewClient(baseURL)
|
||||
})
|
||||
|
||||
AfterEach(func() {
|
||||
if client != nil {
|
||||
Expect(client.Close()).To(Succeed())
|
||||
}
|
||||
})
|
||||
|
||||
Context("Process Management", func() {
|
||||
It("should create and stop a process", func() {
|
||||
ctx := context.Background()
|
||||
// Use a command that doesn't exit immediately
|
||||
process, err := client.CreateProcess(ctx, "sh", []string{"-c", "echo 'Hello, World!'; sleep 10"}, []string{}, "test-group")
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
Expect(process).NotTo(BeNil())
|
||||
Expect(process.ID).NotTo(BeEmpty())
|
||||
|
||||
// Get process IO
|
||||
reader, writer, err := client.GetProcessIO(process.ID)
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
Expect(reader).NotTo(BeNil())
|
||||
Expect(writer).NotTo(BeNil())
|
||||
|
||||
// Write to process
|
||||
_, err = writer.Write([]byte("test input\n"))
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
|
||||
// Read from process with timeout
|
||||
buf := make([]byte, 1024)
|
||||
readDone := make(chan struct{})
|
||||
var readErr error
|
||||
var readN int
|
||||
|
||||
go func() {
|
||||
readN, readErr = reader.Read(buf)
|
||||
close(readDone)
|
||||
}()
|
||||
|
||||
// Wait for read with timeout
|
||||
select {
|
||||
case <-readDone:
|
||||
Expect(readErr).NotTo(HaveOccurred())
|
||||
Expect(readN).To(BeNumerically(">", 0))
|
||||
Expect(string(buf[:readN])).To(ContainSubstring("Hello, World!"))
|
||||
case <-time.After(5 * time.Second):
|
||||
Fail("Timeout waiting for process output")
|
||||
}
|
||||
|
||||
// Stop the process
|
||||
err = client.StopProcess(process.ID)
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
})
|
||||
|
||||
It("should manage process groups", func() {
|
||||
ctx := context.Background()
|
||||
groupID := "test-group"
|
||||
|
||||
// Create multiple processes in the same group
|
||||
process1, err := client.CreateProcess(ctx, "sh", []string{"-c", "echo 'Process 1'; sleep 1"}, []string{}, groupID)
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
Expect(process1).NotTo(BeNil())
|
||||
|
||||
process2, err := client.CreateProcess(ctx, "sh", []string{"-c", "echo 'Process 2'; sleep 1"}, []string{}, groupID)
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
Expect(process2).NotTo(BeNil())
|
||||
|
||||
// Get group processes
|
||||
processes, err := client.GetGroupProcesses(groupID)
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
Expect(processes).To(HaveLen(2))
|
||||
|
||||
// List groups
|
||||
groups := client.ListGroups()
|
||||
Expect(groups).To(ContainElement(groupID))
|
||||
|
||||
// Stop the group
|
||||
err = client.StopGroup(groupID)
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
})
|
||||
|
||||
It("should run a one-time process", func() {
|
||||
ctx := context.Background()
|
||||
output, err := client.RunProcess(ctx, "echo", []string{"One-time process"}, []string{})
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
Expect(output).To(ContainSubstring("One-time process"))
|
||||
})
|
||||
|
||||
It("should handle process with environment variables", func() {
|
||||
ctx := context.Background()
|
||||
env := []string{"TEST_VAR=test_value"}
|
||||
process, err := client.CreateProcess(ctx, "sh", []string{"-c", "env | grep TEST_VAR; sleep 1"}, env, "test-group")
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
Expect(process).NotTo(BeNil())
|
||||
|
||||
// Get process IO
|
||||
reader, _, err := client.GetProcessIO(process.ID)
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
|
||||
// Read environment variables with timeout
|
||||
buf := make([]byte, 1024)
|
||||
readDone := make(chan struct{})
|
||||
var readErr error
|
||||
var readN int
|
||||
|
||||
go func() {
|
||||
readN, readErr = reader.Read(buf)
|
||||
close(readDone)
|
||||
}()
|
||||
|
||||
// Wait for read with timeout
|
||||
select {
|
||||
case <-readDone:
|
||||
Expect(readErr).NotTo(HaveOccurred())
|
||||
Expect(readN).To(BeNumerically(">", 0))
|
||||
Expect(string(buf[:readN])).To(ContainSubstring("TEST_VAR=test_value"))
|
||||
case <-time.After(5 * time.Second):
|
||||
Fail("Timeout waiting for process output")
|
||||
}
|
||||
|
||||
// Stop the process
|
||||
err = client.StopProcess(process.ID)
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
})
|
||||
|
||||
It("should handle long-running processes", func() {
|
||||
ctx := context.Background()
|
||||
process, err := client.CreateProcess(ctx, "sh", []string{"-c", "echo 'Starting long process'; sleep 5"}, []string{}, "test-group")
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
Expect(process).NotTo(BeNil())
|
||||
|
||||
// Get process IO
|
||||
reader, _, err := client.GetProcessIO(process.ID)
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
|
||||
// Read initial output
|
||||
buf := make([]byte, 1024)
|
||||
readDone := make(chan struct{})
|
||||
var readErr error
|
||||
var readN int
|
||||
|
||||
go func() {
|
||||
readN, readErr = reader.Read(buf)
|
||||
close(readDone)
|
||||
}()
|
||||
|
||||
// Wait for read with timeout
|
||||
select {
|
||||
case <-readDone:
|
||||
Expect(readErr).NotTo(HaveOccurred())
|
||||
Expect(readN).To(BeNumerically(">", 0))
|
||||
Expect(string(buf[:readN])).To(ContainSubstring("Starting long process"))
|
||||
case <-time.After(5 * time.Second):
|
||||
Fail("Timeout waiting for process output")
|
||||
}
|
||||
|
||||
// Wait a bit to ensure process is running
|
||||
time.Sleep(time.Second)
|
||||
|
||||
// Stop the process
|
||||
err = client.StopProcess(process.ID)
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
})
|
||||
|
||||
It("MCP", func() {
|
||||
ctx := context.Background()
|
||||
process, err := client.CreateProcess(ctx,
|
||||
"docker", []string{"run", "-i", "--rm", "-e", "GITHUB_PERSONAL_ACCESS_TOKEN", "ghcr.io/github/github-mcp-server"},
|
||||
[]string{"GITHUB_PERSONAL_ACCESS_TOKEN=test"}, "test-group")
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
Expect(process).NotTo(BeNil())
|
||||
Expect(process.ID).NotTo(BeEmpty())
|
||||
|
||||
defer client.StopProcess(process.ID)
|
||||
|
||||
// MCP client
|
||||
|
||||
read, writer, err := client.GetProcessIO(process.ID)
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
Expect(read).NotTo(BeNil())
|
||||
Expect(writer).NotTo(BeNil())
|
||||
|
||||
transport := stdio.NewStdioServerTransportWithIO(read, writer)
|
||||
|
||||
// Create a new client
|
||||
mcpClient := mcp.NewClient(transport)
|
||||
// Initialize the client
|
||||
response, e := mcpClient.Initialize(ctx)
|
||||
Expect(e).NotTo(HaveOccurred())
|
||||
Expect(response).NotTo(BeNil())
|
||||
|
||||
Expect(mcpClient.Ping(ctx)).To(Succeed())
|
||||
|
||||
xlog.Debug("Client initialized: %v", response.Instructions)
|
||||
|
||||
alltools := []mcp.ToolRetType{}
|
||||
var cursor *string
|
||||
for {
|
||||
tools, err := mcpClient.ListTools(ctx, cursor)
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
Expect(tools).NotTo(BeNil())
|
||||
Expect(tools.Tools).NotTo(BeEmpty())
|
||||
alltools = append(alltools, tools.Tools...)
|
||||
|
||||
if tools.NextCursor == nil {
|
||||
break // No more pages
|
||||
}
|
||||
cursor = tools.NextCursor
|
||||
}
|
||||
|
||||
for _, tool := range alltools {
|
||||
xlog.Debug("Tool: %v", tool)
|
||||
}
|
||||
})
|
||||
})
|
||||
})
|
||||
@@ -1,473 +0,0 @@
|
||||
package stdio
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"io"
|
||||
"log"
|
||||
"net/http"
|
||||
"os"
|
||||
"os/exec"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"github.com/gorilla/websocket"
|
||||
"github.com/mudler/LocalAGI/pkg/xlog"
|
||||
)
|
||||
|
||||
// Process represents a running process with its stdio streams
|
||||
type Process struct {
|
||||
ID string
|
||||
GroupID string
|
||||
Cmd *exec.Cmd
|
||||
Stdin io.WriteCloser
|
||||
Stdout io.ReadCloser
|
||||
Stderr io.ReadCloser
|
||||
CreatedAt time.Time
|
||||
}
|
||||
|
||||
// Server handles process management and stdio streaming
|
||||
type Server struct {
|
||||
processes map[string]*Process
|
||||
groups map[string][]string // maps group ID to process IDs
|
||||
mu sync.RWMutex
|
||||
upgrader websocket.Upgrader
|
||||
}
|
||||
|
||||
// NewServer creates a new stdio server
|
||||
func NewServer() *Server {
|
||||
return &Server{
|
||||
processes: make(map[string]*Process),
|
||||
groups: make(map[string][]string),
|
||||
upgrader: websocket.Upgrader{},
|
||||
}
|
||||
}
|
||||
|
||||
// StartProcess starts a new process and returns its ID
|
||||
func (s *Server) StartProcess(ctx context.Context, command string, args []string, env []string, groupID string) (string, error) {
|
||||
xlog.Debug("Starting process", "command", command, "args", args, "groupID", groupID)
|
||||
|
||||
cmd := exec.CommandContext(ctx, command, args...)
|
||||
|
||||
if len(env) > 0 {
|
||||
cmd.Env = append(os.Environ(), env...)
|
||||
xlog.Debug("Process environment", "env", cmd.Env)
|
||||
}
|
||||
|
||||
stdin, err := cmd.StdinPipe()
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("failed to create stdin pipe: %w", err)
|
||||
}
|
||||
|
||||
stdout, err := cmd.StdoutPipe()
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("failed to create stdout pipe: %w", err)
|
||||
}
|
||||
|
||||
stderr, err := cmd.StderrPipe()
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("failed to create stderr pipe: %w", err)
|
||||
}
|
||||
|
||||
if err := cmd.Start(); err != nil {
|
||||
return "", fmt.Errorf("failed to start process: %w", err)
|
||||
}
|
||||
|
||||
process := &Process{
|
||||
ID: fmt.Sprintf("%d", time.Now().UnixNano()),
|
||||
GroupID: groupID,
|
||||
Cmd: cmd,
|
||||
Stdin: stdin,
|
||||
Stdout: stdout,
|
||||
Stderr: stderr,
|
||||
CreatedAt: time.Now(),
|
||||
}
|
||||
|
||||
s.mu.Lock()
|
||||
s.processes[process.ID] = process
|
||||
if groupID != "" {
|
||||
s.groups[groupID] = append(s.groups[groupID], process.ID)
|
||||
}
|
||||
s.mu.Unlock()
|
||||
|
||||
xlog.Debug("Successfully started process", "id", process.ID, "pid", cmd.Process.Pid)
|
||||
return process.ID, nil
|
||||
}
|
||||
|
||||
// StopProcess stops a running process
|
||||
func (s *Server) StopProcess(id string) error {
|
||||
s.mu.Lock()
|
||||
process, exists := s.processes[id]
|
||||
if !exists {
|
||||
s.mu.Unlock()
|
||||
return fmt.Errorf("process not found: %s", id)
|
||||
}
|
||||
|
||||
xlog.Debug("Stopping process", "processID", id, "pid", process.Cmd.Process.Pid)
|
||||
|
||||
// Remove from group if it exists
|
||||
if process.GroupID != "" {
|
||||
groupProcesses := s.groups[process.GroupID]
|
||||
for i, pid := range groupProcesses {
|
||||
if pid == id {
|
||||
s.groups[process.GroupID] = append(groupProcesses[:i], groupProcesses[i+1:]...)
|
||||
break
|
||||
}
|
||||
}
|
||||
if len(s.groups[process.GroupID]) == 0 {
|
||||
delete(s.groups, process.GroupID)
|
||||
}
|
||||
}
|
||||
|
||||
delete(s.processes, id)
|
||||
s.mu.Unlock()
|
||||
|
||||
if err := process.Cmd.Process.Kill(); err != nil {
|
||||
xlog.Debug("Failed to kill process", "processID", id, "pid", process.Cmd.Process.Pid, "error", err)
|
||||
return fmt.Errorf("failed to kill process: %w", err)
|
||||
}
|
||||
|
||||
xlog.Debug("Successfully killed process", "processID", id, "pid", process.Cmd.Process.Pid)
|
||||
return nil
|
||||
}
|
||||
|
||||
// StopGroup stops all processes in a group
|
||||
func (s *Server) StopGroup(groupID string) error {
|
||||
s.mu.Lock()
|
||||
processIDs, exists := s.groups[groupID]
|
||||
if !exists {
|
||||
s.mu.Unlock()
|
||||
return fmt.Errorf("group not found: %s", groupID)
|
||||
}
|
||||
s.mu.Unlock()
|
||||
|
||||
for _, pid := range processIDs {
|
||||
if err := s.StopProcess(pid); err != nil {
|
||||
return fmt.Errorf("failed to stop process %s in group %s: %w", pid, groupID, err)
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// GetGroupProcesses returns all processes in a group
|
||||
func (s *Server) GetGroupProcesses(groupID string) ([]*Process, error) {
|
||||
s.mu.RLock()
|
||||
processIDs, exists := s.groups[groupID]
|
||||
if !exists {
|
||||
s.mu.RUnlock()
|
||||
return nil, fmt.Errorf("group not found: %s", groupID)
|
||||
}
|
||||
|
||||
processes := make([]*Process, 0, len(processIDs))
|
||||
for _, pid := range processIDs {
|
||||
if process, exists := s.processes[pid]; exists {
|
||||
processes = append(processes, process)
|
||||
}
|
||||
}
|
||||
s.mu.RUnlock()
|
||||
|
||||
return processes, nil
|
||||
}
|
||||
|
||||
// ListGroups returns all group IDs
|
||||
func (s *Server) ListGroups() []string {
|
||||
s.mu.RLock()
|
||||
defer s.mu.RUnlock()
|
||||
|
||||
groups := make([]string, 0, len(s.groups))
|
||||
for groupID := range s.groups {
|
||||
groups = append(groups, groupID)
|
||||
}
|
||||
return groups
|
||||
}
|
||||
|
||||
// GetProcess returns a process by ID
|
||||
func (s *Server) GetProcess(id string) (*Process, error) {
|
||||
s.mu.RLock()
|
||||
process, exists := s.processes[id]
|
||||
s.mu.RUnlock()
|
||||
|
||||
if !exists {
|
||||
return nil, fmt.Errorf("process not found: %s", id)
|
||||
}
|
||||
|
||||
return process, nil
|
||||
}
|
||||
|
||||
// ListProcesses returns all running processes
|
||||
func (s *Server) ListProcesses() []*Process {
|
||||
s.mu.RLock()
|
||||
defer s.mu.RUnlock()
|
||||
|
||||
processes := make([]*Process, 0, len(s.processes))
|
||||
for _, p := range s.processes {
|
||||
processes = append(processes, p)
|
||||
}
|
||||
|
||||
return processes
|
||||
}
|
||||
|
||||
// RunProcess executes a command and returns its output
|
||||
func (s *Server) RunProcess(ctx context.Context, command string, args []string, env []string) (string, error) {
|
||||
cmd := exec.CommandContext(ctx, command, args...)
|
||||
|
||||
if len(env) > 0 {
|
||||
cmd.Env = append(os.Environ(), env...)
|
||||
}
|
||||
|
||||
output, err := cmd.CombinedOutput()
|
||||
if err != nil {
|
||||
return string(output), fmt.Errorf("process failed: %w", err)
|
||||
}
|
||||
|
||||
return string(output), nil
|
||||
}
|
||||
|
||||
// Start starts the HTTP server
|
||||
func (s *Server) Start(addr string) error {
|
||||
http.HandleFunc("/processes", s.handleProcesses)
|
||||
http.HandleFunc("/processes/", s.handleProcess)
|
||||
http.HandleFunc("/ws/", s.handleWebSocket)
|
||||
http.HandleFunc("/groups", s.handleGroups)
|
||||
http.HandleFunc("/groups/", s.handleGroup)
|
||||
http.HandleFunc("/run", s.handleRun)
|
||||
|
||||
return http.ListenAndServe(addr, nil)
|
||||
}
|
||||
|
||||
func (s *Server) handleProcesses(w http.ResponseWriter, r *http.Request) {
|
||||
log.Printf("Handling /processes request: method=%s", r.Method)
|
||||
|
||||
switch r.Method {
|
||||
case http.MethodGet:
|
||||
processes := s.ListProcesses()
|
||||
json.NewEncoder(w).Encode(processes)
|
||||
case http.MethodPost:
|
||||
var req struct {
|
||||
Command string `json:"command"`
|
||||
Args []string `json:"args"`
|
||||
Env []string `json:"env"`
|
||||
GroupID string `json:"group_id"`
|
||||
}
|
||||
if err := json.NewDecoder(r.Body).Decode(&req); err != nil {
|
||||
http.Error(w, err.Error(), http.StatusBadRequest)
|
||||
return
|
||||
}
|
||||
|
||||
id, err := s.StartProcess(context.Background(), req.Command, req.Args, req.Env, req.GroupID)
|
||||
if err != nil {
|
||||
http.Error(w, err.Error(), http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
|
||||
w.Header().Set("Content-Type", "application/json")
|
||||
json.NewEncoder(w).Encode(map[string]string{"id": id})
|
||||
default:
|
||||
http.Error(w, "Method not allowed", http.StatusMethodNotAllowed)
|
||||
}
|
||||
}
|
||||
|
||||
func (s *Server) handleProcess(w http.ResponseWriter, r *http.Request) {
|
||||
id := r.URL.Path[len("/processes/"):]
|
||||
|
||||
switch r.Method {
|
||||
case http.MethodGet:
|
||||
process, err := s.GetProcess(id)
|
||||
if err != nil {
|
||||
http.Error(w, err.Error(), http.StatusNotFound)
|
||||
return
|
||||
}
|
||||
json.NewEncoder(w).Encode(process)
|
||||
case http.MethodDelete:
|
||||
if err := s.StopProcess(id); err != nil {
|
||||
http.Error(w, err.Error(), http.StatusNotFound)
|
||||
return
|
||||
}
|
||||
w.WriteHeader(http.StatusNoContent)
|
||||
default:
|
||||
http.Error(w, "Method not allowed", http.StatusMethodNotAllowed)
|
||||
}
|
||||
}
|
||||
|
||||
func (s *Server) handleWebSocket(w http.ResponseWriter, r *http.Request) {
|
||||
id := r.URL.Path[len("/ws/"):]
|
||||
xlog.Debug("Handling WebSocket connection", "processID", id)
|
||||
|
||||
process, err := s.GetProcess(id)
|
||||
if err != nil {
|
||||
http.Error(w, err.Error(), http.StatusNotFound)
|
||||
return
|
||||
}
|
||||
|
||||
if process.Cmd.ProcessState != nil && process.Cmd.ProcessState.Exited() {
|
||||
xlog.Debug("Process already exited", "processID", id)
|
||||
http.Error(w, "Process already exited", http.StatusGone)
|
||||
return
|
||||
}
|
||||
|
||||
xlog.Debug("Process is running", "processID", id, "pid", process.Cmd.Process.Pid)
|
||||
|
||||
conn, err := s.upgrader.Upgrade(w, r, nil)
|
||||
if err != nil {
|
||||
http.Error(w, err.Error(), http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
defer conn.Close()
|
||||
|
||||
xlog.Debug("WebSocket connection established", "processID", id)
|
||||
|
||||
// Create a done channel to signal process completion
|
||||
done := make(chan struct{})
|
||||
|
||||
// Handle stdin
|
||||
go func() {
|
||||
defer func() {
|
||||
select {
|
||||
case <-done:
|
||||
xlog.Debug("Process stdin handler done", "processID", id)
|
||||
default:
|
||||
xlog.Debug("WebSocket stdin connection closed", "processID", id)
|
||||
}
|
||||
}()
|
||||
|
||||
for {
|
||||
_, message, err := conn.ReadMessage()
|
||||
if err != nil {
|
||||
if websocket.IsUnexpectedCloseError(err, websocket.CloseGoingAway, websocket.CloseNormalClosure) {
|
||||
xlog.Debug("WebSocket stdin unexpected error", "processID", id, "error", err)
|
||||
}
|
||||
return
|
||||
}
|
||||
xlog.Debug("Received message", "processID", id, "message", string(message))
|
||||
if _, err := process.Stdin.Write(message); err != nil {
|
||||
if err != io.EOF {
|
||||
xlog.Debug("WebSocket stdin write error", "processID", id, "error", err)
|
||||
}
|
||||
return
|
||||
}
|
||||
xlog.Debug("Message sent to process", "processID", id, "message", string(message))
|
||||
}
|
||||
}()
|
||||
|
||||
// Handle stdout and stderr
|
||||
go func() {
|
||||
defer func() {
|
||||
select {
|
||||
case <-done:
|
||||
xlog.Debug("Process output handler done", "processID", id)
|
||||
default:
|
||||
xlog.Debug("WebSocket output connection closed", "processID", id)
|
||||
}
|
||||
}()
|
||||
|
||||
// Create a buffer for reading
|
||||
buf := make([]byte, 4096)
|
||||
reader := io.MultiReader(process.Stdout, process.Stderr)
|
||||
|
||||
for {
|
||||
n, err := reader.Read(buf)
|
||||
if err != nil {
|
||||
if err != io.EOF {
|
||||
xlog.Debug("Read error", "processID", id, "error", err)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
if n > 0 {
|
||||
xlog.Debug("Sending message", "processID", id, "size", n)
|
||||
if err := conn.WriteMessage(websocket.BinaryMessage, buf[:n]); err != nil {
|
||||
if websocket.IsUnexpectedCloseError(err, websocket.CloseGoingAway, websocket.CloseNormalClosure) {
|
||||
xlog.Debug("WebSocket output write error", "processID", id, "error", err)
|
||||
}
|
||||
return
|
||||
}
|
||||
xlog.Debug("Message sent to client", "processID", id, "size", n)
|
||||
}
|
||||
}
|
||||
}()
|
||||
|
||||
// Wait for process to exit
|
||||
xlog.Debug("Waiting for process to exit", "processID", id)
|
||||
err = process.Cmd.Wait()
|
||||
close(done) // Signal that the process is done
|
||||
|
||||
if err != nil {
|
||||
xlog.Debug("Process exited with error",
|
||||
"processID", id,
|
||||
"pid", process.Cmd.Process.Pid,
|
||||
"error", err)
|
||||
} else {
|
||||
xlog.Debug("Process exited successfully",
|
||||
"processID", id,
|
||||
"pid", process.Cmd.Process.Pid)
|
||||
}
|
||||
}
|
||||
|
||||
// Add new handlers for group management
|
||||
func (s *Server) handleGroups(w http.ResponseWriter, r *http.Request) {
|
||||
switch r.Method {
|
||||
case http.MethodGet:
|
||||
groups := s.ListGroups()
|
||||
json.NewEncoder(w).Encode(groups)
|
||||
default:
|
||||
http.Error(w, "Method not allowed", http.StatusMethodNotAllowed)
|
||||
}
|
||||
}
|
||||
|
||||
func (s *Server) handleGroup(w http.ResponseWriter, r *http.Request) {
|
||||
groupID := r.URL.Path[len("/groups/"):]
|
||||
|
||||
switch r.Method {
|
||||
case http.MethodGet:
|
||||
processes, err := s.GetGroupProcesses(groupID)
|
||||
if err != nil {
|
||||
http.Error(w, err.Error(), http.StatusNotFound)
|
||||
return
|
||||
}
|
||||
json.NewEncoder(w).Encode(processes)
|
||||
case http.MethodDelete:
|
||||
if err := s.StopGroup(groupID); err != nil {
|
||||
http.Error(w, err.Error(), http.StatusNotFound)
|
||||
return
|
||||
}
|
||||
w.WriteHeader(http.StatusNoContent)
|
||||
default:
|
||||
http.Error(w, "Method not allowed", http.StatusMethodNotAllowed)
|
||||
}
|
||||
}
|
||||
|
||||
func (s *Server) handleRun(w http.ResponseWriter, r *http.Request) {
|
||||
if r.Method != http.MethodPost {
|
||||
http.Error(w, "Method not allowed", http.StatusMethodNotAllowed)
|
||||
return
|
||||
}
|
||||
|
||||
log.Printf("Handling /run request")
|
||||
|
||||
var req struct {
|
||||
Command string `json:"command"`
|
||||
Args []string `json:"args"`
|
||||
Env []string `json:"env"`
|
||||
}
|
||||
if err := json.NewDecoder(r.Body).Decode(&req); err != nil {
|
||||
http.Error(w, err.Error(), http.StatusBadRequest)
|
||||
return
|
||||
}
|
||||
|
||||
log.Printf("Executing one-time process: command=%s, args=%v", req.Command, req.Args)
|
||||
|
||||
output, err := s.RunProcess(r.Context(), req.Command, req.Args, req.Env)
|
||||
if err != nil {
|
||||
http.Error(w, err.Error(), http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
|
||||
log.Printf("One-time process completed with output length: %d", len(output))
|
||||
|
||||
w.Header().Set("Content-Type", "application/json")
|
||||
json.NewEncoder(w).Encode(map[string]string{
|
||||
"output": output,
|
||||
})
|
||||
}
|
||||
+315
-172
@@ -4,6 +4,10 @@ import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"slices"
|
||||
"strings"
|
||||
|
||||
"github.com/mudler/LocalAGI/core/action"
|
||||
"github.com/mudler/LocalAGI/core/state"
|
||||
@@ -50,6 +54,17 @@ const (
|
||||
ActionSetReminder = "set_reminder"
|
||||
ActionListReminders = "list_reminders"
|
||||
ActionRemoveReminder = "remove_reminder"
|
||||
ActionAddToMemory = "add_to_memory"
|
||||
ActionListMemory = "list_memory"
|
||||
ActionRemoveFromMemory = "remove_from_memory"
|
||||
ActionPiKVMPowerControl = "pikvm_power_control"
|
||||
ActionWebhook = "webhook"
|
||||
)
|
||||
|
||||
const (
|
||||
nameField = "name"
|
||||
descriptionField = "description"
|
||||
configurationField = "configuration"
|
||||
)
|
||||
|
||||
var AvailableActions = []string{
|
||||
@@ -87,14 +102,261 @@ var AvailableActions = []string{
|
||||
ActionSetReminder,
|
||||
ActionListReminders,
|
||||
ActionRemoveReminder,
|
||||
ActionAddToMemory,
|
||||
ActionListMemory,
|
||||
ActionRemoveFromMemory,
|
||||
ActionPiKVMPowerControl,
|
||||
ActionWebhook,
|
||||
}
|
||||
|
||||
var DefaultActions = []config.FieldGroup{
|
||||
{
|
||||
Name: "search",
|
||||
Label: "Search",
|
||||
Fields: actions.SearchConfigMeta(),
|
||||
},
|
||||
{
|
||||
Name: "browser-agent-runner",
|
||||
Label: "Browser Agent Runner",
|
||||
Fields: actions.BrowserAgentRunnerConfigMeta(),
|
||||
},
|
||||
{
|
||||
Name: "deep-research-runner",
|
||||
Label: "Deep Research Runner",
|
||||
Fields: actions.DeepResearchRunnerConfigMeta(),
|
||||
},
|
||||
{
|
||||
Name: "generate_image",
|
||||
Label: "Generate Image",
|
||||
Fields: actions.GenImageConfigMeta(),
|
||||
},
|
||||
{
|
||||
Name: "add_to_memory",
|
||||
Label: "Add to Memory",
|
||||
Fields: actions.AddToMemoryConfigMeta(),
|
||||
},
|
||||
{
|
||||
Name: "list_memory",
|
||||
Label: "List Memory",
|
||||
Fields: actions.ListMemoryConfigMeta(),
|
||||
},
|
||||
{
|
||||
Name: "remove_from_memory",
|
||||
Label: "Remove from Memory",
|
||||
Fields: actions.RemoveFromMemoryConfigMeta(),
|
||||
},
|
||||
{
|
||||
Name: "github-issue-labeler",
|
||||
Label: "GitHub Issue Labeler",
|
||||
Fields: actions.GithubIssueLabelerConfigMeta(),
|
||||
},
|
||||
{
|
||||
Name: "github-issue-opener",
|
||||
Label: "GitHub Issue Opener",
|
||||
Fields: actions.GithubIssueOpenerConfigMeta(),
|
||||
},
|
||||
{
|
||||
Name: "github-issue-editor",
|
||||
Label: "GitHub Issue Editor",
|
||||
Fields: actions.GithubIssueEditorConfigMeta(),
|
||||
},
|
||||
{
|
||||
Name: "github-issue-closer",
|
||||
Label: "GitHub Issue Closer",
|
||||
Fields: actions.GithubIssueCloserConfigMeta(),
|
||||
},
|
||||
{
|
||||
Name: "github-issue-commenter",
|
||||
Label: "GitHub Issue Commenter",
|
||||
Fields: actions.GithubIssueCommenterConfigMeta(),
|
||||
},
|
||||
{
|
||||
Name: "github-issue-reader",
|
||||
Label: "GitHub Issue Reader",
|
||||
Fields: actions.GithubIssueReaderConfigMeta(),
|
||||
},
|
||||
{
|
||||
Name: "github-issue-searcher",
|
||||
Label: "GitHub Issue Search",
|
||||
Fields: actions.GithubIssueSearchConfigMeta(),
|
||||
},
|
||||
{
|
||||
Name: "github-repository-get-content",
|
||||
Label: "GitHub Repository Get Content",
|
||||
Fields: actions.GithubRepositoryGetContentConfigMeta(),
|
||||
},
|
||||
{
|
||||
Name: "github-get-all-repository-content",
|
||||
Label: "GitHub Get All Repository Content",
|
||||
Fields: actions.GithubRepositoryGetAllContentConfigMeta(),
|
||||
},
|
||||
{
|
||||
Name: "github-repository-search-files",
|
||||
Label: "GitHub Repository Search Files",
|
||||
Fields: actions.GithubRepositorySearchFilesConfigMeta(),
|
||||
},
|
||||
{
|
||||
Name: "github-repository-list-files",
|
||||
Label: "GitHub Repository List Files",
|
||||
Fields: actions.GithubRepositoryListFilesConfigMeta(),
|
||||
},
|
||||
{
|
||||
Name: "github-repository-create-or-update-content",
|
||||
Label: "GitHub Repository Create/Update Content",
|
||||
Fields: actions.GithubRepositoryCreateOrUpdateContentConfigMeta(),
|
||||
},
|
||||
{
|
||||
Name: "github-readme",
|
||||
Label: "GitHub Repository README",
|
||||
Fields: actions.GithubRepositoryREADMEConfigMeta(),
|
||||
},
|
||||
{
|
||||
Name: "github-pr-reader",
|
||||
Label: "GitHub PR Reader",
|
||||
Fields: actions.GithubPRReaderConfigMeta(),
|
||||
},
|
||||
{
|
||||
Name: "github-pr-commenter",
|
||||
Label: "GitHub PR Commenter",
|
||||
Fields: actions.GithubPRCommenterConfigMeta(),
|
||||
},
|
||||
{
|
||||
Name: "github-pr-reviewer",
|
||||
Label: "GitHub PR Reviewer",
|
||||
Fields: actions.GithubPRReviewerConfigMeta(),
|
||||
},
|
||||
{
|
||||
Name: "github-pr-creator",
|
||||
Label: "GitHub PR Creator",
|
||||
Fields: actions.GithubPRCreatorConfigMeta(),
|
||||
},
|
||||
{
|
||||
Name: "twitter-post",
|
||||
Label: "Twitter Post",
|
||||
Fields: actions.TwitterPostConfigMeta(),
|
||||
},
|
||||
{
|
||||
Name: "send-mail",
|
||||
Label: "Send Mail",
|
||||
Fields: actions.SendMailConfigMeta(),
|
||||
},
|
||||
{
|
||||
Name: "shell-command",
|
||||
Label: "Shell Command",
|
||||
Fields: actions.ShellConfigMeta(),
|
||||
},
|
||||
{
|
||||
Name: "custom",
|
||||
Label: "Custom",
|
||||
Fields: action.CustomConfigMeta(),
|
||||
},
|
||||
{
|
||||
Name: "scraper",
|
||||
Label: "Scraper",
|
||||
Fields: []config.Field{},
|
||||
},
|
||||
{
|
||||
Name: "wikipedia",
|
||||
Label: "Wikipedia",
|
||||
Fields: []config.Field{},
|
||||
},
|
||||
{
|
||||
Name: "browse",
|
||||
Label: "Browse",
|
||||
Fields: []config.Field{},
|
||||
},
|
||||
{
|
||||
Name: "counter",
|
||||
Label: "Counter",
|
||||
Fields: []config.Field{},
|
||||
},
|
||||
{
|
||||
Name: "call_agents",
|
||||
Label: "Call Agents",
|
||||
Fields: actions.CallAgentConfigMeta(),
|
||||
},
|
||||
{
|
||||
Name: "send-telegram-message",
|
||||
Label: "Send Telegram Message",
|
||||
Fields: actions.SendTelegramMessageConfigMeta(),
|
||||
},
|
||||
{
|
||||
Name: "set_reminder",
|
||||
Label: "Set Reminder",
|
||||
Fields: []config.Field{},
|
||||
},
|
||||
{
|
||||
Name: "list_reminders",
|
||||
Label: "List Reminders",
|
||||
Fields: []config.Field{},
|
||||
},
|
||||
{
|
||||
Name: "remove_reminder",
|
||||
Label: "Remove Reminder",
|
||||
Fields: []config.Field{},
|
||||
},
|
||||
{
|
||||
Name: "pikvm_power_control",
|
||||
Label: "PiKVM Power Control",
|
||||
Fields: actions.PiKVMConfigMeta(),
|
||||
},
|
||||
{
|
||||
Name: "webhook",
|
||||
Label: "Webhook",
|
||||
Fields: actions.WebhookConfigMeta(),
|
||||
},
|
||||
}
|
||||
|
||||
const (
|
||||
ActionConfigBrowserAgentRunner = "browser-agent-runner-base-url"
|
||||
ActionConfigDeepResearchRunner = "deep-research-runner-base-url"
|
||||
ActionConfigSSHBoxURL = "sshbox-url"
|
||||
ConfigStateDir = "state-dir"
|
||||
CustomActionsDir = "custom-actions-dir"
|
||||
)
|
||||
|
||||
func customActions(customActionsDir string, existingActionConfigs map[string]map[string]string) (allActions []types.Action) {
|
||||
files, err := os.ReadDir(customActionsDir)
|
||||
if err != nil {
|
||||
xlog.Error("Error reading custom actions directory", "error", err)
|
||||
return allActions
|
||||
}
|
||||
|
||||
for _, file := range files {
|
||||
if filepath.Ext(file.Name()) != ".go" {
|
||||
continue
|
||||
}
|
||||
|
||||
content, err := os.ReadFile(filepath.Join(customActionsDir, file.Name()))
|
||||
if err != nil {
|
||||
xlog.Error("Error reading custom action file", "error", err, "file", file.Name())
|
||||
continue
|
||||
}
|
||||
actionName := strings.TrimSuffix(file.Name(), ".go")
|
||||
|
||||
actionConfig := map[string]string{
|
||||
"name": actionName,
|
||||
"description": "",
|
||||
"code": string(content),
|
||||
"unsafe": "false",
|
||||
}
|
||||
|
||||
if c, exists := existingActionConfigs[actionName]; exists {
|
||||
// We allow the user to customize name and description
|
||||
actionConfig[descriptionField] = c[descriptionField]
|
||||
actionConfig[nameField] = c[nameField]
|
||||
actionConfig[configurationField] = c[configurationField]
|
||||
}
|
||||
a, err := Action(ActionCustom, "", actionConfig, nil, map[string]string{})
|
||||
if err != nil {
|
||||
xlog.Error("Error creating custom action", "error", err, "file", file.Name())
|
||||
continue
|
||||
}
|
||||
allActions = append(allActions, a)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func Actions(actionsConfigs map[string]string) func(a *state.AgentConfig) func(ctx context.Context, pool *state.AgentPool) []types.Action {
|
||||
return func(a *state.AgentConfig) func(ctx context.Context, pool *state.AgentPool) []types.Action {
|
||||
return func(ctx context.Context, pool *state.AgentPool) []types.Action {
|
||||
@@ -102,6 +364,7 @@ func Actions(actionsConfigs map[string]string) func(a *state.AgentConfig) func(c
|
||||
|
||||
agentName := a.Name
|
||||
|
||||
existingActionConfigs := map[string]map[string]string{}
|
||||
for _, a := range a.Actions {
|
||||
var config map[string]string
|
||||
if err := json.Unmarshal([]byte(a.Config), &config); err != nil {
|
||||
@@ -109,6 +372,8 @@ func Actions(actionsConfigs map[string]string) func(a *state.AgentConfig) func(c
|
||||
continue
|
||||
}
|
||||
|
||||
existingActionConfigs[a.Name] = config
|
||||
|
||||
a, err := Action(a.Name, agentName, config, pool, actionsConfigs)
|
||||
if err != nil {
|
||||
continue
|
||||
@@ -116,6 +381,11 @@ func Actions(actionsConfigs map[string]string) func(a *state.AgentConfig) func(c
|
||||
allActions = append(allActions, a)
|
||||
}
|
||||
|
||||
// Now we will scan a directory for custom actions
|
||||
if actionsConfigs[CustomActionsDir] != "" {
|
||||
allActions = append(allActions, customActions(actionsConfigs[CustomActionsDir], existingActionConfigs)...)
|
||||
}
|
||||
|
||||
return allActions
|
||||
}
|
||||
}
|
||||
@@ -130,6 +400,8 @@ func Action(name, agentName string, config map[string]string, pool *state.AgentP
|
||||
config = map[string]string{}
|
||||
}
|
||||
|
||||
memoryFilePath := memoryPath(agentName, actionsConfigs)
|
||||
|
||||
switch name {
|
||||
case ActionCustom:
|
||||
a, err = action.NewCustom(config, "")
|
||||
@@ -183,6 +455,8 @@ func Action(name, agentName string, config map[string]string, pool *state.AgentP
|
||||
a = actions.NewBrowse(config)
|
||||
case ActionSendMail:
|
||||
a = actions.NewSendMail(config)
|
||||
case ActionWebhook:
|
||||
a = actions.NewWebhook(config)
|
||||
case ActionTwitterPost:
|
||||
a = actions.NewPostTweet(config)
|
||||
case ActionCounter:
|
||||
@@ -199,6 +473,14 @@ func Action(name, agentName string, config map[string]string, pool *state.AgentP
|
||||
a = action.NewListReminders()
|
||||
case ActionRemoveReminder:
|
||||
a = action.NewRemoveReminder()
|
||||
case ActionAddToMemory:
|
||||
a, _, _ = actions.NewMemoryActions(memoryFilePath, config)
|
||||
case ActionListMemory:
|
||||
_, a, _ = actions.NewMemoryActions(memoryFilePath, config)
|
||||
case ActionRemoveFromMemory:
|
||||
_, _, a = actions.NewMemoryActions(memoryFilePath, config)
|
||||
case ActionPiKVMPowerControl:
|
||||
a = actions.NewPiKVMAction(config)
|
||||
default:
|
||||
xlog.Error("Action not found", "name", name)
|
||||
return nil, fmt.Errorf("Action not found")
|
||||
@@ -211,177 +493,38 @@ func Action(name, agentName string, config map[string]string, pool *state.AgentP
|
||||
return a, nil
|
||||
}
|
||||
|
||||
func ActionsConfigMeta() []config.FieldGroup {
|
||||
return []config.FieldGroup{
|
||||
{
|
||||
Name: "search",
|
||||
Label: "Search",
|
||||
Fields: actions.SearchConfigMeta(),
|
||||
},
|
||||
{
|
||||
Name: "browser-agent-runner",
|
||||
Label: "Browser Agent Runner",
|
||||
Fields: actions.BrowserAgentRunnerConfigMeta(),
|
||||
},
|
||||
{
|
||||
Name: "deep-research-runner",
|
||||
Label: "Deep Research Runner",
|
||||
Fields: actions.DeepResearchRunnerConfigMeta(),
|
||||
},
|
||||
{
|
||||
Name: "generate_image",
|
||||
Label: "Generate Image",
|
||||
Fields: actions.GenImageConfigMeta(),
|
||||
},
|
||||
{
|
||||
Name: "github-issue-labeler",
|
||||
Label: "GitHub Issue Labeler",
|
||||
Fields: actions.GithubIssueLabelerConfigMeta(),
|
||||
},
|
||||
{
|
||||
Name: "github-issue-opener",
|
||||
Label: "GitHub Issue Opener",
|
||||
Fields: actions.GithubIssueOpenerConfigMeta(),
|
||||
},
|
||||
{
|
||||
Name: "github-issue-editor",
|
||||
Label: "GitHub Issue Editor",
|
||||
Fields: actions.GithubIssueEditorConfigMeta(),
|
||||
},
|
||||
{
|
||||
Name: "github-issue-closer",
|
||||
Label: "GitHub Issue Closer",
|
||||
Fields: actions.GithubIssueCloserConfigMeta(),
|
||||
},
|
||||
{
|
||||
Name: "github-issue-commenter",
|
||||
Label: "GitHub Issue Commenter",
|
||||
Fields: actions.GithubIssueCommenterConfigMeta(),
|
||||
},
|
||||
{
|
||||
Name: "github-issue-reader",
|
||||
Label: "GitHub Issue Reader",
|
||||
Fields: actions.GithubIssueReaderConfigMeta(),
|
||||
},
|
||||
{
|
||||
Name: "github-issue-searcher",
|
||||
Label: "GitHub Issue Search",
|
||||
Fields: actions.GithubIssueSearchConfigMeta(),
|
||||
},
|
||||
{
|
||||
Name: "github-repository-get-content",
|
||||
Label: "GitHub Repository Get Content",
|
||||
Fields: actions.GithubRepositoryGetContentConfigMeta(),
|
||||
},
|
||||
{
|
||||
Name: "github-get-all-repository-content",
|
||||
Label: "GitHub Get All Repository Content",
|
||||
Fields: actions.GithubRepositoryGetAllContentConfigMeta(),
|
||||
},
|
||||
{
|
||||
Name: "github-repository-search-files",
|
||||
Label: "GitHub Repository Search Files",
|
||||
Fields: actions.GithubRepositorySearchFilesConfigMeta(),
|
||||
},
|
||||
{
|
||||
Name: "github-repository-list-files",
|
||||
Label: "GitHub Repository List Files",
|
||||
Fields: actions.GithubRepositoryListFilesConfigMeta(),
|
||||
},
|
||||
{
|
||||
Name: "github-repository-create-or-update-content",
|
||||
Label: "GitHub Repository Create/Update Content",
|
||||
Fields: actions.GithubRepositoryCreateOrUpdateContentConfigMeta(),
|
||||
},
|
||||
{
|
||||
Name: "github-readme",
|
||||
Label: "GitHub Repository README",
|
||||
Fields: actions.GithubRepositoryREADMEConfigMeta(),
|
||||
},
|
||||
{
|
||||
Name: "github-pr-reader",
|
||||
Label: "GitHub PR Reader",
|
||||
Fields: actions.GithubPRReaderConfigMeta(),
|
||||
},
|
||||
{
|
||||
Name: "github-pr-commenter",
|
||||
Label: "GitHub PR Commenter",
|
||||
Fields: actions.GithubPRCommenterConfigMeta(),
|
||||
},
|
||||
{
|
||||
Name: "github-pr-reviewer",
|
||||
Label: "GitHub PR Reviewer",
|
||||
Fields: actions.GithubPRReviewerConfigMeta(),
|
||||
},
|
||||
{
|
||||
Name: "github-pr-creator",
|
||||
Label: "GitHub PR Creator",
|
||||
Fields: actions.GithubPRCreatorConfigMeta(),
|
||||
},
|
||||
{
|
||||
Name: "twitter-post",
|
||||
Label: "Twitter Post",
|
||||
Fields: actions.TwitterPostConfigMeta(),
|
||||
},
|
||||
{
|
||||
Name: "send-mail",
|
||||
Label: "Send Mail",
|
||||
Fields: actions.SendMailConfigMeta(),
|
||||
},
|
||||
{
|
||||
Name: "shell-command",
|
||||
Label: "Shell Command",
|
||||
Fields: actions.ShellConfigMeta(),
|
||||
},
|
||||
{
|
||||
Name: "custom",
|
||||
Label: "Custom",
|
||||
Fields: action.CustomConfigMeta(),
|
||||
},
|
||||
{
|
||||
Name: "scraper",
|
||||
Label: "Scraper",
|
||||
Fields: []config.Field{},
|
||||
},
|
||||
{
|
||||
Name: "wikipedia",
|
||||
Label: "Wikipedia",
|
||||
Fields: []config.Field{},
|
||||
},
|
||||
{
|
||||
Name: "browse",
|
||||
Label: "Browse",
|
||||
Fields: []config.Field{},
|
||||
},
|
||||
{
|
||||
Name: "counter",
|
||||
Label: "Counter",
|
||||
Fields: []config.Field{},
|
||||
},
|
||||
{
|
||||
Name: "call_agents",
|
||||
Label: "Call Agents",
|
||||
Fields: actions.CallAgentConfigMeta(),
|
||||
},
|
||||
{
|
||||
Name: "send-telegram-message",
|
||||
Label: "Send Telegram Message",
|
||||
Fields: actions.SendTelegramMessageConfigMeta(),
|
||||
},
|
||||
{
|
||||
Name: "set_reminder",
|
||||
Label: "Set Reminder",
|
||||
Fields: []config.Field{},
|
||||
},
|
||||
{
|
||||
Name: "list_reminders",
|
||||
Label: "List Reminders",
|
||||
Fields: []config.Field{},
|
||||
},
|
||||
{
|
||||
Name: "remove_reminder",
|
||||
Label: "Remove Reminder",
|
||||
Fields: []config.Field{},
|
||||
},
|
||||
func ActionsConfigMeta(customActionDir string) []config.FieldGroup {
|
||||
all := slices.Clone(DefaultActions)
|
||||
|
||||
if customActionDir != "" {
|
||||
actions := customActions(customActionDir, map[string]map[string]string{})
|
||||
|
||||
for _, a := range actions {
|
||||
all = append(all, config.FieldGroup{
|
||||
Name: a.Definition().Name.String(),
|
||||
Label: a.Definition().Name.String(),
|
||||
Fields: []config.Field{
|
||||
{
|
||||
Name: nameField,
|
||||
Label: "Name",
|
||||
Type: config.FieldTypeText,
|
||||
HelpText: "Name of the custom action",
|
||||
},
|
||||
{
|
||||
Name: descriptionField,
|
||||
Label: "Description",
|
||||
Type: config.FieldTypeTextarea,
|
||||
HelpText: "Description of the custom action",
|
||||
},
|
||||
{
|
||||
Name: configurationField,
|
||||
Label: "Configuration",
|
||||
Type: config.FieldTypeTextarea,
|
||||
HelpText: "Configuration of the custom action",
|
||||
},
|
||||
},
|
||||
})
|
||||
}
|
||||
}
|
||||
return all
|
||||
}
|
||||
|
||||
@@ -0,0 +1,284 @@
|
||||
package actions
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"os"
|
||||
"strconv"
|
||||
|
||||
"io"
|
||||
|
||||
"github.com/mudler/LocalAGI/core/types"
|
||||
"github.com/mudler/LocalAGI/pkg/config"
|
||||
"github.com/sashabaranov/go-openai/jsonschema"
|
||||
)
|
||||
|
||||
// Remove global const and mutex, and add them as fields to a struct
|
||||
|
||||
type MemoryActions struct {
|
||||
filePath string
|
||||
customName string
|
||||
customDescription string
|
||||
}
|
||||
|
||||
type AddToMemoryAction struct{ *MemoryActions }
|
||||
type ListMemoryAction struct{ *MemoryActions }
|
||||
type RemoveFromMemoryAction struct{ *MemoryActions }
|
||||
|
||||
// NewMemoryActions returns the three actions, using the provided filePath and config
|
||||
func NewMemoryActions(filePath string, config map[string]string) (*AddToMemoryAction, *ListMemoryAction, *RemoveFromMemoryAction) {
|
||||
ma := &MemoryActions{filePath: filePath}
|
||||
if config != nil {
|
||||
ma.customName = config["custom_name"]
|
||||
ma.customDescription = config["custom_description"]
|
||||
}
|
||||
return &AddToMemoryAction{ma}, &ListMemoryAction{ma}, &RemoveFromMemoryAction{ma}
|
||||
}
|
||||
|
||||
type addToMemoryParams struct {
|
||||
Item string `json:"item"`
|
||||
}
|
||||
|
||||
type removeFromMemoryParams struct {
|
||||
Index *int `json:"index,omitempty"`
|
||||
Value string `json:"value,omitempty"`
|
||||
}
|
||||
|
||||
func (m *MemoryActions) readMemory() ([]string, error) {
|
||||
f, err := os.Open(m.filePath)
|
||||
if err != nil {
|
||||
if os.IsNotExist(err) {
|
||||
return []string{}, nil
|
||||
}
|
||||
return nil, err
|
||||
}
|
||||
defer f.Close()
|
||||
var items []string
|
||||
if err := json.NewDecoder(f).Decode(&items); err != nil {
|
||||
if err == io.EOF {
|
||||
return []string{}, nil
|
||||
}
|
||||
return nil, err
|
||||
}
|
||||
return items, nil
|
||||
}
|
||||
|
||||
func (m *MemoryActions) writeMemory(items []string) error {
|
||||
f, err := os.Create(m.filePath)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer f.Close()
|
||||
return json.NewEncoder(f).Encode(items)
|
||||
}
|
||||
|
||||
func (a *AddToMemoryAction) Run(ctx context.Context, sharedState *types.AgentSharedState, params types.ActionParams) (types.ActionResult, error) {
|
||||
var req addToMemoryParams
|
||||
if err := params.Unmarshal(&req); err != nil {
|
||||
return types.ActionResult{}, fmt.Errorf("invalid parameters: %w", err)
|
||||
}
|
||||
if req.Item == "" {
|
||||
return types.ActionResult{}, fmt.Errorf("item cannot be empty")
|
||||
}
|
||||
items, err := a.readMemory()
|
||||
if err != nil {
|
||||
return types.ActionResult{}, err
|
||||
}
|
||||
items = append(items, req.Item)
|
||||
if err := a.writeMemory(items); err != nil {
|
||||
return types.ActionResult{}, err
|
||||
}
|
||||
return types.ActionResult{
|
||||
Result: fmt.Sprintf("Added item to memory: %s", req.Item),
|
||||
Metadata: map[string]any{"item": req.Item, "count": len(items)},
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (a *ListMemoryAction) Run(ctx context.Context, sharedState *types.AgentSharedState, params types.ActionParams) (types.ActionResult, error) {
|
||||
items, err := a.readMemory()
|
||||
if err != nil {
|
||||
return types.ActionResult{}, err
|
||||
}
|
||||
|
||||
outputResult := "Number of items in memory: " + strconv.Itoa(len(items)) + "\n"
|
||||
for i, item := range items {
|
||||
outputResult += fmt.Sprintf("%d) %s\n", i, item)
|
||||
}
|
||||
|
||||
return types.ActionResult{
|
||||
Result: outputResult,
|
||||
Metadata: map[string]any{"items": items},
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (a *RemoveFromMemoryAction) Run(ctx context.Context, sharedState *types.AgentSharedState, params types.ActionParams) (types.ActionResult, error) {
|
||||
var req removeFromMemoryParams
|
||||
if err := params.Unmarshal(&req); err != nil {
|
||||
return types.ActionResult{}, fmt.Errorf("invalid parameters: %w", err)
|
||||
}
|
||||
items, err := a.readMemory()
|
||||
if err != nil {
|
||||
return types.ActionResult{}, err
|
||||
}
|
||||
var removed string
|
||||
if req.Index != nil {
|
||||
idx := *req.Index
|
||||
if idx < 0 || idx >= len(items) {
|
||||
return types.ActionResult{}, fmt.Errorf("index out of range")
|
||||
}
|
||||
removed = items[idx]
|
||||
items = append(items[:idx], items[idx+1:]...)
|
||||
} else if req.Value != "" {
|
||||
found := false
|
||||
for i, v := range items {
|
||||
if v == req.Value {
|
||||
removed = v
|
||||
items = append(items[:i], items[i+1:]...)
|
||||
found = true
|
||||
break
|
||||
}
|
||||
}
|
||||
if !found {
|
||||
return types.ActionResult{}, fmt.Errorf("value not found in memory")
|
||||
}
|
||||
} else {
|
||||
return types.ActionResult{}, fmt.Errorf("must provide index or value to remove")
|
||||
}
|
||||
if err := a.writeMemory(items); err != nil {
|
||||
return types.ActionResult{}, err
|
||||
}
|
||||
return types.ActionResult{
|
||||
Result: fmt.Sprintf("Removed item from memory: %s", removed),
|
||||
Metadata: map[string]any{"removed": removed, "count": len(items)},
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (a *AddToMemoryAction) Definition() types.ActionDefinition {
|
||||
name := "add_to_memory"
|
||||
description := "Add a string item to memory (stored in a JSON file)."
|
||||
if a.customName != "" {
|
||||
name = a.customName
|
||||
}
|
||||
if a.customDescription != "" {
|
||||
description = a.customDescription
|
||||
}
|
||||
return types.ActionDefinition{
|
||||
Name: types.ActionDefinitionName(name),
|
||||
Description: description,
|
||||
Properties: map[string]jsonschema.Definition{
|
||||
"item": {
|
||||
Type: jsonschema.String,
|
||||
Description: "The string item to add to memory.",
|
||||
},
|
||||
},
|
||||
Required: []string{"item"},
|
||||
}
|
||||
}
|
||||
|
||||
func (a *ListMemoryAction) Definition() types.ActionDefinition {
|
||||
name := "list_memory"
|
||||
description := "List all items currently stored in memory."
|
||||
if a.customName != "" {
|
||||
name = a.customName
|
||||
}
|
||||
if a.customDescription != "" {
|
||||
description = a.customDescription
|
||||
}
|
||||
return types.ActionDefinition{
|
||||
Name: types.ActionDefinitionName(name),
|
||||
Description: description,
|
||||
Properties: map[string]jsonschema.Definition{},
|
||||
Required: []string{},
|
||||
}
|
||||
}
|
||||
|
||||
func (a *RemoveFromMemoryAction) Definition() types.ActionDefinition {
|
||||
name := "remove_from_memory"
|
||||
description := "Remove an item from memory by index or value."
|
||||
if a.customName != "" {
|
||||
name = a.customName
|
||||
}
|
||||
if a.customDescription != "" {
|
||||
description = a.customDescription
|
||||
}
|
||||
return types.ActionDefinition{
|
||||
Name: types.ActionDefinitionName(name),
|
||||
Description: description,
|
||||
Properties: map[string]jsonschema.Definition{
|
||||
"index": {
|
||||
Type: jsonschema.Integer,
|
||||
Description: "The index of the item to remove (optional, 0-based)",
|
||||
},
|
||||
"value": {
|
||||
Type: jsonschema.String,
|
||||
Description: "The value of the item to remove (optional)",
|
||||
},
|
||||
},
|
||||
Required: []string{},
|
||||
}
|
||||
}
|
||||
|
||||
func (a *AddToMemoryAction) Plannable() bool { return true }
|
||||
func (a *ListMemoryAction) Plannable() bool { return true }
|
||||
func (a *RemoveFromMemoryAction) Plannable() bool { return true }
|
||||
|
||||
// AddToMemoryConfigMeta returns the metadata for AddToMemory action configuration fields
|
||||
func AddToMemoryConfigMeta() []config.Field {
|
||||
return []config.Field{
|
||||
{
|
||||
Name: "custom_name",
|
||||
Label: "Custom Name",
|
||||
Type: config.FieldTypeText,
|
||||
Required: false,
|
||||
HelpText: "Custom name for the action (optional, defaults to 'add_to_memory')",
|
||||
},
|
||||
{
|
||||
Name: "custom_description",
|
||||
Label: "Custom Description",
|
||||
Type: config.FieldTypeText,
|
||||
Required: false,
|
||||
HelpText: "Custom description for the action (optional, defaults to 'Add a string item to memory (stored in a JSON file).')",
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
// ListMemoryConfigMeta returns the metadata for ListMemory action configuration fields
|
||||
func ListMemoryConfigMeta() []config.Field {
|
||||
return []config.Field{
|
||||
{
|
||||
Name: "custom_name",
|
||||
Label: "Custom Name",
|
||||
Type: config.FieldTypeText,
|
||||
Required: false,
|
||||
HelpText: "Custom name for the action (optional, defaults to 'list_memory')",
|
||||
},
|
||||
{
|
||||
Name: "custom_description",
|
||||
Label: "Custom Description",
|
||||
Type: config.FieldTypeText,
|
||||
Required: false,
|
||||
HelpText: "Custom description for the action (optional, defaults to 'List all items currently stored in memory.')",
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
// RemoveFromMemoryConfigMeta returns the metadata for RemoveFromMemory action configuration fields
|
||||
func RemoveFromMemoryConfigMeta() []config.Field {
|
||||
return []config.Field{
|
||||
{
|
||||
Name: "custom_name",
|
||||
Label: "Custom Name",
|
||||
Type: config.FieldTypeText,
|
||||
Required: false,
|
||||
HelpText: "Custom name for the action (optional, defaults to 'remove_from_memory')",
|
||||
},
|
||||
{
|
||||
Name: "custom_description",
|
||||
Label: "Custom Description",
|
||||
Type: config.FieldTypeText,
|
||||
Required: false,
|
||||
HelpText: "Custom description for the action (optional, defaults to 'Remove an item from memory by index or value.')",
|
||||
},
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,83 @@
|
||||
package actions_test
|
||||
|
||||
import (
|
||||
"context"
|
||||
"os"
|
||||
|
||||
"github.com/mudler/LocalAGI/core/types"
|
||||
"github.com/mudler/LocalAGI/services/actions"
|
||||
. "github.com/onsi/ginkgo/v2"
|
||||
. "github.com/onsi/gomega"
|
||||
)
|
||||
|
||||
var _ = Describe("MemoryActions", func() {
|
||||
var (
|
||||
tmpFile string
|
||||
aAdd *actions.AddToMemoryAction
|
||||
aList *actions.ListMemoryAction
|
||||
aRemove *actions.RemoveFromMemoryAction
|
||||
)
|
||||
|
||||
BeforeEach(func() {
|
||||
f, err := os.CreateTemp("", "memory_test_*.json")
|
||||
Expect(err).ToNot(HaveOccurred())
|
||||
tmpFile = f.Name()
|
||||
f.Close()
|
||||
aAdd, aList, aRemove = actions.NewMemoryActions(tmpFile, map[string]string{})
|
||||
})
|
||||
|
||||
AfterEach(func() {
|
||||
os.Remove(tmpFile)
|
||||
})
|
||||
|
||||
It("adds and lists items", func() {
|
||||
_, err := aAdd.Run(context.TODO(), nil, types.ActionParams{"item": "foo"})
|
||||
Expect(err).ToNot(HaveOccurred())
|
||||
_, err = aAdd.Run(context.TODO(), nil, types.ActionParams{"item": "bar"})
|
||||
Expect(err).ToNot(HaveOccurred())
|
||||
res, err := aList.Run(context.TODO(), nil, types.ActionParams{})
|
||||
Expect(err).ToNot(HaveOccurred())
|
||||
Expect(res.Metadata["items"]).To(ContainElements("foo", "bar"))
|
||||
})
|
||||
|
||||
It("removes by index", func() {
|
||||
_, _ = aAdd.Run(context.TODO(), nil, types.ActionParams{"item": "foo"})
|
||||
_, _ = aAdd.Run(context.TODO(), nil, types.ActionParams{"item": "bar"})
|
||||
_, err := aRemove.Run(context.TODO(), nil, types.ActionParams{"index": 0})
|
||||
Expect(err).ToNot(HaveOccurred())
|
||||
res, _ := aList.Run(context.TODO(), nil, types.ActionParams{})
|
||||
Expect(res.Metadata["items"]).To(ConsistOf("bar"))
|
||||
})
|
||||
|
||||
It("removes by value", func() {
|
||||
_, _ = aAdd.Run(context.TODO(), nil, types.ActionParams{"item": "foo"})
|
||||
_, _ = aAdd.Run(context.TODO(), nil, types.ActionParams{"item": "bar"})
|
||||
_, err := aRemove.Run(context.TODO(), nil, types.ActionParams{"value": "bar"})
|
||||
Expect(err).ToNot(HaveOccurred())
|
||||
res, _ := aList.Run(context.TODO(), nil, types.ActionParams{})
|
||||
Expect(res.Metadata["items"]).To(ConsistOf("foo"))
|
||||
})
|
||||
|
||||
It("returns error for out of range index", func() {
|
||||
_, _ = aAdd.Run(context.TODO(), nil, types.ActionParams{"item": "foo"})
|
||||
_, err := aRemove.Run(context.TODO(), nil, types.ActionParams{"index": 2})
|
||||
Expect(err).To(HaveOccurred())
|
||||
})
|
||||
|
||||
It("returns error for value not found", func() {
|
||||
_, _ = aAdd.Run(context.TODO(), nil, types.ActionParams{"item": "foo"})
|
||||
_, err := aRemove.Run(context.TODO(), nil, types.ActionParams{"value": "bar"})
|
||||
Expect(err).To(HaveOccurred())
|
||||
})
|
||||
|
||||
It("returns error for empty item", func() {
|
||||
_, err := aAdd.Run(context.TODO(), nil, types.ActionParams{"item": ""})
|
||||
Expect(err).To(HaveOccurred())
|
||||
})
|
||||
|
||||
It("returns error if neither index nor value provided", func() {
|
||||
_, _ = aAdd.Run(context.TODO(), nil, types.ActionParams{"item": "foo"})
|
||||
_, err := aRemove.Run(context.TODO(), nil, types.ActionParams{})
|
||||
Expect(err).To(HaveOccurred())
|
||||
})
|
||||
})
|
||||
@@ -0,0 +1,199 @@
|
||||
package actions
|
||||
|
||||
import (
|
||||
"context"
|
||||
"crypto/tls"
|
||||
"fmt"
|
||||
"net/http"
|
||||
|
||||
"github.com/mudler/LocalAGI/core/types"
|
||||
"github.com/mudler/LocalAGI/pkg/config"
|
||||
"github.com/sashabaranov/go-openai/jsonschema"
|
||||
)
|
||||
|
||||
func NewPiKVMAction(config map[string]string) *PiKVMAction {
|
||||
return &PiKVMAction{
|
||||
hostname: config["hostname"],
|
||||
username: config["username"],
|
||||
password: config["password"],
|
||||
customName: config["custom_name"],
|
||||
customDescription: config["custom_description"],
|
||||
insecure: config["insecure"] == "true",
|
||||
}
|
||||
}
|
||||
|
||||
type PiKVMAction struct {
|
||||
hostname string
|
||||
username string
|
||||
password string
|
||||
customName string
|
||||
customDescription string
|
||||
insecure bool
|
||||
}
|
||||
|
||||
type pikvmPowerParams struct {
|
||||
Action string `json:"action"`
|
||||
}
|
||||
|
||||
func (a *PiKVMAction) Run(ctx context.Context, sharedState *types.AgentSharedState, params types.ActionParams) (types.ActionResult, error) {
|
||||
var req pikvmPowerParams
|
||||
if err := params.Unmarshal(&req); err != nil {
|
||||
return types.ActionResult{}, fmt.Errorf("invalid parameters: %w", err)
|
||||
}
|
||||
|
||||
// Validate action parameter
|
||||
validActions := map[string]bool{
|
||||
"on": true,
|
||||
"off": true,
|
||||
"off_hard": true,
|
||||
"reset_hard": true,
|
||||
}
|
||||
if !validActions[req.Action] {
|
||||
return types.ActionResult{}, fmt.Errorf("invalid action: %s. Valid actions are: on, off, off_hard, reset_hard", req.Action)
|
||||
}
|
||||
|
||||
// Check if required config is provided
|
||||
if a.hostname == "" {
|
||||
return types.ActionResult{}, fmt.Errorf("hostname is required in action configuration")
|
||||
}
|
||||
if a.username == "" {
|
||||
return types.ActionResult{}, fmt.Errorf("username is required in action configuration")
|
||||
}
|
||||
if a.password == "" {
|
||||
return types.ActionResult{}, fmt.Errorf("password is required in action configuration")
|
||||
}
|
||||
|
||||
// Build the API URL
|
||||
apiURL := fmt.Sprintf("https://%s/api/atx/power", a.hostname)
|
||||
|
||||
insecure := false
|
||||
if a.insecure {
|
||||
insecure = true
|
||||
}
|
||||
// Create HTTP client with basic auth
|
||||
tr := &http.Transport{
|
||||
TLSClientConfig: &tls.Config{InsecureSkipVerify: insecure},
|
||||
}
|
||||
client := &http.Client{Transport: tr}
|
||||
|
||||
reqHTTP, err := http.NewRequestWithContext(ctx, "POST", apiURL, nil)
|
||||
if err != nil {
|
||||
return types.ActionResult{}, fmt.Errorf("failed to create HTTP request: %w", err)
|
||||
}
|
||||
|
||||
// Set basic authentication
|
||||
reqHTTP.SetBasicAuth(a.username, a.password)
|
||||
|
||||
// Add query parameters
|
||||
q := reqHTTP.URL.Query()
|
||||
q.Add("action", req.Action)
|
||||
reqHTTP.URL.RawQuery = q.Encode()
|
||||
|
||||
// Make the request
|
||||
resp, err := client.Do(reqHTTP)
|
||||
if err != nil {
|
||||
return types.ActionResult{}, fmt.Errorf("failed to make HTTP request to PiKVM: %w", err)
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
|
||||
// Check response status
|
||||
if resp.StatusCode != http.StatusOK {
|
||||
return types.ActionResult{}, fmt.Errorf("PiKVM API returned status %d: %s", resp.StatusCode, resp.Status)
|
||||
}
|
||||
|
||||
// Determine action description for user-friendly response
|
||||
actionDesc := map[string]string{
|
||||
"on": "power on",
|
||||
"off": "power off",
|
||||
"off_hard": "hard power off",
|
||||
"reset_hard": "hard reset",
|
||||
}
|
||||
|
||||
result := fmt.Sprintf("Successfully sent %s command to PiKVM at %s", actionDesc[req.Action], a.hostname)
|
||||
|
||||
return types.ActionResult{
|
||||
Result: result,
|
||||
Metadata: map[string]any{
|
||||
"action": req.Action,
|
||||
"hostname": a.hostname,
|
||||
"status": "success",
|
||||
},
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (a *PiKVMAction) Definition() types.ActionDefinition {
|
||||
name := "pikvm_power_control"
|
||||
description := "Control power state of a PiKVM device using ATX power management."
|
||||
|
||||
if a.customName != "" {
|
||||
name = a.customName
|
||||
}
|
||||
if a.customDescription != "" {
|
||||
description = a.customDescription
|
||||
}
|
||||
|
||||
return types.ActionDefinition{
|
||||
Name: types.ActionDefinitionName(name),
|
||||
Description: description,
|
||||
Properties: map[string]jsonschema.Definition{
|
||||
"action": {
|
||||
Type: jsonschema.String,
|
||||
Description: "The power action to perform on the PiKVM device.",
|
||||
Enum: []string{"on", "off", "off_hard", "reset_hard"},
|
||||
},
|
||||
},
|
||||
Required: []string{"action"},
|
||||
}
|
||||
}
|
||||
|
||||
func (a *PiKVMAction) Plannable() bool {
|
||||
return true
|
||||
}
|
||||
|
||||
// PiKVMConfigMeta returns the metadata for PiKVM action configuration fields
|
||||
func PiKVMConfigMeta() []config.Field {
|
||||
return []config.Field{
|
||||
{
|
||||
Name: "hostname",
|
||||
Label: "PiKVM Hostname",
|
||||
Type: config.FieldTypeText,
|
||||
Required: true,
|
||||
HelpText: "The hostname or IP address of the PiKVM device (e.g., pikvm.local or 192.168.1.100)",
|
||||
},
|
||||
{
|
||||
Name: "username",
|
||||
Label: "Username",
|
||||
Type: config.FieldTypeText,
|
||||
Required: true,
|
||||
HelpText: "Username for PiKVM authentication (usually 'admin')",
|
||||
},
|
||||
{
|
||||
Name: "password",
|
||||
Label: "Password",
|
||||
Type: config.FieldTypeText,
|
||||
Required: true,
|
||||
HelpText: "Password for PiKVM authentication",
|
||||
},
|
||||
{
|
||||
Name: "custom_name",
|
||||
Label: "Custom Action Name",
|
||||
Type: config.FieldTypeText,
|
||||
Required: false,
|
||||
HelpText: "Custom name for this action (optional, defaults to 'pikvm_power_control')",
|
||||
},
|
||||
{
|
||||
Name: "custom_description",
|
||||
Label: "Custom Description",
|
||||
Type: config.FieldTypeText,
|
||||
Required: false,
|
||||
HelpText: "Custom description for this action (optional)",
|
||||
},
|
||||
{
|
||||
Name: "insecure",
|
||||
Label: "Insecure",
|
||||
Type: config.FieldTypeCheckbox,
|
||||
Required: false,
|
||||
HelpText: "Skip certificate verification (optional)",
|
||||
},
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,256 @@
|
||||
// Package actions contains action implementations used by LocalAGI.
|
||||
// This file implements the "webhook" action which can send an HTTP request
|
||||
// to an external service with a configurable method, content type, and payload.
|
||||
package actions
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"context"
|
||||
"fmt"
|
||||
"io"
|
||||
"net/http"
|
||||
"strings"
|
||||
|
||||
"github.com/mudler/LocalAGI/core/types"
|
||||
"github.com/mudler/LocalAGI/pkg/config"
|
||||
"github.com/sashabaranov/go-openai/jsonschema"
|
||||
)
|
||||
|
||||
// NewWebhook constructs a WebhookAction using provided configuration values:
|
||||
// - url: Destination endpoint for the HTTP request (required).
|
||||
// - method: HTTP method to use (GET, POST, PUT, DELETE, ...). Defaults to POST.
|
||||
// - contentType: Value for the Content-Type header (e.g., application/json).
|
||||
// - payloadTemplate: Optional template for the request body; the runtime parameter
|
||||
// "payload" (if provided) will replace the "{{payload}}" placeholder inside this template.
|
||||
func NewWebhook(cfg map[string]string) *WebhookAction {
|
||||
wa := &WebhookAction{
|
||||
url: strings.TrimSpace(cfg["url"]),
|
||||
method: strings.ToUpper(strings.TrimSpace(cfg["method"])),
|
||||
contentType: strings.TrimSpace(cfg["contentType"]),
|
||||
payloadTemplate: cfg["payloadTemplate"],
|
||||
}
|
||||
// Optional custom overrides
|
||||
if cfg != nil {
|
||||
wa.customName = cfg["custom_name"]
|
||||
wa.customDescription = cfg["custom_description"]
|
||||
wa.customPayloadDescription = cfg["custom_payload_description"]
|
||||
}
|
||||
if wa.method == "" {
|
||||
wa.method = http.MethodPost
|
||||
}
|
||||
return wa
|
||||
}
|
||||
|
||||
// WebhookAction holds the static configuration for the webhook.
|
||||
// These values come from the action configuration (UI/agent config),
|
||||
// while the runtime parameter only carries the dynamic payload.
|
||||
// - url: Target endpoint for the request.
|
||||
// - method: HTTP method to use. Defaults to POST if not provided.
|
||||
// - contentType: Sets the Content-Type header when a body is sent.
|
||||
// - payloadTemplate: Optional template used to build the request body; occurrences
|
||||
// of "{{payload}}" get replaced with the runtime payload string.
|
||||
// If no placeholder is present, the template is used as-is.
|
||||
// For GET requests the body is omitted regardless of payload.
|
||||
//
|
||||
// Note: This action does not follow redirects!
|
||||
type WebhookAction struct {
|
||||
url string
|
||||
method string
|
||||
contentType string
|
||||
payloadTemplate string
|
||||
customName string
|
||||
customDescription string
|
||||
customPayloadDescription string
|
||||
}
|
||||
|
||||
// Run executes the webhook call.
|
||||
// It reads the runtime parameter "payload" (optional), merges it into the
|
||||
// configured payloadTemplate (if any), constructs an HTTP request using the
|
||||
// configured URL, method and content type, and then returns a summary with the
|
||||
// response status and body (truncated to 4KiB for safety).
|
||||
func (a *WebhookAction) Run(ctx context.Context, sharedState *types.AgentSharedState, params types.ActionParams) (types.ActionResult, error) {
|
||||
// Runtime parameters: only payload
|
||||
type input struct {
|
||||
Payload string `json:"payload"`
|
||||
}
|
||||
var in input
|
||||
if err := params.Unmarshal(&in); err != nil {
|
||||
return types.ActionResult{}, err
|
||||
}
|
||||
|
||||
// Validate essential configuration. The URL must be provided via the
|
||||
// action configuration (not via runtime parameters).
|
||||
if a.url == "" {
|
||||
return types.ActionResult{}, fmt.Errorf("configuration.url is required")
|
||||
}
|
||||
|
||||
method := a.method
|
||||
|
||||
// Build the request body based on template and payload:
|
||||
// - If a payloadTemplate is provided, replace occurrences of "{{payload}}"
|
||||
// with the runtime payload value.
|
||||
// - If the template does not contain the placeholder but is provided, we use
|
||||
// the template as-is (common for static JSON bodies prepared at config time).
|
||||
// - If no template is configured, we send the runtime payload as-is.
|
||||
// - For GET requests the body is omitted regardless of payload.
|
||||
var payload string
|
||||
if a.payloadTemplate != "" {
|
||||
payload = strings.ReplaceAll(a.payloadTemplate, "{{payload}}", in.Payload)
|
||||
if payload == a.payloadTemplate && in.Payload != "" {
|
||||
// If no placeholder found, fallback to template or payload alone
|
||||
payload = a.payloadTemplate
|
||||
}
|
||||
} else {
|
||||
payload = in.Payload
|
||||
}
|
||||
|
||||
var body io.Reader
|
||||
if method != http.MethodGet && payload != "" {
|
||||
body = bytes.NewBufferString(payload)
|
||||
}
|
||||
|
||||
// Create the HTTP request bound to the provided context so that cancellation
|
||||
// or timeouts from the caller propagate to the outbound call.
|
||||
req, err := http.NewRequestWithContext(ctx, method, a.url, body)
|
||||
if err != nil {
|
||||
return types.ActionResult{}, err
|
||||
}
|
||||
|
||||
// Set Content-Type header if configured. For GET requests this header is
|
||||
// typically ignored by servers as there is no body.
|
||||
if a.contentType != "" {
|
||||
req.Header.Set("Content-Type", a.contentType)
|
||||
}
|
||||
|
||||
// Use a new http.Client with default settings. Consider configuring timeouts
|
||||
// at the caller level via the context, or wiring a custom client if needed.
|
||||
client := &http.Client{}
|
||||
resp, err := client.Do(req)
|
||||
if err != nil {
|
||||
return types.ActionResult{}, err
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
|
||||
// Read and safely truncate the response body to avoid flooding the agent's
|
||||
// context with very large payloads. Errors on ReadAll are ignored here as
|
||||
// we already have the status code.
|
||||
respBytes, _ := io.ReadAll(resp.Body)
|
||||
respBody := string(respBytes)
|
||||
if len(respBody) > 4096 {
|
||||
respBody = respBody[:4096] + "... (truncated)"
|
||||
}
|
||||
|
||||
return types.ActionResult{
|
||||
// Return the response body as the result.
|
||||
// If the response body is empty, use the status text as the result (e.g. "OK" for status code 200).
|
||||
Result: func() string {
|
||||
if respBody == "" {
|
||||
return http.StatusText(resp.StatusCode)
|
||||
}
|
||||
return respBody
|
||||
}(),
|
||||
// Include the response status code in the metadata.
|
||||
Metadata: map[string]interface{}{
|
||||
"statusCode": resp.StatusCode,
|
||||
},
|
||||
}, nil
|
||||
}
|
||||
|
||||
// Definition returns the action schema exposed to the planner/runtime.
|
||||
// Only the runtime parameter "payload" is accepted; all connection details
|
||||
// are configured statically via the action configuration UI.
|
||||
func (a *WebhookAction) Definition() types.ActionDefinition {
|
||||
name := "webhook"
|
||||
description := "Send an HTTP request to a configured URL/method/content-type. Accepts a runtime payload parameter optionally inserted into the configured payload template."
|
||||
if a.customName != "" {
|
||||
name = a.customName
|
||||
}
|
||||
if a.customDescription != "" {
|
||||
description = a.customDescription
|
||||
}
|
||||
payloadDesc := "Payload/body to send with the request at runtime. If a payloadTemplate is configured, '{{payload}}' will be replaced by this value."
|
||||
if a.customPayloadDescription != "" {
|
||||
payloadDesc = a.customPayloadDescription
|
||||
}
|
||||
return types.ActionDefinition{
|
||||
Name: types.ActionDefinitionName(name),
|
||||
Description: description,
|
||||
Properties: map[string]jsonschema.Definition{
|
||||
"payload": {
|
||||
Type: jsonschema.String,
|
||||
Description: payloadDesc,
|
||||
},
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
// Plannable indicates the action can be suggested/used by planners without
|
||||
// requiring hidden context; inputs are straightforward and safe.
|
||||
func (a *WebhookAction) Plannable() bool { return true }
|
||||
|
||||
// WebhookConfigMeta returns the metadata for Webhook action configuration fields:
|
||||
// - url: The endpoint to send requests to (required).
|
||||
// - method: One of GET/POST/PUT/DELETE. Defaults to POST.
|
||||
// - contentType: Common content types selectable from a dropdown.
|
||||
// - payloadTemplate: Optional body template. At runtime, "{{payload}}" is
|
||||
// replaced by the provided payload parameter. If missing, the template is used
|
||||
// as-is; for GET, no body is sent regardless.
|
||||
func WebhookConfigMeta() []config.Field {
|
||||
return []config.Field{
|
||||
{
|
||||
Name: "custom_name",
|
||||
Label: "Custom Name",
|
||||
Type: config.FieldTypeText,
|
||||
Required: false,
|
||||
HelpText: "Custom name for the action (optional, defaults to 'webhook')",
|
||||
},
|
||||
{
|
||||
Name: "custom_description",
|
||||
Label: "Custom Description",
|
||||
Type: config.FieldTypeText,
|
||||
Required: false,
|
||||
HelpText: "Custom description for the action (optional)",
|
||||
},
|
||||
{
|
||||
Name: "custom_payload_description",
|
||||
Label: "Custom Payload Description",
|
||||
Type: config.FieldTypeText,
|
||||
Required: false,
|
||||
HelpText: "Override the payload parameter description shown in the UI/schema (optional).",
|
||||
},
|
||||
{
|
||||
Name: "url",
|
||||
Label: "URL",
|
||||
Type: config.FieldTypeText,
|
||||
Required: true,
|
||||
HelpText: "Destination URL for the webhook",
|
||||
},
|
||||
{
|
||||
Name: "method",
|
||||
Label: "HTTP Method",
|
||||
Type: config.FieldTypeSelect,
|
||||
Options: []config.FieldOption{{Value: http.MethodGet, Label: "GET"}, {Value: http.MethodPost, Label: "POST"}, {Value: http.MethodPut, Label: "PUT"}, {Value: http.MethodDelete, Label: "DELETE"}},
|
||||
DefaultValue: http.MethodPost,
|
||||
Required: true,
|
||||
HelpText: "HTTP method to use",
|
||||
},
|
||||
{
|
||||
Name: "contentType",
|
||||
Label: "Content Type",
|
||||
Type: config.FieldTypeSelect,
|
||||
Options: []config.FieldOption{
|
||||
{Value: "application/json", Label: "application/json"},
|
||||
{Value: "text/plain", Label: "text/plain"},
|
||||
{Value: "application/x-www-form-urlencoded", Label: "application/x-www-form-urlencoded"},
|
||||
},
|
||||
Required: true,
|
||||
HelpText: "Content-Type header to send",
|
||||
},
|
||||
{
|
||||
Name: "payloadTemplate",
|
||||
Label: "Payload Template",
|
||||
Type: config.FieldTypeTextarea,
|
||||
HelpText: "Optional template used to craft the request body. Use '{{payload}}' as placeholder for the runtime payload.",
|
||||
},
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,28 @@
|
||||
package services
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"os"
|
||||
|
||||
"github.com/mudler/LocalAGI/pkg/xlog"
|
||||
)
|
||||
|
||||
func memoryPath(agentName string, actionsConfigs map[string]string) string {
|
||||
// Compose memory file path based on stateDir and agentName, using a subdirectory
|
||||
memoryFilePath := "memory.json"
|
||||
if actionsConfigs != nil {
|
||||
if stateDir, ok := actionsConfigs[ConfigStateDir]; ok && stateDir != "" {
|
||||
memoryDir := fmt.Sprintf("%s/memory", stateDir)
|
||||
err := os.MkdirAll(memoryDir, 0755) // ensure the directory exists
|
||||
if err != nil {
|
||||
xlog.Error("Error creating memory directory", "error", err)
|
||||
return memoryFilePath
|
||||
}
|
||||
memoryFilePath = fmt.Sprintf("%s/%s.json", memoryDir, agentName)
|
||||
} else {
|
||||
memoryFilePath = fmt.Sprintf("%s.memory.json", agentName)
|
||||
}
|
||||
}
|
||||
|
||||
return memoryFilePath
|
||||
}
|
||||
+213
-42
@@ -56,14 +56,24 @@ func (t *Telegram) isBotMentioned(message string, botUsername string) bool {
|
||||
}
|
||||
|
||||
func (t *Telegram) chatFromMessage(update *models.Update) (openai.ChatCompletionMessage, error) {
|
||||
|
||||
if len(update.Message.Photo) == 0 {
|
||||
return openai.ChatCompletionMessage{
|
||||
Content: update.Message.Text,
|
||||
Role: "user",
|
||||
}, nil
|
||||
// Handle audio messages
|
||||
if update.Message.Voice != nil || update.Message.Audio != nil {
|
||||
return t.handleAudioMessage(update)
|
||||
}
|
||||
|
||||
// Handle photo messages
|
||||
if len(update.Message.Photo) > 0 {
|
||||
return t.handlePhotoMessage(update)
|
||||
}
|
||||
|
||||
// Handle text messages
|
||||
return openai.ChatCompletionMessage{
|
||||
Content: update.Message.Text,
|
||||
Role: "user",
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (t *Telegram) handlePhotoMessage(update *models.Update) (openai.ChatCompletionMessage, error) {
|
||||
xlog.Debug("Image", "found image")
|
||||
// Get the largest photo
|
||||
photo := update.Message.Photo[len(update.Message.Photo)-1]
|
||||
@@ -77,44 +87,122 @@ func (t *Telegram) chatFromMessage(update *models.Update) (openai.ChatCompletion
|
||||
})
|
||||
if err != nil {
|
||||
xlog.Error("Error getting file", "error", err)
|
||||
} else {
|
||||
// Construct the full URL for downloading the file
|
||||
fileURL := fmt.Sprintf("https://api.telegram.org/file/bot%s/%s", t.Token, file.FilePath)
|
||||
|
||||
// Download the file content
|
||||
resp, err := http.Get(fileURL)
|
||||
if err != nil {
|
||||
xlog.Error("Error downloading file", "error", err)
|
||||
} else {
|
||||
defer resp.Body.Close()
|
||||
imageBytes, err := io.ReadAll(resp.Body)
|
||||
if err != nil {
|
||||
xlog.Error("Error reading image", "error", err)
|
||||
} else {
|
||||
// Encode to base64
|
||||
imgBase64 := base64.StdEncoding.EncodeToString(imageBytes)
|
||||
xlog.Debug("Image", "sending encoded image")
|
||||
// Add to conversation as multi-content message
|
||||
return openai.ChatCompletionMessage{
|
||||
Role: "user",
|
||||
MultiContent: []openai.ChatMessagePart{
|
||||
{
|
||||
Text: update.Message.Caption,
|
||||
Type: openai.ChatMessagePartTypeText,
|
||||
},
|
||||
{
|
||||
Type: openai.ChatMessagePartTypeImageURL,
|
||||
ImageURL: &openai.ChatMessageImageURL{
|
||||
URL: fmt.Sprintf("data:image/jpeg;base64,%s", imgBase64),
|
||||
},
|
||||
},
|
||||
},
|
||||
}, nil
|
||||
}
|
||||
}
|
||||
return openai.ChatCompletionMessage{}, err
|
||||
}
|
||||
|
||||
return openai.ChatCompletionMessage{}, errors.New("no image found")
|
||||
// Construct the full URL for downloading the file
|
||||
fileURL := fmt.Sprintf("https://api.telegram.org/file/bot%s/%s", t.Token, file.FilePath)
|
||||
|
||||
// Download the file content
|
||||
resp, err := http.Get(fileURL)
|
||||
if err != nil {
|
||||
xlog.Error("Error downloading file", "error", err)
|
||||
return openai.ChatCompletionMessage{}, err
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
|
||||
imageBytes, err := io.ReadAll(resp.Body)
|
||||
if err != nil {
|
||||
xlog.Error("Error reading image", "error", err)
|
||||
return openai.ChatCompletionMessage{}, err
|
||||
}
|
||||
|
||||
// Encode to base64
|
||||
imgBase64 := base64.StdEncoding.EncodeToString(imageBytes)
|
||||
xlog.Debug("Image", "sending encoded image")
|
||||
// Add to conversation as multi-content message
|
||||
return openai.ChatCompletionMessage{
|
||||
Role: "user",
|
||||
MultiContent: []openai.ChatMessagePart{
|
||||
{
|
||||
Text: update.Message.Caption,
|
||||
Type: openai.ChatMessagePartTypeText,
|
||||
},
|
||||
{
|
||||
Type: openai.ChatMessagePartTypeImageURL,
|
||||
ImageURL: &openai.ChatMessageImageURL{
|
||||
URL: fmt.Sprintf("data:image/jpeg;base64,%s", imgBase64),
|
||||
},
|
||||
},
|
||||
},
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (t *Telegram) handleAudioMessage(update *models.Update) (openai.ChatCompletionMessage, error) {
|
||||
var fileID string
|
||||
var audioType string
|
||||
|
||||
if update.Message.Voice != nil {
|
||||
fileID = update.Message.Voice.FileID
|
||||
audioType = "voice"
|
||||
} else if update.Message.Audio != nil {
|
||||
fileID = update.Message.Audio.FileID
|
||||
audioType = "audio"
|
||||
}
|
||||
|
||||
xlog.Debug("Audio message received", "type", audioType, "fileID", fileID)
|
||||
|
||||
ctx := context.Background()
|
||||
ctx, cancel := context.WithTimeout(ctx, 30*time.Second)
|
||||
defer cancel()
|
||||
|
||||
// Download the audio file
|
||||
file, err := t.bot.GetFile(ctx, &bot.GetFileParams{
|
||||
FileID: fileID,
|
||||
})
|
||||
if err != nil {
|
||||
xlog.Error("Error getting audio file", "error", err)
|
||||
return openai.ChatCompletionMessage{}, err
|
||||
}
|
||||
|
||||
// Construct the full URL for downloading the file
|
||||
fileURL := fmt.Sprintf("https://api.telegram.org/file/bot%s/%s", t.Token, file.FilePath)
|
||||
|
||||
// Download the file content
|
||||
resp, err := http.Get(fileURL)
|
||||
if err != nil {
|
||||
xlog.Error("Error downloading audio file", "error", err)
|
||||
return openai.ChatCompletionMessage{}, err
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
|
||||
audioBytes, err := io.ReadAll(resp.Body)
|
||||
if err != nil {
|
||||
xlog.Error("Error reading audio file", "error", err)
|
||||
return openai.ChatCompletionMessage{}, err
|
||||
}
|
||||
|
||||
// Create a temporary file for transcription
|
||||
tempFile, err := os.CreateTemp("", "telegram_audio_*.ogg")
|
||||
if err != nil {
|
||||
xlog.Error("Error creating temp file", "error", err)
|
||||
return openai.ChatCompletionMessage{}, err
|
||||
}
|
||||
defer os.Remove(tempFile.Name())
|
||||
|
||||
// Write audio data to temp file
|
||||
if _, err := tempFile.Write(audioBytes); err != nil {
|
||||
tempFile.Close()
|
||||
xlog.Error("Error writing audio to temp file", "error", err)
|
||||
return openai.ChatCompletionMessage{}, err
|
||||
}
|
||||
tempFile.Close()
|
||||
|
||||
// Transcribe the audio using the agent's Transcribe method
|
||||
transcription, err := t.agent.Transcribe(ctx, tempFile.Name())
|
||||
if err != nil {
|
||||
xlog.Error("Error transcribing audio", "error", err)
|
||||
return openai.ChatCompletionMessage{
|
||||
Content: fmt.Sprintf("I received an audio message but couldn't transcribe it: %v", err),
|
||||
Role: "user",
|
||||
}, nil
|
||||
}
|
||||
|
||||
xlog.Debug("Audio transcribed successfully", "transcription", transcription)
|
||||
return openai.ChatCompletionMessage{
|
||||
Content: transcription,
|
||||
Role: "user",
|
||||
}, nil
|
||||
}
|
||||
|
||||
// handleGroupMessage handles messages in group chats
|
||||
@@ -176,6 +264,11 @@ func (t *Telegram) handleGroupMessage(ctx context.Context, b *bot.Bot, a *agent.
|
||||
"chatID": update.Message.Chat.ID,
|
||||
}
|
||||
|
||||
// Track if the original message was audio for TTS response
|
||||
if update.Message.Voice != nil || update.Message.Audio != nil {
|
||||
metadata["originalMessageType"] = "audio"
|
||||
}
|
||||
|
||||
chatMessage, err := t.chatFromMessage(update)
|
||||
if err != nil {
|
||||
xlog.Error("Error extracting chat message", "error", err)
|
||||
@@ -251,6 +344,34 @@ func (t *Telegram) handleGroupMessage(ctx context.Context, b *bot.Bot, a *agent.
|
||||
xlog.Error("Error handling multimedia content", "error", err)
|
||||
}
|
||||
|
||||
// Check if original message was audio and generate TTS response
|
||||
if metadata["originalMessageType"] == "audio" && res.Response != "" {
|
||||
|
||||
xlog.Debug("Original message was audio, generating TTS response")
|
||||
audioData, err := t.agent.TTS(ctx, res.Response)
|
||||
if err != nil {
|
||||
xlog.Error("Error generating TTS", "error", err)
|
||||
} else {
|
||||
// Send audio response
|
||||
err = sendAudioToTelegram(ctx, t.bot, update.Message.Chat.ID, audioData, res.Response)
|
||||
if err != nil {
|
||||
xlog.Error("Error sending audio response", "error", err)
|
||||
} else {
|
||||
xlog.Debug("Audio response sent successfully")
|
||||
// Remove the thinking placeholder message before returning
|
||||
_, err := t.bot.DeleteMessage(ctx, &bot.DeleteMessageParams{
|
||||
ChatID: update.Message.Chat.ID,
|
||||
MessageID: msg.ID,
|
||||
})
|
||||
if err != nil {
|
||||
xlog.Error("Error deleting thinking placeholder", "error", err)
|
||||
}
|
||||
// Don't send text response if audio was sent successfully
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Update the message with the final response
|
||||
formattedResponse := formatResponseWithURLs(res.Response, urls)
|
||||
|
||||
@@ -396,6 +517,24 @@ func sendImageToTelegram(ctx context.Context, b *bot.Bot, chatID int64, url stri
|
||||
return nil
|
||||
}
|
||||
|
||||
// sendAudioToTelegram sends audio data to Telegram
|
||||
func sendAudioToTelegram(ctx context.Context, b *bot.Bot, chatID int64, audioData []byte, caption string) error {
|
||||
// Send audio with caption
|
||||
_, err := b.SendVoice(ctx, &bot.SendVoiceParams{
|
||||
ChatID: chatID,
|
||||
Voice: &models.InputFileUpload{
|
||||
Filename: "response.mp3",
|
||||
Data: bytes.NewReader(audioData),
|
||||
},
|
||||
Caption: caption,
|
||||
})
|
||||
if err != nil {
|
||||
return fmt.Errorf("error sending audio: %w", err)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// handleMultimediaContent processes and sends multimedia content from the agent's response
|
||||
func (t *Telegram) handleMultimediaContent(ctx context.Context, chatID int64, res *types.JobResult) ([]string, error) {
|
||||
var urls []string
|
||||
@@ -540,6 +679,11 @@ func (t *Telegram) handleUpdate(ctx context.Context, b *bot.Bot, a *agent.Agent,
|
||||
"chatID": update.Message.Chat.ID,
|
||||
}
|
||||
|
||||
// Track if the original message was audio for TTS response
|
||||
if update.Message.Voice != nil || update.Message.Audio != nil {
|
||||
metadata["originalMessageType"] = "audio"
|
||||
}
|
||||
|
||||
// Create a new job with the conversation history and metadata
|
||||
job := types.NewJob(
|
||||
types.WithConversationHistory(currentConv),
|
||||
@@ -603,6 +747,33 @@ func (t *Telegram) handleUpdate(ctx context.Context, b *bot.Bot, a *agent.Agent,
|
||||
xlog.Error("Error handling multimedia content", "error", err)
|
||||
}
|
||||
|
||||
// Check if original message was audio and generate TTS response
|
||||
if metadata["originalMessageType"] == "audio" && res.Response != "" {
|
||||
xlog.Debug("Original message was audio, generating TTS response")
|
||||
audioData, err := t.agent.TTS(ctx, res.Response)
|
||||
if err != nil {
|
||||
xlog.Error("Error generating TTS", "error", err)
|
||||
} else {
|
||||
// Send audio response
|
||||
err = sendAudioToTelegram(ctx, t.bot, update.Message.Chat.ID, audioData, res.Response)
|
||||
if err != nil {
|
||||
xlog.Error("Error sending audio response", "error", err)
|
||||
} else {
|
||||
xlog.Debug("Audio response sent successfully")
|
||||
// Remove the thinking placeholder message before returning
|
||||
_, err := t.bot.DeleteMessage(ctx, &bot.DeleteMessageParams{
|
||||
ChatID: update.Message.Chat.ID,
|
||||
MessageID: msg.ID,
|
||||
})
|
||||
if err != nil {
|
||||
xlog.Error("Error deleting thinking placeholder", "error", err)
|
||||
}
|
||||
// Don't send text response if audio was sent successfully
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Update the message with the final response
|
||||
formattedResponse := formatResponseWithURLs(res.Response, urls)
|
||||
|
||||
|
||||
+129
-20
@@ -1,10 +1,15 @@
|
||||
package services
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
|
||||
"github.com/mudler/LocalAGI/pkg/config"
|
||||
"github.com/mudler/LocalAGI/pkg/xlog"
|
||||
"github.com/mudler/LocalAGI/services/actions"
|
||||
"github.com/mudler/LocalAGI/services/prompts"
|
||||
|
||||
"github.com/mudler/LocalAGI/core/agent"
|
||||
@@ -13,36 +18,140 @@ import (
|
||||
|
||||
const (
|
||||
DynamicPromptCustom = "custom"
|
||||
DynamicPromptMemory = "memory"
|
||||
)
|
||||
|
||||
var AvailableBlockPrompts = []string{
|
||||
DynamicPromptCustom,
|
||||
DynamicPromptMemory,
|
||||
}
|
||||
|
||||
func DynamicPromptsConfigMeta() []config.FieldGroup {
|
||||
return []config.FieldGroup{
|
||||
prompts.NewDynamicPromptConfigMeta(),
|
||||
type dynamicPrompt struct {
|
||||
agent.DynamicPrompt
|
||||
Name string
|
||||
}
|
||||
|
||||
func dynamicPrompts(customDirectory string, existingConfigs map[string]map[string]string) (allPrompts []dynamicPrompt) {
|
||||
files, err := os.ReadDir(customDirectory)
|
||||
if err != nil {
|
||||
xlog.Error("Error reading custom actions directory", "error", err)
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
func DynamicPrompts(a *state.AgentConfig) []agent.DynamicPrompt {
|
||||
promptblocks := []agent.DynamicPrompt{}
|
||||
|
||||
for _, c := range a.DynamicPrompts {
|
||||
var config map[string]string
|
||||
if err := json.Unmarshal([]byte(c.Config), &config); err != nil {
|
||||
xlog.Info("Error unmarshalling connector config", err)
|
||||
for _, file := range files {
|
||||
if filepath.Ext(file.Name()) != ".go" {
|
||||
continue
|
||||
}
|
||||
switch c.Type {
|
||||
case DynamicPromptCustom:
|
||||
prompt, err := prompts.NewDynamicPrompt(config, "")
|
||||
if err != nil {
|
||||
xlog.Error("Error creating custom prompt", "error", err)
|
||||
continue
|
||||
}
|
||||
promptblocks = append(promptblocks, prompt)
|
||||
|
||||
content, err := os.ReadFile(filepath.Join(customDirectory, file.Name()))
|
||||
if err != nil {
|
||||
xlog.Error("Error reading custom action file", "error", err, "file", file.Name())
|
||||
continue
|
||||
}
|
||||
dynamicPromptName := strings.TrimSuffix(file.Name(), ".go")
|
||||
|
||||
dynamicPromptConfig := map[string]string{
|
||||
"name": dynamicPromptName,
|
||||
"code": string(content),
|
||||
}
|
||||
|
||||
if c, exists := existingConfigs[dynamicPromptName]; exists {
|
||||
dynamicPromptConfig["configuration"] = c["configuration"]
|
||||
}
|
||||
|
||||
a, err := prompts.NewDynamicCustomPrompt(dynamicPromptConfig, "")
|
||||
if err != nil {
|
||||
xlog.Error("Error creating custom dynamic prompt", "error", err, "file", file.Name())
|
||||
continue
|
||||
}
|
||||
|
||||
if !a.CanRender() {
|
||||
continue
|
||||
}
|
||||
|
||||
allPrompts = append(allPrompts, dynamicPrompt{
|
||||
DynamicPrompt: a,
|
||||
Name: dynamicPromptName,
|
||||
})
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func DynamicPromptsConfigMeta(customDirectory string) []config.FieldGroup {
|
||||
defaultDynamicPrompts := []config.FieldGroup{
|
||||
prompts.NewDynamicPromptConfigMeta(),
|
||||
prompts.NewMemoryPromptConfigMeta(),
|
||||
}
|
||||
|
||||
if customDirectory != "" {
|
||||
prompts := dynamicPrompts(customDirectory, map[string]map[string]string{})
|
||||
for _, p := range prompts {
|
||||
defaultDynamicPrompts = append(defaultDynamicPrompts, config.FieldGroup{
|
||||
Name: p.Name,
|
||||
Label: p.Name,
|
||||
Fields: []config.Field{
|
||||
{
|
||||
Name: "configuration",
|
||||
Label: "Configuration",
|
||||
Type: config.FieldTypeTextarea,
|
||||
HelpText: "Configuration for the custom prompt",
|
||||
},
|
||||
},
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
return defaultDynamicPrompts
|
||||
}
|
||||
|
||||
func DynamicPrompts(dynamicConfig map[string]string) func(*state.AgentConfig) func(ctx context.Context, pool *state.AgentPool) []agent.DynamicPrompt {
|
||||
return func(a *state.AgentConfig) func(ctx context.Context, pool *state.AgentPool) []agent.DynamicPrompt {
|
||||
return func(ctx context.Context, pool *state.AgentPool) []agent.DynamicPrompt {
|
||||
customDirectory := dynamicConfig[CustomActionsDir]
|
||||
|
||||
existingDynamicPromptsConfigs := map[string]map[string]string{}
|
||||
for _, c := range a.DynamicPrompts {
|
||||
var config map[string]string
|
||||
if err := json.Unmarshal([]byte(c.Config), &config); err != nil {
|
||||
xlog.Info("Error unmarshalling connector config", err)
|
||||
continue
|
||||
}
|
||||
|
||||
existingDynamicPromptsConfigs[c.Type] = config
|
||||
}
|
||||
|
||||
dynamicPromptsFound := dynamicPrompts(customDirectory, existingDynamicPromptsConfigs)
|
||||
|
||||
memoryFilePath := memoryPath(a.Name, dynamicConfig)
|
||||
promptblocks := []agent.DynamicPrompt{}
|
||||
|
||||
for _, c := range a.DynamicPrompts {
|
||||
config := existingDynamicPromptsConfigs[c.Type]
|
||||
|
||||
switch c.Type {
|
||||
case DynamicPromptCustom:
|
||||
prompt, err := prompts.NewDynamicCustomPrompt(config, "")
|
||||
if err != nil {
|
||||
xlog.Error("Error creating custom prompt", "error", err)
|
||||
continue
|
||||
}
|
||||
promptblocks = append(promptblocks, prompt)
|
||||
case DynamicPromptMemory:
|
||||
_, memory, _ := actions.NewMemoryActions(memoryFilePath, dynamicConfig)
|
||||
|
||||
promptblocks = append(promptblocks,
|
||||
prompts.NewMemoryPrompt(config, memory),
|
||||
)
|
||||
default:
|
||||
// Check if we have configured a custom dynamic prompt coming from a directory
|
||||
for _, p := range dynamicPromptsFound {
|
||||
if p.Name == c.Type {
|
||||
promptblocks = append(promptblocks, p.DynamicPrompt)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return promptblocks
|
||||
}
|
||||
}
|
||||
return promptblocks
|
||||
}
|
||||
|
||||
+51
-15
@@ -2,23 +2,26 @@ package prompts
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"os"
|
||||
"regexp"
|
||||
"strings"
|
||||
|
||||
"github.com/mudler/LocalAGI/core/agent"
|
||||
"github.com/mudler/LocalAGI/core/types"
|
||||
"github.com/mudler/LocalAGI/pkg/config"
|
||||
"github.com/mudler/LocalAGI/pkg/xlog"
|
||||
"github.com/traefik/yaegi/interp"
|
||||
"github.com/traefik/yaegi/stdlib"
|
||||
)
|
||||
|
||||
type DynamicPrompt struct {
|
||||
type DynamicCustomPrompt struct {
|
||||
config map[string]string
|
||||
goPkgPath string
|
||||
i *interp.Interpreter
|
||||
}
|
||||
|
||||
func NewDynamicPrompt(config map[string]string, goPkgPath string) (*DynamicPrompt, error) {
|
||||
a := &DynamicPrompt{
|
||||
func NewDynamicCustomPrompt(config map[string]string, goPkgPath string) (*DynamicCustomPrompt, error) {
|
||||
a := &DynamicCustomPrompt{
|
||||
config: config,
|
||||
goPkgPath: goPkgPath,
|
||||
}
|
||||
@@ -34,19 +37,20 @@ func NewDynamicPrompt(config map[string]string, goPkgPath string) (*DynamicPromp
|
||||
return a, nil
|
||||
}
|
||||
|
||||
func (a *DynamicPrompt) callInit() error {
|
||||
func (a *DynamicCustomPrompt) callInit() error {
|
||||
if a.i == nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
v, err := a.i.Eval(fmt.Sprintf("%s.Init", a.config["name"]))
|
||||
if err != nil {
|
||||
return err
|
||||
xlog.Warn("No init function found for custom prompt", "error", err, "action", a.config["name"])
|
||||
return nil
|
||||
}
|
||||
|
||||
run := v.Interface().(func() error)
|
||||
run := v.Interface().(func(string) error)
|
||||
|
||||
return run()
|
||||
return run(a.config["configuration"])
|
||||
}
|
||||
|
||||
func NewDynamicPromptConfigMeta() config.FieldGroup {
|
||||
@@ -77,16 +81,23 @@ func NewDynamicPromptConfigMeta() config.FieldGroup {
|
||||
Required: false,
|
||||
HelpText: "Enable if the code needs to use unsafe Go features",
|
||||
},
|
||||
{
|
||||
Name: "configuration",
|
||||
Label: "Configuration",
|
||||
Type: config.FieldTypeTextarea,
|
||||
HelpText: "Configuration for the custom prompt",
|
||||
},
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
func (a *DynamicPrompt) initializeInterpreter() error {
|
||||
func (a *DynamicCustomPrompt) initializeInterpreter() error {
|
||||
if _, exists := a.config["code"]; exists && a.i == nil {
|
||||
unsafe := strings.ToLower(a.config["unsafe"]) == "true"
|
||||
i := interp.New(interp.Options{
|
||||
GoPath: a.goPkgPath,
|
||||
Unrestricted: unsafe,
|
||||
Env: os.Environ(),
|
||||
})
|
||||
if err := i.Use(stdlib.Symbols); err != nil {
|
||||
return err
|
||||
@@ -96,6 +107,15 @@ func (a *DynamicPrompt) initializeInterpreter() error {
|
||||
a.config["name"] = "custom"
|
||||
}
|
||||
|
||||
// let's find first if there is already a package declarated in the code
|
||||
// the user might want to specify it to not break syntax with IDEs
|
||||
re := regexp.MustCompile("package (\\w+)")
|
||||
packageName := re.FindStringSubmatch(a.config["code"])
|
||||
if len(packageName) > 1 {
|
||||
// remove it from the code, normalize to `name`
|
||||
a.config["code"] = re.ReplaceAllString(a.config["code"], "")
|
||||
}
|
||||
|
||||
_, err := i.Eval(fmt.Sprintf("package %s\n%s", a.config["name"], a.config["code"]))
|
||||
if err != nil {
|
||||
return err
|
||||
@@ -107,18 +127,34 @@ func (a *DynamicPrompt) initializeInterpreter() error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (a *DynamicPrompt) Render(c *agent.Agent) (string, error) {
|
||||
v, err := a.i.Eval(fmt.Sprintf("%s.Render", a.config["name"]))
|
||||
func (a *DynamicCustomPrompt) CanRender() bool {
|
||||
_, err := a.i.Eval(fmt.Sprintf("%s.Render", a.config["name"]))
|
||||
if err != nil {
|
||||
return "", err
|
||||
return false
|
||||
}
|
||||
|
||||
run := v.Interface().(func() (string, error))
|
||||
|
||||
return run()
|
||||
return true
|
||||
}
|
||||
|
||||
func (a *DynamicPrompt) Role() string {
|
||||
func (a *DynamicCustomPrompt) Render(c *agent.Agent) (types.PromptResult, error) {
|
||||
v, err := a.i.Eval(fmt.Sprintf("%s.Render", a.config["name"]))
|
||||
if err != nil {
|
||||
return types.PromptResult{}, err
|
||||
}
|
||||
|
||||
run := v.Interface().(func() (string, string, error))
|
||||
content, image, err := run()
|
||||
if err != nil {
|
||||
return types.PromptResult{}, err
|
||||
}
|
||||
|
||||
return types.PromptResult{
|
||||
Content: content,
|
||||
ImageBase64: image,
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (a *DynamicCustomPrompt) Role() string {
|
||||
v, err := a.i.Eval(fmt.Sprintf("%s.Role", a.config["name"]))
|
||||
if err != nil {
|
||||
return "system"
|
||||
|
||||
@@ -0,0 +1,46 @@
|
||||
package prompts
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
"github.com/mudler/LocalAGI/core/agent"
|
||||
"github.com/mudler/LocalAGI/core/types"
|
||||
"github.com/mudler/LocalAGI/pkg/config"
|
||||
)
|
||||
|
||||
type MemoryLayer interface {
|
||||
Run(ctx context.Context, sharedState *types.AgentSharedState, params types.ActionParams) (types.ActionResult, error)
|
||||
}
|
||||
type MemoryPrompt struct {
|
||||
config map[string]string
|
||||
memory MemoryLayer
|
||||
}
|
||||
|
||||
func NewMemoryPrompt(config map[string]string, memory MemoryLayer) *MemoryPrompt {
|
||||
return &MemoryPrompt{
|
||||
config: config,
|
||||
memory: memory,
|
||||
}
|
||||
}
|
||||
|
||||
func NewMemoryPromptConfigMeta() config.FieldGroup {
|
||||
return config.FieldGroup{
|
||||
Name: "memory",
|
||||
Label: "Memory",
|
||||
}
|
||||
}
|
||||
|
||||
func (a *MemoryPrompt) Render(c *agent.Agent) (types.PromptResult, error) {
|
||||
result, err := a.memory.Run(c.Context(), c.SharedState(), types.ActionParams{})
|
||||
if err != nil {
|
||||
return types.PromptResult{}, err
|
||||
}
|
||||
|
||||
return types.PromptResult{
|
||||
Content: result.Result,
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (a *MemoryPrompt) Role() string {
|
||||
return "system"
|
||||
}
|
||||
+11
-10
@@ -15,8 +15,8 @@ import (
|
||||
. "github.com/onsi/gomega"
|
||||
)
|
||||
|
||||
var _ = Describe("Agent test", func() {
|
||||
Context("Creates an agent and it answers", func() {
|
||||
var _ = Describe("E2E test", func() {
|
||||
Context("Creates an agent and it answers", Label("E2E"), func() {
|
||||
BeforeEach(func() {
|
||||
Eventually(func() error {
|
||||
// test apiURL is working and available
|
||||
@@ -24,7 +24,7 @@ var _ = Describe("Agent test", func() {
|
||||
return err
|
||||
}, "10m", "10s").ShouldNot(HaveOccurred())
|
||||
|
||||
client := localagi.NewClient(localagiURL, "", time.Minute)
|
||||
client := localagi.NewClient(localagiURL, "", 5*time.Minute)
|
||||
err := client.DeleteAgent("testagent1")
|
||||
Expect(err).ToNot(HaveOccurred())
|
||||
})
|
||||
@@ -44,7 +44,7 @@ var _ = Describe("Agent test", func() {
|
||||
})
|
||||
})
|
||||
|
||||
Context("Can do user defined tool calls", Ordered, func() {
|
||||
Context("Can do user defined tool calls", Ordered, Label("E2E"), func() {
|
||||
BeforeAll(func() {
|
||||
Eventually(func() error {
|
||||
// test apiURL is working and available
|
||||
@@ -56,13 +56,14 @@ var _ = Describe("Agent test", func() {
|
||||
err := client.DeleteAgent("testagent2")
|
||||
Expect(err).ToNot(HaveOccurred())
|
||||
err = client.CreateAgent(&localagi.AgentConfig{
|
||||
Name: "testagent2",
|
||||
Name: "testagent2",
|
||||
EnableReasoning: true,
|
||||
})
|
||||
Expect(err).ToNot(HaveOccurred())
|
||||
})
|
||||
|
||||
It("can create a task", func() {
|
||||
client := localagi.NewClient(localagiURL, "", 5*time.Minute)
|
||||
client := localagi.NewClient(localagiURL, "", 10*time.Minute)
|
||||
|
||||
req := localagi.RequestBody{
|
||||
Model: "testagent2",
|
||||
@@ -170,7 +171,7 @@ var _ = Describe("Agent test", func() {
|
||||
msg, err := out.ToMessage()
|
||||
if err == nil && msg.Role == "assistant" {
|
||||
xlog.Info("Agent returned message", "message", msg)
|
||||
Expect(len(result.Output)).To(BeNumerically(">", 1))
|
||||
Expect(len(result.Output)).To(BeNumerically(">", 1), fmt.Sprintf("%+v", result.Output))
|
||||
continue
|
||||
}
|
||||
fnc, err := out.ToFunctionToolCall()
|
||||
@@ -204,7 +205,7 @@ var _ = Describe("Agent test", func() {
|
||||
localagi.InputMessage{
|
||||
Type: "message",
|
||||
Role: "user",
|
||||
Content: "Was the appointment created?",
|
||||
Content: "Was the appointment created? Reply using the ChooseAnswer tool.",
|
||||
},
|
||||
},
|
||||
Tools: []localagi.Tool{
|
||||
@@ -235,12 +236,12 @@ var _ = Describe("Agent test", func() {
|
||||
Expect(err).ToNot(HaveOccurred())
|
||||
Expect(len(result.Output)).To(BeNumerically(">", 0))
|
||||
fnc, err := result.Output[len(result.Output)-1].ToFunctionToolCall()
|
||||
Expect(err).ToNot(HaveOccurred())
|
||||
Expect(err).ToNot(HaveOccurred(), fmt.Sprintf("%+v", result))
|
||||
Expect(fnc.Arguments).To(ContainSubstring("true"))
|
||||
})
|
||||
|
||||
It("can tool call; web search", func() {
|
||||
client := localagi.NewClient(localagiURL, "", 5*time.Minute)
|
||||
client := localagi.NewClient(localagiURL, "", 10*time.Minute)
|
||||
|
||||
req := localagi.RequestBody{
|
||||
Model: "testagent2",
|
||||
|
||||
+9
-16
@@ -25,14 +25,11 @@ import (
|
||||
"github.com/mudler/LocalAGI/core/sse"
|
||||
"github.com/mudler/LocalAGI/core/state"
|
||||
|
||||
"github.com/donseba/go-htmx"
|
||||
fiber "github.com/gofiber/fiber/v2"
|
||||
"github.com/gofiber/template/html/v2"
|
||||
)
|
||||
|
||||
type (
|
||||
App struct {
|
||||
htmx *htmx.HTMX
|
||||
config *Config
|
||||
*fiber.App
|
||||
sharedState *internalTypes.AgentSharedState
|
||||
@@ -41,16 +38,12 @@ type (
|
||||
|
||||
func NewApp(opts ...Option) *App {
|
||||
config := NewConfig(opts...)
|
||||
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,
|
||||
})
|
||||
webapp := fiber.New(fiber.Config{})
|
||||
|
||||
a := &App{
|
||||
htmx: htmx.New(),
|
||||
config: config,
|
||||
App: webapp,
|
||||
sharedState: internalTypes.NewAgentSharedState(5 * time.Minute),
|
||||
@@ -492,7 +485,7 @@ func (a *App) ListActions() func(c *fiber.Ctx) error {
|
||||
func (a *App) createToolCallResponse(id, agentName string, actionState coreTypes.ActionState, conv []openai.ChatCompletionMessage) types.ResponseBody {
|
||||
// Create tool call ID
|
||||
toolCallID := fmt.Sprintf("call_%d", time.Now().UnixNano())
|
||||
|
||||
|
||||
// Get function name and arguments
|
||||
functionName := actionState.Action.Definition().Name.String()
|
||||
argumentsJSON, err := json.Marshal(actionState.Params)
|
||||
@@ -501,7 +494,7 @@ func (a *App) createToolCallResponse(id, agentName string, actionState coreTypes
|
||||
// Fallback to empty arguments
|
||||
argumentsJSON = []byte("{}")
|
||||
}
|
||||
|
||||
|
||||
// Create message object with reasoning
|
||||
messageObj := types.ResponseMessage{
|
||||
Type: "message",
|
||||
@@ -515,7 +508,7 @@ func (a *App) createToolCallResponse(id, agentName string, actionState coreTypes
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
|
||||
// Create function tool call object
|
||||
functionToolCall := types.FunctionToolCall{
|
||||
Arguments: string(argumentsJSON),
|
||||
@@ -525,7 +518,7 @@ func (a *App) createToolCallResponse(id, agentName string, actionState coreTypes
|
||||
ID: fmt.Sprintf("tool_%d", time.Now().UnixNano()),
|
||||
Status: "completed",
|
||||
}
|
||||
|
||||
|
||||
// Create response with both message and tool call in output array
|
||||
return types.ResponseBody{
|
||||
ID: id,
|
||||
@@ -612,7 +605,7 @@ func (a *App) Responses(pool *state.AgentPool, tracker *conversations.Conversati
|
||||
lastAction := res.State[len(res.State)-1]
|
||||
if coreTypes.IsActionUserDefined(lastAction.Action) {
|
||||
xlog.Debug("Detected user-defined action, creating tool call response", "action", lastAction.Action.Definition().Name)
|
||||
|
||||
|
||||
// Generate tool call response
|
||||
response := a.createToolCallResponse(id, agentName, lastAction, conv)
|
||||
tracker.SetConversation(id, conv) // Save conversation without adding assistant message
|
||||
@@ -737,13 +730,13 @@ func (a *App) CreateGroup(pool *state.AgentPool) func(c *fiber.Ctx) error {
|
||||
}
|
||||
|
||||
// GetAgentConfigMeta returns the metadata for agent configuration fields
|
||||
func (a *App) GetAgentConfigMeta() func(c *fiber.Ctx) error {
|
||||
func (a *App) GetAgentConfigMeta(customDirectory string) func(c *fiber.Ctx) error {
|
||||
return func(c *fiber.Ctx) error {
|
||||
// Create a new instance of AgentConfigMeta
|
||||
configMeta := state.NewAgentConfigMeta(
|
||||
services.ActionsConfigMeta(),
|
||||
services.ActionsConfigMeta(customDirectory),
|
||||
services.ConnectorsConfigMeta(),
|
||||
services.DynamicPromptsConfigMeta(),
|
||||
services.DynamicPromptsConfigMeta(customDirectory),
|
||||
services.FiltersConfigMeta(),
|
||||
)
|
||||
return c.JSON(configMeta)
|
||||
|
||||
@@ -1,714 +0,0 @@
|
||||
:root {
|
||||
--primary: #00ff95;
|
||||
--secondary: #ff00b1;
|
||||
--tertiary: #5e00ff;
|
||||
--dark-bg: #111111;
|
||||
--darker-bg: #0a0a0a;
|
||||
--medium-bg: #222222;
|
||||
--light-bg: #333333;
|
||||
--neon-glow: 0 0 8px rgba(0, 255, 149, 0.7);
|
||||
--pink-glow: 0 0 8px rgba(255, 0, 177, 0.7);
|
||||
--purple-glow: 0 0 8px rgba(94, 0, 255, 0.7);
|
||||
}
|
||||
|
||||
/* Glitch effect animation */
|
||||
@keyframes glitch {
|
||||
0% { transform: translate(0); }
|
||||
20% { transform: translate(-2px, 2px); }
|
||||
40% { transform: translate(-2px, -2px); }
|
||||
60% { transform: translate(2px, 2px); }
|
||||
80% { transform: translate(2px, -2px); }
|
||||
100% { transform: translate(0); }
|
||||
}
|
||||
|
||||
/* Neon pulse animation */
|
||||
@keyframes neonPulse {
|
||||
0% { text-shadow: 0 0 7px var(--primary), 0 0 10px var(--primary); }
|
||||
50% { text-shadow: 0 0 15px var(--primary), 0 0 25px var(--primary); }
|
||||
100% { text-shadow: 0 0 7px var(--primary), 0 0 10px var(--primary); }
|
||||
}
|
||||
|
||||
/* Scanning line effect */
|
||||
@keyframes scanline {
|
||||
0% { transform: translateY(-100%); }
|
||||
100% { transform: translateY(100%); }
|
||||
}
|
||||
|
||||
body {
|
||||
font-family: 'Outfit', sans-serif;
|
||||
background-color: var(--dark-bg);
|
||||
color: #ffffff;
|
||||
padding: 20px;
|
||||
position: relative;
|
||||
overflow-x: hidden;
|
||||
background-image:
|
||||
radial-gradient(circle at 10% 20%, rgba(0, 255, 149, 0.05) 0%, transparent 20%),
|
||||
radial-gradient(circle at 90% 80%, rgba(255, 0, 177, 0.05) 0%, transparent 20%),
|
||||
radial-gradient(circle at 50% 50%, rgba(94, 0, 255, 0.05) 0%, transparent 30%),
|
||||
linear-gradient(180deg, var(--darker-bg) 0%, var(--dark-bg) 100%);
|
||||
background-attachment: fixed;
|
||||
}
|
||||
|
||||
body::before {
|
||||
content: "";
|
||||
position: fixed;
|
||||
top: 0;
|
||||
left: 0;
|
||||
right: 0;
|
||||
bottom: 0;
|
||||
background: repeating-linear-gradient(
|
||||
transparent,
|
||||
transparent 2px,
|
||||
rgba(0, 0, 0, 0.1) 2px,
|
||||
rgba(0, 0, 0, 0.1) 4px
|
||||
);
|
||||
pointer-events: none;
|
||||
z-index: 1000;
|
||||
opacity: 0.3;
|
||||
}
|
||||
|
||||
body::after {
|
||||
content: "";
|
||||
position: fixed;
|
||||
top: 0;
|
||||
left: 0;
|
||||
width: 100%;
|
||||
height: 4px;
|
||||
background: linear-gradient(90deg, var(--primary), var(--secondary));
|
||||
opacity: 0.7;
|
||||
z-index: 1001;
|
||||
animation: scanline 6s linear infinite;
|
||||
pointer-events: none;
|
||||
}
|
||||
|
||||
h1, h2, h3, h4, h5, h6 {
|
||||
font-weight: 700;
|
||||
}
|
||||
|
||||
h1 {
|
||||
font-family: 'Permanent Marker', cursive;
|
||||
color: var(--primary);
|
||||
text-shadow: var(--neon-glow);
|
||||
margin-bottom: 1rem;
|
||||
position: relative;
|
||||
animation: neonPulse 2s infinite;
|
||||
}
|
||||
|
||||
h1:hover {
|
||||
animation: glitch 0.3s infinite;
|
||||
}
|
||||
|
||||
h2 {
|
||||
font-size: 1.5rem;
|
||||
color: var(--secondary);
|
||||
text-shadow: var(--pink-glow);
|
||||
margin-bottom: 0.5rem;
|
||||
}
|
||||
|
||||
.section-box {
|
||||
background-color: rgba(17, 17, 17, 0.85);
|
||||
border: 1px solid var(--primary);
|
||||
padding: 25px;
|
||||
margin-bottom: 20px;
|
||||
border-radius: 6px;
|
||||
box-shadow: 0 8px 20px rgba(0, 0, 0, 0.4), 0 0 0 1px var(--primary), inset 0 0 20px rgba(0, 0, 0, 0.3);
|
||||
position: relative;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.section-box::before {
|
||||
content: "";
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
right: 0;
|
||||
height: 2px;
|
||||
background: linear-gradient(90deg, var(--primary), var(--secondary), var(--tertiary), var(--primary));
|
||||
background-size: 200% 100%;
|
||||
animation: gradientMove 3s linear infinite;
|
||||
}
|
||||
|
||||
@keyframes gradientMove {
|
||||
0% { background-position: 0% 50%; }
|
||||
100% { background-position: 100% 50%; }
|
||||
}
|
||||
|
||||
input, button, textarea, select {
|
||||
width: 100%;
|
||||
padding: 12px;
|
||||
margin-top: 8px;
|
||||
border-radius: 4px;
|
||||
border: 1px solid var(--medium-bg);
|
||||
background-color: var(--light-bg);
|
||||
color: white;
|
||||
transition: all 0.3s ease;
|
||||
}
|
||||
|
||||
input[type="text"], input[type="file"], textarea {
|
||||
background-color: var(--light-bg);
|
||||
border-left: 3px solid var(--primary);
|
||||
color: white;
|
||||
}
|
||||
|
||||
input:focus, textarea:focus, select:focus {
|
||||
outline: none;
|
||||
border-color: var(--primary);
|
||||
box-shadow: var(--neon-glow);
|
||||
}
|
||||
|
||||
button {
|
||||
background: linear-gradient(135deg, var(--tertiary), var(--secondary));
|
||||
color: white;
|
||||
cursor: pointer;
|
||||
border: none;
|
||||
position: relative;
|
||||
overflow: hidden;
|
||||
font-weight: 600;
|
||||
text-transform: uppercase;
|
||||
letter-spacing: 1px;
|
||||
transition: all 0.3s ease;
|
||||
}
|
||||
|
||||
button::before {
|
||||
content: "";
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: -100%;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
background: linear-gradient(90deg, transparent, rgba(255, 255, 255, 0.2), transparent);
|
||||
transition: all 0.5s;
|
||||
}
|
||||
|
||||
button:hover {
|
||||
transform: translateY(-3px);
|
||||
box-shadow: 0 7px 14px rgba(0, 0, 0, 0.3), 0 0 10px rgba(94, 0, 255, 0.5);
|
||||
}
|
||||
|
||||
button:hover::before {
|
||||
left: 100%;
|
||||
}
|
||||
|
||||
textarea {
|
||||
height: 200px;
|
||||
resize: vertical;
|
||||
}
|
||||
|
||||
/* Select styling */
|
||||
select {
|
||||
appearance: none;
|
||||
background-color: var(--light-bg);
|
||||
border-left: 3px solid var(--tertiary);
|
||||
color: white;
|
||||
padding: 12px;
|
||||
border-radius: 4px;
|
||||
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>');
|
||||
background-repeat: no-repeat;
|
||||
background-position: right 10px center;
|
||||
background-size: 12px;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
select:hover {
|
||||
border-color: var(--secondary);
|
||||
box-shadow: 0 0 0 1px var(--secondary);
|
||||
}
|
||||
|
||||
select:focus {
|
||||
border-color: var(--tertiary);
|
||||
box-shadow: var(--purple-glow);
|
||||
}
|
||||
|
||||
select {
|
||||
overflow-y: auto;
|
||||
}
|
||||
|
||||
option {
|
||||
background-color: var(--medium-bg);
|
||||
color: white;
|
||||
padding: 8px 10px;
|
||||
}
|
||||
|
||||
/* Custom Scrollbars */
|
||||
::-webkit-scrollbar {
|
||||
width: 8px;
|
||||
height: 8px;
|
||||
}
|
||||
|
||||
::-webkit-scrollbar-track {
|
||||
background: var(--medium-bg);
|
||||
border-radius: 10px;
|
||||
}
|
||||
|
||||
::-webkit-scrollbar-thumb {
|
||||
background: linear-gradient(var(--primary), var(--secondary));
|
||||
border-radius: 10px;
|
||||
}
|
||||
|
||||
::-webkit-scrollbar-thumb:hover {
|
||||
background: var(--tertiary);
|
||||
}
|
||||
|
||||
/* Checkbox styling */
|
||||
.checkbox-custom {
|
||||
position: relative;
|
||||
display: inline-block;
|
||||
width: 22px;
|
||||
height: 22px;
|
||||
margin: 5px;
|
||||
cursor: pointer;
|
||||
vertical-align: middle;
|
||||
}
|
||||
|
||||
.checkbox-custom input {
|
||||
opacity: 0;
|
||||
width: 0;
|
||||
height: 0;
|
||||
}
|
||||
|
||||
.checkbox-custom .checkmark {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
height: 22px;
|
||||
width: 22px;
|
||||
background-color: var(--light-bg);
|
||||
border-radius: 4px;
|
||||
border: 1px solid var(--medium-bg);
|
||||
transition: all 0.3s ease;
|
||||
}
|
||||
|
||||
.checkbox-custom:hover .checkmark {
|
||||
border-color: var(--primary);
|
||||
box-shadow: var(--neon-glow);
|
||||
}
|
||||
|
||||
.checkbox-custom input:checked ~ .checkmark {
|
||||
background: linear-gradient(135deg, var(--primary), var(--tertiary));
|
||||
border-color: transparent;
|
||||
}
|
||||
|
||||
.checkbox-custom .checkmark:after {
|
||||
content: "";
|
||||
position: absolute;
|
||||
display: none;
|
||||
}
|
||||
|
||||
.checkbox-custom input:checked ~ .checkmark:after {
|
||||
display: block;
|
||||
}
|
||||
|
||||
.checkbox-custom .checkmark:after {
|
||||
left: 8px;
|
||||
top: 4px;
|
||||
width: 6px;
|
||||
height: 12px;
|
||||
border: solid white;
|
||||
border-width: 0 2px 2px 0;
|
||||
transform: rotate(45deg);
|
||||
}
|
||||
|
||||
/* Card styling */
|
||||
.container {
|
||||
max-width: 1200px;
|
||||
margin: 0 auto;
|
||||
padding: 20px;
|
||||
}
|
||||
|
||||
.card-link {
|
||||
text-decoration: none;
|
||||
display: block;
|
||||
}
|
||||
|
||||
.card {
|
||||
background: linear-gradient(145deg, rgba(34, 34, 34, 0.9), rgba(17, 17, 17, 0.9));
|
||||
border: 1px solid rgba(94, 0, 255, 0.2);
|
||||
border-radius: 8px;
|
||||
padding: 25px;
|
||||
margin: 25px auto;
|
||||
text-align: left;
|
||||
width: 90%;
|
||||
transition: all 0.4s cubic-bezier(0.175, 0.885, 0.32, 1.275);
|
||||
position: relative;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.card::before {
|
||||
content: "";
|
||||
position: absolute;
|
||||
left: 0;
|
||||
bottom: 0;
|
||||
width: 100%;
|
||||
height: 3px;
|
||||
background: linear-gradient(90deg, var(--primary), var(--secondary), var(--tertiary));
|
||||
transform: scaleX(0);
|
||||
transform-origin: left;
|
||||
transition: transform 0.4s ease-out;
|
||||
}
|
||||
|
||||
.card:hover {
|
||||
transform: translateY(-8px) scale(1.02);
|
||||
box-shadow: 0 15px 30px rgba(0, 0, 0, 0.4), 0 0 15px rgba(94, 0, 255, 0.3);
|
||||
}
|
||||
|
||||
.card:hover::before {
|
||||
transform: scaleX(1);
|
||||
}
|
||||
|
||||
.card h2 {
|
||||
font-family: 'Outfit', sans-serif;
|
||||
font-size: 1.5em;
|
||||
font-weight: 600;
|
||||
color: var(--primary);
|
||||
margin-bottom: 0.8em;
|
||||
position: relative;
|
||||
display: inline-block;
|
||||
}
|
||||
|
||||
.card a {
|
||||
color: var(--secondary);
|
||||
transition: color 0.3s;
|
||||
text-decoration: none;
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.card a:hover {
|
||||
color: var(--primary);
|
||||
}
|
||||
|
||||
.card a::after {
|
||||
content: "";
|
||||
position: absolute;
|
||||
bottom: -2px;
|
||||
left: 0;
|
||||
width: 100%;
|
||||
height: 1px;
|
||||
background: var(--primary);
|
||||
transform: scaleX(0);
|
||||
transform-origin: right;
|
||||
transition: transform 0.3s ease;
|
||||
}
|
||||
|
||||
.card a:hover::after {
|
||||
transform: scaleX(1);
|
||||
transform-origin: left;
|
||||
}
|
||||
|
||||
.card p {
|
||||
color: #cccccc;
|
||||
font-size: 1em;
|
||||
line-height: 1.6;
|
||||
}
|
||||
|
||||
/* Button container */
|
||||
.button-container {
|
||||
display: flex;
|
||||
justify-content: flex-end;
|
||||
gap: 10px;
|
||||
margin-bottom: 12px;
|
||||
}
|
||||
|
||||
/* Alert and Toast styling */
|
||||
.alert {
|
||||
padding: 12px 15px;
|
||||
border-radius: 4px;
|
||||
margin: 15px 0;
|
||||
display: none;
|
||||
position: relative;
|
||||
border-left: 4px solid;
|
||||
animation: fadeIn 0.3s ease-in;
|
||||
}
|
||||
|
||||
@keyframes fadeIn {
|
||||
from { opacity: 0; transform: translateY(-10px); }
|
||||
to { opacity: 1; transform: translateY(0); }
|
||||
}
|
||||
|
||||
.alert-success {
|
||||
background-color: rgba(0, 255, 149, 0.1);
|
||||
border-color: var(--primary);
|
||||
color: var(--primary);
|
||||
}
|
||||
|
||||
.alert-error {
|
||||
background-color: rgba(255, 0, 177, 0.1);
|
||||
border-color: var(--secondary);
|
||||
color: var(--secondary);
|
||||
}
|
||||
|
||||
.toast {
|
||||
position: fixed;
|
||||
top: 30px;
|
||||
right: 30px;
|
||||
max-width: 350px;
|
||||
padding: 15px 20px;
|
||||
border-radius: 6px;
|
||||
box-shadow: 0 10px 20px rgba(0, 0, 0, 0.5);
|
||||
z-index: 2000;
|
||||
opacity: 0;
|
||||
transform: translateX(30px);
|
||||
transition: all 0.4s cubic-bezier(0.175, 0.885, 0.32, 1.275);
|
||||
display: flex;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.toast::before {
|
||||
content: "";
|
||||
width: 20px;
|
||||
height: 20px;
|
||||
margin-right: 15px;
|
||||
background-position: center;
|
||||
background-repeat: no-repeat;
|
||||
background-size: contain;
|
||||
}
|
||||
|
||||
.toast-success {
|
||||
background: linear-gradient(135deg, rgba(0, 255, 149, 0.9), rgba(0, 255, 149, 0.7));
|
||||
color: #111111;
|
||||
border-left: 4px solid var(--primary);
|
||||
}
|
||||
|
||||
.toast-success::before {
|
||||
background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='%23111111'%3E%3Cpath d='M9 16.17L4.83 12l-1.42 1.41L9 19 21 7l-1.41-1.41z'/%3E%3C/svg%3E");
|
||||
}
|
||||
|
||||
.toast-error {
|
||||
background: linear-gradient(135deg, rgba(255, 0, 177, 0.9), rgba(255, 0, 177, 0.7));
|
||||
color: #ffffff;
|
||||
border-left: 4px solid var(--secondary);
|
||||
}
|
||||
|
||||
.toast-error::before {
|
||||
background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='%23ffffff'%3E%3Cpath d='M19 6.41L17.59 5 12 10.59 6.41 5 5 6.41 10.59 12 5 17.59 6.41 19 12 13.41 17.59 19 19 17.59 13.41 12z'/%3E%3C/svg%3E");
|
||||
}
|
||||
|
||||
.toast-visible {
|
||||
opacity: 1;
|
||||
transform: translateX(0);
|
||||
}
|
||||
|
||||
/* Action buttons */
|
||||
.action-btn {
|
||||
background: var(--medium-bg);
|
||||
color: white;
|
||||
border: 1px solid rgba(255, 255, 255, 0.1);
|
||||
padding: 8px 15px;
|
||||
border-radius: 4px;
|
||||
cursor: pointer;
|
||||
transition: all 0.3s ease;
|
||||
font-weight: 500;
|
||||
font-size: 0.9rem;
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
gap: 8px;
|
||||
}
|
||||
|
||||
.action-btn i {
|
||||
font-size: 1rem;
|
||||
}
|
||||
|
||||
.action-btn:hover {
|
||||
transform: translateY(-2px);
|
||||
}
|
||||
|
||||
.start-btn {
|
||||
background: linear-gradient(135deg, var(--primary), rgba(0, 255, 149, 0.7));
|
||||
color: #111111;
|
||||
border: none;
|
||||
}
|
||||
|
||||
.start-btn:hover {
|
||||
box-shadow: 0 0 15px rgba(0, 255, 149, 0.5);
|
||||
background: var(--primary);
|
||||
}
|
||||
|
||||
.pause-btn {
|
||||
background: linear-gradient(135deg, var(--tertiary), rgba(94, 0, 255, 0.7));
|
||||
color: white;
|
||||
border: none;
|
||||
}
|
||||
|
||||
.pause-btn:hover {
|
||||
box-shadow: 0 0 15px rgba(94, 0, 255, 0.5);
|
||||
background: var(--tertiary);
|
||||
}
|
||||
|
||||
/* Badge styling */
|
||||
.badge {
|
||||
display: inline-block;
|
||||
padding: 3px 10px;
|
||||
border-radius: 12px;
|
||||
font-size: 0.75rem;
|
||||
font-weight: 600;
|
||||
text-transform: uppercase;
|
||||
letter-spacing: 0.5px;
|
||||
}
|
||||
|
||||
.badge-primary {
|
||||
background-color: var(--primary);
|
||||
color: #111111;
|
||||
}
|
||||
|
||||
.badge-secondary {
|
||||
background-color: var(--secondary);
|
||||
color: white;
|
||||
}
|
||||
|
||||
.badge-tertiary {
|
||||
background-color: var(--tertiary);
|
||||
color: white;
|
||||
}
|
||||
|
||||
/* Data display tables */
|
||||
.data-table {
|
||||
width: 100%;
|
||||
border-collapse: separate;
|
||||
border-spacing: 0;
|
||||
margin: 20px 0;
|
||||
border-radius: 6px;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.data-table th, .data-table td {
|
||||
text-align: left;
|
||||
padding: 12px 15px;
|
||||
border-bottom: 1px solid var(--medium-bg);
|
||||
}
|
||||
|
||||
.data-table th {
|
||||
background-color: rgba(94, 0, 255, 0.2);
|
||||
color: var(--tertiary);
|
||||
font-weight: 600;
|
||||
text-transform: uppercase;
|
||||
letter-spacing: 1px;
|
||||
font-size: 0.85rem;
|
||||
}
|
||||
|
||||
.data-table tr:last-child td {
|
||||
border-bottom: none;
|
||||
}
|
||||
|
||||
.data-table tr:nth-child(odd) td {
|
||||
background-color: rgba(17, 17, 17, 0.6);
|
||||
}
|
||||
|
||||
.data-table tr:nth-child(even) td {
|
||||
background-color: rgba(34, 34, 34, 0.6);
|
||||
}
|
||||
|
||||
.data-table tr:hover td {
|
||||
background-color: rgba(94, 0, 255, 0.1);
|
||||
}
|
||||
|
||||
/* Terminal-style code display */
|
||||
.code-terminal {
|
||||
background-color: #0a0a0a;
|
||||
border-radius: 6px;
|
||||
padding: 15px;
|
||||
font-family: 'Courier New', monospace;
|
||||
color: #00ff95;
|
||||
margin: 20px 0;
|
||||
position: relative;
|
||||
box-shadow: 0 5px 15px rgba(0, 0, 0, 0.4);
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.code-terminal::before {
|
||||
content: "";
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
width: 100%;
|
||||
height: 25px;
|
||||
background: #222;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
padding: 0 10px;
|
||||
}
|
||||
|
||||
.code-terminal::after {
|
||||
content: "• • •";
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 12px;
|
||||
height: 25px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
color: #666;
|
||||
font-size: 20px;
|
||||
letter-spacing: -2px;
|
||||
}
|
||||
|
||||
.code-terminal pre {
|
||||
margin-top: 25px;
|
||||
white-space: pre-wrap;
|
||||
word-break: break-word;
|
||||
line-height: 1.5;
|
||||
}
|
||||
|
||||
.code-terminal .prompt {
|
||||
color: var(--secondary);
|
||||
user-select: none;
|
||||
}
|
||||
|
||||
/* User info badge */
|
||||
.user-info {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
background: linear-gradient(135deg, rgba(17, 17, 17, 0.8), rgba(34, 34, 34, 0.8));
|
||||
border: 1px solid var(--tertiary);
|
||||
border-radius: 30px;
|
||||
padding: 6px 15px;
|
||||
margin: 10px 0;
|
||||
font-size: 0.9rem;
|
||||
box-shadow: var(--purple-glow);
|
||||
}
|
||||
|
||||
.user-info::before {
|
||||
content: "";
|
||||
width: 10px;
|
||||
height: 10px;
|
||||
background-color: var(--primary);
|
||||
border-radius: 50%;
|
||||
margin-right: 10px;
|
||||
animation: pulse 2s infinite;
|
||||
}
|
||||
|
||||
@keyframes pulse {
|
||||
0% { box-shadow: 0 0 0 0 rgba(0, 255, 149, 0.7); }
|
||||
70% { box-shadow: 0 0 0 10px rgba(0, 255, 149, 0); }
|
||||
100% { box-shadow: 0 0 0 0 rgba(0, 255, 149, 0); }
|
||||
}
|
||||
|
||||
.timestamp {
|
||||
margin-left: auto;
|
||||
font-family: 'Courier New', monospace;
|
||||
color: var(--secondary);
|
||||
}
|
||||
|
||||
/* Responsive design adjustments */
|
||||
@media (max-width: 768px) {
|
||||
.container {
|
||||
padding: 10px;
|
||||
}
|
||||
|
||||
.card {
|
||||
width: 100%;
|
||||
padding: 15px;
|
||||
}
|
||||
|
||||
.section-box {
|
||||
padding: 15px;
|
||||
}
|
||||
|
||||
.button-container {
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
.toast {
|
||||
top: 10px;
|
||||
right: 10px;
|
||||
left: 10px;
|
||||
max-width: none;
|
||||
}
|
||||
}
|
||||
@@ -1,254 +0,0 @@
|
||||
/* Agent Form Wizard Styles */
|
||||
.agent-form-container {
|
||||
display: flex;
|
||||
gap: 2rem;
|
||||
margin-bottom: 2rem;
|
||||
}
|
||||
|
||||
/* Wizard Sidebar */
|
||||
.wizard-sidebar {
|
||||
width: 250px;
|
||||
background: var(--surface);
|
||||
border-radius: 8px;
|
||||
box-shadow: 0 2px 10px rgba(0, 0, 0, 0.1);
|
||||
padding: 1.5rem 0;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
.wizard-nav {
|
||||
list-style: none;
|
||||
padding: 0;
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
.wizard-nav-item {
|
||||
padding: 12px 20px;
|
||||
cursor: pointer;
|
||||
transition: all 0.2s ease;
|
||||
border-left: 4px solid transparent;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.wizard-nav-item i {
|
||||
margin-right: 10px;
|
||||
width: 20px;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.wizard-nav-item:hover {
|
||||
background: rgba(var(--primary-rgb), 0.1);
|
||||
}
|
||||
|
||||
.wizard-nav-item.active {
|
||||
background: rgba(var(--primary-rgb), 0.15);
|
||||
border-left-color: var(--primary);
|
||||
color: var(--primary);
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
/* Form Content Area */
|
||||
.form-content-area {
|
||||
flex: 1;
|
||||
padding: 1.5rem;
|
||||
background: var(--surface);
|
||||
border-radius: 8px;
|
||||
box-shadow: 0 2px 10px rgba(0, 0, 0, 0.1);
|
||||
}
|
||||
|
||||
.section-title {
|
||||
font-size: 1.5rem;
|
||||
margin-bottom: 1.5rem;
|
||||
padding-bottom: 0.75rem;
|
||||
border-bottom: 1px solid rgba(var(--border-rgb), 0.5);
|
||||
color: var(--text);
|
||||
}
|
||||
|
||||
.form-section {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.form-section.active {
|
||||
display: block;
|
||||
animation: fadeIn 0.3s ease;
|
||||
}
|
||||
|
||||
@keyframes fadeIn {
|
||||
from {
|
||||
opacity: 0;
|
||||
transform: translateY(10px);
|
||||
}
|
||||
to {
|
||||
opacity: 1;
|
||||
transform: translateY(0);
|
||||
}
|
||||
}
|
||||
|
||||
/* Improved input styles */
|
||||
.mb-4 {
|
||||
margin-bottom: 1.5rem;
|
||||
}
|
||||
|
||||
.form-section label {
|
||||
display: block;
|
||||
margin-bottom: 0.5rem;
|
||||
font-weight: 500;
|
||||
color: var(--text);
|
||||
}
|
||||
|
||||
.form-section input[type="text"],
|
||||
.form-section input[type="number"],
|
||||
.form-section textarea,
|
||||
.form-section select {
|
||||
width: 100%;
|
||||
padding: 10px 12px;
|
||||
border-radius: 6px;
|
||||
border: 1px solid rgba(var(--border-rgb), 0.8);
|
||||
background-color: var(--input-bg);
|
||||
color: var(--text);
|
||||
font-size: 16px;
|
||||
transition: border-color 0.2s ease;
|
||||
}
|
||||
|
||||
.form-section textarea {
|
||||
min-height: 120px;
|
||||
resize: vertical;
|
||||
}
|
||||
|
||||
.form-section input[type="text"]:focus,
|
||||
.form-section input[type="number"]:focus,
|
||||
.form-section textarea:focus,
|
||||
.form-section select:focus {
|
||||
border-color: var(--primary);
|
||||
outline: none;
|
||||
box-shadow: 0 0 0 2px rgba(var(--primary-rgb), 0.2);
|
||||
}
|
||||
|
||||
/* Button Styles */
|
||||
.button-container {
|
||||
margin: 1.5rem 0;
|
||||
}
|
||||
|
||||
.action-btn {
|
||||
background: linear-gradient(135deg, var(--primary), var(--secondary));
|
||||
color: white;
|
||||
border: none;
|
||||
padding: 10px 16px;
|
||||
border-radius: 6px;
|
||||
cursor: pointer;
|
||||
font-weight: 500;
|
||||
transition: all 0.2s ease;
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
.action-btn i {
|
||||
margin-right: 6px;
|
||||
}
|
||||
|
||||
.action-btn:hover {
|
||||
transform: translateY(-2px);
|
||||
box-shadow: 0 4px 8px rgba(0, 0, 0, 0.15);
|
||||
}
|
||||
|
||||
.action-btn:active {
|
||||
transform: translateY(0);
|
||||
}
|
||||
/* Navigation controls - improved layout */
|
||||
.wizard-controls {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
margin-top: 1.5rem;
|
||||
padding-top: 1.5rem;
|
||||
border-top: 1px solid rgba(var(--border-rgb), 0.5);
|
||||
}
|
||||
|
||||
.wizard-controls-left {
|
||||
flex: 1;
|
||||
text-align: left;
|
||||
}
|
||||
|
||||
.wizard-controls-center {
|
||||
flex: 2;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.wizard-controls-right {
|
||||
flex: 1;
|
||||
text-align: right;
|
||||
}
|
||||
|
||||
.nav-btn {
|
||||
background: var(--surface);
|
||||
color: var(--text);
|
||||
border: 1px solid rgba(var(--border-rgb), 0.8);
|
||||
padding: 8px 16px;
|
||||
border-radius: 6px;
|
||||
cursor: pointer;
|
||||
font-weight: 500;
|
||||
transition: all 0.2s ease;
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.nav-btn i {
|
||||
margin-right: 6px;
|
||||
}
|
||||
|
||||
.nav-btn:last-child i {
|
||||
margin-right: 0;
|
||||
margin-left: 6px;
|
||||
}
|
||||
|
||||
.nav-btn:hover {
|
||||
background: rgba(var(--primary-rgb), 0.1);
|
||||
}
|
||||
|
||||
.progress-indicator {
|
||||
display: inline-block;
|
||||
font-size: 0.9rem;
|
||||
color: var(--text-muted);
|
||||
font-weight: 500;
|
||||
margin-top: 0.5rem;
|
||||
}
|
||||
|
||||
.progress-dots {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
margin-bottom: 8px;
|
||||
gap: 6px;
|
||||
}
|
||||
|
||||
.progress-dot {
|
||||
width: 10px;
|
||||
height: 10px;
|
||||
border-radius: 50%;
|
||||
background-color: rgba(var(--border-rgb), 0.4);
|
||||
transition: all 0.2s ease;
|
||||
}
|
||||
|
||||
.progress-dot.active {
|
||||
background-color: var(--primary);
|
||||
transform: scale(1.2);
|
||||
}
|
||||
|
||||
/* Responsive adjustments */
|
||||
@media (max-width: 768px) {
|
||||
.wizard-controls {
|
||||
flex-direction: column;
|
||||
gap: 1rem;
|
||||
}
|
||||
|
||||
.wizard-controls-left,
|
||||
.wizard-controls-center,
|
||||
.wizard-controls-right {
|
||||
width: 100%;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.progress-dots {
|
||||
margin: 12px 0;
|
||||
}
|
||||
}
|
||||
@@ -1,564 +0,0 @@
|
||||
// Common utility functions for agent forms
|
||||
const AgentFormUtils = {
|
||||
// Add dynamic component based on template
|
||||
addDynamicComponent: function(sectionId, templateFunction, dataItems) {
|
||||
const section = document.getElementById(sectionId);
|
||||
const newIndex = section.getElementsByClassName(dataItems.className).length;
|
||||
|
||||
// Generate HTML from template function
|
||||
const newHtml = templateFunction(newIndex, dataItems);
|
||||
|
||||
// Add to DOM
|
||||
section.insertAdjacentHTML('beforeend', newHtml);
|
||||
},
|
||||
|
||||
// Process form data into JSON structure
|
||||
processFormData: function(formData) {
|
||||
const jsonData = {};
|
||||
|
||||
// Process basic form fields
|
||||
for (const [key, value] of formData.entries()) {
|
||||
// Skip the array fields as they'll be processed separately
|
||||
if (!key.includes('[') && !key.includes('].')) {
|
||||
// Handle checkboxes
|
||||
if (value === 'on') {
|
||||
jsonData[key] = true;
|
||||
}
|
||||
// Handle numeric fields - specifically kb_results
|
||||
else if (key === 'kb_results') {
|
||||
// Convert to integer or default to 3 if empty
|
||||
jsonData[key] = value ? parseInt(value, 10) : 3;
|
||||
|
||||
// Check if the parse was successful
|
||||
if (isNaN(jsonData[key])) {
|
||||
showToast('Knowledge Base Results must be a number', 'error');
|
||||
return null; // Indicate validation error
|
||||
}
|
||||
}
|
||||
// Handle other numeric fields if needed
|
||||
else if (key === 'periodic_runs' && value) {
|
||||
// Try to parse as number if it looks like one
|
||||
const numValue = parseInt(value, 10);
|
||||
if (!isNaN(numValue) && String(numValue) === value) {
|
||||
jsonData[key] = numValue;
|
||||
} else {
|
||||
jsonData[key] = value;
|
||||
}
|
||||
}
|
||||
else {
|
||||
jsonData[key] = value;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return jsonData;
|
||||
},
|
||||
|
||||
// Process connectors from form
|
||||
processConnectors: function(button) {
|
||||
const connectors = [];
|
||||
const connectorElements = document.querySelectorAll('.connector');
|
||||
|
||||
for (let i = 0; i < connectorElements.length; i++) {
|
||||
const typeSelect = document.getElementById(`connectorType${i}`);
|
||||
if (!typeSelect) {
|
||||
showToast(`Error: Could not find connector type select for index ${i}`, 'error');
|
||||
button.innerHTML = button.getAttribute('data-original-text');
|
||||
button.disabled = false;
|
||||
return null; // Validation failed
|
||||
}
|
||||
|
||||
const type = typeSelect.value;
|
||||
if (!type) {
|
||||
showToast(`Please select a connector type for connector ${i+1}`, 'error');
|
||||
button.innerHTML = button.getAttribute('data-original-text');
|
||||
button.disabled = false;
|
||||
return null; // Validation failed
|
||||
}
|
||||
|
||||
// Get all config fields for this connector
|
||||
const connector = {
|
||||
type: type,
|
||||
config: {}
|
||||
};
|
||||
|
||||
// Find all config inputs for this connector
|
||||
const configInputs = document.querySelectorAll(`[name^="connectors[${i}][config]"]`);
|
||||
|
||||
// Check if we have a JSON textarea (fallback template)
|
||||
const jsonTextarea = document.getElementById(`connectorConfig${i}`);
|
||||
if (jsonTextarea && jsonTextarea.value) {
|
||||
try {
|
||||
// If it's a JSON textarea, parse it and use the result
|
||||
const jsonConfig = JSON.parse(jsonTextarea.value);
|
||||
// Convert the parsed JSON back to a string for the backend
|
||||
connector.config = JSON.stringify(jsonConfig);
|
||||
} catch (e) {
|
||||
// If it's not valid JSON, use it as is
|
||||
connector.config = jsonTextarea.value;
|
||||
}
|
||||
} else {
|
||||
// Process individual form fields
|
||||
configInputs.forEach(input => {
|
||||
// Extract the key from the name attribute
|
||||
// Format: connectors[0][config][key]
|
||||
const keyMatch = input.name.match(/\[config\]\[([^\]]+)\]/);
|
||||
if (keyMatch && keyMatch[1]) {
|
||||
const key = keyMatch[1];
|
||||
// For checkboxes, set true/false based on checked state
|
||||
if (input.type === 'checkbox') {
|
||||
connector.config[key] = input.checked ? 'true' : 'false';
|
||||
} else {
|
||||
connector.config[key] = input.value;
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
// Convert the config object to a JSON string for the backend
|
||||
connector.config = JSON.stringify(connector.config);
|
||||
}
|
||||
|
||||
connectors.push(connector);
|
||||
}
|
||||
|
||||
return connectors;
|
||||
},
|
||||
|
||||
// Process MCP servers from form
|
||||
processMCPServers: function() {
|
||||
const mcpServers = [];
|
||||
const mcpElements = document.querySelectorAll('.mcp_server');
|
||||
|
||||
for (let i = 0; i < mcpElements.length; i++) {
|
||||
const urlInput = document.getElementById(`mcpURL${i}`);
|
||||
const tokenInput = document.getElementById(`mcpToken${i}`);
|
||||
|
||||
if (urlInput && urlInput.value) {
|
||||
const server = {
|
||||
url: urlInput.value
|
||||
};
|
||||
|
||||
// Add token if present
|
||||
if (tokenInput && tokenInput.value) {
|
||||
server.token = tokenInput.value;
|
||||
}
|
||||
|
||||
mcpServers.push(server);
|
||||
}
|
||||
}
|
||||
|
||||
return mcpServers;
|
||||
},
|
||||
|
||||
// Process actions from form
|
||||
processActions: function(button) {
|
||||
const actions = [];
|
||||
const actionElements = document.querySelectorAll('.action');
|
||||
|
||||
for (let i = 0; i < actionElements.length; i++) {
|
||||
const nameSelect = document.getElementById(`actionsName${i}`);
|
||||
const configTextarea = document.getElementById(`actionsConfig${i}`);
|
||||
|
||||
if (!nameSelect) {
|
||||
showToast(`Error: Could not find action name select for index ${i}`, 'error');
|
||||
button.innerHTML = button.getAttribute('data-original-text');
|
||||
button.disabled = false;
|
||||
return null; // Validation failed
|
||||
}
|
||||
|
||||
const name = nameSelect.value;
|
||||
if (!name) {
|
||||
showToast(`Please select an action type for action ${i+1}`, 'error');
|
||||
button.innerHTML = button.getAttribute('data-original-text');
|
||||
button.disabled = false;
|
||||
return null; // Validation failed
|
||||
}
|
||||
|
||||
let config = {};
|
||||
if (configTextarea && configTextarea.value) {
|
||||
try {
|
||||
config = JSON.parse(configTextarea.value);
|
||||
} catch (e) {
|
||||
showToast(`Invalid JSON in action ${i+1} config: ${e.message}`, 'error');
|
||||
button.innerHTML = button.getAttribute('data-original-text');
|
||||
button.disabled = false;
|
||||
return null; // Validation failed
|
||||
}
|
||||
}
|
||||
|
||||
actions.push({
|
||||
name: name,
|
||||
config: JSON.stringify(config) // Convert to JSON string for backend
|
||||
});
|
||||
}
|
||||
|
||||
return actions;
|
||||
},
|
||||
|
||||
// Process prompt blocks from form
|
||||
processPromptBlocks: function(button) {
|
||||
const promptBlocks = [];
|
||||
const promptElements = document.querySelectorAll('.prompt_block');
|
||||
|
||||
for (let i = 0; i < promptElements.length; i++) {
|
||||
const nameSelect = document.getElementById(`promptName${i}`);
|
||||
const configTextarea = document.getElementById(`promptConfig${i}`);
|
||||
|
||||
if (!nameSelect) {
|
||||
showToast(`Error: Could not find prompt block name select for index ${i}`, 'error');
|
||||
button.innerHTML = button.getAttribute('data-original-text');
|
||||
button.disabled = false;
|
||||
return null; // Validation failed
|
||||
}
|
||||
|
||||
const name = nameSelect.value;
|
||||
if (!name) {
|
||||
showToast(`Please select a prompt block type for block ${i+1}`, 'error');
|
||||
button.innerHTML = button.getAttribute('data-original-text');
|
||||
button.disabled = false;
|
||||
return null; // Validation failed
|
||||
}
|
||||
|
||||
let config = {};
|
||||
if (configTextarea && configTextarea.value) {
|
||||
try {
|
||||
config = JSON.parse(configTextarea.value);
|
||||
} catch (e) {
|
||||
showToast(`Invalid JSON in prompt block ${i+1} config: ${e.message}`, 'error');
|
||||
button.innerHTML = button.getAttribute('data-original-text');
|
||||
button.disabled = false;
|
||||
return null; // Validation failed
|
||||
}
|
||||
}
|
||||
|
||||
promptBlocks.push({
|
||||
name: name,
|
||||
config: JSON.stringify(config) // Convert to JSON string for backend
|
||||
});
|
||||
}
|
||||
|
||||
return promptBlocks;
|
||||
},
|
||||
|
||||
// Helper function to format config values (for edit form)
|
||||
formatConfigValue: function(configElement, configValue) {
|
||||
if (!configElement) return;
|
||||
|
||||
// If configValue is an object, stringify it
|
||||
if (typeof configValue === 'object' && configValue !== null) {
|
||||
try {
|
||||
configElement.value = JSON.stringify(configValue, null, 2);
|
||||
} catch (e) {
|
||||
console.error('Error stringifying config value:', e);
|
||||
configElement.value = '{}';
|
||||
}
|
||||
}
|
||||
// If it's a string that looks like JSON, try to parse and pretty print it
|
||||
else if (typeof configValue === 'string' && (configValue.startsWith('{') || configValue.startsWith('['))) {
|
||||
try {
|
||||
const parsed = JSON.parse(configValue);
|
||||
configElement.value = JSON.stringify(parsed, null, 2);
|
||||
} catch (e) {
|
||||
// If it's not valid JSON, just use the string as is
|
||||
configElement.value = configValue;
|
||||
}
|
||||
}
|
||||
// Otherwise, just use the value as is
|
||||
else {
|
||||
configElement.value = configValue || '';
|
||||
}
|
||||
},
|
||||
|
||||
// Helper function to set select value (with fallback if option doesn't exist)
|
||||
setSelectValue: function(selectElement, value) {
|
||||
if (!selectElement) return;
|
||||
|
||||
// Check if the option exists
|
||||
let optionExists = false;
|
||||
for (let i = 0; i < selectElement.options.length; i++) {
|
||||
if (selectElement.options[i].value === value) {
|
||||
optionExists = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// Set the value if the option exists
|
||||
if (optionExists) {
|
||||
selectElement.value = value;
|
||||
} else if (selectElement.options.length > 0) {
|
||||
// Otherwise, select the first option
|
||||
selectElement.selectedIndex = 0;
|
||||
}
|
||||
},
|
||||
|
||||
// Render connector form based on type
|
||||
renderConnectorForm: function(index, type, config = {}) {
|
||||
const formContainer = document.getElementById(`connectorFormContainer${index}`);
|
||||
if (!formContainer) return;
|
||||
|
||||
// Clear existing form
|
||||
formContainer.innerHTML = '';
|
||||
|
||||
// Debug log to see what's happening
|
||||
console.log(`Rendering connector form for type: ${type}`);
|
||||
console.log(`Config for connector:`, config);
|
||||
console.log(`Available templates:`, ConnectorTemplates ? Object.keys(ConnectorTemplates) : 'None');
|
||||
|
||||
// Ensure config is an object
|
||||
let configObj = config;
|
||||
if (typeof config === 'string') {
|
||||
try {
|
||||
configObj = JSON.parse(config);
|
||||
} catch (e) {
|
||||
console.error('Error parsing connector config string:', e);
|
||||
configObj = {};
|
||||
}
|
||||
}
|
||||
|
||||
// If we have a template for this connector type in the global ConnectorTemplates object
|
||||
if (ConnectorTemplates && type && ConnectorTemplates[type]) {
|
||||
console.log(`Found template for ${type}`);
|
||||
// Get the template result which contains HTML and setValues function
|
||||
const templateResult = ConnectorTemplates[type](configObj, index);
|
||||
|
||||
// Set the HTML content
|
||||
formContainer.innerHTML = templateResult.html;
|
||||
|
||||
// Call the setValues function to set input values safely
|
||||
if (typeof templateResult.setValues === 'function') {
|
||||
setTimeout(templateResult.setValues, 0);
|
||||
}
|
||||
} else {
|
||||
console.log(`No template found for ${type}, using fallback`);
|
||||
// Use the fallback template
|
||||
if (ConnectorTemplates && ConnectorTemplates.fallback) {
|
||||
const fallbackResult = ConnectorTemplates.fallback(configObj, index);
|
||||
formContainer.innerHTML = fallbackResult.html;
|
||||
|
||||
if (typeof fallbackResult.setValues === 'function') {
|
||||
setTimeout(fallbackResult.setValues, 0);
|
||||
}
|
||||
} else {
|
||||
// Fallback to generic JSON textarea if no fallback template
|
||||
formContainer.innerHTML = `
|
||||
<div class="form-group">
|
||||
<label for="connectorConfig${index}">Connector Config (JSON)</label>
|
||||
<textarea id="connectorConfig${index}"
|
||||
name="connectors[${index}][config]"
|
||||
class="form-control"
|
||||
placeholder='{"key":"value"}'></textarea>
|
||||
</div>
|
||||
`;
|
||||
|
||||
// Set the value safely after DOM is created
|
||||
setTimeout(function() {
|
||||
const configTextarea = document.getElementById(`connectorConfig${index}`);
|
||||
if (configTextarea) {
|
||||
if (typeof configObj === 'object' && configObj !== null) {
|
||||
configTextarea.value = JSON.stringify(configObj, null, 2);
|
||||
} else if (typeof config === 'string') {
|
||||
configTextarea.value = config;
|
||||
}
|
||||
}
|
||||
}, 0);
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
// HTML Templates for dynamic elements
|
||||
const AgentFormTemplates = {
|
||||
// Connector template
|
||||
connectorTemplate: function(index, data) {
|
||||
return `
|
||||
<div class="connector mb-4 section-box" style="margin-top: 15px; padding: 15px;">
|
||||
<h2>Connector ${index + 1}</h2>
|
||||
<div class="mb-4">
|
||||
<label for="connectorType${index}">Connector Type</label>
|
||||
<select name="connectors[${index}][type]"
|
||||
id="connectorType${index}"
|
||||
class="form-control"
|
||||
onchange="AgentFormUtils.renderConnectorForm(${index}, this.value)">
|
||||
<option value="">Select Connector Type</option>
|
||||
${data.options}
|
||||
</select>
|
||||
</div>
|
||||
<div id="connectorFormContainer${index}">
|
||||
<!-- Connector form will be dynamically inserted here -->
|
||||
<div class="form-group">
|
||||
<div class="placeholder-text">Select a connector type to configure</div>
|
||||
</div>
|
||||
</div>
|
||||
<button type="button" class="remove-btn" onclick="this.closest('.connector').remove()">
|
||||
<i class="fas fa-trash"></i> Remove Connector
|
||||
</button>
|
||||
</div>
|
||||
`;
|
||||
},
|
||||
|
||||
// MCP Server template
|
||||
mcpServerTemplate: function(index, data) {
|
||||
return `
|
||||
<div class="mcp_server mb-4 section-box" style="margin-top: 15px; padding: 15px;">
|
||||
<h2>MCP Server ${index + 1}</h2>
|
||||
<div class="mb-4">
|
||||
<label for="mcpURL${index}">Server URL</label>
|
||||
<input type="text" id="mcpURL${index}" name="mcp_servers[${index}][url]" placeholder="https://example.com">
|
||||
</div>
|
||||
<div class="mb-4">
|
||||
<label for="mcpToken${index}">API Token (Optional)</label>
|
||||
<input type="text" id="mcpToken${index}" name="mcp_servers[${index}][token]" placeholder="API token">
|
||||
</div>
|
||||
<button type="button" class="remove-btn" onclick="this.closest('.mcp_server').remove()">
|
||||
<i class="fas fa-trash"></i> Remove Server
|
||||
</button>
|
||||
</div>
|
||||
`;
|
||||
},
|
||||
|
||||
// Action template
|
||||
actionTemplate: function(index, data) {
|
||||
return `
|
||||
<div class="action mb-4 section-box" style="margin-top: 15px; padding: 15px;">
|
||||
<h2>Action ${index + 1}</h2>
|
||||
<div class="mb-4">
|
||||
<label for="actionsName${index}">Action Type</label>
|
||||
<select name="actions[${index}][name]" id="actionsName${index}">
|
||||
${data.options}
|
||||
</select>
|
||||
</div>
|
||||
<div class="mb-4">
|
||||
<label for="actionsConfig${index}">Action Config (JSON)</label>
|
||||
<textarea id="actionsConfig${index}" name="actions[${index}][config]" placeholder='{"key":"value"}'>{}</textarea>
|
||||
</div>
|
||||
<button type="button" class="remove-btn" onclick="this.closest('.action').remove()">
|
||||
<i class="fas fa-trash"></i> Remove Action
|
||||
</button>
|
||||
</div>
|
||||
`;
|
||||
},
|
||||
|
||||
// Prompt Block template
|
||||
promptBlockTemplate: function(index, data) {
|
||||
return `
|
||||
<div class="prompt_block mb-4 section-box" style="margin-top: 15px; padding: 15px;">
|
||||
<h2>Prompt Block ${index + 1}</h2>
|
||||
<div class="mb-4">
|
||||
<label for="promptName${index}">Prompt Block Type</label>
|
||||
<select name="promptblocks[${index}][name]" id="promptName${index}">
|
||||
${data.options}
|
||||
</select>
|
||||
</div>
|
||||
<div class="mb-4">
|
||||
<label for="promptConfig${index}">Prompt Block Config (JSON)</label>
|
||||
<textarea id="promptConfig${index}" name="promptblocks[${index}][config]" placeholder='{"key":"value"}'>{}</textarea>
|
||||
</div>
|
||||
<button type="button" class="remove-btn" onclick="this.closest('.prompt_block').remove()">
|
||||
<i class="fas fa-trash"></i> Remove Prompt Block
|
||||
</button>
|
||||
</div>
|
||||
`;
|
||||
}
|
||||
};
|
||||
|
||||
// Initialize form event listeners
|
||||
function initAgentFormCommon(options = {}) {
|
||||
// Add connector button
|
||||
const addConnectorButton = document.getElementById('addConnectorButton');
|
||||
if (addConnectorButton) {
|
||||
addConnectorButton.addEventListener('click', function() {
|
||||
// Create options string
|
||||
let optionsHtml = '';
|
||||
if (options.connectors) {
|
||||
optionsHtml = options.connectors;
|
||||
}
|
||||
|
||||
// Add new connector form
|
||||
AgentFormUtils.addDynamicComponent('connectorsSection', AgentFormTemplates.connectorTemplate, {
|
||||
className: 'connector',
|
||||
options: optionsHtml
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
// Add MCP server button
|
||||
const addMCPButton = document.getElementById('addMCPButton');
|
||||
if (addMCPButton) {
|
||||
addMCPButton.addEventListener('click', function() {
|
||||
// Add new MCP server form
|
||||
AgentFormUtils.addDynamicComponent('mcpSection', AgentFormTemplates.mcpServerTemplate, {
|
||||
className: 'mcp_server'
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
// Add action button
|
||||
const actionButton = document.getElementById('action_button');
|
||||
if (actionButton) {
|
||||
actionButton.addEventListener('click', function() {
|
||||
// Create options string
|
||||
let optionsHtml = '';
|
||||
if (options.actions) {
|
||||
optionsHtml = options.actions;
|
||||
}
|
||||
|
||||
// Add new action form
|
||||
AgentFormUtils.addDynamicComponent('action_box', AgentFormTemplates.actionTemplate, {
|
||||
className: 'action',
|
||||
options: optionsHtml
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
// Add prompt block button
|
||||
const dynamicButton = document.getElementById('dynamic_button');
|
||||
if (dynamicButton) {
|
||||
dynamicButton.addEventListener('click', function() {
|
||||
// Create options string
|
||||
let optionsHtml = '';
|
||||
if (options.promptBlocks) {
|
||||
optionsHtml = options.promptBlocks;
|
||||
}
|
||||
|
||||
// Add new prompt block form
|
||||
AgentFormUtils.addDynamicComponent('dynamic_box', AgentFormTemplates.promptBlockTemplate, {
|
||||
className: 'prompt_block',
|
||||
options: optionsHtml
|
||||
});
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
// Simple toast notification function
|
||||
function showToast(message, type) {
|
||||
// Check if toast container exists, if not create it
|
||||
let toast = document.getElementById('toast');
|
||||
if (!toast) {
|
||||
toast = document.createElement('div');
|
||||
toast.id = 'toast';
|
||||
toast.className = 'toast';
|
||||
|
||||
const toastMessage = document.createElement('div');
|
||||
toastMessage.id = 'toast-message';
|
||||
toast.appendChild(toastMessage);
|
||||
|
||||
document.body.appendChild(toast);
|
||||
}
|
||||
|
||||
const toastMessage = document.getElementById('toast-message');
|
||||
|
||||
// Set message
|
||||
toastMessage.textContent = message;
|
||||
|
||||
// Set type class
|
||||
toast.className = 'toast';
|
||||
toast.classList.add(`toast-${type}`);
|
||||
|
||||
// Show toast
|
||||
toast.classList.add('show');
|
||||
|
||||
// Hide after 3 seconds
|
||||
setTimeout(() => {
|
||||
toast.classList.remove('show');
|
||||
}, 3000);
|
||||
}
|
||||
@@ -1,44 +0,0 @@
|
||||
// Function to show toast notifications with enhanced animation
|
||||
function showToast(message, type) {
|
||||
const toast = document.getElementById('toast');
|
||||
const toastMessage = document.getElementById('toast-message');
|
||||
|
||||
// Set message
|
||||
toastMessage.textContent = message;
|
||||
|
||||
// Set toast type (success/error)
|
||||
toast.className = 'toast';
|
||||
toast.classList.add(type === 'success' ? 'toast-success' : 'toast-error');
|
||||
|
||||
// Show toast with enhanced animation
|
||||
setTimeout(() => {
|
||||
toast.classList.add('toast-visible');
|
||||
}, 100);
|
||||
|
||||
// Hide toast after 3 seconds with animation
|
||||
setTimeout(() => {
|
||||
toast.classList.remove('toast-visible');
|
||||
|
||||
// Clean up after animation completes
|
||||
setTimeout(() => {
|
||||
toast.className = 'toast';
|
||||
}, 400);
|
||||
}, 3000);
|
||||
}
|
||||
|
||||
// Function to create the glitch effect on headings
|
||||
document.addEventListener('DOMContentLoaded', function() {
|
||||
const headings = document.querySelectorAll('h1');
|
||||
|
||||
headings.forEach(heading => {
|
||||
heading.addEventListener('mouseover', function() {
|
||||
this.style.animation = 'glitch 0.3s infinite';
|
||||
});
|
||||
|
||||
heading.addEventListener('mouseout', function() {
|
||||
this.style.animation = 'neonPulse 2s infinite';
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
});
|
||||
@@ -1,475 +0,0 @@
|
||||
/**
|
||||
* Connector Templates
|
||||
*
|
||||
* This file contains templates for all connector types supported by LocalAGI.
|
||||
* Each template is a function that returns an HTML string for the connector's form.
|
||||
*
|
||||
* Note: We don't need to escape HTML in the value attributes because browsers
|
||||
* handle these values safely when setting them via DOM properties after rendering.
|
||||
*/
|
||||
|
||||
/**
|
||||
* Connector Templates
|
||||
* Each function takes a config object and returns an HTML string
|
||||
*/
|
||||
const ConnectorTemplates = {
|
||||
/**
|
||||
* Telegram Connector Template
|
||||
* @param {Object} config - Existing configuration values
|
||||
* @param {Number} index - Connector index
|
||||
* @returns {Object} HTML template and setValues function
|
||||
*/
|
||||
telegram: function(config = {}, index) {
|
||||
// Return HTML without values in the template string
|
||||
const html = `
|
||||
<div class="form-group">
|
||||
<label for="telegramToken${index}">Telegram Bot Token</label>
|
||||
<input type="text"
|
||||
id="telegramToken${index}"
|
||||
name="connectors[${index}][config][token]"
|
||||
class="form-control"
|
||||
placeholder="123456:ABC-DEF1234ghIkl-zyx57W2v1u123ew11">
|
||||
<small class="form-text text-muted">Get this from @BotFather on Telegram</small>
|
||||
</div>
|
||||
`;
|
||||
|
||||
// Function to set values after HTML is added to DOM to avoid XSS
|
||||
const setValues = function() {
|
||||
const input = document.getElementById(`telegramToken${index}`);
|
||||
if (input) input.value = config.token || '';
|
||||
};
|
||||
|
||||
return { html, setValues };
|
||||
},
|
||||
|
||||
/**
|
||||
* Slack Connector Template
|
||||
* @param {Object} config - Existing configuration values
|
||||
* @param {Number} index - Connector index
|
||||
* @returns {Object} HTML template and setValues function
|
||||
*/
|
||||
slack: function(config = {}, index) {
|
||||
// Return HTML without values in the template string
|
||||
const html = `
|
||||
<div class="form-group">
|
||||
<label for="slackAppToken${index}">Slack App Token</label>
|
||||
<input type="text"
|
||||
id="slackAppToken${index}"
|
||||
name="connectors[${index}][config][appToken]"
|
||||
class="form-control"
|
||||
placeholder="xapp-...">
|
||||
<small class="form-text text-muted">App-level token starting with xapp-</small>
|
||||
</div>
|
||||
|
||||
<div class="form-group">
|
||||
<label for="slackBotToken${index}">Slack Bot Token</label>
|
||||
<input type="text"
|
||||
id="slackBotToken${index}"
|
||||
name="connectors[${index}][config][botToken]"
|
||||
class="form-control"
|
||||
placeholder="xoxb-...">
|
||||
<small class="form-text text-muted">Bot token starting with xoxb-</small>
|
||||
</div>
|
||||
|
||||
<div class="form-group">
|
||||
<label for="slackChannelID${index}">Slack Channel ID</label>
|
||||
<input type="text"
|
||||
id="slackChannelID${index}"
|
||||
name="connectors[${index}][config][channelID]"
|
||||
class="form-control"
|
||||
placeholder="C012345678">
|
||||
<small class="form-text text-muted">Channel ID where the bot will operate</small>
|
||||
</div>
|
||||
|
||||
<div class="form-group form-check">
|
||||
<input type="checkbox"
|
||||
class="form-check-input"
|
||||
id="slackAlwaysReply${index}"
|
||||
name="connectors[${index}][config][alwaysReply]">
|
||||
<label class="form-check-label" for="slackAlwaysReply${index}">Always Reply</label>
|
||||
<small class="form-text text-muted">If checked, the bot will reply to all messages in the channel</small>
|
||||
</div>
|
||||
`;
|
||||
|
||||
// Function to set values after HTML is added to DOM to avoid XSS
|
||||
const setValues = function() {
|
||||
const appTokenInput = document.getElementById(`slackAppToken${index}`);
|
||||
const botTokenInput = document.getElementById(`slackBotToken${index}`);
|
||||
const channelIDInput = document.getElementById(`slackChannelID${index}`);
|
||||
const alwaysReplyInput = document.getElementById(`slackAlwaysReply${index}`);
|
||||
|
||||
if (appTokenInput) appTokenInput.value = config.appToken || '';
|
||||
if (botTokenInput) botTokenInput.value = config.botToken || '';
|
||||
if (channelIDInput) channelIDInput.value = config.channelID || '';
|
||||
if (alwaysReplyInput) alwaysReplyInput.checked = config.alwaysReply === 'true';
|
||||
};
|
||||
|
||||
return { html, setValues };
|
||||
},
|
||||
|
||||
/**
|
||||
* Discord Connector Template
|
||||
* @param {Object} config - Existing configuration values
|
||||
* @param {Number} index - Connector index
|
||||
* @returns {Object} HTML template and setValues function
|
||||
*/
|
||||
discord: function(config = {}, index) {
|
||||
// Return HTML without values in the template string
|
||||
const html = `
|
||||
<div class="form-group">
|
||||
<label for="discordToken${index}">Discord Bot Token</label>
|
||||
<input type="text"
|
||||
id="discordToken${index}"
|
||||
name="connectors[${index}][config][token]"
|
||||
class="form-control"
|
||||
placeholder="Bot token from Discord Developer Portal">
|
||||
</div>
|
||||
|
||||
<div class="form-group">
|
||||
<label for="discordChannelID${index}">Default Channel ID</label>
|
||||
<input type="text"
|
||||
id="discordChannelID${index}"
|
||||
name="connectors[${index}][config][defaultChannel]"
|
||||
class="form-control"
|
||||
placeholder="Channel ID where the bot will operate">
|
||||
</div>
|
||||
`;
|
||||
|
||||
// Function to set values after HTML is added to DOM
|
||||
const setValues = function() {
|
||||
const tokenInput = document.getElementById(`discordToken${index}`);
|
||||
const channelIDInput = document.getElementById(`discordChannelID${index}`);
|
||||
|
||||
if (tokenInput) tokenInput.value = config.token || '';
|
||||
if (channelIDInput) channelIDInput.value = config.defaultChannel || '';
|
||||
};
|
||||
|
||||
return { html, setValues };
|
||||
},
|
||||
|
||||
/**
|
||||
* GitHub Issues Connector Template
|
||||
* @param {Object} config - Existing configuration values
|
||||
* @param {Number} index - Connector index
|
||||
* @returns {Object} HTML template and setValues function
|
||||
*/
|
||||
'github-issues': function(config = {}, index) {
|
||||
// Return HTML without values in the template string
|
||||
const html = `
|
||||
<div class="form-group">
|
||||
<label for="githubIssuesToken${index}">GitHub Personal Access Token</label>
|
||||
<input type="text"
|
||||
id="githubIssuesToken${index}"
|
||||
name="connectors[${index}][config][token]"
|
||||
class="form-control"
|
||||
placeholder="ghp_...">
|
||||
<small class="form-text text-muted">Needs repo and read:org permissions</small>
|
||||
</div>
|
||||
|
||||
<div class="form-group">
|
||||
<label for="githubIssuesOwner${index}">Repository Owner</label>
|
||||
<input type="text"
|
||||
id="githubIssuesOwner${index}"
|
||||
name="connectors[${index}][config][owner]"
|
||||
class="form-control"
|
||||
placeholder="username or organization">
|
||||
</div>
|
||||
|
||||
<div class="form-group">
|
||||
<label for="githubIssuesRepo${index}">Repository Name</label>
|
||||
<input type="text"
|
||||
id="githubIssuesRepo${index}"
|
||||
name="connectors[${index}][config][repository]"
|
||||
class="form-control"
|
||||
placeholder="repository-name">
|
||||
</div>
|
||||
|
||||
<div class="form-group form-check">
|
||||
<input type="checkbox"
|
||||
class="form-check-input"
|
||||
id="githubIssuesReplyIfNoReplies${index}"
|
||||
name="connectors[${index}][config][replyIfNoReplies]"
|
||||
value="true">
|
||||
<label class="form-check-label" for="githubIssuesReplyIfNoReplies${index}">Reply to issues with no replies</label>
|
||||
<small class="form-text text-muted">If checked, the bot will reply to issues that have no replies yet</small>
|
||||
</div>
|
||||
|
||||
<div class="form-group">
|
||||
<label for="githubIssuesPollInterval${index}">Poll Interval (seconds)</label>
|
||||
<input type="number"
|
||||
id="githubIssuesPollInterval${index}"
|
||||
name="connectors[${index}][config][pollInterval]"
|
||||
class="form-control"
|
||||
placeholder="60">
|
||||
<small class="form-text text-muted">How often to check for new issues (in seconds)</small>
|
||||
</div>
|
||||
`;
|
||||
|
||||
// Function to set values after HTML is added to DOM to avoid XSS
|
||||
const setValues = function() {
|
||||
const tokenInput = document.getElementById(`githubIssuesToken${index}`);
|
||||
const ownerInput = document.getElementById(`githubIssuesOwner${index}`);
|
||||
const repoInput = document.getElementById(`githubIssuesRepo${index}`);
|
||||
const replyIfNoRepliesInput = document.getElementById(`githubIssuesReplyIfNoReplies${index}`);
|
||||
const pollIntervalInput = document.getElementById(`githubIssuesPollInterval${index}`);
|
||||
|
||||
if (tokenInput) tokenInput.value = config.token || '';
|
||||
if (ownerInput) ownerInput.value = config.owner || '';
|
||||
if (repoInput) repoInput.value = config.repository || '';
|
||||
if (replyIfNoRepliesInput) replyIfNoRepliesInput.checked = config.replyIfNoReplies === 'true';
|
||||
if (pollIntervalInput) pollIntervalInput.value = config.pollInterval || '60';
|
||||
};
|
||||
|
||||
return { html, setValues };
|
||||
},
|
||||
|
||||
/**
|
||||
* GitHub PRs Connector Template
|
||||
* @param {Object} config - Existing configuration values
|
||||
* @param {Number} index - Connector index
|
||||
* @returns {Object} HTML template and setValues function
|
||||
*/
|
||||
'github-prs': function(config = {}, index) {
|
||||
// Return HTML without values in the template string
|
||||
const html = `
|
||||
<div class="form-group">
|
||||
<label for="githubPRsToken${index}">GitHub Token</label>
|
||||
<input type="text"
|
||||
id="githubPRsToken${index}"
|
||||
name="connectors[${index}][config][token]"
|
||||
class="form-control"
|
||||
placeholder="ghp_...">
|
||||
<small class="form-text text-muted">Personal Access Token with repo permissions</small>
|
||||
</div>
|
||||
|
||||
<div class="form-group">
|
||||
<label for="githubPRsOwner${index}">Repository Owner</label>
|
||||
<input type="text"
|
||||
id="githubPRsOwner${index}"
|
||||
name="connectors[${index}][config][owner]"
|
||||
class="form-control"
|
||||
placeholder="username or organization">
|
||||
</div>
|
||||
|
||||
<div class="form-group">
|
||||
<label for="githubPRsRepo${index}">Repository Name</label>
|
||||
<input type="text"
|
||||
id="githubPRsRepo${index}"
|
||||
name="connectors[${index}][config][repository]"
|
||||
class="form-control"
|
||||
placeholder="repository-name">
|
||||
</div>
|
||||
|
||||
<div class="form-group form-check">
|
||||
<input type="checkbox"
|
||||
class="form-check-input"
|
||||
id="githubPRsReplyIfNoReplies${index}"
|
||||
name="connectors[${index}][config][replyIfNoReplies]"
|
||||
value="true">
|
||||
<label class="form-check-label" for="githubPRsReplyIfNoReplies${index}">Reply to PRs with no replies</label>
|
||||
<small class="form-text text-muted">If checked, the bot will reply to pull requests that have no replies yet</small>
|
||||
</div>
|
||||
|
||||
<div class="form-group">
|
||||
<label for="githubPRsPollInterval${index}">Poll Interval (seconds)</label>
|
||||
<input type="number"
|
||||
id="githubPRsPollInterval${index}"
|
||||
name="connectors[${index}][config][pollInterval]"
|
||||
class="form-control"
|
||||
placeholder="60">
|
||||
<small class="form-text text-muted">How often to check for new pull requests (in seconds)</small>
|
||||
</div>
|
||||
`;
|
||||
|
||||
// Function to set values after HTML is added to DOM to avoid XSS
|
||||
const setValues = function() {
|
||||
const tokenInput = document.getElementById(`githubPRsToken${index}`);
|
||||
const ownerInput = document.getElementById(`githubPRsOwner${index}`);
|
||||
const repoInput = document.getElementById(`githubPRsRepo${index}`);
|
||||
const replyIfNoRepliesInput = document.getElementById(`githubPRsReplyIfNoReplies${index}`);
|
||||
const pollIntervalInput = document.getElementById(`githubPRsPollInterval${index}`);
|
||||
|
||||
if (tokenInput) tokenInput.value = config.token || '';
|
||||
if (ownerInput) ownerInput.value = config.owner || '';
|
||||
if (repoInput) repoInput.value = config.repository || '';
|
||||
if (replyIfNoRepliesInput) replyIfNoRepliesInput.checked = config.replyIfNoReplies === 'true';
|
||||
if (pollIntervalInput) pollIntervalInput.value = config.pollInterval || '60';
|
||||
};
|
||||
|
||||
return { html, setValues };
|
||||
},
|
||||
|
||||
/**
|
||||
* IRC Connector Template
|
||||
* @param {Object} config - Existing configuration values
|
||||
* @param {Number} index - Connector index
|
||||
* @returns {Object} HTML template and setValues function
|
||||
*/
|
||||
irc: function(config = {}, index) {
|
||||
// Return HTML without values in the template string
|
||||
const html = `
|
||||
<div class="form-group">
|
||||
<label for="ircServer${index}">IRC Server</label>
|
||||
<input type="text"
|
||||
id="ircServer${index}"
|
||||
name="connectors[${index}][config][server]"
|
||||
class="form-control"
|
||||
placeholder="irc.libera.chat">
|
||||
</div>
|
||||
|
||||
<div class="form-group">
|
||||
<label for="ircPort${index}">Port</label>
|
||||
<input type="text"
|
||||
id="ircPort${index}"
|
||||
name="connectors[${index}][config][port]"
|
||||
class="form-control"
|
||||
placeholder="6667">
|
||||
</div>
|
||||
|
||||
<div class="form-group">
|
||||
<label for="ircChannel${index}">Channel</label>
|
||||
<input type="text"
|
||||
id="ircChannel${index}"
|
||||
name="connectors[${index}][config][channel]"
|
||||
class="form-control"
|
||||
placeholder="#channel">
|
||||
</div>
|
||||
|
||||
<div class="form-group">
|
||||
<label for="ircNick${index}">Nickname</label>
|
||||
<input type="text"
|
||||
id="ircNick${index}"
|
||||
name="connectors[${index}][config][nickname]"
|
||||
class="form-control"
|
||||
placeholder="MyBot">
|
||||
</div>
|
||||
|
||||
<div class="form-group form-check">
|
||||
<input type="checkbox"
|
||||
class="form-check-input"
|
||||
id="ircAlwaysReply${index}"
|
||||
name="connectors[${index}][config][alwaysReply]"
|
||||
value="true">
|
||||
<label class="form-check-label" for="ircAlwaysReply${index}">Always reply to messages</label>
|
||||
<small class="form-text text-muted">If checked, the bot will always reply to messages, even if they are not directed at it</small>
|
||||
</div>
|
||||
`;
|
||||
|
||||
// Function to set values after HTML is added to DOM
|
||||
const setValues = function() {
|
||||
const serverInput = document.getElementById(`ircServer${index}`);
|
||||
const portInput = document.getElementById(`ircPort${index}`);
|
||||
const channelInput = document.getElementById(`ircChannel${index}`);
|
||||
const nickInput = document.getElementById(`ircNick${index}`);
|
||||
const alwaysReplyInput = document.getElementById(`ircAlwaysReply${index}`);
|
||||
|
||||
if (serverInput) serverInput.value = config.server || '';
|
||||
if (portInput) portInput.value = config.port || '6667';
|
||||
if (channelInput) channelInput.value = config.channel || '';
|
||||
if (nickInput) nickInput.value = config.nickname || '';
|
||||
if (alwaysReplyInput) alwaysReplyInput.checked = config.alwaysReply === 'true';
|
||||
};
|
||||
|
||||
return { html, setValues };
|
||||
},
|
||||
|
||||
/**
|
||||
* Twitter Connector Template
|
||||
* @param {Object} config - Existing configuration values
|
||||
* @param {Number} index - Connector index
|
||||
* @returns {Object} HTML template and setValues function
|
||||
*/
|
||||
twitter: function(config = {}, index) {
|
||||
// Return HTML without values in the template string
|
||||
const html = `
|
||||
<div class="form-group">
|
||||
<label for="twitterToken${index}">Twitter API Token</label>
|
||||
<input type="text"
|
||||
id="twitterToken${index}"
|
||||
name="connectors[${index}][config][token]"
|
||||
class="form-control"
|
||||
placeholder="Your Twitter API token">
|
||||
</div>
|
||||
|
||||
<div class="form-group">
|
||||
<label for="twitterBotUsername${index}">Bot Username</label>
|
||||
<input type="text"
|
||||
id="twitterBotUsername${index}"
|
||||
name="connectors[${index}][config][botUsername]"
|
||||
class="form-control"
|
||||
placeholder="@YourBotUsername">
|
||||
<small class="form-text text-muted">Username of your Twitter bot (with or without @)</small>
|
||||
</div>
|
||||
|
||||
<div class="form-group form-check">
|
||||
<input type="checkbox"
|
||||
class="form-check-input"
|
||||
id="twitterNoCharLimit${index}"
|
||||
name="connectors[${index}][config][noCharacterLimit]"
|
||||
value="true">
|
||||
<label class="form-check-label" for="twitterNoCharLimit${index}">Disable character limit</label>
|
||||
<small class="form-text text-muted">If checked, the bot will not enforce Twitter's character limit</small>
|
||||
</div>
|
||||
`;
|
||||
|
||||
// Function to set values after HTML is added to DOM
|
||||
const setValues = function() {
|
||||
const tokenInput = document.getElementById(`twitterToken${index}`);
|
||||
const botUsernameInput = document.getElementById(`twitterBotUsername${index}`);
|
||||
const noCharLimitInput = document.getElementById(`twitterNoCharLimit${index}`);
|
||||
|
||||
if (tokenInput) tokenInput.value = config.token || '';
|
||||
if (botUsernameInput) botUsernameInput.value = config.botUsername || '';
|
||||
if (noCharLimitInput) noCharLimitInput.checked = config.noCharacterLimit === 'true';
|
||||
};
|
||||
|
||||
return { html, setValues };
|
||||
},
|
||||
|
||||
/**
|
||||
* Fallback template for any connector without a specific template
|
||||
* @param {Object} config - Existing configuration values
|
||||
* @param {Number} index - Connector index
|
||||
* @returns {Object} HTML template and setValues function
|
||||
*/
|
||||
fallback: function(config = {}, index) {
|
||||
// Convert config to a pretty-printed JSON string
|
||||
let configStr = '{}';
|
||||
try {
|
||||
if (typeof config === 'string') {
|
||||
// If it's already a string, try to parse it first to pretty-print
|
||||
configStr = JSON.stringify(JSON.parse(config), null, 2);
|
||||
} else if (typeof config === 'object' && config !== null) {
|
||||
configStr = JSON.stringify(config, null, 2);
|
||||
}
|
||||
} catch (e) {
|
||||
console.error('Error formatting config:', e);
|
||||
// If it's a string but not valid JSON, just use it as is
|
||||
if (typeof config === 'string') {
|
||||
configStr = config;
|
||||
}
|
||||
}
|
||||
|
||||
// Return HTML without values in the template string
|
||||
const html = `
|
||||
<div class="form-group">
|
||||
<label for="connectorConfig${index}">Connector Configuration (JSON)</label>
|
||||
<textarea id="connectorConfig${index}"
|
||||
name="connectors[${index}][config]"
|
||||
class="form-control"
|
||||
rows="10"
|
||||
placeholder='{"key":"value"}'>${escapeHTML(configStr)}</textarea>
|
||||
<small class="form-text text-muted">Enter the connector configuration as a JSON object</small>
|
||||
</div>
|
||||
`;
|
||||
|
||||
// Function to set values after HTML is added to DOM
|
||||
const setValues = function() {
|
||||
const configInput = document.getElementById(`connectorConfig${index}`);
|
||||
|
||||
if (configInput) configInput.value = configStr;
|
||||
};
|
||||
|
||||
return { html, setValues };
|
||||
}
|
||||
};
|
||||
@@ -1,139 +0,0 @@
|
||||
/**
|
||||
* Agent Form Wizard - Navigation and UI functionality
|
||||
*/
|
||||
document.addEventListener('DOMContentLoaded', function() {
|
||||
// Check if the wizard exists on the page
|
||||
const wizardSidebar = document.querySelector('.wizard-sidebar');
|
||||
if (!wizardSidebar) return;
|
||||
|
||||
// Get all sections and nav items
|
||||
const navItems = document.querySelectorAll('.wizard-nav-item');
|
||||
const sections = document.querySelectorAll('.form-section');
|
||||
const prevButton = document.getElementById('prevSection');
|
||||
const nextButton = document.getElementById('nextSection');
|
||||
const currentStepLabelEl = document.getElementById('currentStepLabel');
|
||||
const progressDotsContainer = document.getElementById('progressDots');
|
||||
|
||||
// Create progress dots
|
||||
const totalSteps = sections.length;
|
||||
|
||||
// Create dots for each section
|
||||
if (progressDotsContainer) {
|
||||
for (let i = 0; i < totalSteps; i++) {
|
||||
const dot = document.createElement('div');
|
||||
dot.className = 'progress-dot';
|
||||
dot.setAttribute('data-index', i);
|
||||
dot.addEventListener('click', () => setActiveSection(i));
|
||||
progressDotsContainer.appendChild(dot);
|
||||
}
|
||||
}
|
||||
|
||||
// Get all progress dots
|
||||
const progressDots = document.querySelectorAll('.progress-dot');
|
||||
|
||||
// Track current active section
|
||||
let currentSectionIndex = 0;
|
||||
|
||||
// Initialize
|
||||
updateNavigation();
|
||||
|
||||
// Add click events to nav items
|
||||
navItems.forEach((item, index) => {
|
||||
item.addEventListener('click', () => {
|
||||
setActiveSection(index);
|
||||
});
|
||||
});
|
||||
|
||||
// Add click events to prev/next buttons
|
||||
if (prevButton) {
|
||||
prevButton.addEventListener('click', () => {
|
||||
if (currentSectionIndex > 0) {
|
||||
setActiveSection(currentSectionIndex - 1);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
if (nextButton) {
|
||||
nextButton.addEventListener('click', () => {
|
||||
if (currentSectionIndex < sections.length - 1) {
|
||||
setActiveSection(currentSectionIndex + 1);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the active section and update navigation
|
||||
*/
|
||||
function setActiveSection(index) {
|
||||
// Remove active class from all sections and nav items
|
||||
sections.forEach(section => section.classList.remove('active'));
|
||||
navItems.forEach(item => item.classList.remove('active'));
|
||||
progressDots.forEach(dot => dot.classList.remove('active'));
|
||||
|
||||
// Add active class to current section, nav item, and dot
|
||||
sections[index].classList.add('active');
|
||||
navItems[index].classList.add('active');
|
||||
if (progressDots[index]) {
|
||||
progressDots[index].classList.add('active');
|
||||
}
|
||||
|
||||
// Update current section index
|
||||
currentSectionIndex = index;
|
||||
|
||||
// Update navigation state
|
||||
updateNavigation();
|
||||
|
||||
// Scroll to top of section
|
||||
sections[index].scrollIntoView({behavior: 'smooth', block: 'start'});
|
||||
}
|
||||
|
||||
/**
|
||||
* Update navigation buttons and progress
|
||||
*/
|
||||
function updateNavigation() {
|
||||
// Update section label
|
||||
if (currentStepLabelEl && navItems[currentSectionIndex]) {
|
||||
// Extract text content without the icon
|
||||
const navText = navItems[currentSectionIndex].textContent.trim();
|
||||
currentStepLabelEl.textContent = navText;
|
||||
}
|
||||
|
||||
// Update prev/next buttons
|
||||
if (prevButton) {
|
||||
prevButton.disabled = currentSectionIndex === 0;
|
||||
prevButton.style.opacity = currentSectionIndex === 0 ? 0.5 : 1;
|
||||
}
|
||||
|
||||
if (nextButton) {
|
||||
nextButton.disabled = currentSectionIndex === sections.length - 1;
|
||||
nextButton.style.opacity = currentSectionIndex === sections.length - 1 ? 0.5 : 1;
|
||||
|
||||
// Change text for last step
|
||||
if (currentSectionIndex === sections.length - 2) {
|
||||
nextButton.innerHTML = 'Finish <i class="fas fa-check"></i>';
|
||||
} else {
|
||||
nextButton.innerHTML = 'Next <i class="fas fa-arrow-right"></i>';
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Helper function to validate current section before proceeding
|
||||
function validateCurrentSection() {
|
||||
// Implement validation logic here based on the current section
|
||||
// Return true if valid, false if not
|
||||
return true;
|
||||
}
|
||||
|
||||
// Add to initAgentFormCommon function if it exists
|
||||
if (typeof window.initAgentFormCommon === 'function') {
|
||||
const originalInit = window.initAgentFormCommon;
|
||||
|
||||
window.initAgentFormCommon = function(options) {
|
||||
// Call the original initialization function
|
||||
originalInit(options);
|
||||
|
||||
// Now initialize the wizard navigation
|
||||
setActiveSection(0);
|
||||
};
|
||||
}
|
||||
});
|
||||
Binary file not shown.
|
Before Width: | Height: | Size: 933 KiB |
Binary file not shown.
|
Before Width: | Height: | Size: 1.8 MiB |
@@ -1,291 +0,0 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>Actions Playground</title>
|
||||
{{template "old/views/partials/header"}}
|
||||
</head>
|
||||
<body>
|
||||
{{template "old/views/partials/menu"}}
|
||||
|
||||
<!-- Toast for notifications -->
|
||||
<div id="toast" class="toast">
|
||||
<span id="toast-message"></span>
|
||||
</div>
|
||||
|
||||
<div class="container mx-auto">
|
||||
<header class="text-center mb-8">
|
||||
<h1 class="text-4xl md:text-6xl font-bold">Actions Playground</h1>
|
||||
<p class="mt-4 text-gray-400">Test and execute actions directly from the UI</p>
|
||||
</header>
|
||||
|
||||
<section class="section-box mb-8">
|
||||
<h2 class="mb-4">Select an Action</h2>
|
||||
<div class="mb-4">
|
||||
<label for="action-select" class="block mb-2">Available Actions:</label>
|
||||
<select id="action-select" class="w-full">
|
||||
<option value="">-- Select an action --</option>
|
||||
<!-- Actions will be loaded here -->
|
||||
</select>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section id="config-section" class="section-box mb-8 hidden">
|
||||
<h2 class="mb-4">Action Configuration</h2>
|
||||
<form id="action-form">
|
||||
<div class="mb-6">
|
||||
<label for="config-json" class="block mb-2">Configuration (JSON):</label>
|
||||
<textarea id="config-json" class="w-full" rows="5" placeholder='{"key": "value"}'>{}</textarea>
|
||||
<p class="text-xs text-gray-400 mt-1">Enter JSON configuration for the action</p>
|
||||
</div>
|
||||
|
||||
<div class="mb-6">
|
||||
<label for="params-json" class="block mb-2">Parameters (JSON):</label>
|
||||
<textarea id="params-json" class="w-full" rows="5" placeholder='{"key": "value"}'>{}</textarea>
|
||||
<p class="text-xs text-gray-400 mt-1">Enter JSON parameters for the action</p>
|
||||
</div>
|
||||
|
||||
<div class="flex justify-end">
|
||||
<button type="submit" class="action-btn start-btn">
|
||||
<i class="fas fa-play"></i> Execute Action
|
||||
</button>
|
||||
</div>
|
||||
</form>
|
||||
</section>
|
||||
|
||||
<section id="results-section" class="section-box mb-8 hidden">
|
||||
<h2 class="mb-4">Action Results</h2>
|
||||
<div id="action-results">
|
||||
<!-- Results will appear here -->
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<footer class="text-center text-gray-500 text-sm mb-8">
|
||||
<p>© 2025 LocalAGI.</p>
|
||||
</footer>
|
||||
</div>
|
||||
|
||||
<script>
|
||||
document.addEventListener('DOMContentLoaded', function() {
|
||||
// Load available actions
|
||||
fetchActions();
|
||||
|
||||
// Handle action selection
|
||||
document.getElementById('action-select').addEventListener('change', function() {
|
||||
const actionId = this.value;
|
||||
if (actionId) {
|
||||
document.getElementById('config-section').classList.remove('hidden');
|
||||
} else {
|
||||
document.getElementById('config-section').classList.add('hidden');
|
||||
}
|
||||
|
||||
// Hide results when changing actions
|
||||
document.getElementById('results-section').classList.add('hidden');
|
||||
});
|
||||
|
||||
// Handle form submission
|
||||
document.getElementById('action-form').addEventListener('submit', function(e) {
|
||||
e.preventDefault();
|
||||
const actionId = document.getElementById('action-select').value;
|
||||
if (actionId) {
|
||||
executeAction(actionId);
|
||||
} else {
|
||||
showToast('Please select an action first', 'error');
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
function fetchActions() {
|
||||
fetch('/api/actions')
|
||||
.then(response => response.json())
|
||||
.then(actions => {
|
||||
const select = document.getElementById('action-select');
|
||||
|
||||
// Clear existing options except the first one
|
||||
while (select.options.length > 1) {
|
||||
select.remove(1);
|
||||
}
|
||||
|
||||
if (actions.length === 0) {
|
||||
const option = document.createElement('option');
|
||||
option.text = 'No actions available';
|
||||
option.disabled = true;
|
||||
select.add(option);
|
||||
return;
|
||||
}
|
||||
|
||||
// Add options for each action
|
||||
actions.forEach(actionId => {
|
||||
const option = document.createElement('option');
|
||||
option.value = actionId;
|
||||
option.text = actionId; // Using actionId as display text
|
||||
select.add(option);
|
||||
});
|
||||
})
|
||||
.catch(error => {
|
||||
console.error('Error fetching actions:', error);
|
||||
showToast('Failed to load actions: ' + error.message, 'error');
|
||||
|
||||
const select = document.getElementById('action-select');
|
||||
const option = document.createElement('option');
|
||||
option.text = 'Error loading actions';
|
||||
option.disabled = true;
|
||||
|
||||
// Clear existing options except the first one
|
||||
while (select.options.length > 1) {
|
||||
select.remove(1);
|
||||
}
|
||||
|
||||
select.add(option);
|
||||
});
|
||||
}
|
||||
|
||||
function executeAction(actionId) {
|
||||
// Get the JSON data from textareas
|
||||
let config = {};
|
||||
let params = {};
|
||||
|
||||
try {
|
||||
const configText = document.getElementById('config-json').value.trim();
|
||||
if (configText && configText !== '{}') {
|
||||
config = JSON.parse(configText);
|
||||
}
|
||||
|
||||
const paramsText = document.getElementById('params-json').value.trim();
|
||||
if (paramsText && paramsText !== '{}') {
|
||||
params = JSON.parse(paramsText);
|
||||
}
|
||||
} catch (error) {
|
||||
showToast('Invalid JSON: ' + error.message, 'error');
|
||||
return;
|
||||
}
|
||||
|
||||
// Show the results section with loading indicator
|
||||
const resultsSection = document.getElementById('results-section');
|
||||
resultsSection.classList.remove('hidden');
|
||||
|
||||
const resultDiv = document.getElementById('action-results');
|
||||
resultDiv.innerHTML = `
|
||||
<div class="flex justify-center items-center py-8">
|
||||
<div class="loader"></div>
|
||||
</div>
|
||||
`;
|
||||
|
||||
// Execute the action
|
||||
fetch(`/api/action/${actionId}/run`, {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
},
|
||||
body: JSON.stringify({
|
||||
config: config,
|
||||
params: params
|
||||
})
|
||||
})
|
||||
.then(response => {
|
||||
return response.json();
|
||||
})
|
||||
.then(result => {
|
||||
if (result.error) {
|
||||
throw new Error(result.error);
|
||||
}
|
||||
// Display the results
|
||||
showActionResult(result);
|
||||
showToast('Action executed successfully!', 'success');
|
||||
})
|
||||
.catch(error => {
|
||||
resultDiv.innerHTML = `
|
||||
<div class="alert alert-error" style="display: block;">
|
||||
<i class="fas fa-exclamation-circle mr-2"></i> Error: ${error.message}
|
||||
</div>
|
||||
`;
|
||||
showToast('Error executing action', 'error');
|
||||
});
|
||||
}
|
||||
|
||||
function showActionResult(result) {
|
||||
const resultDiv = document.getElementById('action-results');
|
||||
|
||||
let html = '';
|
||||
|
||||
// Display result
|
||||
if (result.Result) {
|
||||
html += `
|
||||
<div class="mb-4">
|
||||
<h4 class="text-lg mb-2" style="color: var(--secondary);">Result:</h4>
|
||||
<div class="code-terminal">
|
||||
<pre>${escapeHtml(result.Result)}</pre>
|
||||
</div>
|
||||
</div>
|
||||
`;
|
||||
}
|
||||
|
||||
// Display metadata if available
|
||||
if (result.Metadata && Object.keys(result.Metadata).length > 0) {
|
||||
html += `
|
||||
<div class="mb-4">
|
||||
<h4 class="text-lg mb-2" style="color: var(--secondary);">Metadata:</h4>
|
||||
<div class="code-terminal">
|
||||
<pre>${escapeHtml(JSON.stringify(result.Metadata, null, 2))}</pre>
|
||||
</div>
|
||||
</div>
|
||||
`;
|
||||
}
|
||||
|
||||
if (!html) {
|
||||
html = '<p class="text-gray-400">No results returned from the action.</p>';
|
||||
}
|
||||
|
||||
resultDiv.innerHTML = html;
|
||||
|
||||
// Scroll to results
|
||||
resultDiv.scrollIntoView({ behavior: 'smooth' });
|
||||
}
|
||||
|
||||
function escapeHtml(unsafe) {
|
||||
return unsafe
|
||||
.replace(/&/g, "&")
|
||||
.replace(/</g, "<")
|
||||
.replace(/>/g, ">")
|
||||
.replace(/"/g, """)
|
||||
.replace(/'/g, "'");
|
||||
}
|
||||
|
||||
function showToast(message, type) {
|
||||
const toast = document.getElementById('toast');
|
||||
const toastMessage = document.getElementById('toast-message');
|
||||
|
||||
toastMessage.textContent = message;
|
||||
toast.className = 'toast toast-' + type;
|
||||
toast.classList.add('toast-visible');
|
||||
|
||||
setTimeout(() => {
|
||||
toast.classList.remove('toast-visible');
|
||||
}, 3000);
|
||||
}
|
||||
</script>
|
||||
|
||||
<style>
|
||||
.loader {
|
||||
width: 48px;
|
||||
height: 48px;
|
||||
border: 5px solid var(--tertiary);
|
||||
border-bottom-color: transparent;
|
||||
border-radius: 50%;
|
||||
display: inline-block;
|
||||
box-sizing: border-box;
|
||||
animation: rotation 1s linear infinite;
|
||||
}
|
||||
|
||||
@keyframes rotation {
|
||||
0% { transform: rotate(0deg); }
|
||||
100% { transform: rotate(360deg); }
|
||||
}
|
||||
|
||||
.code-terminal {
|
||||
margin-top: 0;
|
||||
}
|
||||
</style>
|
||||
</body>
|
||||
</html>
|
||||
@@ -1,332 +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 "old/views/partials/header"}}
|
||||
<style>
|
||||
.avatar-placeholder {
|
||||
width: 96px;
|
||||
height: 96px;
|
||||
border-radius: 50%;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
background: linear-gradient(135deg, #2a2a2a, #3a3a3a);
|
||||
color: var(--primary);
|
||||
font-size: 1.5rem;
|
||||
margin-bottom: 1rem;
|
||||
border: 2px solid var(--primary);
|
||||
box-shadow: var(--neon-glow);
|
||||
position: relative;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.avatar-placeholder::after {
|
||||
content: "";
|
||||
position: absolute;
|
||||
width: 100%;
|
||||
height: 4px;
|
||||
background: var(--primary);
|
||||
bottom: 0;
|
||||
left: 0;
|
||||
animation: loading-progress 2s infinite linear;
|
||||
}
|
||||
|
||||
@keyframes loading-progress {
|
||||
0% { width: 0; }
|
||||
50% { width: 100%; }
|
||||
100% { width: 0; }
|
||||
}
|
||||
|
||||
.placeholder-text {
|
||||
z-index: 1;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
{{template "old/views/partials/menu"}}
|
||||
|
||||
<!-- Toast for notifications -->
|
||||
<div id="toast" class="toast">
|
||||
<span id="toast-message"></span>
|
||||
</div>
|
||||
|
||||
<div class="container mx-auto">
|
||||
<header class="text-center mb-8">
|
||||
<h1 class="text-4xl md:text-6xl font-bold">Agent List</h1>
|
||||
<p class="mt-4 text-gray-400">Manage and interact with your AI agents</p>
|
||||
</header>
|
||||
|
||||
<div class="button-container justify-center mb-6">
|
||||
<a href="/old/create" class="action-btn start-btn">
|
||||
<i class="fas fa-plus-circle"></i> Add New Agent
|
||||
</a>
|
||||
<button id="toggle-import" class="action-btn" style="background: linear-gradient(135deg, var(--tertiary), #4a76a8);">
|
||||
<i class="fas fa-file-import"></i> Import Agent
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<section id="import-section" class="hidden mb-8">
|
||||
<div class="section-box">
|
||||
<h2>Import Agent</h2>
|
||||
|
||||
<!-- Response Messages Container -->
|
||||
<div id="response-container">
|
||||
<!-- Success Alert -->
|
||||
<div id="success-alert" class="alert alert-success" style="display: none;">
|
||||
Agent imported successfully! The page will refresh in a moment.
|
||||
</div>
|
||||
|
||||
<!-- Error Alert -->
|
||||
<div id="error-alert" class="alert alert-error" style="display: none;">
|
||||
<span id="error-message">Error importing agent.</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<form id='import-form' hx-encoding='multipart/form-data' hx-post='/settings/import' hx-target="#response-container" hx-swap="none">
|
||||
<div class="mb-4">
|
||||
<label for="file" class="block mb-2">Select Agent File:</label>
|
||||
<input type='file' name='file' id='file'>
|
||||
</div>
|
||||
<div class="flex items-center">
|
||||
<button id="import-button" type="submit" class="action-btn">
|
||||
<i class="fas fa-cloud-upload-alt"></i> Import Agent
|
||||
</button>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section class="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-8">
|
||||
{{ $status := .Status }}
|
||||
{{ range .Agents }}
|
||||
<div hx-ext="sse" data-agent-name="{{.}}" class="card">
|
||||
<div class="flex flex-col items-center text-center p-4">
|
||||
<div class="avatar-container mb-4">
|
||||
<img src="/avatars/{{.}}.png" alt="{{.}}" class="w-24 h-24 rounded-full"
|
||||
style="border: 2px solid var(--primary); box-shadow: var(--neon-glow); display: none;"
|
||||
onload="this.style.display = 'block'; this.nextElementSibling.style.display = 'none';"
|
||||
onerror="this.style.display = 'none'; this.nextElementSibling.style.display = 'flex';">
|
||||
<div class="avatar-placeholder">
|
||||
<span class="placeholder-text"><i class="fas fa-sync fa-spin"></i></span>
|
||||
</div>
|
||||
</div>
|
||||
<h2>{{.}}</h2>
|
||||
<div class="mb-4 flex items-center justify-center">
|
||||
<span class="badge {{ if eq (index $status .) true }}badge-primary{{ else }}badge-secondary{{ end }} mr-2">
|
||||
{{ if eq (index $status .) true }}Active{{ else }}Inactive{{ end }}
|
||||
</span>
|
||||
</div>
|
||||
|
||||
<div class="grid grid-cols-2 gap-2 w-full mb-4">
|
||||
<a href="/old/status/{{.}}" class="action-btn flex items-center justify-center"
|
||||
style="background: linear-gradient(135deg, #2a2a2a, #3a3a3a);">
|
||||
<i class="fas fa-info-circle mr-2"></i> Status
|
||||
</a>
|
||||
<a href="/old/talk/{{.}}" class="action-btn flex items-center justify-center"
|
||||
style="background: linear-gradient(135deg, #2a2a2a, #3a3a3a);">
|
||||
<i class="fas fa-comments mr-2"></i> Talk
|
||||
</a>
|
||||
</div>
|
||||
|
||||
<div class="grid grid-cols-2 gap-2 w-full">
|
||||
<button class="action-btn toggle-btn col-span-1"
|
||||
data-agent="{{.}}"
|
||||
data-active="{{ if eq (index $status .) true }}true{{ else }}false{{ end }}">
|
||||
{{ if eq (index $status .) true }}
|
||||
<i class="fas fa-pause"></i> Pause
|
||||
{{ else }}
|
||||
<i class="fas fa-play"></i> Start
|
||||
{{ end }}
|
||||
</button>
|
||||
<a href="/old/settings/{{.}}" class="action-btn col-span-1 flex items-center justify-center"
|
||||
style="background: linear-gradient(135deg, #2a2a2a, #3a3a3a);">
|
||||
<i class="fas fa-cog"></i>
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{{ end }}
|
||||
</section>
|
||||
|
||||
<div class="user-info mt-8 mb-4">
|
||||
<span></span>
|
||||
<span class="timestamp"></span>
|
||||
</div>
|
||||
|
||||
<footer class="text-center text-gray-500 text-sm mb-8">
|
||||
<p>© 2025 LocalAGI.</p>
|
||||
</footer>
|
||||
</div>
|
||||
|
||||
<script>
|
||||
document.addEventListener('DOMContentLoaded', function() {
|
||||
// Image loading handler
|
||||
document.querySelectorAll('.avatar-container img').forEach(img => {
|
||||
// Check if image is already cached
|
||||
if (img.complete) {
|
||||
if (img.naturalHeight === 0) {
|
||||
// Image failed to load
|
||||
img.style.display = 'none';
|
||||
img.nextElementSibling.style.display = 'flex';
|
||||
} else {
|
||||
// Image loaded successfully
|
||||
img.style.display = 'block';
|
||||
img.nextElementSibling.style.display = 'none';
|
||||
}
|
||||
}
|
||||
// onload and onerror handlers are already in the HTML
|
||||
});
|
||||
|
||||
const importSection = document.getElementById('import-section');
|
||||
const toggleImport = document.getElementById('toggle-import');
|
||||
|
||||
// Toggle import section visibility
|
||||
toggleImport.addEventListener('click', function() {
|
||||
importSection.classList.toggle('hidden');
|
||||
// Add glitch effect when showing
|
||||
if (!importSection.classList.contains('hidden')) {
|
||||
importSection.style.animation = 'glitch 0.3s';
|
||||
setTimeout(() => {
|
||||
importSection.style.animation = '';
|
||||
}, 300);
|
||||
}
|
||||
});
|
||||
|
||||
// Handle import form submission
|
||||
document.getElementById('import-form').addEventListener('htmx:afterRequest', function(event) {
|
||||
const xhr = event.detail.xhr;
|
||||
const successAlert = document.getElementById('success-alert');
|
||||
const errorAlert = document.getElementById('error-alert');
|
||||
const errorMessage = document.getElementById('error-message');
|
||||
|
||||
// Hide both alerts initially
|
||||
successAlert.style.display = 'none';
|
||||
errorAlert.style.display = 'none';
|
||||
|
||||
if (xhr.status === 200) {
|
||||
try {
|
||||
const response = JSON.parse(xhr.responseText);
|
||||
|
||||
if (response.status === "ok") {
|
||||
// Show success message
|
||||
successAlert.style.display = 'block';
|
||||
showToast("Agent imported successfully!", "success");
|
||||
|
||||
// Refresh the page after a short delay
|
||||
setTimeout(() => {
|
||||
window.location.reload();
|
||||
}, 2000);
|
||||
} else if (response.error) {
|
||||
// Show error message
|
||||
errorMessage.textContent = response.error;
|
||||
errorAlert.style.display = 'block';
|
||||
showToast("Import failed: " + response.error, "error");
|
||||
}
|
||||
} catch (e) {
|
||||
// Handle parsing error
|
||||
errorMessage.textContent = "Invalid response format";
|
||||
errorAlert.style.display = 'block';
|
||||
showToast("Invalid response format", "error");
|
||||
}
|
||||
} else {
|
||||
// Handle HTTP error
|
||||
errorMessage.textContent = "Server error: " + xhr.status;
|
||||
errorAlert.style.display = 'block';
|
||||
showToast("Server error: " + xhr.status, "error");
|
||||
}
|
||||
});
|
||||
|
||||
// Handle toggle buttons - using pure JavaScript
|
||||
document.querySelectorAll('.toggle-btn').forEach(button => {
|
||||
button.addEventListener('click', function() {
|
||||
const agent = this.getAttribute('data-agent');
|
||||
const isActive = this.getAttribute('data-active') === 'true';
|
||||
const endpoint = isActive ? `/api/agent/${agent}/pause` : `/api/agent/${agent}/start`;
|
||||
|
||||
// Add animation
|
||||
this.style.animation = 'pulse 0.5s';
|
||||
|
||||
// Create a new XMLHttpRequest
|
||||
const xhr = new XMLHttpRequest();
|
||||
xhr.open('PUT', endpoint);
|
||||
xhr.setRequestHeader('Content-Type', 'application/json');
|
||||
|
||||
xhr.onload = () => {
|
||||
// Clear animation
|
||||
this.style.animation = '';
|
||||
|
||||
if (xhr.status === 200) {
|
||||
try {
|
||||
const response = JSON.parse(xhr.responseText);
|
||||
|
||||
if (response.status === "ok") {
|
||||
// Toggle the button state
|
||||
const newState = !isActive;
|
||||
this.setAttribute('data-active', newState ? 'true' : 'false');
|
||||
|
||||
// Update button text and icon
|
||||
if (newState) {
|
||||
this.innerHTML = '<i class="fas fa-pause"></i> Pause';
|
||||
} else {
|
||||
this.innerHTML = '<i class="fas fa-play"></i> Start';
|
||||
}
|
||||
|
||||
// Show success toast
|
||||
const action = isActive ? 'pause' : 'start';
|
||||
showToast(`Agent "${agent}" ${action}ed successfully`, 'success');
|
||||
|
||||
// Update the status badge
|
||||
updateAgentStatus(agent, newState);
|
||||
} else if (response.error) {
|
||||
// Show error toast
|
||||
showToast(`Error: ${response.error}`, 'error');
|
||||
}
|
||||
} catch (e) {
|
||||
// Handle parsing error
|
||||
showToast("Invalid response format", 'error');
|
||||
console.error("Error parsing response:", e);
|
||||
}
|
||||
} else {
|
||||
// Handle HTTP error
|
||||
showToast(`Server error: ${xhr.status}`, 'error');
|
||||
}
|
||||
};
|
||||
|
||||
xhr.onerror = () => {
|
||||
// Clear animation
|
||||
this.style.animation = '';
|
||||
showToast("Network error occurred", 'error');
|
||||
console.error("Network error occurred");
|
||||
};
|
||||
|
||||
// Send the request
|
||||
xhr.send(JSON.stringify({}));
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
// Function to update agent status in the UI
|
||||
function updateAgentStatus(agentName, isOnline) {
|
||||
// Find the card for this agent
|
||||
const cards = document.querySelectorAll('.card');
|
||||
cards.forEach(card => {
|
||||
const agentTitle = card.querySelector('h2').textContent;
|
||||
if (agentTitle === agentName) {
|
||||
// Update the badge
|
||||
const badge = card.querySelector('.badge');
|
||||
if (isOnline) {
|
||||
badge.className = 'badge badge-primary mr-2';
|
||||
badge.textContent = 'Active';
|
||||
} else {
|
||||
badge.className = 'badge badge-secondary mr-2';
|
||||
badge.textContent = 'Inactive';
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
@@ -1,91 +0,0 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<title>Smart Agent Interface</title>
|
||||
{{template "old/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 "old/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="/old/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>
|
||||
@@ -1,154 +0,0 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<title>Create New Agent</title>
|
||||
{{template "old/views/partials/header"}}
|
||||
<script src="/old/public/js/wizard.js"></script>
|
||||
<link rel="stylesheet" href="/old/public/css/wizard.css">
|
||||
<script src="/old/public/js/connector-templates.js"></script>
|
||||
<script src="/old/public/js/agent-form.js"></script>
|
||||
</head>
|
||||
<body>
|
||||
{{template "old/views/partials/menu"}}
|
||||
<div class="container">
|
||||
<div class="section-box">
|
||||
<h1>Create New Agent</h1>
|
||||
|
||||
<form id="create-agent-form" action="/api/agent/create" method="POST">
|
||||
{{template "old/views/partials/agent-form" . }}
|
||||
|
||||
<button type="submit" id="create-button" data-original-text="<i class='fas fa-robot'></i> Create Agent">
|
||||
<i class="fas fa-robot"></i> Create Agent
|
||||
</button>
|
||||
</form>
|
||||
</div>
|
||||
|
||||
<!-- Response Messages Container -->
|
||||
<div id="response-container">
|
||||
<!-- Alert messages will be shown here -->
|
||||
<div id="success-alert" class="alert alert-success" style="display: none;">
|
||||
Agent created successfully! Redirecting to agent list...
|
||||
</div>
|
||||
|
||||
<div id="error-alert" class="alert alert-error" style="display: none;">
|
||||
<span id="error-message">Error creating agent.</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Toast notification container -->
|
||||
<div id="toast" class="toast">
|
||||
<span id="toast-message"></span>
|
||||
</div>
|
||||
|
||||
<script>
|
||||
const actions = `{{ range .Actions }}<option value="{{.}}">{{.}}</option>{{ end }}`;
|
||||
const connectors = `{{ range .Connectors }}<option value="{{.}}">{{.}}</option>{{ end }}`;
|
||||
const promptBlocks = `{{ range .PromptBlocks }}<option value="{{.}}">{{.}}</option>{{ end }}`;
|
||||
|
||||
document.addEventListener('DOMContentLoaded', function() {
|
||||
// Initialize common form components
|
||||
initAgentFormCommon({
|
||||
actions: actions,
|
||||
connectors: connectors,
|
||||
promptBlocks: promptBlocks
|
||||
});
|
||||
|
||||
// Form submission handling
|
||||
const form = document.getElementById('create-agent-form');
|
||||
|
||||
form.addEventListener('submit', function(e) {
|
||||
e.preventDefault();
|
||||
|
||||
// Show loading state
|
||||
const createButton = document.getElementById('create-button');
|
||||
const originalButtonText = createButton.innerHTML;
|
||||
createButton.setAttribute('data-original-text', originalButtonText);
|
||||
createButton.innerHTML = '<i class="fas fa-spinner fa-spin"></i> Creating...';
|
||||
createButton.disabled = true;
|
||||
|
||||
// Build a structured data object
|
||||
const formData = new FormData(form);
|
||||
const jsonData = AgentFormUtils.processFormData(formData);
|
||||
|
||||
// Process special fields
|
||||
jsonData.connectors = AgentFormUtils.processConnectors(createButton);
|
||||
if (jsonData.connectors === null) return; // Validation failed
|
||||
|
||||
jsonData.mcp_servers = AgentFormUtils.processMCPServers();
|
||||
|
||||
jsonData.actions = AgentFormUtils.processActions(createButton);
|
||||
if (jsonData.actions === null) return; // Validation failed
|
||||
|
||||
jsonData.promptblocks = AgentFormUtils.processPromptBlocks(createButton);
|
||||
if (jsonData.promptblocks === null) return; // Validation failed
|
||||
|
||||
// Send the structured data as JSON
|
||||
fetch('/api/agent/create', {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/json'
|
||||
},
|
||||
body: JSON.stringify(jsonData)
|
||||
})
|
||||
.then(response => response.json())
|
||||
.then(data => {
|
||||
const successAlert = document.getElementById('success-alert');
|
||||
const errorAlert = document.getElementById('error-alert');
|
||||
const errorMessage = document.getElementById('error-message');
|
||||
|
||||
// Hide both alerts initially
|
||||
successAlert.style.display = 'none';
|
||||
errorAlert.style.display = 'none';
|
||||
|
||||
if (data.status === "ok") {
|
||||
// Show success toast
|
||||
showToast('Agent created successfully!', 'success');
|
||||
|
||||
// Show success message
|
||||
successAlert.style.display = 'block';
|
||||
|
||||
// Redirect to agent list page after a delay
|
||||
setTimeout(() => {
|
||||
window.location.href = '/old/agents';
|
||||
}, 2000);
|
||||
} else if (data.error) {
|
||||
// Show error toast
|
||||
showToast('Error: ' + data.error, 'error');
|
||||
|
||||
// Show error message
|
||||
errorMessage.textContent = data.error;
|
||||
errorAlert.style.display = 'block';
|
||||
|
||||
// Restore button state
|
||||
createButton.innerHTML = originalButtonText;
|
||||
createButton.disabled = false;
|
||||
} else {
|
||||
// Handle unexpected response format
|
||||
showToast('Unexpected response format', 'error');
|
||||
errorMessage.textContent = "Unexpected response format";
|
||||
errorAlert.style.display = 'block';
|
||||
|
||||
// Restore button state
|
||||
createButton.innerHTML = originalButtonText;
|
||||
createButton.disabled = false;
|
||||
}
|
||||
})
|
||||
.catch(error => {
|
||||
// Handle network or other errors
|
||||
showToast('Network error: ' + error.message, 'error');
|
||||
const errorAlert = document.getElementById('error-alert');
|
||||
const errorMessage = document.getElementById('error-message');
|
||||
|
||||
errorMessage.textContent = "Network error: " + error.message;
|
||||
errorAlert.style.display = 'block';
|
||||
|
||||
// Restore button state
|
||||
createButton.innerHTML = originalButtonText;
|
||||
createButton.disabled = false;
|
||||
});
|
||||
});
|
||||
});
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
@@ -1,600 +0,0 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<title>Create Agent Group</title>
|
||||
{{template "old/views/partials/header"}}
|
||||
<script src="/old/public/js/wizard.js"></script>
|
||||
<link rel="stylesheet" href="/old/public/css/wizard.css">
|
||||
<script src="/old/public/js/connector-templates.js"></script>
|
||||
<script src="/old/public/js/agent-form.js"></script>
|
||||
<style>
|
||||
.agent-profile {
|
||||
border: 1px solid var(--medium-bg);
|
||||
border-radius: 8px;
|
||||
padding: 15px;
|
||||
margin-bottom: 20px;
|
||||
background-color: var(--lighter-bg);
|
||||
position: relative;
|
||||
transition: all 0.3s ease;
|
||||
}
|
||||
.agent-profile:hover {
|
||||
transform: translateY(-3px);
|
||||
box-shadow: 0 10px 20px rgba(0, 0, 0, 0.2);
|
||||
}
|
||||
.agent-profile h3 {
|
||||
color: var(--primary);
|
||||
text-shadow: var(--neon-glow);
|
||||
margin-top: 0;
|
||||
margin-bottom: 15px;
|
||||
border-bottom: 1px solid var(--medium-bg);
|
||||
padding-bottom: 10px;
|
||||
}
|
||||
.agent-profile .description {
|
||||
color: var(--text);
|
||||
font-size: 0.9rem;
|
||||
margin-bottom: 15px;
|
||||
}
|
||||
.agent-profile .system-prompt {
|
||||
background-color: var(--darker-bg);
|
||||
border-radius: 6px;
|
||||
padding: 10px;
|
||||
font-size: 0.85rem;
|
||||
max-height: 150px;
|
||||
overflow-y: auto;
|
||||
margin-bottom: 10px;
|
||||
white-space: pre-wrap;
|
||||
}
|
||||
.agent-profile.selected {
|
||||
border: 2px solid var(--primary);
|
||||
background-color: rgba(var(--primary-rgb), 0.1);
|
||||
}
|
||||
.agent-profile .select-checkbox {
|
||||
position: absolute;
|
||||
top: 10px;
|
||||
right: 10px;
|
||||
}
|
||||
.page-section {
|
||||
display: none;
|
||||
animation: fadeIn 0.5s;
|
||||
}
|
||||
.page-section.active {
|
||||
display: block;
|
||||
}
|
||||
.progress-container {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
margin-bottom: 30px;
|
||||
}
|
||||
.progress-step {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
position: relative;
|
||||
padding: 0 20px;
|
||||
}
|
||||
.progress-step:not(:last-child)::after {
|
||||
content: '';
|
||||
position: absolute;
|
||||
top: 12px;
|
||||
right: -30px;
|
||||
width: 60px;
|
||||
height: 3px;
|
||||
background-color: var(--medium-bg);
|
||||
}
|
||||
.progress-step.active:not(:last-child)::after {
|
||||
background-color: var(--primary);
|
||||
}
|
||||
.step-circle {
|
||||
width: 28px;
|
||||
height: 28px;
|
||||
border-radius: 50%;
|
||||
background-color: var(--medium-bg);
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
color: var(--text);
|
||||
margin-bottom: 8px;
|
||||
transition: all 0.3s ease;
|
||||
}
|
||||
.progress-step.active .step-circle {
|
||||
background-color: var(--primary);
|
||||
box-shadow: 0 0 10px var(--primary);
|
||||
}
|
||||
.step-label {
|
||||
font-size: 0.9rem;
|
||||
color: var(--muted-text);
|
||||
transition: all 0.3s ease;
|
||||
}
|
||||
.progress-step.active .step-label {
|
||||
color: var(--primary);
|
||||
font-weight: bold;
|
||||
}
|
||||
.prompt-container {
|
||||
margin-bottom: 30px;
|
||||
}
|
||||
.prompt-container textarea {
|
||||
width: 100%;
|
||||
min-height: 120px;
|
||||
padding: 15px;
|
||||
border-radius: 6px;
|
||||
background-color: var(--lighter-bg);
|
||||
border: 1px solid var(--medium-bg);
|
||||
color: var(--text);
|
||||
font-size: 1rem;
|
||||
resize: vertical;
|
||||
}
|
||||
.action-buttons {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
margin-top: 30px;
|
||||
}
|
||||
.select-all-container {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
.loader {
|
||||
display: none;
|
||||
text-align: center;
|
||||
margin: 40px 0;
|
||||
}
|
||||
.loader i {
|
||||
color: var(--primary);
|
||||
font-size: 2rem;
|
||||
}
|
||||
@keyframes fadeIn {
|
||||
from { opacity: 0; }
|
||||
to { opacity: 1; }
|
||||
}
|
||||
/* Make form elements auto in group mode */
|
||||
#wizard-container .form-section {
|
||||
height: auto;
|
||||
overflow: visible;
|
||||
}
|
||||
.info-message {
|
||||
background-color: rgba(var(--primary-rgb), 0.1);
|
||||
border-left: 4px solid var(--primary);
|
||||
padding: 15px;
|
||||
margin: 20px 0;
|
||||
border-radius: 0 8px 8px 0;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
}
|
||||
.info-message i {
|
||||
font-size: 1.5rem;
|
||||
color: var(--primary);
|
||||
margin-right: 15px;
|
||||
}
|
||||
.info-message-content {
|
||||
flex: 1;
|
||||
}
|
||||
.info-message-content h4 {
|
||||
margin-top: 0;
|
||||
margin-bottom: 5px;
|
||||
color: var(--primary);
|
||||
}
|
||||
.info-message-content p {
|
||||
margin-bottom: 0;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
{{template "old/views/partials/menu"}}
|
||||
<div class="container">
|
||||
<div class="section-box">
|
||||
<h1>Create Agent Group</h1>
|
||||
|
||||
<!-- Progress Bar -->
|
||||
<div class="progress-container">
|
||||
<div class="progress-step active" data-step="1">
|
||||
<div class="step-circle">1</div>
|
||||
<div class="step-label">Generate Profiles</div>
|
||||
</div>
|
||||
<div class="progress-step" data-step="2">
|
||||
<div class="step-circle">2</div>
|
||||
<div class="step-label">Review & Select</div>
|
||||
</div>
|
||||
<div class="progress-step" data-step="3">
|
||||
<div class="step-circle">3</div>
|
||||
<div class="step-label">Configure Settings</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Step 1: Generate Profiles -->
|
||||
<div id="step1" class="page-section active">
|
||||
<h2>Generate Agent Profiles</h2>
|
||||
<p>Describe the group of agents you want to create. Be specific about their roles, relationships, and purpose.</p>
|
||||
|
||||
<div class="prompt-container">
|
||||
<textarea id="group-description" placeholder="Example: Create a team of agents for a software development project including a project manager, developer, tester, and designer. They should collaborate to build web applications."></textarea>
|
||||
</div>
|
||||
|
||||
<div class="action-buttons">
|
||||
<button type="button" id="generate-profiles-btn" class="action-btn">
|
||||
<i class="fas fa-magic"></i> Generate Profiles
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Loader -->
|
||||
<div id="loader" class="loader">
|
||||
<i class="fas fa-spinner fa-spin"></i>
|
||||
<p>Generating agent profiles...</p>
|
||||
</div>
|
||||
|
||||
<!-- Step 2: Review & Select Profiles -->
|
||||
<div id="step2" class="page-section">
|
||||
<h2>Review & Select Agent Profiles</h2>
|
||||
<p>Select the agents you want to create. You can customize their details before creation.</p>
|
||||
|
||||
<div class="select-all-container">
|
||||
<label for="select-all" class="checkbox-label">
|
||||
<span class="checkbox-custom">
|
||||
<input type="checkbox" id="select-all">
|
||||
<span class="checkmark"></span>
|
||||
</span>
|
||||
Select All
|
||||
</label>
|
||||
</div>
|
||||
|
||||
<div id="agent-profiles-container">
|
||||
<!-- Agent profiles will be generated here -->
|
||||
</div>
|
||||
|
||||
<div class="action-buttons">
|
||||
<button type="button" id="back-to-step1-btn" class="nav-btn">
|
||||
<i class="fas fa-arrow-left"></i> Back
|
||||
</button>
|
||||
<button type="button" id="to-step3-btn" class="action-btn">
|
||||
Continue <i class="fas fa-arrow-right"></i>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Step 3: Common Settings -->
|
||||
<div id="step3" class="page-section">
|
||||
<h2>Configure Common Settings</h2>
|
||||
<p>Configure common settings for all selected agents. These settings will be applied to each agent.</p>
|
||||
|
||||
<form id="group-settings-form">
|
||||
<!-- Informative message about profile data -->
|
||||
<div class="info-message">
|
||||
<i class="fas fa-info-circle"></i>
|
||||
<div class="info-message-content">
|
||||
<h4>Basic Information from Profiles</h4>
|
||||
<p>The name, description, and system prompt for each agent will be taken from the profiles you selected in the previous step.</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Use the existing agent-form partial -->
|
||||
<div id="group-agent-form">
|
||||
{{template "old/views/partials/agent-form" . }}
|
||||
</div>
|
||||
</form>
|
||||
|
||||
<div class="action-buttons">
|
||||
<button type="button" id="back-to-step2-btn" class="nav-btn">
|
||||
<i class="fas fa-arrow-left"></i> Back
|
||||
</button>
|
||||
<button type="button" id="create-group-btn" class="action-btn" data-original-text="<i class='fas fa-users'></i> Create Agent Group">
|
||||
<i class="fas fa-users"></i> Create Agent Group
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Response Messages Container -->
|
||||
<div id="response-container">
|
||||
<!-- Alert messages will be shown here -->
|
||||
<div id="success-alert" class="alert alert-success" style="display: none;">
|
||||
Agents created successfully! Redirecting to agent list...
|
||||
</div>
|
||||
|
||||
<div id="error-alert" class="alert alert-error" style="display: none;">
|
||||
<span id="error-message">Error creating agents.</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Toast notification container -->
|
||||
<div id="toast" class="toast">
|
||||
<span id="toast-message"></span>
|
||||
</div>
|
||||
|
||||
<script>
|
||||
const actions = `{{ range .Actions }}<option value="{{.}}">{{.}}</option>{{ end }}`;
|
||||
const connectors = `{{ range .Connectors }}<option value="{{.}}">{{.}}</option>{{ end }}`;
|
||||
const promptBlocks = `{{ range .PromptBlocks }}<option value="{{.}}">{{.}}</option>{{ end }}`;
|
||||
|
||||
// Store generated agent profiles
|
||||
let agentProfiles = [];
|
||||
|
||||
document.addEventListener('DOMContentLoaded', function() {
|
||||
// Initialize the form components
|
||||
initAgentFormCommon({
|
||||
actions: actions,
|
||||
connectors: connectors,
|
||||
promptBlocks: promptBlocks
|
||||
});
|
||||
|
||||
// Hide the Basic Information section
|
||||
const basicSection = document.getElementById('basic-section');
|
||||
if (basicSection) {
|
||||
basicSection.style.display = 'none';
|
||||
}
|
||||
|
||||
// Update the wizard navigation items to skip Basic Information
|
||||
const basicNavItem = document.querySelector('.wizard-nav-item[data-target="basic-section"]');
|
||||
if (basicNavItem) {
|
||||
basicNavItem.style.display = 'none';
|
||||
}
|
||||
|
||||
// Make sure Connectors section is active by default
|
||||
const connectorsSection = document.getElementById('connectors-section');
|
||||
if (connectorsSection) {
|
||||
document.querySelectorAll('.form-section').forEach(section => {
|
||||
section.classList.remove('active');
|
||||
});
|
||||
connectorsSection.classList.add('active');
|
||||
}
|
||||
|
||||
// Update the active nav item
|
||||
const connectorsNavItem = document.querySelector('.wizard-nav-item[data-target="connectors-section"]');
|
||||
if (connectorsNavItem) {
|
||||
document.querySelectorAll('.wizard-nav-item').forEach(item => {
|
||||
item.classList.remove('active');
|
||||
});
|
||||
connectorsNavItem.classList.add('active');
|
||||
}
|
||||
|
||||
// Update the current step label
|
||||
const currentStepLabel = document.getElementById('currentStepLabel');
|
||||
if (currentStepLabel) {
|
||||
currentStepLabel.textContent = 'Connectors';
|
||||
}
|
||||
|
||||
// Navigation between steps
|
||||
const goToStep = (stepNumber) => {
|
||||
// Hide all steps
|
||||
document.querySelectorAll('.page-section').forEach(section => {
|
||||
section.classList.remove('active');
|
||||
});
|
||||
|
||||
// Show the target step
|
||||
document.getElementById(`step${stepNumber}`).classList.add('active');
|
||||
|
||||
// Update progress bar
|
||||
document.querySelectorAll('.progress-step').forEach(step => {
|
||||
step.classList.remove('active');
|
||||
if (parseInt(step.dataset.step) <= stepNumber) {
|
||||
step.classList.add('active');
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
// Step 1: Generate Profiles
|
||||
document.getElementById('generate-profiles-btn').addEventListener('click', function() {
|
||||
const description = document.getElementById('group-description').value.trim();
|
||||
|
||||
if (!description) {
|
||||
showToast('Please enter a description for your agent group', 'error');
|
||||
return;
|
||||
}
|
||||
|
||||
// Show loader
|
||||
document.getElementById('loader').style.display = 'block';
|
||||
document.getElementById('step1').style.display = 'none';
|
||||
|
||||
// Send request to generate profiles
|
||||
fetch('/api/agent/group/generateProfiles', {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/json'
|
||||
},
|
||||
body: JSON.stringify({ description: description })
|
||||
})
|
||||
.then(response => response.json())
|
||||
.then(data => {
|
||||
// Hide loader
|
||||
document.getElementById('loader').style.display = 'none';
|
||||
|
||||
agentProfiles = Array.isArray(data) ? data : [];
|
||||
|
||||
if (agentProfiles.length === 0) {
|
||||
showToast('No agent profiles were generated. Please try again with a more detailed description.', 'error');
|
||||
document.getElementById('step1').style.display = 'block';
|
||||
return;
|
||||
}
|
||||
|
||||
// Render agent profiles
|
||||
renderAgentProfiles();
|
||||
|
||||
// Go to step 2
|
||||
goToStep(2);
|
||||
})
|
||||
.catch(error => {
|
||||
document.getElementById('loader').style.display = 'none';
|
||||
document.getElementById('step1').style.display = 'block';
|
||||
showToast('Error generating profiles: ' + error.message, 'error');
|
||||
console.error('Error:', error);
|
||||
});
|
||||
});
|
||||
|
||||
// Render agent profiles in step 2
|
||||
function renderAgentProfiles() {
|
||||
const container = document.getElementById('agent-profiles-container');
|
||||
container.innerHTML = '';
|
||||
|
||||
agentProfiles.forEach((profile, index) => {
|
||||
const profileElement = document.createElement('div');
|
||||
profileElement.className = 'agent-profile';
|
||||
profileElement.dataset.index = index;
|
||||
profileElement.innerHTML = `
|
||||
<label class="select-checkbox checkbox-label">
|
||||
<span class="checkbox-custom">
|
||||
<input type="checkbox" class="profile-checkbox" checked>
|
||||
<span class="checkmark"></span>
|
||||
</span>
|
||||
</label>
|
||||
<h3>${profile.name}</h3>
|
||||
<div class="description">${profile.description}</div>
|
||||
<div class="system-prompt">${profile.system_prompt}</div>
|
||||
`;
|
||||
|
||||
profileElement.querySelector('.profile-checkbox').addEventListener('change', function() {
|
||||
profileElement.classList.toggle('selected', this.checked);
|
||||
updateSelectAllCheckbox();
|
||||
});
|
||||
|
||||
// Initially set as selected
|
||||
profileElement.classList.add('selected');
|
||||
|
||||
container.appendChild(profileElement);
|
||||
});
|
||||
}
|
||||
|
||||
// Select all checkbox functionality
|
||||
document.getElementById('select-all').addEventListener('change', function() {
|
||||
const isChecked = this.checked;
|
||||
document.querySelectorAll('.profile-checkbox').forEach(checkbox => {
|
||||
checkbox.checked = isChecked;
|
||||
checkbox.closest('.agent-profile').classList.toggle('selected', isChecked);
|
||||
});
|
||||
});
|
||||
|
||||
function updateSelectAllCheckbox() {
|
||||
const checkboxes = document.querySelectorAll('.profile-checkbox');
|
||||
const selectAllCheckbox = document.getElementById('select-all');
|
||||
const allChecked = Array.from(checkboxes).every(checkbox => checkbox.checked);
|
||||
const someChecked = Array.from(checkboxes).some(checkbox => checkbox.checked);
|
||||
|
||||
selectAllCheckbox.checked = allChecked;
|
||||
selectAllCheckbox.indeterminate = !allChecked && someChecked;
|
||||
}
|
||||
|
||||
// Navigation buttons
|
||||
document.getElementById('back-to-step1-btn').addEventListener('click', () => goToStep(1));
|
||||
document.getElementById('to-step3-btn').addEventListener('click', () => {
|
||||
// Check if at least one profile is selected
|
||||
const selectedProfiles = document.querySelectorAll('.profile-checkbox:checked');
|
||||
if (selectedProfiles.length === 0) {
|
||||
showToast('Please select at least one agent profile', 'error');
|
||||
return;
|
||||
}
|
||||
goToStep(3);
|
||||
});
|
||||
document.getElementById('back-to-step2-btn').addEventListener('click', () => goToStep(2));
|
||||
|
||||
// Create group button
|
||||
document.getElementById('create-group-btn').addEventListener('click', function() {
|
||||
// Get selected profiles
|
||||
const selectedProfileIndices = Array.from(document.querySelectorAll('.profile-checkbox:checked'))
|
||||
.map(checkbox => parseInt(checkbox.closest('.agent-profile').dataset.index));
|
||||
|
||||
if (selectedProfileIndices.length === 0) {
|
||||
showToast('Please select at least one agent profile', 'error');
|
||||
return;
|
||||
}
|
||||
|
||||
const selectedProfiles = selectedProfileIndices.map(index => agentProfiles[index]);
|
||||
|
||||
// Process form data for common settings
|
||||
const formData = new FormData(document.getElementById('group-settings-form'));
|
||||
const commonSettings = AgentFormUtils.processFormData(formData);
|
||||
|
||||
// Process special fields
|
||||
commonSettings.connectors = AgentFormUtils.processConnectors(this);
|
||||
if (commonSettings.connectors === null) return; // Validation failed
|
||||
|
||||
commonSettings.mcp_servers = AgentFormUtils.processMCPServers();
|
||||
|
||||
commonSettings.actions = AgentFormUtils.processActions(this);
|
||||
if (commonSettings.actions === null) return; // Validation failed
|
||||
|
||||
commonSettings.promptblocks = AgentFormUtils.processPromptBlocks(this);
|
||||
if (commonSettings.promptblocks === null) return; // Validation failed
|
||||
|
||||
// Show loading state
|
||||
const createButton = document.getElementById('create-group-btn');
|
||||
const originalButtonText = createButton.innerHTML;
|
||||
createButton.setAttribute('data-original-text', originalButtonText);
|
||||
createButton.innerHTML = '<i class="fas fa-spinner fa-spin"></i> Creating...';
|
||||
createButton.disabled = true;
|
||||
|
||||
// Create payload
|
||||
const payload = {
|
||||
agents: selectedProfiles,
|
||||
agent_config: commonSettings
|
||||
};
|
||||
|
||||
// Send request to create agents
|
||||
fetch('/api/agent/group/create', {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/json'
|
||||
},
|
||||
body: JSON.stringify(payload)
|
||||
})
|
||||
.then(response => response.json())
|
||||
.then(data => {
|
||||
const successAlert = document.getElementById('success-alert');
|
||||
const errorAlert = document.getElementById('error-alert');
|
||||
const errorMessage = document.getElementById('error-message');
|
||||
|
||||
// Hide both alerts initially
|
||||
successAlert.style.display = 'none';
|
||||
errorAlert.style.display = 'none';
|
||||
|
||||
if (data.status === "ok") {
|
||||
// Show success toast
|
||||
showToast(`${selectedProfiles.length} agent(s) created successfully!`, 'success');
|
||||
|
||||
// Show success message
|
||||
successAlert.style.display = 'block';
|
||||
|
||||
// Redirect to agent list page after a delay
|
||||
setTimeout(() => {
|
||||
window.location.href = '/old/agents';
|
||||
}, 2000);
|
||||
} else if (data.error) {
|
||||
// Show error toast
|
||||
showToast('Error: ' + data.error, 'error');
|
||||
|
||||
// Show error message
|
||||
errorMessage.textContent = data.error;
|
||||
errorAlert.style.display = 'block';
|
||||
|
||||
// Restore button state
|
||||
createButton.innerHTML = originalButtonText;
|
||||
createButton.disabled = false;
|
||||
} else {
|
||||
// Handle unexpected response format
|
||||
showToast('Unexpected response format', 'error');
|
||||
errorMessage.textContent = "Unexpected response format";
|
||||
errorAlert.style.display = 'block';
|
||||
|
||||
// Restore button state
|
||||
createButton.innerHTML = originalButtonText;
|
||||
createButton.disabled = false;
|
||||
}
|
||||
})
|
||||
.catch(error => {
|
||||
// Handle network or other errors
|
||||
showToast('Network error: ' + error.message, 'error');
|
||||
const errorAlert = document.getElementById('error-alert');
|
||||
const errorMessage = document.getElementById('error-message');
|
||||
|
||||
errorMessage.textContent = "Network error: " + error.message;
|
||||
errorAlert.style.display = 'block';
|
||||
|
||||
// Restore button state
|
||||
createButton.innerHTML = originalButtonText;
|
||||
createButton.disabled = false;
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
// Toast notification function - assuming this exists in your global scope
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
@@ -1,224 +0,0 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<title>Smart Assistant Dashboard</title>
|
||||
{{template "old/views/partials/header"}}
|
||||
<style>
|
||||
.image-container {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
margin: 40px 0;
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.image-container img {
|
||||
filter: drop-shadow(0 0 15px rgba(94, 0, 255, 0.6));
|
||||
transition: all 0.5s ease;
|
||||
}
|
||||
|
||||
.image-container:hover img {
|
||||
transform: scale(1.05);
|
||||
filter: drop-shadow(0 0 25px rgba(0, 255, 149, 0.8));
|
||||
}
|
||||
|
||||
.image-container::after {
|
||||
content: "";
|
||||
position: absolute;
|
||||
bottom: -15px;
|
||||
left: 50%;
|
||||
transform: translateX(-50%);
|
||||
width: 40%;
|
||||
height: 2px;
|
||||
background: linear-gradient(90deg, transparent, var(--primary), var(--secondary), var(--tertiary), transparent);
|
||||
}
|
||||
|
||||
.dashboard-title {
|
||||
text-align: center;
|
||||
margin-bottom: 40px;
|
||||
font-size: 2.5rem;
|
||||
letter-spacing: 2px;
|
||||
position: relative;
|
||||
display: inline-block;
|
||||
left: 50%;
|
||||
transform: translateX(-50%);
|
||||
color: var(--primary);
|
||||
text-shadow: var(--neon-glow);
|
||||
animation: gentlePulse 3s infinite;
|
||||
}
|
||||
|
||||
/* Gentle pulse animation for the title */
|
||||
@keyframes gentlePulse {
|
||||
0% { text-shadow: 0 0 7px var(--primary), 0 0 10px var(--primary); }
|
||||
50% { text-shadow: 0 0 12px var(--primary), 0 0 20px var(--primary); }
|
||||
100% { text-shadow: 0 0 7px var(--primary), 0 0 10px var(--primary); }
|
||||
}
|
||||
|
||||
/* Subtle glitch effect for hover */
|
||||
@keyframes subtleGlitch {
|
||||
0% { transform: translateX(-50%); }
|
||||
20% { transform: translateX(-50%) translate(-1px, 1px); }
|
||||
40% { transform: translateX(-50%) translate(-1px, -1px); }
|
||||
60% { transform: translateX(-50%) translate(1px, 1px); }
|
||||
80% { transform: translateX(-50%) translate(1px, -1px); }
|
||||
100% { transform: translateX(-50%); }
|
||||
}
|
||||
|
||||
.cards-container {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(auto-fill, minmax(300px, 1fr));
|
||||
gap: 30px;
|
||||
margin-top: 30px;
|
||||
}
|
||||
|
||||
.user-info {
|
||||
position: absolute;
|
||||
top: 20px;
|
||||
right: 20px;
|
||||
}
|
||||
|
||||
.dashboard-stats {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
gap: 20px;
|
||||
margin: 20px 0 40px;
|
||||
}
|
||||
|
||||
.stat-item {
|
||||
background: rgba(17, 17, 17, 0.7);
|
||||
border-radius: 8px;
|
||||
padding: 15px;
|
||||
min-width: 150px;
|
||||
text-align: center;
|
||||
position: relative;
|
||||
overflow: hidden;
|
||||
border: 1px solid rgba(94, 0, 255, 0.2);
|
||||
}
|
||||
|
||||
.stat-item::before {
|
||||
content: "";
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
width: 100%;
|
||||
height: 2px;
|
||||
background: linear-gradient(90deg, var(--primary), var(--secondary));
|
||||
opacity: 0.7;
|
||||
}
|
||||
|
||||
.stat-count {
|
||||
font-size: 2rem;
|
||||
font-weight: 700;
|
||||
color: var(--primary);
|
||||
text-shadow: var(--neon-glow);
|
||||
}
|
||||
|
||||
.stat-label {
|
||||
font-size: 0.9rem;
|
||||
color: #cccccc;
|
||||
text-transform: uppercase;
|
||||
letter-spacing: 1px;
|
||||
}
|
||||
|
||||
/* Badge positioning */
|
||||
.card {
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.badge {
|
||||
position: absolute;
|
||||
top: 15px;
|
||||
right: 15px;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
{{template "old/views/partials/menu"}}
|
||||
<div class="container">
|
||||
|
||||
|
||||
<div class="image-container">
|
||||
<img src="/old/public/logo_1.png" width="250" alt="Company Logo">
|
||||
</div>
|
||||
|
||||
<h1 class="dashboard-title">LocalAGI</h1>
|
||||
|
||||
<!-- Simple stats display -->
|
||||
<div class="dashboard-stats">
|
||||
<div class="stat-item">
|
||||
<div class="stat-count">{{.Actions}}</div>
|
||||
<div class="stat-label">Available Actions</div>
|
||||
</div>
|
||||
<div class="stat-item">
|
||||
<div class="stat-count">{{.Connectors}}</div>
|
||||
<div class="stat-label">Available Connectors</div>
|
||||
</div>
|
||||
<div class="stat-item">
|
||||
<div class="stat-count">{{ .AgentCount }}</div>
|
||||
<div class="stat-label">Agents</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="cards-container">
|
||||
<!-- Card for Agent List Page -->
|
||||
<a href="/old/agents" class="card-link">
|
||||
<div class="card">
|
||||
<h2><i class="fas fa-robot"></i> Agent List</h2>
|
||||
<p>View and manage your list of agents, including detailed profiles and statistics.</p>
|
||||
</div>
|
||||
</a>
|
||||
|
||||
<!-- Card for Create Agent -->
|
||||
<a href="/old/create" class="card-link">
|
||||
<div class="card">
|
||||
<h2><i class="fas fa-plus-circle"></i> Create Agent</h2>
|
||||
<p>Create a new intelligent agent with custom behaviors, connectors, and actions.</p>
|
||||
</div>
|
||||
</a>
|
||||
|
||||
<!-- Additional Cards for Future Features -->
|
||||
<a href="#" class="card-link">
|
||||
<div class="card">
|
||||
<h2><i class="fas fa-chart-line"></i> Analytics</h2>
|
||||
<p>View performance metrics and insights from your agent operations.</p>
|
||||
<span class="badge badge-secondary">Coming Soon</span>
|
||||
</div>
|
||||
</a>
|
||||
|
||||
<a href="#" class="card-link">
|
||||
<div class="card">
|
||||
<h2><i class="fas fa-cogs"></i> Settings</h2>
|
||||
<p>Configure system preferences and global settings for all agents.</p>
|
||||
<span class="badge badge-secondary">Coming Soon</span>
|
||||
</div>
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Toast notification container -->
|
||||
<div id="toast" class="toast">
|
||||
<span id="toast-message"></span>
|
||||
</div>
|
||||
|
||||
<script>
|
||||
// Add the controlled glitch effect to dashboard title
|
||||
document.addEventListener('DOMContentLoaded', function() {
|
||||
const title = document.querySelector('.dashboard-title');
|
||||
|
||||
title.addEventListener('mouseover', function() {
|
||||
// Use the more subtle glitch animation
|
||||
this.style.animation = 'subtleGlitch 0.5s infinite';
|
||||
});
|
||||
|
||||
title.addEventListener('mouseout', function() {
|
||||
// Return to gentle pulse animation
|
||||
this.style.animation = 'gentlePulse 3s infinite';
|
||||
});
|
||||
|
||||
// Welcome toast notification
|
||||
setTimeout(() => {
|
||||
showToast('Welcome to Smart Assistant Dashboard', 'success');
|
||||
}, 1000);
|
||||
});
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
@@ -1,215 +0,0 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
{{template "old/views/partials/header" .}}
|
||||
|
||||
<body class="bg-gradient-to-br from-gray-900 to-gray-950 text-gray-200">
|
||||
<div class="flex flex-col min-h-screen">
|
||||
|
||||
{{template "old/views/partials/menu" .}}
|
||||
|
||||
<div class="container mx-auto px-4 py-8 flex-grow flex items-center justify-center">
|
||||
<!-- Auth Card -->
|
||||
<div class="max-w-md w-full bg-gray-800/90 border border-gray-700/50 rounded-xl overflow-hidden shadow-xl">
|
||||
<div class="animation-container">
|
||||
<div class="text-overlay">
|
||||
<!-- <i class="fas fa-circle-nodes text-5xl text-blue-400 mb-2"></i> -->
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="p-8">
|
||||
<div class="text-center mb-6">
|
||||
<h2 class="text-2xl font-bold text-white">
|
||||
<span class="bg-clip-text text-transparent bg-gradient-to-r from-blue-400 to-indigo-400">
|
||||
Authorization Required
|
||||
</span>
|
||||
</h2>
|
||||
<p class="text-gray-400 mt-2">Please enter your access token to continue</p>
|
||||
</div>
|
||||
|
||||
<form id="login-form" class="space-y-6" onsubmit="login(); return false;">
|
||||
<div>
|
||||
<label for="token" class="block text-sm font-medium text-gray-300 mb-2">Access Token</label>
|
||||
<div class="relative">
|
||||
<div class="absolute inset-y-0 left-0 pl-3 flex items-center pointer-events-none">
|
||||
<i class="fas fa-key text-gray-500"></i>
|
||||
</div>
|
||||
<input
|
||||
type="password"
|
||||
id="token"
|
||||
name="token"
|
||||
placeholder="Enter your token"
|
||||
class="bg-gray-700/50 border border-gray-600 text-white placeholder-gray-400 text-sm rounded-lg focus:ring-blue-500 focus:border-blue-500 block w-full pl-10 p-2.5"
|
||||
required
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<button
|
||||
type="submit"
|
||||
class="group w-full flex items-center justify-center bg-gradient-to-r from-blue-600 to-indigo-600 hover:from-blue-700 hover:to-indigo-700 text-white py-3 px-6 rounded-lg transition duration-300 ease-in-out transform hover:scale-[1.02] hover:shadow-lg font-medium"
|
||||
>
|
||||
<i class="fas fa-sign-in-alt mr-2"></i>
|
||||
<span>Login</span>
|
||||
<i class="fas fa-arrow-right opacity-0 group-hover:opacity-100 group-hover:translate-x-2 ml-2 transition-all duration-300"></i>
|
||||
</button>
|
||||
</div>
|
||||
</form>
|
||||
|
||||
<div class="mt-8 pt-6 border-t border-gray-700/50 text-center text-sm text-gray-400">
|
||||
<div class="flex items-center justify-center mb-2">
|
||||
<i class="fas fa-shield-alt mr-2 text-blue-400"></i>
|
||||
<span>Instance is token protected</span>
|
||||
</div>
|
||||
<p>Current time (UTC): <span id="current-time">{{.CurrentDate}}</span></p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
|
||||
<script>
|
||||
function login() {
|
||||
const token = document.getElementById('token').value;
|
||||
if (!token.trim()) {
|
||||
// Show error with fading effect
|
||||
const form = document.getElementById('login-form');
|
||||
const errorMsg = document.createElement('div');
|
||||
errorMsg.className = 'p-3 mt-4 bg-red-900/50 text-red-200 rounded-lg border border-red-700/50 text-sm flex items-center';
|
||||
errorMsg.innerHTML = '<i class="fas fa-exclamation-circle mr-2"></i> Please enter a valid token';
|
||||
|
||||
// Remove any existing error message
|
||||
const existingError = form.querySelector('.bg-red-900/50');
|
||||
if (existingError) form.removeChild(existingError);
|
||||
|
||||
// Add new error message with animation
|
||||
form.appendChild(errorMsg);
|
||||
setTimeout(() => {
|
||||
errorMsg.style.opacity = '0';
|
||||
errorMsg.style.transition = 'opacity 0.5s ease';
|
||||
setTimeout(() => errorMsg.remove(), 500);
|
||||
}, 3000);
|
||||
return;
|
||||
}
|
||||
|
||||
var date = new Date();
|
||||
date.setTime(date.getTime() + (24*60*60*1000));
|
||||
document.cookie = `token=${token}; expires=${date.toGMTString()}; path=/`;
|
||||
|
||||
// Show loading state
|
||||
const button = document.querySelector('button[type="submit"]');
|
||||
const originalContent = button.innerHTML;
|
||||
button.disabled = true;
|
||||
button.innerHTML = '<i class="fas fa-spinner fa-spin mr-2"></i> Authenticating...';
|
||||
button.classList.add('bg-gray-600');
|
||||
|
||||
// Reload after short delay to show loading state
|
||||
setTimeout(() => {
|
||||
window.location.reload();
|
||||
}, 800);
|
||||
}
|
||||
|
||||
// Update current time
|
||||
function updateCurrentTime() {
|
||||
const timeElement = document.getElementById('current-time');
|
||||
if (timeElement) {
|
||||
const now = new Date();
|
||||
const year = now.getUTCFullYear();
|
||||
const month = String(now.getUTCMonth() + 1).padStart(2, '0');
|
||||
const day = String(now.getUTCDate()).padStart(2, '0');
|
||||
const hours = String(now.getUTCHours()).padStart(2, '0');
|
||||
const minutes = String(now.getUTCMinutes()).padStart(2, '0');
|
||||
const seconds = String(now.getUTCSeconds()).padStart(2, '0');
|
||||
timeElement.textContent = `${year}-${month}-${day} ${hours}:${minutes}:${seconds}`;
|
||||
}
|
||||
}
|
||||
|
||||
// Initialize current time and update it every second
|
||||
updateCurrentTime();
|
||||
setInterval(updateCurrentTime, 1000);
|
||||
|
||||
// Add subtle particle animation to the background
|
||||
document.addEventListener('DOMContentLoaded', function() {
|
||||
const animContainer = document.querySelector('.animation-container');
|
||||
if (animContainer) {
|
||||
const canvas = document.createElement('canvas');
|
||||
animContainer.appendChild(canvas);
|
||||
|
||||
const ctx = canvas.getContext('2d');
|
||||
canvas.width = animContainer.offsetWidth;
|
||||
canvas.height = animContainer.offsetHeight;
|
||||
|
||||
// Create particles
|
||||
const particles = [];
|
||||
const particleCount = 30;
|
||||
|
||||
for (let i = 0; i < particleCount; i++) {
|
||||
particles.push({
|
||||
x: Math.random() * canvas.width,
|
||||
y: Math.random() * canvas.height,
|
||||
radius: Math.random() * 3 + 1,
|
||||
color: `rgba(${Math.random() * 50 + 50}, ${Math.random() * 100 + 100}, ${Math.random() * 155 + 100}, ${Math.random() * 0.4 + 0.1})`,
|
||||
speedX: Math.random() * 0.5 - 0.25,
|
||||
speedY: Math.random() * 0.5 - 0.25
|
||||
});
|
||||
}
|
||||
|
||||
// Animation loop
|
||||
function animate() {
|
||||
requestAnimationFrame(animate);
|
||||
ctx.clearRect(0, 0, canvas.width, canvas.height);
|
||||
|
||||
particles.forEach(particle => {
|
||||
particle.x += particle.speedX;
|
||||
particle.y += particle.speedY;
|
||||
|
||||
// Bounce off edges
|
||||
if (particle.x < 0 || particle.x > canvas.width) {
|
||||
particle.speedX = -particle.speedX;
|
||||
}
|
||||
|
||||
if (particle.y < 0 || particle.y > canvas.height) {
|
||||
particle.speedY = -particle.speedY;
|
||||
}
|
||||
|
||||
// Draw particle
|
||||
ctx.beginPath();
|
||||
ctx.arc(particle.x, particle.y, particle.radius, 0, Math.PI * 2);
|
||||
ctx.fillStyle = particle.color;
|
||||
ctx.fill();
|
||||
});
|
||||
|
||||
// Connect nearby particles with lines
|
||||
for (let i = 0; i < particles.length; i++) {
|
||||
for (let j = i + 1; j < particles.length; j++) {
|
||||
const dx = particles[i].x - particles[j].x;
|
||||
const dy = particles[i].y - particles[j].y;
|
||||
const distance = Math.sqrt(dx * dx + dy * dy);
|
||||
|
||||
if (distance < 100) {
|
||||
ctx.beginPath();
|
||||
ctx.moveTo(particles[i].x, particles[i].y);
|
||||
ctx.lineTo(particles[j].x, particles[j].y);
|
||||
ctx.strokeStyle = `rgba(100, 150, 255, ${0.1 * (1 - distance / 100)})`;
|
||||
ctx.lineWidth = 1;
|
||||
ctx.stroke();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Start animation
|
||||
animate();
|
||||
|
||||
// Resize handling
|
||||
window.addEventListener('resize', () => {
|
||||
canvas.width = animContainer.offsetWidth;
|
||||
canvas.height = animContainer.offsetHeight;
|
||||
});
|
||||
}
|
||||
});
|
||||
</script>
|
||||
|
||||
</body>
|
||||
</html>
|
||||
@@ -1,307 +0,0 @@
|
||||
<div class="agent-form-container">
|
||||
<!-- Wizard Sidebar -->
|
||||
<div class="wizard-sidebar">
|
||||
<ul class="wizard-nav">
|
||||
<li class="wizard-nav-item active" data-target="basic-section">
|
||||
<i class="fas fa-info-circle"></i> Basic Information
|
||||
</li>
|
||||
<li class="wizard-nav-item" data-target="connectors-section">
|
||||
<i class="fas fa-plug"></i> Connectors
|
||||
</li>
|
||||
<li class="wizard-nav-item" data-target="actions-section">
|
||||
<i class="fas fa-bolt"></i> Actions
|
||||
</li>
|
||||
<li class="wizard-nav-item" data-target="mcp-section">
|
||||
<i class="fas fa-server"></i> MCP Servers
|
||||
</li>
|
||||
<li class="wizard-nav-item" data-target="memory-section">
|
||||
<i class="fas fa-memory"></i> Memory Settings
|
||||
</li>
|
||||
<li class="wizard-nav-item" data-target="model-section">
|
||||
<i class="fas fa-robot"></i> Model Settings
|
||||
</li>
|
||||
<li class="wizard-nav-item" data-target="prompts-section">
|
||||
<i class="fas fa-comment-alt"></i> Prompts & Goals
|
||||
</li>
|
||||
<li class="wizard-nav-item" data-target="advanced-section">
|
||||
<i class="fas fa-cogs"></i> Advanced Settings
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
<!-- Form Content Area -->
|
||||
<div class="form-content-area">
|
||||
<!-- Basic Information Section -->
|
||||
<div class="form-section active" id="basic-section">
|
||||
<h3 class="section-title">Basic Information</h3>
|
||||
|
||||
<div class="mb-4">
|
||||
<label for="name">Name</label>
|
||||
{{ if .Name }}
|
||||
<input type="text" name="name" id="name" placeholder="Name" value="{{.Name}}" readonly >
|
||||
{{ else }}
|
||||
<input type="text" name="name" id="name" placeholder="Name">
|
||||
{{ end }}
|
||||
</div>
|
||||
|
||||
<div class="mb-4">
|
||||
<label for="description">Description</label>
|
||||
<textarea name="description" id="description" placeholder="Description"></textarea>
|
||||
</div>
|
||||
|
||||
<div class="mb-4">
|
||||
<label for="identity_guidance">Identity Guidance</label>
|
||||
<textarea name="identity_guidance" id="identity_guidance" placeholder="Identity Guidance"></textarea>
|
||||
</div>
|
||||
|
||||
<div class="mb-4">
|
||||
<label for="random_identity" class="checkbox-label">
|
||||
<span class="checkbox-custom">
|
||||
<input type="checkbox" name="random_identity" id="random_identity">
|
||||
<span class="checkmark"></span>
|
||||
</span>
|
||||
Random Identity
|
||||
</label>
|
||||
</div>
|
||||
|
||||
<div class="mb-4">
|
||||
<label for="hud" class="checkbox-label">
|
||||
<span class="checkbox-custom">
|
||||
<input type="checkbox" name="hud" id="hud">
|
||||
<span class="checkmark"></span>
|
||||
</span>
|
||||
HUD
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Connectors Section -->
|
||||
<div class="form-section" id="connectors-section">
|
||||
<h3 class="section-title">Connectors</h3>
|
||||
|
||||
<div id="connectorsSection">
|
||||
<!-- Connectors will be added here dynamically -->
|
||||
</div>
|
||||
|
||||
<div class="button-container">
|
||||
<button type="button" id="addConnectorButton" class="action-btn">
|
||||
<i class="fas fa-plus-circle"></i> Add Connector
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Actions Section -->
|
||||
<div class="form-section" id="actions-section">
|
||||
<h3 class="section-title">Actions</h3>
|
||||
|
||||
<div class="mb-4" id="action_box">
|
||||
<!-- Actions will be added here dynamically -->
|
||||
</div>
|
||||
|
||||
<div class="button-container">
|
||||
<button id="action_button" type="button" class="action-btn">
|
||||
<i class="fas fa-plus-circle"></i> Add Action
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- MCP Servers Section -->
|
||||
<div class="form-section" id="mcp-section">
|
||||
<h3 class="section-title">MCP Servers</h3>
|
||||
|
||||
<div id="mcpSection">
|
||||
<!-- MCP servers will be added here dynamically -->
|
||||
</div>
|
||||
|
||||
<div class="button-container">
|
||||
<button type="button" id="addMCPButton" class="action-btn">
|
||||
<i class="fas fa-plus-circle"></i> Add MCP Server
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Memory Settings Section -->
|
||||
<div class="form-section" id="memory-section">
|
||||
<h3 class="section-title">Memory Settings</h3>
|
||||
|
||||
<div class="mb-4">
|
||||
<label for="enable_kb" class="checkbox-label">
|
||||
<span class="checkbox-custom">
|
||||
<input type="checkbox" name="enable_kb" id="enable_kb">
|
||||
<span class="checkmark"></span>
|
||||
</span>
|
||||
Enable Knowledge Base
|
||||
</label>
|
||||
</div>
|
||||
|
||||
<div class="mb-4">
|
||||
<label for="kb_results">Knowledge Base Results</label>
|
||||
<input type="number" name="kb_results" id="kb_results" placeholder="3">
|
||||
</div>
|
||||
|
||||
<div class="mb-4">
|
||||
<label for="long_term_memory" class="checkbox-label">
|
||||
<span class="checkbox-custom">
|
||||
<input type="checkbox" name="long_term_memory" id="long_term_memory">
|
||||
<span class="checkmark"></span>
|
||||
</span>
|
||||
Long Term Memory
|
||||
</label>
|
||||
</div>
|
||||
|
||||
<div class="mb-4">
|
||||
<label for="summary_long_term_memory" class="checkbox-label">
|
||||
<span class="checkbox-custom">
|
||||
<input type="checkbox" name="summary_long_term_memory" id="summary_long_term_memory">
|
||||
<span class="checkmark"></span>
|
||||
</span>
|
||||
Long Term Memory (Summarize!)
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Model Settings Section -->
|
||||
<div class="form-section" id="model-section">
|
||||
<h3 class="section-title">Model Settings</h3>
|
||||
|
||||
<div class="mb-4">
|
||||
<label for="model">Model </label>
|
||||
<input type="text" name="model" id="model" placeholder="Model name">
|
||||
</div>
|
||||
|
||||
<div class="mb-4">
|
||||
<label for="multimodal_model">Multimodal Model </label>
|
||||
<input type="text" name="multimodal_model" id="multimodal_model" placeholder="Model name">
|
||||
</div>
|
||||
|
||||
<div class="mb-4">
|
||||
<label for="api_url">API URL </label>
|
||||
<input type="text" name="api_url" id="api_url" placeholder="API URL">
|
||||
</div>
|
||||
|
||||
<div class="mb-4">
|
||||
<label for="api_key">API Key </label>
|
||||
<input type="text" name="api_key" id="api_key" placeholder="API Key">
|
||||
</div>
|
||||
|
||||
<div class="mb-4">
|
||||
<label for="local_rag_url">LocalRAG API URL </label>
|
||||
<input type="text" name="local_rag_url" id="local_rag_url" placeholder="LocalRAG API URL">
|
||||
</div>
|
||||
|
||||
<div class="mb-4">
|
||||
<label for="local_rag_api_key">LocalRAG API Key </label>
|
||||
<input type="text" name="local_rag_api_key" id="local_rag_api_key" placeholder="LocalRAG API Key">
|
||||
</div>
|
||||
|
||||
<div class="mb-4">
|
||||
<label for="enable_reasoning" class="checkbox-label">
|
||||
<span class="checkbox-custom">
|
||||
<input type="checkbox" name="enable_reasoning" id="enable_reasoning">
|
||||
<span class="checkmark"></span>
|
||||
</span>
|
||||
Enable Reasoning
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Prompts & Goals Section -->
|
||||
<div class="form-section" id="prompts-section">
|
||||
<h3 class="section-title">Prompts & Goals</h3>
|
||||
|
||||
<div class="mb-4" id="dynamic_box">
|
||||
<!-- Dynamic prompts will be added here dynamically -->
|
||||
</div>
|
||||
|
||||
<div class="button-container">
|
||||
<button id="dynamic_button" type="button" class="action-btn">
|
||||
<i class="fas fa-plus-circle"></i> Add Dynamic Prompt
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<div class="mb-4">
|
||||
<label for="system_prompt">System Prompt</label>
|
||||
<textarea name="system_prompt" id="system_prompt" placeholder="System prompt"></textarea>
|
||||
</div>
|
||||
|
||||
<div class="mb-4">
|
||||
<label for="permanent_goal">Permanent Goal</label>
|
||||
<textarea name="permanent_goal" id="permanent_goal" placeholder="Permanent goal"></textarea>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Advanced Settings Section -->
|
||||
<div class="form-section" id="advanced-section">
|
||||
<h3 class="section-title">Advanced Settings</h3>
|
||||
|
||||
<div class="mb-4">
|
||||
<label for="standalone_job" class="checkbox-label">
|
||||
<span class="checkbox-custom">
|
||||
<input type="checkbox" name="standalone_job" id="standalone_job">
|
||||
<span class="checkmark"></span>
|
||||
</span>
|
||||
Standalone Job
|
||||
</label>
|
||||
</div>
|
||||
|
||||
<div class="mb-4">
|
||||
<label for="initiate_conversations" class="checkbox-label">
|
||||
<span class="checkbox-custom">
|
||||
<input type="checkbox" name="initiate_conversations" id="initiate_conversations">
|
||||
<span class="checkmark"></span>
|
||||
</span>
|
||||
Initiate Conversations
|
||||
</label>
|
||||
</div>
|
||||
|
||||
<div class="mb-4">
|
||||
<label for="enable_planning" class="checkbox-label">
|
||||
<span class="checkbox-custom">
|
||||
<input type="checkbox" name="enable_planning" id="enable_planning">
|
||||
<span class="checkmark"></span>
|
||||
</span>
|
||||
Enable Planning
|
||||
</label>
|
||||
</div>
|
||||
|
||||
<div class="mb-4">
|
||||
<label for="can_stop_itself" class="checkbox-label">
|
||||
<span class="checkbox-custom">
|
||||
<input type="checkbox" name="can_stop_itself" id="can_stop_itself">
|
||||
<span class="checkmark"></span>
|
||||
</span>
|
||||
Can Stop Itself
|
||||
</label>
|
||||
</div>
|
||||
|
||||
<div class="mb-4">
|
||||
<label for="periodic_runs">Periodic Runs</label>
|
||||
<input type="text" name="periodic_runs" id="periodic_runs" placeholder="Periodic Runs">
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Add navigation controls at the bottom -->
|
||||
<div class="wizard-controls">
|
||||
<div class="wizard-controls-left">
|
||||
<button type="button" id="prevSection" class="nav-btn">
|
||||
<i class="fas fa-arrow-left"></i> Previous
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<div class="wizard-controls-center">
|
||||
<div class="progress-dots" id="progressDots">
|
||||
<!-- Dots will be added dynamically via JS -->
|
||||
</div>
|
||||
<div class="progress-indicator">
|
||||
<span id="currentStepLabel">Basic Information</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="wizard-controls-right">
|
||||
<button type="button" id="nextSection" class="nav-btn">
|
||||
Next <i class="fas fa-arrow-right"></i>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
@@ -1,10 +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 rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.0.0-beta3/css/all.min.css">
|
||||
<link href="https://fonts.googleapis.com/css2?family=Outfit:wght@300;400;600;700&family=Permanent+Marker&display=swap" rel="stylesheet">
|
||||
<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>
|
||||
<link rel="stylesheet" href="/old/public/css/styles.css">
|
||||
<script src="/old/public/js/common.js"></script>
|
||||
@@ -1,105 +0,0 @@
|
||||
<nav class="relative z-10 w-full" style="background-color: var(--darker-bg); border-bottom: 1px solid var(--medium-bg);">
|
||||
<div class="px-6 sm:px-8 lg:px-10">
|
||||
<div class="flex justify-between h-16 items-center">
|
||||
<div class="flex items-center">
|
||||
<!-- Logo container -->
|
||||
<div class="flex-shrink-0">
|
||||
<!-- Logo with glow effect -->
|
||||
<a href="/old" class="flex items-center group">
|
||||
<div class="relative">
|
||||
<img src="/old/public/logo_2.png" alt="Logo" class="h-10 w-auto mr-4 transition-transform duration-300 group-hover:scale-105"
|
||||
style="filter: drop-shadow(0 0 5px var(--primary));">
|
||||
<!-- Animated scan line on hover -->
|
||||
<div class="absolute inset-0 overflow-hidden opacity-0 group-hover:opacity-100 transition-opacity duration-300">
|
||||
<div class="absolute inset-0 bg-gradient-to-b from-transparent via-var(--primary) to-transparent opacity-30"
|
||||
style="height: 10px; animation: scanline 1.5s linear infinite;"></div>
|
||||
</div>
|
||||
</div>
|
||||
<span class="text-xl font-bold transition-colors duration-300"
|
||||
style="color: var(--primary); text-shadow: var(--neon-glow);">LocalAGI</span>
|
||||
</a>
|
||||
</div>
|
||||
<div class="hidden md:block ml-10">
|
||||
<div class="flex space-x-4">
|
||||
<a href="/old" class="px-3 py-2 rounded-md text-lg font-medium text-white hover:bg-gray-800 transition duration-300 relative overflow-hidden group">
|
||||
<i class="fas fa-home mr-2"></i> Home
|
||||
<!-- Underline animation -->
|
||||
<span class="absolute bottom-0 left-0 w-0 h-0.5 group-hover:w-full transition-all duration-300"
|
||||
style="background: linear-gradient(90deg, var(--primary), var(--secondary));"></span>
|
||||
</a>
|
||||
<a href="/old/agents" class="px-3 py-2 rounded-md text-lg font-medium text-gray-400 hover:bg-gray-800 transition duration-300 relative overflow-hidden group">
|
||||
<i class="fas fa-users mr-2"></i> Agent List
|
||||
<!-- Underline animation -->
|
||||
<span class="absolute bottom-0 left-0 w-0 h-0.5 group-hover:w-full transition-all duration-300"
|
||||
style="background: linear-gradient(90deg, var(--secondary), var(--tertiary));"></span>
|
||||
</a>
|
||||
<a href="/old/actions-playground" class="px-3 py-2 rounded-md text-lg font-medium text-gray-400 hover:bg-gray-800 transition duration-300 relative overflow-hidden group">
|
||||
<i class="fas fa-bolt mr-2"></i> Actions Playground
|
||||
<!-- Underline animation -->
|
||||
<span class="absolute bottom-0 left-0 w-0 h-0.5 group-hover:w-full transition-all duration-300"
|
||||
style="background: linear-gradient(90deg, var(--tertiary), var(--primary));"></span>
|
||||
</a>
|
||||
<a href="/old/group-create" class="px-3 py-2 rounded-md text-lg font-medium text-gray-400 hover:bg-gray-800 transition duration-300 relative overflow-hidden group">
|
||||
<i class="fas fa-users-cog mr-2"></i> Create Agent Group
|
||||
<!-- Underline animation -->
|
||||
<span class="absolute bottom-0 left-0 w-0 h-0.5 group-hover:w-full transition-all duration-300"
|
||||
style="background: linear-gradient(90deg, var(--secondary), var(--primary));"></span>
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="flex items-center space-x-4">
|
||||
<!-- Status badge -->
|
||||
<div class="hidden md:flex items-center">
|
||||
<span class="flex items-center text-sm">
|
||||
<span class="w-2 h-2 rounded-full mr-2"
|
||||
style="background-color: var(--primary); box-shadow: 0 0 5px var(--primary); animation: pulse 2s infinite;"></span>
|
||||
<span>State: <span style="color: var(--secondary); text-shadow: var(--pink-glow);">active</span></span>
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="md:hidden flex items-center">
|
||||
<button class="text-gray-400 hover:text-white focus:outline-none focus:text-white transition duration-300"
|
||||
style="text-shadow: var(--neon-glow);"
|
||||
onclick="toggleMobileMenu()">
|
||||
<i class="fas fa-bars fa-lg"></i>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Mobile menu, toggle based on menu state -->
|
||||
<div id="mobile-menu" class="md:hidden hidden"
|
||||
style="background-color: var(--darker-bg); border-top: 1px solid var(--medium-bg);">
|
||||
<div class="px-2 pt-2 pb-3 space-y-1">
|
||||
<a href="/old" class="block px-3 py-2 rounded-md text-base font-medium text-white hover:bg-gray-800 transition duration-300"
|
||||
style="border-left: 3px solid var(--primary);">
|
||||
<i class="fas fa-home mr-2"></i> Home
|
||||
</a>
|
||||
<a href="/old/agents" class="block px-3 py-2 rounded-md text-base font-medium text-gray-400 hover:bg-gray-800 transition duration-300"
|
||||
style="border-left: 3px solid var(--secondary);">
|
||||
<i class="fas fa-users mr-2"></i> Agent List
|
||||
</a>
|
||||
<a href="/old/actions-playground" class="px-3 py-2 rounded-md text-lg font-medium text-gray-400 hover:bg-gray-800 transition duration-300 relative overflow-hidden group">
|
||||
<i class="fas fa-bolt mr-2"></i> Actions Playground
|
||||
<!-- Underline animation -->
|
||||
<span class="absolute bottom-0 left-0 w-0 h-0.5 group-hover:w-full transition-all duration-300"
|
||||
style="background: linear-gradient(90deg, var(--tertiary), var(--primary));"></span>
|
||||
</a>
|
||||
<a href="/old/group-create" class="px-3 py-2 rounded-md text-lg font-medium text-gray-400 hover:bg-gray-800 transition duration-300 relative overflow-hidden group">
|
||||
<i class="fas fa-users-cog mr-2"></i> Create Agent Group
|
||||
<!-- Underline animation -->
|
||||
<span class="absolute bottom-0 left-0 w-0 h-0.5 group-hover:w-full transition-all duration-300"
|
||||
style="background: linear-gradient(90deg, var(--secondary), var(--primary));"></span>
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
</nav>
|
||||
<br>
|
||||
|
||||
<script>
|
||||
function toggleMobileMenu() {
|
||||
const mobileMenu = document.getElementById('mobile-menu');
|
||||
mobileMenu.classList.toggle('hidden');
|
||||
}
|
||||
</script>
|
||||
@@ -1,462 +0,0 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<title>Agent settings {{.Name}}</title>
|
||||
{{template "old/views/partials/header"}}
|
||||
<script src="/old/public/js/wizard.js"></script>
|
||||
<link rel="stylesheet" href="/old/public/css/wizard.css">
|
||||
<script src="/old/public/js/connector-templates.js"></script>
|
||||
<script src="/old/public/js/agent-form.js"></script>
|
||||
</head>
|
||||
<body>
|
||||
{{template "old/views/partials/menu"}}
|
||||
|
||||
<!-- Toast notification container -->
|
||||
<div id="toast" class="toast">
|
||||
<span id="toast-message"></span>
|
||||
</div>
|
||||
|
||||
<div class="container">
|
||||
<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">
|
||||
|
||||
<!-- Agent Configuration Form Section -->
|
||||
<div class="section-box">
|
||||
<h2>Edit Agent Configuration</h2>
|
||||
<form id="edit-agent-form">
|
||||
<input type="hidden" name="name" id="name" value="{{.Name}}">
|
||||
|
||||
{{template "old/views/partials/agent-form" .}}
|
||||
|
||||
<button type="submit" id="update-button" class="action-btn" data-original-text="<i class='fas fa-save'></i> Update Agent">
|
||||
<i class="fas fa-save"></i> Update Agent
|
||||
</button>
|
||||
</form>
|
||||
</div>
|
||||
|
||||
<div class="section-box">
|
||||
<h2>Agent Control</h2>
|
||||
<div class="button-container">
|
||||
<button
|
||||
class="action-btn toggle-btn"
|
||||
data-agent="{{.Name}}"
|
||||
data-active="{{.Status}}">
|
||||
{{if .Status}}
|
||||
<i class="fas fa-pause"></i> Pause Agent
|
||||
{{else}}
|
||||
<i class="fas fa-play"></i> Start Agent
|
||||
{{end}}
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="section-box">
|
||||
<h2>Export Data</h2>
|
||||
<p class="mb-4">Export your agent configuration for backup or transfer.</p>
|
||||
<button
|
||||
class="action-btn"
|
||||
onclick="window.location.href='/old/settings/export/{{.Name}}'">
|
||||
<i class="fas fa-file-export"></i> Export Configuration
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<div class="section-box">
|
||||
<h2>Danger Zone</h2>
|
||||
<p class="mb-4">Permanently delete this agent and all associated data. This action cannot be undone.</p>
|
||||
<button
|
||||
class="action-btn"
|
||||
style="background: linear-gradient(135deg, #ff4545, var(--secondary)); color: white;"
|
||||
hx-delete="/api/agent/{{.Name}}"
|
||||
hx-swap="none"
|
||||
data-action="delete"
|
||||
data-agent="{{.Name}}">
|
||||
<i class="fas fa-trash-alt"></i> Delete Agent
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<div class="user-info">
|
||||
<span>Agent: {{.Name}}</span>
|
||||
<span class="timestamp">Last modified: <span id="current-date"></span></span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<script>
|
||||
const actions = `{{ range .Actions }}<option value="{{.}}">{{.}}</option>{{ end }}`;
|
||||
const connectors = `{{ range .Connectors }}<option value="{{.}}">{{.}}</option>{{ end }}`;
|
||||
const promptBlocks = `{{ range .PromptBlocks }}<option value="{{.}}">{{.}}</option>{{ end }}`;
|
||||
let agentConfig = null;
|
||||
|
||||
document.addEventListener('DOMContentLoaded', function() {
|
||||
// Initialize common form components
|
||||
initAgentFormCommon({
|
||||
actions: actions,
|
||||
connectors: connectors,
|
||||
promptBlocks: promptBlocks
|
||||
});
|
||||
|
||||
// Load agent configuration when page loads
|
||||
loadAgentConfig();
|
||||
|
||||
// Add event listener for delete button
|
||||
document.querySelectorAll('[data-action="delete"]').forEach(button => {
|
||||
button.addEventListener('htmx:afterRequest', function(event) {
|
||||
handleActionResponse(event, this);
|
||||
});
|
||||
});
|
||||
|
||||
// Handle toggle button
|
||||
const toggleButton = document.querySelector('.toggle-btn');
|
||||
if (toggleButton) {
|
||||
toggleButton.addEventListener('click', function() {
|
||||
const agent = this.getAttribute('data-agent');
|
||||
const isActive = this.getAttribute('data-active') === "true";
|
||||
const endpoint = isActive ? `/api/agent/${agent}/pause` : `/api/agent/${agent}/start`;
|
||||
|
||||
// Add animation
|
||||
this.style.animation = 'pulse 0.5s';
|
||||
|
||||
// Create a new XMLHttpRequest
|
||||
const xhr = new XMLHttpRequest();
|
||||
xhr.open('PUT', endpoint);
|
||||
xhr.setRequestHeader('Content-Type', 'application/json');
|
||||
|
||||
xhr.onload = () => {
|
||||
// Clear animation
|
||||
this.style.animation = '';
|
||||
|
||||
if (xhr.status === 200) {
|
||||
try {
|
||||
const response = JSON.parse(xhr.responseText);
|
||||
|
||||
if (response.status === "ok") {
|
||||
// Toggle the button state
|
||||
const newState = !isActive;
|
||||
this.setAttribute('data-active', newState.toString());
|
||||
|
||||
// Update button text and icon
|
||||
if (newState) {
|
||||
this.innerHTML = '<i class="fas fa-pause"></i> Pause Agent';
|
||||
} else {
|
||||
this.innerHTML = '<i class="fas fa-play"></i> Start Agent';
|
||||
}
|
||||
|
||||
// Show success toast
|
||||
const action = isActive ? 'pause' : 'start';
|
||||
showToast(`Agent "${agent}" ${action}ed successfully`, 'success');
|
||||
|
||||
} else if (response.error) {
|
||||
// Show error toast
|
||||
showToast(`Error: ${response.error}`, 'error');
|
||||
}
|
||||
} catch (e) {
|
||||
// Handle parsing error
|
||||
showToast("Invalid response format", 'error');
|
||||
console.error("Error parsing response:", e);
|
||||
}
|
||||
} else {
|
||||
// Handle HTTP error
|
||||
showToast(`Server error: ${xhr.status}`, 'error');
|
||||
}
|
||||
};
|
||||
|
||||
xhr.onerror = () => {
|
||||
// Clear animation
|
||||
this.style.animation = '';
|
||||
showToast("Network error occurred", 'error');
|
||||
console.error("Network error occurred");
|
||||
};
|
||||
|
||||
// Send the request
|
||||
xhr.send(JSON.stringify({}));
|
||||
});
|
||||
}
|
||||
|
||||
// Set current date for timestamp
|
||||
const now = new Date();
|
||||
document.getElementById('current-date').textContent = now.toISOString().split('T')[0];
|
||||
|
||||
// Handle form submission for updating agent
|
||||
const form = document.getElementById('edit-agent-form');
|
||||
form.addEventListener('submit', function(e) {
|
||||
e.preventDefault();
|
||||
|
||||
// Show a loading state
|
||||
const updateButton = document.getElementById('update-button');
|
||||
const originalButtonText = updateButton.innerHTML;
|
||||
updateButton.setAttribute('data-original-text', originalButtonText);
|
||||
updateButton.innerHTML = '<i class="fas fa-spinner fa-spin"></i> Updating...';
|
||||
updateButton.disabled = true;
|
||||
|
||||
// Build a structured data object
|
||||
const formData = new FormData(form);
|
||||
const jsonData = AgentFormUtils.processFormData(formData);
|
||||
|
||||
// Process special fields
|
||||
jsonData.connectors = AgentFormUtils.processConnectors(updateButton);
|
||||
if (jsonData.connectors === null) return; // Validation failed
|
||||
|
||||
jsonData.mcp_servers = AgentFormUtils.processMCPServers();
|
||||
|
||||
jsonData.actions = AgentFormUtils.processActions(updateButton);
|
||||
if (jsonData.actions === null) return; // Validation failed
|
||||
|
||||
jsonData.promptblocks = AgentFormUtils.processPromptBlocks(updateButton);
|
||||
if (jsonData.promptblocks === null) return; // Validation failed
|
||||
|
||||
console.log('Sending data:', jsonData);
|
||||
|
||||
// Send the structured data as JSON
|
||||
fetch(`/api/agent/${jsonData.name}/config`, {
|
||||
method: 'PUT',
|
||||
headers: {
|
||||
'Content-Type': 'application/json'
|
||||
},
|
||||
body: JSON.stringify(jsonData)
|
||||
})
|
||||
.then(response => {
|
||||
if (!response.ok) {
|
||||
return response.json().then(err => {
|
||||
throw new Error(err.error || `Server error: ${response.status}`);
|
||||
});
|
||||
}
|
||||
return response.json();
|
||||
})
|
||||
.then(data => {
|
||||
// Restore button state
|
||||
updateButton.innerHTML = originalButtonText;
|
||||
updateButton.disabled = false;
|
||||
|
||||
if (data.status === "ok") {
|
||||
// Show success toast
|
||||
showToast('Agent updated successfully!', 'success');
|
||||
|
||||
// Reload agent config to get updated values
|
||||
setTimeout(() => {
|
||||
loadAgentConfig();
|
||||
}, 500);
|
||||
} else if (data.error) {
|
||||
// Show error toast
|
||||
showToast('Error: ' + data.error, 'error');
|
||||
} else {
|
||||
// Handle unexpected response format
|
||||
showToast('Unexpected response format', 'error');
|
||||
}
|
||||
})
|
||||
.catch(error => {
|
||||
// Handle network or other errors
|
||||
showToast('Error: ' + error.message, 'error');
|
||||
console.error('Update error:', error);
|
||||
|
||||
// Restore button state
|
||||
updateButton.innerHTML = originalButtonText;
|
||||
updateButton.disabled = false;
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
// Function to handle API responses for delete action
|
||||
function handleActionResponse(event, button) {
|
||||
const xhr = event.detail.xhr;
|
||||
const action = button.getAttribute('data-action');
|
||||
const agent = button.getAttribute('data-agent');
|
||||
|
||||
if (xhr.status === 200) {
|
||||
try {
|
||||
const response = JSON.parse(xhr.responseText);
|
||||
|
||||
if (response.status === "ok") {
|
||||
// Action successful
|
||||
let message = "";
|
||||
|
||||
switch(action) {
|
||||
case 'delete':
|
||||
message = `Agent "${agent}" deleted successfully`;
|
||||
// Redirect to agent list page after short delay for delete
|
||||
setTimeout(() => {
|
||||
window.location.href = "/old/agents";
|
||||
}, 2000);
|
||||
break;
|
||||
default:
|
||||
message = "Operation completed successfully";
|
||||
}
|
||||
|
||||
// Show success message
|
||||
showToast(message, 'success');
|
||||
|
||||
} else if (response.error) {
|
||||
// Show error message
|
||||
showToast(`Error: ${response.error}`, 'error');
|
||||
}
|
||||
} catch (e) {
|
||||
// Handle JSON parsing error
|
||||
showToast("Invalid response format", 'error');
|
||||
}
|
||||
} else {
|
||||
// Handle HTTP error
|
||||
showToast(`Server error: ${xhr.status}`, 'error');
|
||||
}
|
||||
}
|
||||
|
||||
// Load agent configuration from server
|
||||
function loadAgentConfig() {
|
||||
const agentName = document.getElementById('name').value;
|
||||
fetch(`/api/agent/${agentName}/config`)
|
||||
.then(response => {
|
||||
if (!response.ok) {
|
||||
throw new Error(`Failed to load agent config: ${response.status}`);
|
||||
}
|
||||
return response.json();
|
||||
})
|
||||
.then(data => {
|
||||
agentConfig = data;
|
||||
populateFormWithConfig(data);
|
||||
showToast('Agent configuration loaded', 'success');
|
||||
})
|
||||
.catch(error => {
|
||||
console.error('Error loading agent config:', error);
|
||||
showToast('Error loading agent configuration: ' + error.message, 'error');
|
||||
});
|
||||
}
|
||||
|
||||
// Populate form with agent configuration
|
||||
function populateFormWithConfig(config) {
|
||||
// Clear existing dynamic sections
|
||||
document.getElementById('connectorsSection').innerHTML = '';
|
||||
document.getElementById('mcpSection').innerHTML = '';
|
||||
document.getElementById('action_box').innerHTML = '';
|
||||
document.getElementById('dynamic_box').innerHTML = '';
|
||||
|
||||
// Populate simple fields
|
||||
document.getElementById('hud').checked = config.hud || false;
|
||||
document.getElementById('enable_kb').checked = config.enable_kb || false;
|
||||
document.getElementById('enable_reasoning').checked = config.enable_reasoning || false;
|
||||
document.getElementById('kb_results').value = config.kb_results || '';
|
||||
document.getElementById('standalone_job').checked = config.standalone_job || false;
|
||||
document.getElementById('initiate_conversations').checked = config.initiate_conversations || false;
|
||||
document.getElementById('enable_planning').checked = config.enable_planning || false;
|
||||
document.getElementById('can_stop_itself').checked = config.can_stop_itself || false;
|
||||
document.getElementById('random_identity').checked = config.random_identity || false;
|
||||
document.getElementById('long_term_memory').checked = config.long_term_memory || false;
|
||||
document.getElementById('summary_long_term_memory').checked = config.summary_long_term_memory || false;
|
||||
document.getElementById('identity_guidance').value = config.identity_guidance || '';
|
||||
document.getElementById('description').value = config.description || '';
|
||||
document.getElementById('periodic_runs').value = config.periodic_runs || '';
|
||||
document.getElementById('model').value = config.model || '';
|
||||
document.getElementById('multimodal_model').value = config.multimodal_model || '';
|
||||
document.getElementById('api_url').value = config.api_url || '';
|
||||
document.getElementById('api_key').value = config.api_key || '';
|
||||
document.getElementById('local_rag_url').value = config.local_rag_url || '';
|
||||
document.getElementById('local_rag_api_key').value = config.local_rag_token || '';
|
||||
document.getElementById('permanent_goal').value = config.permanent_goal || '';
|
||||
document.getElementById('system_prompt').value = config.system_prompt || '';
|
||||
|
||||
// Populate connectors
|
||||
if (config.connectors && Array.isArray(config.connectors)) {
|
||||
config.connectors.forEach((connector, index) => {
|
||||
// Add connector section
|
||||
document.getElementById('addConnectorButton').click();
|
||||
|
||||
// Find the added connector elements
|
||||
const connectorType = document.getElementById(`connectorType${index}`);
|
||||
|
||||
// Set values
|
||||
if (connectorType) {
|
||||
// First set the connector type
|
||||
AgentFormUtils.setSelectValue(connectorType, connector.type);
|
||||
|
||||
// Parse the config if it's a string (from backend)
|
||||
let configObj = connector.config;
|
||||
if (typeof connector.config === 'string') {
|
||||
try {
|
||||
configObj = JSON.parse(connector.config);
|
||||
} catch (e) {
|
||||
console.error('Error parsing connector config:', e);
|
||||
configObj = {}; // Fallback to empty object if parsing fails
|
||||
}
|
||||
}
|
||||
|
||||
// Now render the appropriate form for this connector type with the config values
|
||||
AgentFormUtils.renderConnectorForm(index, connector.type, configObj);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// Populate MCP servers
|
||||
if (config.mcp_servers && Array.isArray(config.mcp_servers)) {
|
||||
config.mcp_servers.forEach((server, index) => {
|
||||
// Add MCP server section
|
||||
document.getElementById('addMCPButton').click();
|
||||
|
||||
// Find the added MCP server elements
|
||||
const mcpURL = document.getElementById(`mcpURL${index}`);
|
||||
const mcpToken = document.getElementById(`mcpToken${index}`);
|
||||
|
||||
// Set values
|
||||
if (mcpURL) {
|
||||
// If server is a string (old format), use it as URL
|
||||
if (typeof server === 'string') {
|
||||
mcpURL.value = server;
|
||||
}
|
||||
// If server is an object (new format), extract URL
|
||||
else if (typeof server === 'object' && server !== null) {
|
||||
mcpURL.value = server.url || '';
|
||||
|
||||
if (mcpToken && server.token) {
|
||||
mcpToken.value = server.token;
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// Populate actions
|
||||
if (config.actions && Array.isArray(config.actions)) {
|
||||
config.actions.forEach((action, index) => {
|
||||
// Add action section
|
||||
document.getElementById('action_button').click();
|
||||
|
||||
// Find the added action elements
|
||||
const actionName = document.getElementById(`actionsName${index}`);
|
||||
const actionConfig = document.getElementById(`actionsConfig${index}`);
|
||||
|
||||
// Set values
|
||||
if (actionName) {
|
||||
AgentFormUtils.setSelectValue(actionName, action.name);
|
||||
}
|
||||
|
||||
if (actionConfig) {
|
||||
// Format the config value
|
||||
AgentFormUtils.formatConfigValue(actionConfig, action.config);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// Populate prompt blocks
|
||||
if (config.promptblocks && Array.isArray(config.promptblocks)) {
|
||||
config.promptblocks.forEach((block, index) => {
|
||||
// Add prompt block section
|
||||
document.getElementById('dynamic_button').click();
|
||||
|
||||
// Find the added prompt block elements
|
||||
const promptName = document.getElementById(`promptName${index}`);
|
||||
const promptConfig = document.getElementById(`promptConfig${index}`);
|
||||
|
||||
// Set values
|
||||
if (promptName) {
|
||||
AgentFormUtils.setSelectValue(promptName, block.name);
|
||||
}
|
||||
|
||||
if (promptConfig) {
|
||||
// Format the config value
|
||||
AgentFormUtils.formatConfigValue(promptConfig, block.config);
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
@@ -1,61 +0,0 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<title>Smart Agent status</title>
|
||||
{{template "old/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 "old/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>
|
||||
@@ -14,6 +14,7 @@ type Config struct {
|
||||
LLMAPIKey string
|
||||
LLMModel string
|
||||
StateDir string
|
||||
CustomActionsDir string
|
||||
ConversationStoreDuration time.Duration
|
||||
}
|
||||
|
||||
@@ -59,6 +60,12 @@ func WithLLMAPIKey(key string) Option {
|
||||
}
|
||||
}
|
||||
|
||||
func WithCustomActionsDir(dir string) Option {
|
||||
return func(c *Config) {
|
||||
c.CustomActionsDir = dir
|
||||
}
|
||||
}
|
||||
|
||||
func WithPool(pool *state.AgentPool) Option {
|
||||
return func(c *Config) {
|
||||
c.Pool = pool
|
||||
|
||||
+88
-210
@@ -5,65 +5,51 @@
|
||||
"name": "react-ui",
|
||||
"dependencies": {
|
||||
"highlight.js": "^11.11.1",
|
||||
"react": "^19.1.0",
|
||||
"react-dom": "^19.1.0",
|
||||
"react": "^19.2.0",
|
||||
"react-dom": "^19.2.0",
|
||||
},
|
||||
"devDependencies": {
|
||||
"@eslint/js": "^9.30.1",
|
||||
"@types/react": "^19.1.8",
|
||||
"@types/react-dom": "^19.1.6",
|
||||
"@vitejs/plugin-react": "^4.6.0",
|
||||
"eslint": "^9.30.1",
|
||||
"eslint-plugin-react-hooks": "^6.0.0",
|
||||
"eslint-plugin-react-refresh": "^0.4.20",
|
||||
"globals": "^16.3.0",
|
||||
"react-router-dom": "^7.6.3",
|
||||
"vite": "^7.0.3",
|
||||
"@eslint/js": "^9.39.1",
|
||||
"@types/react": "^19.2.2",
|
||||
"@types/react-dom": "^19.2.2",
|
||||
"@vitejs/plugin-react": "^5.1.0",
|
||||
"eslint": "^9.39.0",
|
||||
"eslint-plugin-react-hooks": "^7.0.1",
|
||||
"eslint-plugin-react-refresh": "^0.4.24",
|
||||
"globals": "^16.5.0",
|
||||
"react-router-dom": "^7.9.5",
|
||||
"vite": "^7.1.12",
|
||||
},
|
||||
},
|
||||
},
|
||||
"packages": {
|
||||
"@ampproject/remapping": ["@ampproject/remapping@2.3.0", "", { "dependencies": { "@jridgewell/gen-mapping": "^0.3.5", "@jridgewell/trace-mapping": "^0.3.24" } }, "sha512-30iZtAPgz+LTIYoeivqYo853f02jBYSd5uGnGpkFV0M3xOt9aN73erkgYAmZU43x4VfqcnLxW9Kpg3R5LC4YYw=="],
|
||||
|
||||
"@babel/code-frame": ["@babel/code-frame@7.27.1", "", { "dependencies": { "@babel/helper-validator-identifier": "^7.27.1", "js-tokens": "^4.0.0", "picocolors": "^1.1.1" } }, "sha512-cjQ7ZlQ0Mv3b47hABuTevyTuYN4i+loJKGeV9flcCgIK37cCXRh+L1bd3iBHlynerhQ7BhCkn2BPbQUL+rGqFg=="],
|
||||
|
||||
"@babel/compat-data": ["@babel/compat-data@7.27.5", "", {}, "sha512-KiRAp/VoJaWkkte84TvUd9qjdbZAdiqyvMxrGl1N6vzFogKmaLgoM3L1kgtLicp2HP5fBJS8JrZKLVIZGVJAVg=="],
|
||||
|
||||
"@babel/core": ["@babel/core@7.27.4", "", { "dependencies": { "@ampproject/remapping": "^2.2.0", "@babel/code-frame": "^7.27.1", "@babel/generator": "^7.27.3", "@babel/helper-compilation-targets": "^7.27.2", "@babel/helper-module-transforms": "^7.27.3", "@babel/helpers": "^7.27.4", "@babel/parser": "^7.27.4", "@babel/template": "^7.27.2", "@babel/traverse": "^7.27.4", "@babel/types": "^7.27.3", "convert-source-map": "^2.0.0", "debug": "^4.1.0", "gensync": "^1.0.0-beta.2", "json5": "^2.2.3", "semver": "^6.3.1" } }, "sha512-bXYxrXFubeYdvB0NhD/NBB3Qi6aZeV20GOWVI47t2dkecCEoneR4NPVcb7abpXDEvejgrUfFtG6vG/zxAKmg+g=="],
|
||||
"@babel/core": ["@babel/core@7.28.4", "", { "dependencies": { "@babel/code-frame": "^7.27.1", "@babel/generator": "^7.28.3", "@babel/helper-compilation-targets": "^7.27.2", "@babel/helper-module-transforms": "^7.28.3", "@babel/helpers": "^7.28.4", "@babel/parser": "^7.28.4", "@babel/template": "^7.27.2", "@babel/traverse": "^7.28.4", "@babel/types": "^7.28.4", "@jridgewell/remapping": "^2.3.5", "convert-source-map": "^2.0.0", "debug": "^4.1.0", "gensync": "^1.0.0-beta.2", "json5": "^2.2.3", "semver": "^6.3.1" } }, "sha512-2BCOP7TN8M+gVDj7/ht3hsaO/B/n5oDbiAyyvnRlNOs+u1o+JWNYTQrmpuNp1/Wq2gcFrI01JAW+paEKDMx/CA=="],
|
||||
|
||||
"@babel/generator": ["@babel/generator@7.27.5", "", { "dependencies": { "@babel/parser": "^7.27.5", "@babel/types": "^7.27.3", "@jridgewell/gen-mapping": "^0.3.5", "@jridgewell/trace-mapping": "^0.3.25", "jsesc": "^3.0.2" } }, "sha512-ZGhA37l0e/g2s1Cnzdix0O3aLYm66eF8aufiVteOgnwxgnRP8GoyMj7VWsgWnQbVKXyge7hqrFh2K2TQM6t1Hw=="],
|
||||
|
||||
"@babel/helper-annotate-as-pure": ["@babel/helper-annotate-as-pure@7.25.9", "", { "dependencies": { "@babel/types": "^7.25.9" } }, "sha512-gv7320KBUFJz1RnylIg5WWYPRXKZ884AGkYpgpWW02TH66Dl+HaC1t1CKd0z3R4b6hdYEcmrNZHUmfCP+1u3/g=="],
|
||||
"@babel/generator": ["@babel/generator@7.28.3", "", { "dependencies": { "@babel/parser": "^7.28.3", "@babel/types": "^7.28.2", "@jridgewell/gen-mapping": "^0.3.12", "@jridgewell/trace-mapping": "^0.3.28", "jsesc": "^3.0.2" } }, "sha512-3lSpxGgvnmZznmBkCRnVREPUFJv2wrv9iAoFDvADJc0ypmdOxdUtcLeBgBJ6zE0PMeTKnxeQzyk0xTBq4Ep7zw=="],
|
||||
|
||||
"@babel/helper-compilation-targets": ["@babel/helper-compilation-targets@7.27.2", "", { "dependencies": { "@babel/compat-data": "^7.27.2", "@babel/helper-validator-option": "^7.27.1", "browserslist": "^4.24.0", "lru-cache": "^5.1.1", "semver": "^6.3.1" } }, "sha512-2+1thGUUWWjLTYTHZWK1n8Yga0ijBz1XAhUXcKy81rd5g6yh7hGqMp45v7cadSbEHc9G3OTv45SyneRN3ps4DQ=="],
|
||||
|
||||
"@babel/helper-create-class-features-plugin": ["@babel/helper-create-class-features-plugin@7.27.0", "", { "dependencies": { "@babel/helper-annotate-as-pure": "^7.25.9", "@babel/helper-member-expression-to-functions": "^7.25.9", "@babel/helper-optimise-call-expression": "^7.25.9", "@babel/helper-replace-supers": "^7.26.5", "@babel/helper-skip-transparent-expression-wrappers": "^7.25.9", "@babel/traverse": "^7.27.0", "semver": "^6.3.1" }, "peerDependencies": { "@babel/core": "^7.0.0" } }, "sha512-vSGCvMecvFCd/BdpGlhpXYNhhC4ccxyvQWpbGL4CWbvfEoLFWUZuSuf7s9Aw70flgQF+6vptvgK2IfOnKlRmBg=="],
|
||||
|
||||
"@babel/helper-member-expression-to-functions": ["@babel/helper-member-expression-to-functions@7.25.9", "", { "dependencies": { "@babel/traverse": "^7.25.9", "@babel/types": "^7.25.9" } }, "sha512-wbfdZ9w5vk0C0oyHqAJbc62+vet5prjj01jjJ8sKn3j9h3MQQlflEdXYvuqRWjHnM12coDEqiC1IRCi0U/EKwQ=="],
|
||||
"@babel/helper-globals": ["@babel/helper-globals@7.28.0", "", {}, "sha512-+W6cISkXFa1jXsDEdYA8HeevQT/FULhxzR99pxphltZcVaugps53THCeiWA8SguxxpSp3gKPiuYfSWopkLQ4hw=="],
|
||||
|
||||
"@babel/helper-module-imports": ["@babel/helper-module-imports@7.27.1", "", { "dependencies": { "@babel/traverse": "^7.27.1", "@babel/types": "^7.27.1" } }, "sha512-0gSFWUPNXNopqtIPQvlD5WgXYI5GY2kP2cCvoT8kczjbfcfuIljTbcWrulD1CIPIX2gt1wghbDy08yE1p+/r3w=="],
|
||||
|
||||
"@babel/helper-module-transforms": ["@babel/helper-module-transforms@7.27.3", "", { "dependencies": { "@babel/helper-module-imports": "^7.27.1", "@babel/helper-validator-identifier": "^7.27.1", "@babel/traverse": "^7.27.3" }, "peerDependencies": { "@babel/core": "^7.0.0" } }, "sha512-dSOvYwvyLsWBeIRyOeHXp5vPj5l1I011r52FM1+r1jCERv+aFXYk4whgQccYEGYxK2H3ZAIA8nuPkQ0HaUo3qg=="],
|
||||
|
||||
"@babel/helper-optimise-call-expression": ["@babel/helper-optimise-call-expression@7.25.9", "", { "dependencies": { "@babel/types": "^7.25.9" } }, "sha512-FIpuNaz5ow8VyrYcnXQTDRGvV6tTjkNtCK/RYNDXGSLlUD6cBuQTSw43CShGxjvfBTfcUA/r6UhUCbtYqkhcuQ=="],
|
||||
"@babel/helper-module-transforms": ["@babel/helper-module-transforms@7.28.3", "", { "dependencies": { "@babel/helper-module-imports": "^7.27.1", "@babel/helper-validator-identifier": "^7.27.1", "@babel/traverse": "^7.28.3" }, "peerDependencies": { "@babel/core": "^7.0.0" } }, "sha512-gytXUbs8k2sXS9PnQptz5o0QnpLL51SwASIORY6XaBKF88nsOT0Zw9szLqlSGQDP/4TljBAD5y98p2U1fqkdsw=="],
|
||||
|
||||
"@babel/helper-plugin-utils": ["@babel/helper-plugin-utils@7.27.1", "", {}, "sha512-1gn1Up5YXka3YYAHGKpbideQ5Yjf1tDa9qYcgysz+cNCXukyLl6DjPXhD3VRwSb8c0J9tA4b2+rHEZtc6R0tlw=="],
|
||||
|
||||
"@babel/helper-replace-supers": ["@babel/helper-replace-supers@7.26.5", "", { "dependencies": { "@babel/helper-member-expression-to-functions": "^7.25.9", "@babel/helper-optimise-call-expression": "^7.25.9", "@babel/traverse": "^7.26.5" }, "peerDependencies": { "@babel/core": "^7.0.0" } }, "sha512-bJ6iIVdYX1YooY2X7w1q6VITt+LnUILtNk7zT78ykuwStx8BauCzxvFqFaHjOpW1bVnSUM1PN1f0p5P21wHxvg=="],
|
||||
|
||||
"@babel/helper-skip-transparent-expression-wrappers": ["@babel/helper-skip-transparent-expression-wrappers@7.25.9", "", { "dependencies": { "@babel/traverse": "^7.25.9", "@babel/types": "^7.25.9" } }, "sha512-K4Du3BFa3gvyhzgPcntrkDgZzQaq6uozzcpGbOO1OEJaI+EJdqWIMTLgFgQf6lrfiDFo5FU+BxKepI9RmZqahA=="],
|
||||
|
||||
"@babel/helper-string-parser": ["@babel/helper-string-parser@7.27.1", "", {}, "sha512-qMlSxKbpRlAridDExk92nSobyDdpPijUq2DW6oDnUqd0iOGxmQjyqhMIihI9+zv4LPyZdRje2cavWPbCbWm3eA=="],
|
||||
|
||||
"@babel/helper-validator-identifier": ["@babel/helper-validator-identifier@7.27.1", "", {}, "sha512-D2hP9eA+Sqx1kBZgzxZh0y1trbuU+JoDkiEwqhQ36nodYqJwyEIhPSdMNd7lOm/4io72luTPWH20Yda0xOuUow=="],
|
||||
|
||||
"@babel/helper-validator-option": ["@babel/helper-validator-option@7.27.1", "", {}, "sha512-YvjJow9FxbhFFKDSuFnVCe2WxXk1zWc22fFePVNEaWJEu8IrZVlda6N0uHwzZrUM1il7NC9Mlp4MaJYbYd9JSg=="],
|
||||
|
||||
"@babel/helpers": ["@babel/helpers@7.27.6", "", { "dependencies": { "@babel/template": "^7.27.2", "@babel/types": "^7.27.6" } }, "sha512-muE8Tt8M22638HU31A3CgfSUciwz1fhATfoVai05aPXGor//CdWDCbnlY1yvBPo07njuVOCNGCSp/GTt12lIug=="],
|
||||
"@babel/helpers": ["@babel/helpers@7.28.4", "", { "dependencies": { "@babel/template": "^7.27.2", "@babel/types": "^7.28.4" } }, "sha512-HFN59MmQXGHVyYadKLVumYsA9dBFun/ldYxipEjzA4196jpLZd8UjEEBLkbEkvfYreDqJhZxYAWFPtrfhNpj4w=="],
|
||||
|
||||
"@babel/parser": ["@babel/parser@7.27.0", "", { "dependencies": { "@babel/types": "^7.27.0" }, "bin": "./bin/babel-parser.js" }, "sha512-iaepho73/2Pz7w2eMS0Q5f83+0RKI7i4xmiYeBmDzfRVbQtTOG7Ts0S4HzJVsTMGI9keU8rNfuZr8DKfSt7Yyg=="],
|
||||
|
||||
"@babel/plugin-transform-private-methods": ["@babel/plugin-transform-private-methods@7.25.9", "", { "dependencies": { "@babel/helper-create-class-features-plugin": "^7.25.9", "@babel/helper-plugin-utils": "^7.25.9" }, "peerDependencies": { "@babel/core": "^7.0.0-0" } }, "sha512-D/JUozNpQLAPUVusvqMxyvjzllRaF8/nSrP1s2YGQT/W4LHK4xxsMcHjhOGTS01mp9Hda8nswb+FblLdJornQw=="],
|
||||
"@babel/parser": ["@babel/parser@7.28.4", "", { "dependencies": { "@babel/types": "^7.28.4" }, "bin": "./bin/babel-parser.js" }, "sha512-yZbBqeM6TkpP9du/I2pUZnJsRMGGvOuIrhjzC1AwHwW+6he4mni6Bp/m8ijn0iOuZuPI2BfkCoSRunpyjnrQKg=="],
|
||||
|
||||
"@babel/plugin-transform-react-jsx-self": ["@babel/plugin-transform-react-jsx-self@7.27.1", "", { "dependencies": { "@babel/helper-plugin-utils": "^7.27.1" }, "peerDependencies": { "@babel/core": "^7.0.0-0" } }, "sha512-6UzkCs+ejGdZ5mFFC/OCUrv028ab2fp1znZmCZjAOBKiBK2jXD1O+BPSfX8X2qjJ75fZBMSnQn3Rq2mrBJK2mw=="],
|
||||
|
||||
@@ -71,9 +57,9 @@
|
||||
|
||||
"@babel/template": ["@babel/template@7.27.2", "", { "dependencies": { "@babel/code-frame": "^7.27.1", "@babel/parser": "^7.27.2", "@babel/types": "^7.27.1" } }, "sha512-LPDZ85aEJyYSd18/DkjNh4/y1ntkE5KwUHWTiqgRxruuZL2F1yuHligVHLvcHY2vMHXttKFpJn6LwfI7cw7ODw=="],
|
||||
|
||||
"@babel/traverse": ["@babel/traverse@7.27.4", "", { "dependencies": { "@babel/code-frame": "^7.27.1", "@babel/generator": "^7.27.3", "@babel/parser": "^7.27.4", "@babel/template": "^7.27.2", "@babel/types": "^7.27.3", "debug": "^4.3.1", "globals": "^11.1.0" } }, "sha512-oNcu2QbHqts9BtOWJosOVJapWjBDSxGCpFvikNR5TGDYDQf3JwpIoMzIKrvfoti93cLfPJEG4tH9SPVeyCGgdA=="],
|
||||
"@babel/traverse": ["@babel/traverse@7.28.4", "", { "dependencies": { "@babel/code-frame": "^7.27.1", "@babel/generator": "^7.28.3", "@babel/helper-globals": "^7.28.0", "@babel/parser": "^7.28.4", "@babel/template": "^7.27.2", "@babel/types": "^7.28.4", "debug": "^4.3.1" } }, "sha512-YEzuboP2qvQavAcjgQNVgsvHIDv6ZpwXvcvjmyySP2DIMuByS/6ioU5G9pYrWHM6T2YDfc7xga9iNzYOs12CFQ=="],
|
||||
|
||||
"@babel/types": ["@babel/types@7.27.6", "", { "dependencies": { "@babel/helper-string-parser": "^7.27.1", "@babel/helper-validator-identifier": "^7.27.1" } }, "sha512-ETyHEk2VHHvl9b9jZP5IHPavHYk57EhanlRRuae9XCpb/j5bDCbPPMOBfCWhnl/7EDJz0jEMCi/RhccCE8r1+Q=="],
|
||||
"@babel/types": ["@babel/types@7.28.4", "", { "dependencies": { "@babel/helper-string-parser": "^7.27.1", "@babel/helper-validator-identifier": "^7.27.1" } }, "sha512-bkFqkLhh3pMBUQQkpVgWDWq/lqzc2678eUyDlTBhRqhCHFguYYGM0Efga7tYk4TogG/3x0EEl66/OQ+WGbWB/Q=="],
|
||||
|
||||
"@esbuild/aix-ppc64": ["@esbuild/aix-ppc64@0.25.2", "", { "os": "aix", "cpu": "ppc64" }, "sha512-wCIboOL2yXZym2cgm6mlA742s9QeJ8DjGVaL39dLN4rRwrOgOyYSnOaFPhKZGLb2ngj4EyfAFjsNJwPXZvseag=="],
|
||||
|
||||
@@ -125,23 +111,23 @@
|
||||
|
||||
"@esbuild/win32-x64": ["@esbuild/win32-x64@0.25.2", "", { "os": "win32", "cpu": "x64" }, "sha512-kM3HKb16VIXZyIeVrM1ygYmZBKybX8N4p754bw390wGO3Tf2j4L2/WYL+4suWujpgf6GBYs3jv7TyUivdd05JA=="],
|
||||
|
||||
"@eslint-community/eslint-utils": ["@eslint-community/eslint-utils@4.6.1", "", { "dependencies": { "eslint-visitor-keys": "^3.4.3" }, "peerDependencies": { "eslint": "^6.0.0 || ^7.0.0 || >=8.0.0" } }, "sha512-KTsJMmobmbrFLe3LDh0PC2FXpcSYJt/MLjlkh/9LEnmKYLSYmT/0EW9JWANjeoemiuZrmogti0tW5Ch+qNUYDw=="],
|
||||
"@eslint-community/eslint-utils": ["@eslint-community/eslint-utils@4.9.0", "", { "dependencies": { "eslint-visitor-keys": "^3.4.3" }, "peerDependencies": { "eslint": "^6.0.0 || ^7.0.0 || >=8.0.0" } }, "sha512-ayVFHdtZ+hsq1t2Dy24wCmGXGe4q9Gu3smhLYALJrr473ZH27MsnSL+LKUlimp4BWJqMDMLmPpx/Q9R3OAlL4g=="],
|
||||
|
||||
"@eslint-community/regexpp": ["@eslint-community/regexpp@4.12.1", "", {}, "sha512-CCZCDJuduB9OUkFkY2IgppNZMi2lBQgD2qzwXkEia16cge2pijY/aXi96CJMquDMn3nJdlPV1A5KrJEXwfLNzQ=="],
|
||||
|
||||
"@eslint/config-array": ["@eslint/config-array@0.21.0", "", { "dependencies": { "@eslint/object-schema": "^2.1.6", "debug": "^4.3.1", "minimatch": "^3.1.2" } }, "sha512-ENIdc4iLu0d93HeYirvKmrzshzofPw6VkZRKQGe9Nv46ZnWUzcF1xV01dcvEg/1wXUR61OmmlSfyeyO7EvjLxQ=="],
|
||||
"@eslint/config-array": ["@eslint/config-array@0.21.1", "", { "dependencies": { "@eslint/object-schema": "^2.1.7", "debug": "^4.3.1", "minimatch": "^3.1.2" } }, "sha512-aw1gNayWpdI/jSYVgzN5pL0cfzU02GT3NBpeT/DXbx1/1x7ZKxFPd9bwrzygx/qiwIQiJ1sw/zD8qY/kRvlGHA=="],
|
||||
|
||||
"@eslint/config-helpers": ["@eslint/config-helpers@0.3.0", "", {}, "sha512-ViuymvFmcJi04qdZeDc2whTHryouGcDlaxPqarTD0ZE10ISpxGUVZGZDx4w01upyIynL3iu6IXH2bS1NhclQMw=="],
|
||||
"@eslint/config-helpers": ["@eslint/config-helpers@0.4.2", "", { "dependencies": { "@eslint/core": "^0.17.0" } }, "sha512-gBrxN88gOIf3R7ja5K9slwNayVcZgK6SOUORm2uBzTeIEfeVaIhOpCtTox3P6R7o2jLFwLFTLnC7kU/RGcYEgw=="],
|
||||
|
||||
"@eslint/core": ["@eslint/core@0.14.0", "", { "dependencies": { "@types/json-schema": "^7.0.15" } }, "sha512-qIbV0/JZr7iSDjqAc60IqbLdsj9GDt16xQtWD+B78d/HAlvysGdZZ6rpJHGAc2T0FQx1X6thsSPdnoiGKdNtdg=="],
|
||||
"@eslint/core": ["@eslint/core@0.17.0", "", { "dependencies": { "@types/json-schema": "^7.0.15" } }, "sha512-yL/sLrpmtDaFEiUj1osRP4TI2MDz1AddJL+jZ7KSqvBuliN4xqYY54IfdN8qD8Toa6g1iloph1fxQNkjOxrrpQ=="],
|
||||
|
||||
"@eslint/eslintrc": ["@eslint/eslintrc@3.3.1", "", { "dependencies": { "ajv": "^6.12.4", "debug": "^4.3.2", "espree": "^10.0.1", "globals": "^14.0.0", "ignore": "^5.2.0", "import-fresh": "^3.2.1", "js-yaml": "^4.1.0", "minimatch": "^3.1.2", "strip-json-comments": "^3.1.1" } }, "sha512-gtF186CXhIl1p4pJNGZw8Yc6RlshoePRvE0X91oPGb3vZ8pM3qOS9W9NGPat9LziaBV7XrJWGylNQXkGcnM3IQ=="],
|
||||
|
||||
"@eslint/js": ["@eslint/js@9.30.1", "", {}, "sha512-zXhuECFlyep42KZUhWjfvsmXGX39W8K8LFb8AWXM9gSV9dQB+MrJGLKvW6Zw0Ggnbpw0VHTtrhFXYe3Gym18jg=="],
|
||||
"@eslint/js": ["@eslint/js@9.39.1", "", {}, "sha512-S26Stp4zCy88tH94QbBv3XCuzRQiZ9yXofEILmglYTh/Ug/a9/umqvgFtYBAo3Lp0nsI/5/qH1CCrbdK3AP1Tw=="],
|
||||
|
||||
"@eslint/object-schema": ["@eslint/object-schema@2.1.6", "", {}, "sha512-RBMg5FRL0I0gs51M/guSAj5/e14VQ4tpZnQNWwuDT66P14I43ItmPfIZRhO9fUVIPOAQXU47atlywZ/czoqFPA=="],
|
||||
"@eslint/object-schema": ["@eslint/object-schema@2.1.7", "", {}, "sha512-VtAOaymWVfZcmZbp6E2mympDIHvyjXs/12LqWYjVw6qjrfF+VK+fyG33kChz3nnK+SU5/NeHOqrTEHS8sXO3OA=="],
|
||||
|
||||
"@eslint/plugin-kit": ["@eslint/plugin-kit@0.3.1", "", { "dependencies": { "@eslint/core": "^0.14.0", "levn": "^0.4.1" } }, "sha512-0J+zgWxHN+xXONWIyPWKFMgVuJoZuGiIFu8yxk7RJjxkzpGmyja5wRFqZIVtjDVOQpV+Rw0iOAjYPE2eQyjr0w=="],
|
||||
"@eslint/plugin-kit": ["@eslint/plugin-kit@0.4.1", "", { "dependencies": { "@eslint/core": "^0.17.0", "levn": "^0.4.1" } }, "sha512-43/qtrDUokr7LJqoF2c3+RInu/t4zfrpYdoSDfYyhg52rwLV6TnOvdG4fXm7IkSB3wErkcmJS9iEhjVtOSEjjA=="],
|
||||
|
||||
"@humanfs/core": ["@humanfs/core@0.19.1", "", {}, "sha512-5DyQ4+1JEUzejeK1JGICcideyfUbGixgS9jNgex5nqkW+cY7WZhxBigmieN5Qnw9ZosSNVC9KQKyb+GUaGyKUA=="],
|
||||
|
||||
@@ -151,7 +137,9 @@
|
||||
|
||||
"@humanwhocodes/retry": ["@humanwhocodes/retry@0.4.2", "", {}, "sha512-xeO57FpIu4p1Ri3Jq/EXq4ClRm86dVF2z/+kvFnyqVYRavTZmaFaUBbWCOuuTh0o/g7DSsk6kc2vrS4Vl5oPOQ=="],
|
||||
|
||||
"@jridgewell/gen-mapping": ["@jridgewell/gen-mapping@0.3.8", "", { "dependencies": { "@jridgewell/set-array": "^1.2.1", "@jridgewell/sourcemap-codec": "^1.4.10", "@jridgewell/trace-mapping": "^0.3.24" } }, "sha512-imAbBGkb+ebQyxKgzv5Hu2nmROxoDOXHh80evxdoXNOrvAnVx7zimzc1Oo5h9RlfV4vPXaE2iM5pOFbvOCClWA=="],
|
||||
"@jridgewell/gen-mapping": ["@jridgewell/gen-mapping@0.3.12", "", { "dependencies": { "@jridgewell/sourcemap-codec": "^1.5.0", "@jridgewell/trace-mapping": "^0.3.24" } }, "sha512-OuLGC46TjB5BbN1dH8JULVVZY4WTdkF7tV9Ys6wLL1rubZnCMstOhNHueU5bLCrnRuDhKPDM4g6sw4Bel5Gzqg=="],
|
||||
|
||||
"@jridgewell/remapping": ["@jridgewell/remapping@2.3.5", "", { "dependencies": { "@jridgewell/gen-mapping": "^0.3.5", "@jridgewell/trace-mapping": "^0.3.24" } }, "sha512-LI9u/+laYG4Ds1TDKSJW2YPrIlcVYOwi2fUC6xB43lueCjgxV4lffOCZCtYFiH6TNOX+tQKXx97T4IKHbhyHEQ=="],
|
||||
|
||||
"@jridgewell/resolve-uri": ["@jridgewell/resolve-uri@3.1.2", "", {}, "sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw=="],
|
||||
|
||||
@@ -159,49 +147,51 @@
|
||||
|
||||
"@jridgewell/sourcemap-codec": ["@jridgewell/sourcemap-codec@1.5.0", "", {}, "sha512-gv3ZRaISU3fjPAgNsriBRqGWQL6quFx04YMPW/zD8XMLsU32mhCCbfbO6KZFLjvYpCZ8zyDEgqsgf+PwPaM7GQ=="],
|
||||
|
||||
"@jridgewell/trace-mapping": ["@jridgewell/trace-mapping@0.3.25", "", { "dependencies": { "@jridgewell/resolve-uri": "^3.1.0", "@jridgewell/sourcemap-codec": "^1.4.14" } }, "sha512-vNk6aEwybGtawWmy/PzwnGDOjCkLWSD2wqvjGGAgOAwCGWySYXfYoxt00IJkTF+8Lb57DwOb3Aa0o9CApepiYQ=="],
|
||||
"@jridgewell/trace-mapping": ["@jridgewell/trace-mapping@0.3.29", "", { "dependencies": { "@jridgewell/resolve-uri": "^3.1.0", "@jridgewell/sourcemap-codec": "^1.4.14" } }, "sha512-uw6guiW/gcAGPDhLmd77/6lW8QLeiV5RUTsAX46Db6oLhGaVj4lhnPwb184s1bkc8kdVg/+h988dro8GRDpmYQ=="],
|
||||
|
||||
"@rolldown/pluginutils": ["@rolldown/pluginutils@1.0.0-beta.19", "", {}, "sha512-3FL3mnMbPu0muGOCaKAhhFEYmqv9eTfPSJRJmANrCwtgK8VuxpsZDGK+m0LYAGoyO8+0j5uRe4PeyPDK1yA/hA=="],
|
||||
"@rolldown/pluginutils": ["@rolldown/pluginutils@1.0.0-beta.43", "", {}, "sha512-5Uxg7fQUCmfhax7FJke2+8B6cqgeUJUD9o2uXIKXhD+mG0mL6NObmVoi9wXEU1tY89mZKgAYA6fTbftx3q2ZPQ=="],
|
||||
|
||||
"@rollup/rollup-android-arm-eabi": ["@rollup/rollup-android-arm-eabi@4.40.0", "", { "os": "android", "cpu": "arm" }, "sha512-+Fbls/diZ0RDerhE8kyC6hjADCXA1K4yVNlH0EYfd2XjyH0UGgzaQ8MlT0pCXAThfxv3QUAczHaL+qSv1E4/Cg=="],
|
||||
"@rollup/rollup-android-arm-eabi": ["@rollup/rollup-android-arm-eabi@4.50.0", "", { "os": "android", "cpu": "arm" }, "sha512-lVgpeQyy4fWN5QYebtW4buT/4kn4p4IJ+kDNB4uYNT5b8c8DLJDg6titg20NIg7E8RWwdWZORW6vUFfrLyG3KQ=="],
|
||||
|
||||
"@rollup/rollup-android-arm64": ["@rollup/rollup-android-arm64@4.40.0", "", { "os": "android", "cpu": "arm64" }, "sha512-PPA6aEEsTPRz+/4xxAmaoWDqh67N7wFbgFUJGMnanCFs0TV99M0M8QhhaSCks+n6EbQoFvLQgYOGXxlMGQe/6w=="],
|
||||
"@rollup/rollup-android-arm64": ["@rollup/rollup-android-arm64@4.50.0", "", { "os": "android", "cpu": "arm64" }, "sha512-2O73dR4Dc9bp+wSYhviP6sDziurB5/HCym7xILKifWdE9UsOe2FtNcM+I4xZjKrfLJnq5UR8k9riB87gauiQtw=="],
|
||||
|
||||
"@rollup/rollup-darwin-arm64": ["@rollup/rollup-darwin-arm64@4.40.0", "", { "os": "darwin", "cpu": "arm64" }, "sha512-GwYOcOakYHdfnjjKwqpTGgn5a6cUX7+Ra2HeNj/GdXvO2VJOOXCiYYlRFU4CubFM67EhbmzLOmACKEfvp3J1kQ=="],
|
||||
"@rollup/rollup-darwin-arm64": ["@rollup/rollup-darwin-arm64@4.50.0", "", { "os": "darwin", "cpu": "arm64" }, "sha512-vwSXQN8T4sKf1RHr1F0s98Pf8UPz7pS6P3LG9NSmuw0TVh7EmaE+5Ny7hJOZ0M2yuTctEsHHRTMi2wuHkdS6Hg=="],
|
||||
|
||||
"@rollup/rollup-darwin-x64": ["@rollup/rollup-darwin-x64@4.40.0", "", { "os": "darwin", "cpu": "x64" }, "sha512-CoLEGJ+2eheqD9KBSxmma6ld01czS52Iw0e2qMZNpPDlf7Z9mj8xmMemxEucinev4LgHalDPczMyxzbq+Q+EtA=="],
|
||||
"@rollup/rollup-darwin-x64": ["@rollup/rollup-darwin-x64@4.50.0", "", { "os": "darwin", "cpu": "x64" }, "sha512-cQp/WG8HE7BCGyFVuzUg0FNmupxC+EPZEwWu2FCGGw5WDT1o2/YlENbm5e9SMvfDFR6FRhVCBePLqj0o8MN7Vw=="],
|
||||
|
||||
"@rollup/rollup-freebsd-arm64": ["@rollup/rollup-freebsd-arm64@4.40.0", "", { "os": "freebsd", "cpu": "arm64" }, "sha512-r7yGiS4HN/kibvESzmrOB/PxKMhPTlz+FcGvoUIKYoTyGd5toHp48g1uZy1o1xQvybwwpqpe010JrcGG2s5nkg=="],
|
||||
"@rollup/rollup-freebsd-arm64": ["@rollup/rollup-freebsd-arm64@4.50.0", "", { "os": "freebsd", "cpu": "arm64" }, "sha512-UR1uTJFU/p801DvvBbtDD7z9mQL8J80xB0bR7DqW7UGQHRm/OaKzp4is7sQSdbt2pjjSS72eAtRh43hNduTnnQ=="],
|
||||
|
||||
"@rollup/rollup-freebsd-x64": ["@rollup/rollup-freebsd-x64@4.40.0", "", { "os": "freebsd", "cpu": "x64" }, "sha512-mVDxzlf0oLzV3oZOr0SMJ0lSDd3xC4CmnWJ8Val8isp9jRGl5Dq//LLDSPFrasS7pSm6m5xAcKaw3sHXhBjoRw=="],
|
||||
"@rollup/rollup-freebsd-x64": ["@rollup/rollup-freebsd-x64@4.50.0", "", { "os": "freebsd", "cpu": "x64" }, "sha512-G/DKyS6PK0dD0+VEzH/6n/hWDNPDZSMBmqsElWnCRGrYOb2jC0VSupp7UAHHQ4+QILwkxSMaYIbQ72dktp8pKA=="],
|
||||
|
||||
"@rollup/rollup-linux-arm-gnueabihf": ["@rollup/rollup-linux-arm-gnueabihf@4.40.0", "", { "os": "linux", "cpu": "arm" }, "sha512-y/qUMOpJxBMy8xCXD++jeu8t7kzjlOCkoxxajL58G62PJGBZVl/Gwpm7JK9+YvlB701rcQTzjUZ1JgUoPTnoQA=="],
|
||||
"@rollup/rollup-linux-arm-gnueabihf": ["@rollup/rollup-linux-arm-gnueabihf@4.50.0", "", { "os": "linux", "cpu": "arm" }, "sha512-u72Mzc6jyJwKjJbZZcIYmd9bumJu7KNmHYdue43vT1rXPm2rITwmPWF0mmPzLm9/vJWxIRbao/jrQmxTO0Sm9w=="],
|
||||
|
||||
"@rollup/rollup-linux-arm-musleabihf": ["@rollup/rollup-linux-arm-musleabihf@4.40.0", "", { "os": "linux", "cpu": "arm" }, "sha512-GoCsPibtVdJFPv/BOIvBKO/XmwZLwaNWdyD8TKlXuqp0veo2sHE+A/vpMQ5iSArRUz/uaoj4h5S6Pn0+PdhRjg=="],
|
||||
"@rollup/rollup-linux-arm-musleabihf": ["@rollup/rollup-linux-arm-musleabihf@4.50.0", "", { "os": "linux", "cpu": "arm" }, "sha512-S4UefYdV0tnynDJV1mdkNawp0E5Qm2MtSs330IyHgaccOFrwqsvgigUD29uT+B/70PDY1eQ3t40+xf6wIvXJyg=="],
|
||||
|
||||
"@rollup/rollup-linux-arm64-gnu": ["@rollup/rollup-linux-arm64-gnu@4.40.0", "", { "os": "linux", "cpu": "arm64" }, "sha512-L5ZLphTjjAD9leJzSLI7rr8fNqJMlGDKlazW2tX4IUF9P7R5TMQPElpH82Q7eNIDQnQlAyiNVfRPfP2vM5Avvg=="],
|
||||
"@rollup/rollup-linux-arm64-gnu": ["@rollup/rollup-linux-arm64-gnu@4.50.0", "", { "os": "linux", "cpu": "arm64" }, "sha512-1EhkSvUQXJsIhk4msxP5nNAUWoB4MFDHhtc4gAYvnqoHlaL9V3F37pNHabndawsfy/Tp7BPiy/aSa6XBYbaD1g=="],
|
||||
|
||||
"@rollup/rollup-linux-arm64-musl": ["@rollup/rollup-linux-arm64-musl@4.40.0", "", { "os": "linux", "cpu": "arm64" }, "sha512-ATZvCRGCDtv1Y4gpDIXsS+wfFeFuLwVxyUBSLawjgXK2tRE6fnsQEkE4csQQYWlBlsFztRzCnBvWVfcae/1qxQ=="],
|
||||
"@rollup/rollup-linux-arm64-musl": ["@rollup/rollup-linux-arm64-musl@4.50.0", "", { "os": "linux", "cpu": "arm64" }, "sha512-EtBDIZuDtVg75xIPIK1l5vCXNNCIRM0OBPUG+tbApDuJAy9mKago6QxX+tfMzbCI6tXEhMuZuN1+CU8iDW+0UQ=="],
|
||||
|
||||
"@rollup/rollup-linux-loongarch64-gnu": ["@rollup/rollup-linux-loongarch64-gnu@4.40.0", "", { "os": "linux", "cpu": "none" }, "sha512-wG9e2XtIhd++QugU5MD9i7OnpaVb08ji3P1y/hNbxrQ3sYEelKJOq1UJ5dXczeo6Hj2rfDEL5GdtkMSVLa/AOg=="],
|
||||
"@rollup/rollup-linux-loongarch64-gnu": ["@rollup/rollup-linux-loongarch64-gnu@4.50.0", "", { "os": "linux", "cpu": "none" }, "sha512-BGYSwJdMP0hT5CCmljuSNx7+k+0upweM2M4YGfFBjnFSZMHOLYR0gEEj/dxyYJ6Zc6AiSeaBY8dWOa11GF/ppQ=="],
|
||||
|
||||
"@rollup/rollup-linux-powerpc64le-gnu": ["@rollup/rollup-linux-powerpc64le-gnu@4.40.0", "", { "os": "linux", "cpu": "ppc64" }, "sha512-vgXfWmj0f3jAUvC7TZSU/m/cOE558ILWDzS7jBhiCAFpY2WEBn5jqgbqvmzlMjtp8KlLcBlXVD2mkTSEQE6Ixw=="],
|
||||
"@rollup/rollup-linux-ppc64-gnu": ["@rollup/rollup-linux-ppc64-gnu@4.50.0", "", { "os": "linux", "cpu": "ppc64" }, "sha512-I1gSMzkVe1KzAxKAroCJL30hA4DqSi+wGc5gviD0y3IL/VkvcnAqwBf4RHXHyvH66YVHxpKO8ojrgc4SrWAnLg=="],
|
||||
|
||||
"@rollup/rollup-linux-riscv64-gnu": ["@rollup/rollup-linux-riscv64-gnu@4.40.0", "", { "os": "linux", "cpu": "none" }, "sha512-uJkYTugqtPZBS3Z136arevt/FsKTF/J9dEMTX/cwR7lsAW4bShzI2R0pJVw+hcBTWF4dxVckYh72Hk3/hWNKvA=="],
|
||||
"@rollup/rollup-linux-riscv64-gnu": ["@rollup/rollup-linux-riscv64-gnu@4.50.0", "", { "os": "linux", "cpu": "none" }, "sha512-bSbWlY3jZo7molh4tc5dKfeSxkqnf48UsLqYbUhnkdnfgZjgufLS/NTA8PcP/dnvct5CCdNkABJ56CbclMRYCA=="],
|
||||
|
||||
"@rollup/rollup-linux-riscv64-musl": ["@rollup/rollup-linux-riscv64-musl@4.40.0", "", { "os": "linux", "cpu": "none" }, "sha512-rKmSj6EXQRnhSkE22+WvrqOqRtk733x3p5sWpZilhmjnkHkpeCgWsFFo0dGnUGeA+OZjRl3+VYq+HyCOEuwcxQ=="],
|
||||
"@rollup/rollup-linux-riscv64-musl": ["@rollup/rollup-linux-riscv64-musl@4.50.0", "", { "os": "linux", "cpu": "none" }, "sha512-LSXSGumSURzEQLT2e4sFqFOv3LWZsEF8FK7AAv9zHZNDdMnUPYH3t8ZlaeYYZyTXnsob3htwTKeWtBIkPV27iQ=="],
|
||||
|
||||
"@rollup/rollup-linux-s390x-gnu": ["@rollup/rollup-linux-s390x-gnu@4.40.0", "", { "os": "linux", "cpu": "s390x" }, "sha512-SpnYlAfKPOoVsQqmTFJ0usx0z84bzGOS9anAC0AZ3rdSo3snecihbhFTlJZ8XMwzqAcodjFU4+/SM311dqE5Sw=="],
|
||||
"@rollup/rollup-linux-s390x-gnu": ["@rollup/rollup-linux-s390x-gnu@4.50.0", "", { "os": "linux", "cpu": "s390x" }, "sha512-CxRKyakfDrsLXiCyucVfVWVoaPA4oFSpPpDwlMcDFQvrv3XY6KEzMtMZrA+e/goC8xxp2WSOxHQubP8fPmmjOQ=="],
|
||||
|
||||
"@rollup/rollup-linux-x64-gnu": ["@rollup/rollup-linux-x64-gnu@4.40.0", "", { "os": "linux", "cpu": "x64" }, "sha512-RcDGMtqF9EFN8i2RYN2W+64CdHruJ5rPqrlYw+cgM3uOVPSsnAQps7cpjXe9be/yDp8UC7VLoCoKC8J3Kn2FkQ=="],
|
||||
"@rollup/rollup-linux-x64-gnu": ["@rollup/rollup-linux-x64-gnu@4.50.0", "", { "os": "linux", "cpu": "x64" }, "sha512-8PrJJA7/VU8ToHVEPu14FzuSAqVKyo5gg/J8xUerMbyNkWkO9j2ExBho/68RnJsMGNJq4zH114iAttgm7BZVkA=="],
|
||||
|
||||
"@rollup/rollup-linux-x64-musl": ["@rollup/rollup-linux-x64-musl@4.40.0", "", { "os": "linux", "cpu": "x64" }, "sha512-HZvjpiUmSNx5zFgwtQAV1GaGazT2RWvqeDi0hV+AtC8unqqDSsaFjPxfsO6qPtKRRg25SisACWnJ37Yio8ttaw=="],
|
||||
"@rollup/rollup-linux-x64-musl": ["@rollup/rollup-linux-x64-musl@4.50.0", "", { "os": "linux", "cpu": "x64" }, "sha512-SkE6YQp+CzpyOrbw7Oc4MgXFvTw2UIBElvAvLCo230pyxOLmYwRPwZ/L5lBe/VW/qT1ZgND9wJfOsdy0XptRvw=="],
|
||||
|
||||
"@rollup/rollup-win32-arm64-msvc": ["@rollup/rollup-win32-arm64-msvc@4.40.0", "", { "os": "win32", "cpu": "arm64" }, "sha512-UtZQQI5k/b8d7d3i9AZmA/t+Q4tk3hOC0tMOMSq2GlMYOfxbesxG4mJSeDp0EHs30N9bsfwUvs3zF4v/RzOeTQ=="],
|
||||
"@rollup/rollup-openharmony-arm64": ["@rollup/rollup-openharmony-arm64@4.50.0", "", { "os": "none", "cpu": "arm64" }, "sha512-PZkNLPfvXeIOgJWA804zjSFH7fARBBCpCXxgkGDRjjAhRLOR8o0IGS01ykh5GYfod4c2yiiREuDM8iZ+pVsT+Q=="],
|
||||
|
||||
"@rollup/rollup-win32-ia32-msvc": ["@rollup/rollup-win32-ia32-msvc@4.40.0", "", { "os": "win32", "cpu": "ia32" }, "sha512-+m03kvI2f5syIqHXCZLPVYplP8pQch9JHyXKZ3AGMKlg8dCyr2PKHjwRLiW53LTrN/Nc3EqHOKxUxzoSPdKddA=="],
|
||||
"@rollup/rollup-win32-arm64-msvc": ["@rollup/rollup-win32-arm64-msvc@4.50.0", "", { "os": "win32", "cpu": "arm64" }, "sha512-q7cIIdFvWQoaCbLDUyUc8YfR3Jh2xx3unO8Dn6/TTogKjfwrax9SyfmGGK6cQhKtjePI7jRfd7iRYcxYs93esg=="],
|
||||
|
||||
"@rollup/rollup-win32-x64-msvc": ["@rollup/rollup-win32-x64-msvc@4.40.0", "", { "os": "win32", "cpu": "x64" }, "sha512-lpPE1cLfP5oPzVjKMx10pgBmKELQnFJXHgvtHCtuJWOv8MxqdEIMNtgHgBFf7Ea2/7EuVwa9fodWUfXAlXZLZQ=="],
|
||||
"@rollup/rollup-win32-ia32-msvc": ["@rollup/rollup-win32-ia32-msvc@4.50.0", "", { "os": "win32", "cpu": "ia32" }, "sha512-XzNOVg/YnDOmFdDKcxxK410PrcbcqZkBmz+0FicpW5jtjKQxcW1BZJEQOF0NJa6JO7CZhett8GEtRN/wYLYJuw=="],
|
||||
|
||||
"@rollup/rollup-win32-x64-msvc": ["@rollup/rollup-win32-x64-msvc@4.50.0", "", { "os": "win32", "cpu": "x64" }, "sha512-xMmiWRR8sp72Zqwjgtf3QbZfF1wdh8X2ABu3EaozvZcyHJeU0r+XAnXdKgs4cCAp6ORoYoCygipYP1mjmbjrsg=="],
|
||||
|
||||
"@types/babel__core": ["@types/babel__core@7.20.5", "", { "dependencies": { "@babel/parser": "^7.20.7", "@babel/types": "^7.20.7", "@types/babel__generator": "*", "@types/babel__template": "*", "@types/babel__traverse": "*" } }, "sha512-qoQprZvz5wQFJwMDqeseRXWv3rqMvhgpbXFfVyWhbx9X47POIA6i/+dXefEmZKoAgOaTdaIgNSMqMIU61yRyzA=="],
|
||||
|
||||
@@ -211,15 +201,15 @@
|
||||
|
||||
"@types/babel__traverse": ["@types/babel__traverse@7.20.7", "", { "dependencies": { "@babel/types": "^7.20.7" } }, "sha512-dkO5fhS7+/oos4ciWxyEyjWe48zmG6wbCheo/G2ZnHx4fs3EU6YC6UM8rk56gAjNJ9P3MTH2jo5jb92/K6wbng=="],
|
||||
|
||||
"@types/estree": ["@types/estree@1.0.7", "", {}, "sha512-w28IoSUCJpidD/TGviZwwMJckNESJZXFu7NBZ5YJ4mEUnNraUn9Pm8HSZm/jDF1pDWYKspWE7oVphigUPRakIQ=="],
|
||||
"@types/estree": ["@types/estree@1.0.8", "", {}, "sha512-dWHzHa2WqEXI/O1E9OjrocMTKJl2mSrEolh1Iomrv6U+JuNwaHXsXx9bLu5gG7BUWFIN0skIQJQ/L1rIex4X6w=="],
|
||||
|
||||
"@types/json-schema": ["@types/json-schema@7.0.15", "", {}, "sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA=="],
|
||||
|
||||
"@types/react": ["@types/react@19.1.8", "", { "dependencies": { "csstype": "^3.0.2" } }, "sha512-AwAfQ2Wa5bCx9WP8nZL2uMZWod7J7/JSplxbTmBQ5ms6QpqNYm672H0Vu9ZVKVngQ+ii4R/byguVEUZQyeg44g=="],
|
||||
"@types/react": ["@types/react@19.2.2", "", { "dependencies": { "csstype": "^3.0.2" } }, "sha512-6mDvHUFSjyT2B2yeNx2nUgMxh9LtOWvkhIU3uePn2I2oyNymUAX1NIsdgviM4CH+JSrp2D2hsMvJOkxY+0wNRA=="],
|
||||
|
||||
"@types/react-dom": ["@types/react-dom@19.1.6", "", { "peerDependencies": { "@types/react": "^19.0.0" } }, "sha512-4hOiT/dwO8Ko0gV1m/TJZYk3y0KBnY9vzDh7W+DH17b2HFSOGgdj33dhihPeuy3l0q23+4e+hoXHV6hCC4dCXw=="],
|
||||
"@types/react-dom": ["@types/react-dom@19.2.2", "", { "peerDependencies": { "@types/react": "^19.2.0" } }, "sha512-9KQPoO6mZCi7jcIStSnlOWn2nEF3mNmyr3rIAsGnAbQKYbRLyqmeSc39EVgtxXVia+LMT8j3knZLAZAh+xLmrw=="],
|
||||
|
||||
"@vitejs/plugin-react": ["@vitejs/plugin-react@4.6.0", "", { "dependencies": { "@babel/core": "^7.27.4", "@babel/plugin-transform-react-jsx-self": "^7.27.1", "@babel/plugin-transform-react-jsx-source": "^7.27.1", "@rolldown/pluginutils": "1.0.0-beta.19", "@types/babel__core": "^7.20.5", "react-refresh": "^0.17.0" }, "peerDependencies": { "vite": "^4.2.0 || ^5.0.0 || ^6.0.0 || ^7.0.0-beta.0" } }, "sha512-5Kgff+m8e2PB+9j51eGHEpn5kUzRKH2Ry0qGoe8ItJg7pqnkPrYPkDQZGgGmTa0EGarHrkjLvOdU3b1fzI8otQ=="],
|
||||
"@vitejs/plugin-react": ["@vitejs/plugin-react@5.1.0", "", { "dependencies": { "@babel/core": "^7.28.4", "@babel/plugin-transform-react-jsx-self": "^7.27.1", "@babel/plugin-transform-react-jsx-source": "^7.27.1", "@rolldown/pluginutils": "1.0.0-beta.43", "@types/babel__core": "^7.20.5", "react-refresh": "^0.18.0" }, "peerDependencies": { "vite": "^4.2.0 || ^5.0.0 || ^6.0.0 || ^7.0.0" } }, "sha512-4LuWrg7EKWgQaMJfnN+wcmbAW+VSsCmqGohftWjuct47bv8uE4n/nPpq4XjJPsxgq00GGG5J8dvBczp8uxScew=="],
|
||||
|
||||
"acorn": ["acorn@8.15.0", "", { "bin": { "acorn": "bin/acorn" } }, "sha512-NZyJarBfL7nWwIq+FDL6Zp/yHEhePMNnnJ0y3qfieCrmNvYct8uvtiV41UvlSe6apAfk0fY1FbWx+NwfmpvtTg=="],
|
||||
|
||||
@@ -269,11 +259,11 @@
|
||||
|
||||
"escape-string-regexp": ["escape-string-regexp@4.0.0", "", {}, "sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA=="],
|
||||
|
||||
"eslint": ["eslint@9.30.1", "", { "dependencies": { "@eslint-community/eslint-utils": "^4.2.0", "@eslint-community/regexpp": "^4.12.1", "@eslint/config-array": "^0.21.0", "@eslint/config-helpers": "^0.3.0", "@eslint/core": "^0.14.0", "@eslint/eslintrc": "^3.3.1", "@eslint/js": "9.30.1", "@eslint/plugin-kit": "^0.3.1", "@humanfs/node": "^0.16.6", "@humanwhocodes/module-importer": "^1.0.1", "@humanwhocodes/retry": "^0.4.2", "@types/estree": "^1.0.6", "@types/json-schema": "^7.0.15", "ajv": "^6.12.4", "chalk": "^4.0.0", "cross-spawn": "^7.0.6", "debug": "^4.3.2", "escape-string-regexp": "^4.0.0", "eslint-scope": "^8.4.0", "eslint-visitor-keys": "^4.2.1", "espree": "^10.4.0", "esquery": "^1.5.0", "esutils": "^2.0.2", "fast-deep-equal": "^3.1.3", "file-entry-cache": "^8.0.0", "find-up": "^5.0.0", "glob-parent": "^6.0.2", "ignore": "^5.2.0", "imurmurhash": "^0.1.4", "is-glob": "^4.0.0", "json-stable-stringify-without-jsonify": "^1.0.1", "lodash.merge": "^4.6.2", "minimatch": "^3.1.2", "natural-compare": "^1.4.0", "optionator": "^0.9.3" }, "peerDependencies": { "jiti": "*" }, "optionalPeers": ["jiti"], "bin": { "eslint": "bin/eslint.js" } }, "sha512-zmxXPNMOXmwm9E0yQLi5uqXHs7uq2UIiqEKo3Gq+3fwo1XrJ+hijAZImyF7hclW3E6oHz43Yk3RP8at6OTKflQ=="],
|
||||
"eslint": ["eslint@9.39.0", "", { "dependencies": { "@eslint-community/eslint-utils": "^4.8.0", "@eslint-community/regexpp": "^4.12.1", "@eslint/config-array": "^0.21.1", "@eslint/config-helpers": "^0.4.2", "@eslint/core": "^0.17.0", "@eslint/eslintrc": "^3.3.1", "@eslint/js": "9.39.0", "@eslint/plugin-kit": "^0.4.1", "@humanfs/node": "^0.16.6", "@humanwhocodes/module-importer": "^1.0.1", "@humanwhocodes/retry": "^0.4.2", "@types/estree": "^1.0.6", "ajv": "^6.12.4", "chalk": "^4.0.0", "cross-spawn": "^7.0.6", "debug": "^4.3.2", "escape-string-regexp": "^4.0.0", "eslint-scope": "^8.4.0", "eslint-visitor-keys": "^4.2.1", "espree": "^10.4.0", "esquery": "^1.5.0", "esutils": "^2.0.2", "fast-deep-equal": "^3.1.3", "file-entry-cache": "^8.0.0", "find-up": "^5.0.0", "glob-parent": "^6.0.2", "ignore": "^5.2.0", "imurmurhash": "^0.1.4", "is-glob": "^4.0.0", "json-stable-stringify-without-jsonify": "^1.0.1", "lodash.merge": "^4.6.2", "minimatch": "^3.1.2", "natural-compare": "^1.4.0", "optionator": "^0.9.3" }, "peerDependencies": { "jiti": "*" }, "optionalPeers": ["jiti"], "bin": { "eslint": "bin/eslint.js" } }, "sha512-iy2GE3MHrYTL5lrCtMZ0X1KLEKKUjmK0kzwcnefhR66txcEmXZD2YWgR5GNdcEwkNx3a0siYkSvl0vIC+Svjmg=="],
|
||||
|
||||
"eslint-plugin-react-hooks": ["eslint-plugin-react-hooks@6.0.0", "", { "dependencies": { "@babel/core": "^7.24.4", "@babel/parser": "^7.24.4", "@babel/plugin-transform-private-methods": "^7.24.4", "hermes-parser": "^0.25.1", "zod": "^3.22.4", "zod-validation-error": "^3.0.3" }, "peerDependencies": { "eslint": "^3.0.0 || ^4.0.0 || ^5.0.0 || ^6.0.0 || ^7.0.0 || ^8.0.0-0 || ^9.0.0" } }, "sha512-NyC3yIC9fazLitYiN8eHykV5wLp/SMuUZMh+sdPSHIeN4ReXIc7if40jtGjDplAgVL/4OkN1d9gneWe9lFZgag=="],
|
||||
"eslint-plugin-react-hooks": ["eslint-plugin-react-hooks@7.0.1", "", { "dependencies": { "@babel/core": "^7.24.4", "@babel/parser": "^7.24.4", "hermes-parser": "^0.25.1", "zod": "^3.25.0 || ^4.0.0", "zod-validation-error": "^3.5.0 || ^4.0.0" }, "peerDependencies": { "eslint": "^3.0.0 || ^4.0.0 || ^5.0.0 || ^6.0.0 || ^7.0.0 || ^8.0.0-0 || ^9.0.0" } }, "sha512-O0d0m04evaNzEPoSW+59Mezf8Qt0InfgGIBJnpC0h3NH/WjUAR7BIKUfysC6todmtiZ/A0oUVS8Gce0WhBrHsA=="],
|
||||
|
||||
"eslint-plugin-react-refresh": ["eslint-plugin-react-refresh@0.4.20", "", { "peerDependencies": { "eslint": ">=8.40" } }, "sha512-XpbHQ2q5gUF8BGOX4dHe+71qoirYMhApEPZ7sfhF/dNnOF1UXnCMGZf79SFTBO7Bz5YEIT4TMieSlJBWhP9WBA=="],
|
||||
"eslint-plugin-react-refresh": ["eslint-plugin-react-refresh@0.4.24", "", { "peerDependencies": { "eslint": ">=8.40" } }, "sha512-nLHIW7TEq3aLrEYWpVaJ1dRgFR+wLDPN8e8FpYAql/bMV2oBEfC37K0gLEGgv9fy66juNShSMV8OkTqzltcG/w=="],
|
||||
|
||||
"eslint-scope": ["eslint-scope@8.4.0", "", { "dependencies": { "esrecurse": "^4.3.0", "estraverse": "^5.2.0" } }, "sha512-sNXOfKCn74rt8RICKMvJS7XKV/Xk9kA7DyJr8mJik3S7Cwgy3qlkkmyS2uQB3jiJg6VNdZd/pDBJu0nvG2NlTg=="],
|
||||
|
||||
@@ -295,7 +285,7 @@
|
||||
|
||||
"fast-levenshtein": ["fast-levenshtein@2.0.6", "", {}, "sha512-DCXu6Ifhqcks7TZKY3Hxp3y6qphY5SJZmrWMDrKcERSOXWQdMhU9Ig/PYrzyw/ul9jOIyh0N4M0tbC5hodg8dw=="],
|
||||
|
||||
"fdir": ["fdir@6.4.6", "", { "peerDependencies": { "picomatch": "^3 || ^4" }, "optionalPeers": ["picomatch"] }, "sha512-hiFoqpyZcfNm1yc4u8oWCf9A2c4D3QjCrks3zmoVKVxpQRzmPNar1hUJcBG2RQHvEVGDN+Jm81ZheVLAQMK6+w=="],
|
||||
"fdir": ["fdir@6.5.0", "", { "peerDependencies": { "picomatch": "^3 || ^4" }, "optionalPeers": ["picomatch"] }, "sha512-tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg=="],
|
||||
|
||||
"file-entry-cache": ["file-entry-cache@8.0.0", "", { "dependencies": { "flat-cache": "^4.0.0" } }, "sha512-XXTUwCvisa5oacNGRP9SfNtYBNAMi+RPwBFmblZEF7N7swHYQS6/Zfk7SRwx4D5j3CH211YNRco1DEMNVfZCnQ=="],
|
||||
|
||||
@@ -311,7 +301,7 @@
|
||||
|
||||
"glob-parent": ["glob-parent@6.0.2", "", { "dependencies": { "is-glob": "^4.0.3" } }, "sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A=="],
|
||||
|
||||
"globals": ["globals@16.3.0", "", {}, "sha512-bqWEnJ1Nt3neqx2q5SFfGS8r/ahumIakg3HcwtNlrVlwXIeNumWn/c7Pn/wKzGhf6SaW6H6uWXLqC30STCMchQ=="],
|
||||
"globals": ["globals@16.5.0", "", {}, "sha512-c/c15i26VrJ4IRt5Z89DnIzCGDn9EcebibhAOjw5ibqEHsE1wLUgkPn9RDmNcUKyU87GeaL633nyJ+pplFR2ZQ=="],
|
||||
|
||||
"has-flag": ["has-flag@4.0.0", "", {}, "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ=="],
|
||||
|
||||
@@ -381,7 +371,7 @@
|
||||
|
||||
"picocolors": ["picocolors@1.1.1", "", {}, "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA=="],
|
||||
|
||||
"picomatch": ["picomatch@4.0.2", "", {}, "sha512-M7BAV6Rlcy5u+m6oPhAPFgJTzAioX/6B0DxyvDlo9l8+T3nLKbrczg2WLUyzd45L8RqfUMyGPzekbMvX2Ldkwg=="],
|
||||
"picomatch": ["picomatch@4.0.3", "", {}, "sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q=="],
|
||||
|
||||
"postcss": ["postcss@8.5.6", "", { "dependencies": { "nanoid": "^3.3.11", "picocolors": "^1.1.1", "source-map-js": "^1.2.1" } }, "sha512-3Ybi1tAuwAP9s0r1UQ2J4n5Y0G05bJkpUIO0/bI9MhwmD70S5aTWbXGBwxHrelT+XM1k6dM0pk+SwNkpTRN7Pg=="],
|
||||
|
||||
@@ -389,21 +379,21 @@
|
||||
|
||||
"punycode": ["punycode@2.3.1", "", {}, "sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg=="],
|
||||
|
||||
"react": ["react@19.1.0", "", {}, "sha512-FS+XFBNvn3GTAWq26joslQgWNoFu08F4kl0J4CgdNKADkdSGXQyTCnKteIAJy96Br6YbpEU1LSzV5dYtjMkMDg=="],
|
||||
"react": ["react@19.2.0", "", {}, "sha512-tmbWg6W31tQLeB5cdIBOicJDJRR2KzXsV7uSK9iNfLWQ5bIZfxuPEHp7M8wiHyHnn0DD1i7w3Zmin0FtkrwoCQ=="],
|
||||
|
||||
"react-dom": ["react-dom@19.1.0", "", { "dependencies": { "scheduler": "^0.26.0" }, "peerDependencies": { "react": "^19.1.0" } }, "sha512-Xs1hdnE+DyKgeHJeJznQmYMIBG3TKIHJJT95Q58nHLSrElKlGQqDTR2HQ9fx5CN/Gk6Vh/kupBTDLU11/nDk/g=="],
|
||||
"react-dom": ["react-dom@19.2.0", "", { "dependencies": { "scheduler": "^0.27.0" }, "peerDependencies": { "react": "^19.2.0" } }, "sha512-UlbRu4cAiGaIewkPyiRGJk0imDN2T3JjieT6spoL2UeSf5od4n5LB/mQ4ejmxhCFT1tYe8IvaFulzynWovsEFQ=="],
|
||||
|
||||
"react-refresh": ["react-refresh@0.17.0", "", {}, "sha512-z6F7K9bV85EfseRCp2bzrpyQ0Gkw1uLoCel9XBVWPg/TjRj94SkJzUTGfOa4bs7iJvBWtQG0Wq7wnI0syw3EBQ=="],
|
||||
"react-refresh": ["react-refresh@0.18.0", "", {}, "sha512-QgT5//D3jfjJb6Gsjxv0Slpj23ip+HtOpnNgnb2S5zU3CB26G/IDPGoy4RJB42wzFE46DRsstbW6tKHoKbhAxw=="],
|
||||
|
||||
"react-router": ["react-router@7.6.3", "", { "dependencies": { "cookie": "^1.0.1", "set-cookie-parser": "^2.6.0" }, "peerDependencies": { "react": ">=18", "react-dom": ">=18" }, "optionalPeers": ["react-dom"] }, "sha512-zf45LZp5skDC6I3jDLXQUu0u26jtuP4lEGbc7BbdyxenBN1vJSTA18czM2D+h5qyMBuMrD+9uB+mU37HIoKGRA=="],
|
||||
"react-router": ["react-router@7.9.5", "", { "dependencies": { "cookie": "^1.0.1", "set-cookie-parser": "^2.6.0" }, "peerDependencies": { "react": ">=18", "react-dom": ">=18" }, "optionalPeers": ["react-dom"] }, "sha512-JmxqrnBZ6E9hWmf02jzNn9Jm3UqyeimyiwzD69NjxGySG6lIz/1LVPsoTCwN7NBX2XjCEa1LIX5EMz1j2b6u6A=="],
|
||||
|
||||
"react-router-dom": ["react-router-dom@7.6.3", "", { "dependencies": { "react-router": "7.6.3" }, "peerDependencies": { "react": ">=18", "react-dom": ">=18" } }, "sha512-DiWJm9qdUAmiJrVWaeJdu4TKu13+iB/8IEi0EW/XgaHCjW/vWGrwzup0GVvaMteuZjKnh5bEvJP/K0MDnzawHw=="],
|
||||
"react-router-dom": ["react-router-dom@7.9.5", "", { "dependencies": { "react-router": "7.9.5" }, "peerDependencies": { "react": ">=18", "react-dom": ">=18" } }, "sha512-mkEmq/K8tKN63Ae2M7Xgz3c9l9YNbY+NHH6NNeUmLA3kDkhKXRsNb/ZpxaEunvGo2/3YXdk5EJU3Hxp3ocaBPw=="],
|
||||
|
||||
"resolve-from": ["resolve-from@4.0.0", "", {}, "sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g=="],
|
||||
|
||||
"rollup": ["rollup@4.40.0", "", { "dependencies": { "@types/estree": "1.0.7" }, "optionalDependencies": { "@rollup/rollup-android-arm-eabi": "4.40.0", "@rollup/rollup-android-arm64": "4.40.0", "@rollup/rollup-darwin-arm64": "4.40.0", "@rollup/rollup-darwin-x64": "4.40.0", "@rollup/rollup-freebsd-arm64": "4.40.0", "@rollup/rollup-freebsd-x64": "4.40.0", "@rollup/rollup-linux-arm-gnueabihf": "4.40.0", "@rollup/rollup-linux-arm-musleabihf": "4.40.0", "@rollup/rollup-linux-arm64-gnu": "4.40.0", "@rollup/rollup-linux-arm64-musl": "4.40.0", "@rollup/rollup-linux-loongarch64-gnu": "4.40.0", "@rollup/rollup-linux-powerpc64le-gnu": "4.40.0", "@rollup/rollup-linux-riscv64-gnu": "4.40.0", "@rollup/rollup-linux-riscv64-musl": "4.40.0", "@rollup/rollup-linux-s390x-gnu": "4.40.0", "@rollup/rollup-linux-x64-gnu": "4.40.0", "@rollup/rollup-linux-x64-musl": "4.40.0", "@rollup/rollup-win32-arm64-msvc": "4.40.0", "@rollup/rollup-win32-ia32-msvc": "4.40.0", "@rollup/rollup-win32-x64-msvc": "4.40.0", "fsevents": "~2.3.2" }, "bin": { "rollup": "dist/bin/rollup" } }, "sha512-Noe455xmA96nnqH5piFtLobsGbCij7Tu+tb3c1vYjNbTkfzGqXqQXG3wJaYXkRZuQ0vEYN4bhwg7QnIrqB5B+w=="],
|
||||
"rollup": ["rollup@4.50.0", "", { "dependencies": { "@types/estree": "1.0.8" }, "optionalDependencies": { "@rollup/rollup-android-arm-eabi": "4.50.0", "@rollup/rollup-android-arm64": "4.50.0", "@rollup/rollup-darwin-arm64": "4.50.0", "@rollup/rollup-darwin-x64": "4.50.0", "@rollup/rollup-freebsd-arm64": "4.50.0", "@rollup/rollup-freebsd-x64": "4.50.0", "@rollup/rollup-linux-arm-gnueabihf": "4.50.0", "@rollup/rollup-linux-arm-musleabihf": "4.50.0", "@rollup/rollup-linux-arm64-gnu": "4.50.0", "@rollup/rollup-linux-arm64-musl": "4.50.0", "@rollup/rollup-linux-loongarch64-gnu": "4.50.0", "@rollup/rollup-linux-ppc64-gnu": "4.50.0", "@rollup/rollup-linux-riscv64-gnu": "4.50.0", "@rollup/rollup-linux-riscv64-musl": "4.50.0", "@rollup/rollup-linux-s390x-gnu": "4.50.0", "@rollup/rollup-linux-x64-gnu": "4.50.0", "@rollup/rollup-linux-x64-musl": "4.50.0", "@rollup/rollup-openharmony-arm64": "4.50.0", "@rollup/rollup-win32-arm64-msvc": "4.50.0", "@rollup/rollup-win32-ia32-msvc": "4.50.0", "@rollup/rollup-win32-x64-msvc": "4.50.0", "fsevents": "~2.3.2" }, "bin": { "rollup": "dist/bin/rollup" } }, "sha512-/Zl4D8zPifNmyGzJS+3kVoyXeDeT/GrsJM94sACNg9RtUE0hrHa1bNPtRSrfHTMH5HjRzce6K7rlTh3Khiw+pw=="],
|
||||
|
||||
"scheduler": ["scheduler@0.26.0", "", {}, "sha512-NlHwttCI/l5gCPR3D1nNXtWABUmBwvZpEQiD4IXSbIDq8BzLIK/7Ir5gTFSGZDUu37K5cMNp0hFtzO38sC7gWA=="],
|
||||
"scheduler": ["scheduler@0.27.0", "", {}, "sha512-eNv+WrVbKu1f3vbYJT/xtiF5syA5HPIMtf9IgY/nKg0sWqzAUEvqY/xm7OcZc/qafLx/iO9FgOmeSAp4v5ti/Q=="],
|
||||
|
||||
"semver": ["semver@6.3.1", "", { "bin": { "semver": "bin/semver.js" } }, "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA=="],
|
||||
|
||||
@@ -419,7 +409,7 @@
|
||||
|
||||
"supports-color": ["supports-color@7.2.0", "", { "dependencies": { "has-flag": "^4.0.0" } }, "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw=="],
|
||||
|
||||
"tinyglobby": ["tinyglobby@0.2.14", "", { "dependencies": { "fdir": "^6.4.4", "picomatch": "^4.0.2" } }, "sha512-tX5e7OM1HnYr2+a2C/4V0htOcSQcoSTH9KgJnVvNm5zm/cyEWKJ7j7YutsH9CxMdtOkkLFy2AHrMci9IM8IPZQ=="],
|
||||
"tinyglobby": ["tinyglobby@0.2.15", "", { "dependencies": { "fdir": "^6.5.0", "picomatch": "^4.0.3" } }, "sha512-j2Zq4NyQYG5XMST4cbs02Ak8iJUdxRM0XI5QyxXuZOzKOINmWurp3smXu3y5wDcJrptwpSjgXHzIQxR0omXljQ=="],
|
||||
|
||||
"type-check": ["type-check@0.4.0", "", { "dependencies": { "prelude-ls": "^1.2.1" } }, "sha512-XleUoc9uwGXqjWwXaUTZAmzMcFZ5858QA2vvx1Ur5xIcixXIP+8LnFDgRplU30us6teqdlskFfu+ae4K79Ooew=="],
|
||||
|
||||
@@ -427,7 +417,7 @@
|
||||
|
||||
"uri-js": ["uri-js@4.4.1", "", { "dependencies": { "punycode": "^2.1.0" } }, "sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg=="],
|
||||
|
||||
"vite": ["vite@7.0.3", "", { "dependencies": { "esbuild": "^0.25.0", "fdir": "^6.4.6", "picomatch": "^4.0.2", "postcss": "^8.5.6", "rollup": "^4.40.0", "tinyglobby": "^0.2.14" }, "optionalDependencies": { "fsevents": "~2.3.3" }, "peerDependencies": { "@types/node": "^20.19.0 || >=22.12.0", "jiti": ">=1.21.0", "less": "^4.0.0", "lightningcss": "^1.21.0", "sass": "^1.70.0", "sass-embedded": "^1.70.0", "stylus": ">=0.54.8", "sugarss": "^5.0.0", "terser": "^5.16.0", "tsx": "^4.8.1", "yaml": "^2.4.2" }, "optionalPeers": ["@types/node", "jiti", "less", "lightningcss", "sass", "sass-embedded", "stylus", "sugarss", "terser", "tsx", "yaml"], "bin": { "vite": "bin/vite.js" } }, "sha512-y2L5oJZF7bj4c0jgGYgBNSdIu+5HF+m68rn2cQXFbGoShdhV1phX9rbnxy9YXj82aS8MMsCLAAFkRxZeWdldrQ=="],
|
||||
"vite": ["vite@7.1.12", "", { "dependencies": { "esbuild": "^0.25.0", "fdir": "^6.5.0", "picomatch": "^4.0.3", "postcss": "^8.5.6", "rollup": "^4.43.0", "tinyglobby": "^0.2.15" }, "optionalDependencies": { "fsevents": "~2.3.3" }, "peerDependencies": { "@types/node": "^20.19.0 || >=22.12.0", "jiti": ">=1.21.0", "less": "^4.0.0", "lightningcss": "^1.21.0", "sass": "^1.70.0", "sass-embedded": "^1.70.0", "stylus": ">=0.54.8", "sugarss": "^5.0.0", "terser": "^5.16.0", "tsx": "^4.8.1", "yaml": "^2.4.2" }, "optionalPeers": ["@types/node", "jiti", "less", "lightningcss", "sass", "sass-embedded", "stylus", "sugarss", "terser", "tsx", "yaml"], "bin": { "vite": "bin/vite.js" } }, "sha512-ZWyE8YXEXqJrrSLvYgrRP7p62OziLW7xI5HYGWFzOvupfAlrLvURSzv/FyGyy0eidogEM3ujU+kUG1zuHgb6Ug=="],
|
||||
|
||||
"which": ["which@2.0.2", "", { "dependencies": { "isexe": "^2.0.0" }, "bin": { "node-which": "./bin/node-which" } }, "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA=="],
|
||||
|
||||
@@ -437,39 +427,17 @@
|
||||
|
||||
"yocto-queue": ["yocto-queue@0.1.0", "", {}, "sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q=="],
|
||||
|
||||
"zod": ["zod@3.24.3", "", {}, "sha512-HhY1oqzWCQWuUqvBFnsyrtZRhyPeR7SUGv+C4+MsisMuVfSPx8HpwWqH8tRahSlt6M3PiFAcoeFhZAqIXTxoSg=="],
|
||||
"zod": ["zod@4.1.12", "", {}, "sha512-JInaHOamG8pt5+Ey8kGmdcAcg3OL9reK8ltczgHTAwNhMys/6ThXHityHxVV2p3fkw/c+MAvBHFVYHFZDmjMCQ=="],
|
||||
|
||||
"zod-validation-error": ["zod-validation-error@3.4.0", "", { "peerDependencies": { "zod": "^3.18.0" } }, "sha512-ZOPR9SVY6Pb2qqO5XHt+MkkTRxGXb4EVtnjc9JpXUOtUB1T9Ru7mZOT361AN3MsetVe7R0a1KZshJDZdgp9miQ=="],
|
||||
"zod-validation-error": ["zod-validation-error@4.0.2", "", { "peerDependencies": { "zod": "^3.25.0 || ^4.0.0" } }, "sha512-Q6/nZLe6jxuU80qb/4uJ4t5v2VEZ44lzQjPDhYJNztRQ4wyWc6VF3D3Kb/fAuPetZQnhS3hnajCf9CsWesghLQ=="],
|
||||
|
||||
"@babel/core/@babel/parser": ["@babel/parser@7.27.5", "", { "dependencies": { "@babel/types": "^7.27.3" }, "bin": "./bin/babel-parser.js" }, "sha512-OsQd175SxWkGlzbny8J3K8TnnDD0N3lrIUtB92xwyRpzaenGZhxDvxN/JgU00U3CDZNj9tPuDJ5H0WS4Nt3vKg=="],
|
||||
"@babel/helper-module-imports/@babel/traverse": ["@babel/traverse@7.27.4", "", { "dependencies": { "@babel/code-frame": "^7.27.1", "@babel/generator": "^7.27.3", "@babel/parser": "^7.27.4", "@babel/template": "^7.27.2", "@babel/types": "^7.27.3", "debug": "^4.3.1", "globals": "^11.1.0" } }, "sha512-oNcu2QbHqts9BtOWJosOVJapWjBDSxGCpFvikNR5TGDYDQf3JwpIoMzIKrvfoti93cLfPJEG4tH9SPVeyCGgdA=="],
|
||||
|
||||
"@babel/generator/@babel/parser": ["@babel/parser@7.27.5", "", { "dependencies": { "@babel/types": "^7.27.3" }, "bin": "./bin/babel-parser.js" }, "sha512-OsQd175SxWkGlzbny8J3K8TnnDD0N3lrIUtB92xwyRpzaenGZhxDvxN/JgU00U3CDZNj9tPuDJ5H0WS4Nt3vKg=="],
|
||||
|
||||
"@babel/helper-annotate-as-pure/@babel/types": ["@babel/types@7.27.0", "", { "dependencies": { "@babel/helper-string-parser": "^7.25.9", "@babel/helper-validator-identifier": "^7.25.9" } }, "sha512-H45s8fVLYjbhFH62dIJ3WtmJ6RSPt/3DRO0ZcT2SUiYiQyz3BLVb9ADEnLl91m74aQPS3AzzeajZHYOalWe3bg=="],
|
||||
|
||||
"@babel/helper-create-class-features-plugin/@babel/traverse": ["@babel/traverse@7.27.0", "", { "dependencies": { "@babel/code-frame": "^7.26.2", "@babel/generator": "^7.27.0", "@babel/parser": "^7.27.0", "@babel/template": "^7.27.0", "@babel/types": "^7.27.0", "debug": "^4.3.1", "globals": "^11.1.0" } }, "sha512-19lYZFzYVQkkHkl4Cy4WrAVcqBkgvV2YM2TU3xG6DIwO7O3ecbDPfW3yM3bjAGcqcQHi+CCtjMR3dIEHxsd6bA=="],
|
||||
|
||||
"@babel/helper-member-expression-to-functions/@babel/traverse": ["@babel/traverse@7.27.0", "", { "dependencies": { "@babel/code-frame": "^7.26.2", "@babel/generator": "^7.27.0", "@babel/parser": "^7.27.0", "@babel/template": "^7.27.0", "@babel/types": "^7.27.0", "debug": "^4.3.1", "globals": "^11.1.0" } }, "sha512-19lYZFzYVQkkHkl4Cy4WrAVcqBkgvV2YM2TU3xG6DIwO7O3ecbDPfW3yM3bjAGcqcQHi+CCtjMR3dIEHxsd6bA=="],
|
||||
|
||||
"@babel/helper-member-expression-to-functions/@babel/types": ["@babel/types@7.27.0", "", { "dependencies": { "@babel/helper-string-parser": "^7.25.9", "@babel/helper-validator-identifier": "^7.25.9" } }, "sha512-H45s8fVLYjbhFH62dIJ3WtmJ6RSPt/3DRO0ZcT2SUiYiQyz3BLVb9ADEnLl91m74aQPS3AzzeajZHYOalWe3bg=="],
|
||||
|
||||
"@babel/helper-optimise-call-expression/@babel/types": ["@babel/types@7.27.0", "", { "dependencies": { "@babel/helper-string-parser": "^7.25.9", "@babel/helper-validator-identifier": "^7.25.9" } }, "sha512-H45s8fVLYjbhFH62dIJ3WtmJ6RSPt/3DRO0ZcT2SUiYiQyz3BLVb9ADEnLl91m74aQPS3AzzeajZHYOalWe3bg=="],
|
||||
|
||||
"@babel/helper-replace-supers/@babel/traverse": ["@babel/traverse@7.27.0", "", { "dependencies": { "@babel/code-frame": "^7.26.2", "@babel/generator": "^7.27.0", "@babel/parser": "^7.27.0", "@babel/template": "^7.27.0", "@babel/types": "^7.27.0", "debug": "^4.3.1", "globals": "^11.1.0" } }, "sha512-19lYZFzYVQkkHkl4Cy4WrAVcqBkgvV2YM2TU3xG6DIwO7O3ecbDPfW3yM3bjAGcqcQHi+CCtjMR3dIEHxsd6bA=="],
|
||||
|
||||
"@babel/helper-skip-transparent-expression-wrappers/@babel/traverse": ["@babel/traverse@7.27.0", "", { "dependencies": { "@babel/code-frame": "^7.26.2", "@babel/generator": "^7.27.0", "@babel/parser": "^7.27.0", "@babel/template": "^7.27.0", "@babel/types": "^7.27.0", "debug": "^4.3.1", "globals": "^11.1.0" } }, "sha512-19lYZFzYVQkkHkl4Cy4WrAVcqBkgvV2YM2TU3xG6DIwO7O3ecbDPfW3yM3bjAGcqcQHi+CCtjMR3dIEHxsd6bA=="],
|
||||
|
||||
"@babel/helper-skip-transparent-expression-wrappers/@babel/types": ["@babel/types@7.27.0", "", { "dependencies": { "@babel/helper-string-parser": "^7.25.9", "@babel/helper-validator-identifier": "^7.25.9" } }, "sha512-H45s8fVLYjbhFH62dIJ3WtmJ6RSPt/3DRO0ZcT2SUiYiQyz3BLVb9ADEnLl91m74aQPS3AzzeajZHYOalWe3bg=="],
|
||||
|
||||
"@babel/parser/@babel/types": ["@babel/types@7.27.0", "", { "dependencies": { "@babel/helper-string-parser": "^7.25.9", "@babel/helper-validator-identifier": "^7.25.9" } }, "sha512-H45s8fVLYjbhFH62dIJ3WtmJ6RSPt/3DRO0ZcT2SUiYiQyz3BLVb9ADEnLl91m74aQPS3AzzeajZHYOalWe3bg=="],
|
||||
|
||||
"@babel/plugin-transform-private-methods/@babel/helper-plugin-utils": ["@babel/helper-plugin-utils@7.26.5", "", {}, "sha512-RS+jZcRdZdRFzMyr+wcsaqOmld1/EqTghfaBGQQd/WnRdzdlvSZ//kF7U8VQTxf1ynZ4cjUcYgjVGx13ewNPMg=="],
|
||||
"@babel/helper-module-imports/@babel/types": ["@babel/types@7.27.6", "", { "dependencies": { "@babel/helper-string-parser": "^7.27.1", "@babel/helper-validator-identifier": "^7.27.1" } }, "sha512-ETyHEk2VHHvl9b9jZP5IHPavHYk57EhanlRRuae9XCpb/j5bDCbPPMOBfCWhnl/7EDJz0jEMCi/RhccCE8r1+Q=="],
|
||||
|
||||
"@babel/template/@babel/parser": ["@babel/parser@7.27.5", "", { "dependencies": { "@babel/types": "^7.27.3" }, "bin": "./bin/babel-parser.js" }, "sha512-OsQd175SxWkGlzbny8J3K8TnnDD0N3lrIUtB92xwyRpzaenGZhxDvxN/JgU00U3CDZNj9tPuDJ5H0WS4Nt3vKg=="],
|
||||
|
||||
"@babel/traverse/@babel/parser": ["@babel/parser@7.27.5", "", { "dependencies": { "@babel/types": "^7.27.3" }, "bin": "./bin/babel-parser.js" }, "sha512-OsQd175SxWkGlzbny8J3K8TnnDD0N3lrIUtB92xwyRpzaenGZhxDvxN/JgU00U3CDZNj9tPuDJ5H0WS4Nt3vKg=="],
|
||||
|
||||
"@babel/traverse/globals": ["globals@11.12.0", "", {}, "sha512-WOBp/EEGUiIsJSp7wcv/y6MO+lV9UoncWqxuFfm8eBwzWNgyfBd6Gz+IeKQ9jCmyhoH99g15M3T+QaVHFjizVA=="],
|
||||
"@babel/template/@babel/types": ["@babel/types@7.27.6", "", { "dependencies": { "@babel/helper-string-parser": "^7.27.1", "@babel/helper-validator-identifier": "^7.27.1" } }, "sha512-ETyHEk2VHHvl9b9jZP5IHPavHYk57EhanlRRuae9XCpb/j5bDCbPPMOBfCWhnl/7EDJz0jEMCi/RhccCE8r1+Q=="],
|
||||
|
||||
"@eslint-community/eslint-utils/eslint-visitor-keys": ["eslint-visitor-keys@3.4.3", "", {}, "sha512-wpc+LXeiyiisxPlEkUzU6svyS1frIO3Mgxj1fdy7Pm8Ygzguax2N3Fa/D/ag1WqbOprdI+uY6wMUl8/a2G+iag=="],
|
||||
|
||||
@@ -479,71 +447,25 @@
|
||||
|
||||
"@humanfs/node/@humanwhocodes/retry": ["@humanwhocodes/retry@0.3.1", "", {}, "sha512-JBxkERygn7Bv/GbN5Rv8Ul6LVknS+5Bp6RgDC/O8gEBU/yeH5Ui5C/OlWrTb6qct7LjjfT6Re2NxB0ln0yYybA=="],
|
||||
|
||||
"@types/babel__core/@babel/parser": ["@babel/parser@7.27.0", "", { "dependencies": { "@babel/types": "^7.27.0" }, "bin": "./bin/babel-parser.js" }, "sha512-iaepho73/2Pz7w2eMS0Q5f83+0RKI7i4xmiYeBmDzfRVbQtTOG7Ts0S4HzJVsTMGI9keU8rNfuZr8DKfSt7Yyg=="],
|
||||
|
||||
"@types/babel__core/@babel/types": ["@babel/types@7.27.0", "", { "dependencies": { "@babel/helper-string-parser": "^7.25.9", "@babel/helper-validator-identifier": "^7.25.9" } }, "sha512-H45s8fVLYjbhFH62dIJ3WtmJ6RSPt/3DRO0ZcT2SUiYiQyz3BLVb9ADEnLl91m74aQPS3AzzeajZHYOalWe3bg=="],
|
||||
|
||||
"@types/babel__generator/@babel/types": ["@babel/types@7.27.0", "", { "dependencies": { "@babel/helper-string-parser": "^7.25.9", "@babel/helper-validator-identifier": "^7.25.9" } }, "sha512-H45s8fVLYjbhFH62dIJ3WtmJ6RSPt/3DRO0ZcT2SUiYiQyz3BLVb9ADEnLl91m74aQPS3AzzeajZHYOalWe3bg=="],
|
||||
|
||||
"@types/babel__template/@babel/parser": ["@babel/parser@7.27.0", "", { "dependencies": { "@babel/types": "^7.27.0" }, "bin": "./bin/babel-parser.js" }, "sha512-iaepho73/2Pz7w2eMS0Q5f83+0RKI7i4xmiYeBmDzfRVbQtTOG7Ts0S4HzJVsTMGI9keU8rNfuZr8DKfSt7Yyg=="],
|
||||
|
||||
"@types/babel__template/@babel/types": ["@babel/types@7.27.0", "", { "dependencies": { "@babel/helper-string-parser": "^7.25.9", "@babel/helper-validator-identifier": "^7.25.9" } }, "sha512-H45s8fVLYjbhFH62dIJ3WtmJ6RSPt/3DRO0ZcT2SUiYiQyz3BLVb9ADEnLl91m74aQPS3AzzeajZHYOalWe3bg=="],
|
||||
|
||||
"@types/babel__traverse/@babel/types": ["@babel/types@7.27.0", "", { "dependencies": { "@babel/helper-string-parser": "^7.25.9", "@babel/helper-validator-identifier": "^7.25.9" } }, "sha512-H45s8fVLYjbhFH62dIJ3WtmJ6RSPt/3DRO0ZcT2SUiYiQyz3BLVb9ADEnLl91m74aQPS3AzzeajZHYOalWe3bg=="],
|
||||
|
||||
"eslint-plugin-react-hooks/@babel/core": ["@babel/core@7.26.10", "", { "dependencies": { "@ampproject/remapping": "^2.2.0", "@babel/code-frame": "^7.26.2", "@babel/generator": "^7.26.10", "@babel/helper-compilation-targets": "^7.26.5", "@babel/helper-module-transforms": "^7.26.0", "@babel/helpers": "^7.26.10", "@babel/parser": "^7.26.10", "@babel/template": "^7.26.9", "@babel/traverse": "^7.26.10", "@babel/types": "^7.26.10", "convert-source-map": "^2.0.0", "debug": "^4.1.0", "gensync": "^1.0.0-beta.2", "json5": "^2.2.3", "semver": "^6.3.1" } }, "sha512-vMqyb7XCDMPvJFFOaT9kxtiRh42GwlZEg1/uIgtZshS5a/8OaduUfCi7kynKgc3Tw/6Uo2D+db9qBttghhmxwQ=="],
|
||||
"eslint/@eslint/js": ["@eslint/js@9.39.0", "", {}, "sha512-BIhe0sW91JGPiaF1mOuPy5v8NflqfjIcDNpC+LbW9f609WVRX1rArrhi6Z2ymvrAry9jw+5POTj4t2t62o8Bmw=="],
|
||||
|
||||
"@babel/helper-annotate-as-pure/@babel/types/@babel/helper-string-parser": ["@babel/helper-string-parser@7.25.9", "", {}, "sha512-4A/SCr/2KLd5jrtOMFzaKjVtAei3+2r/NChoBNoZ3EyP/+GlhoaEGoWOZUmFmoITP7zOJyHIMm+DYRd8o3PvHA=="],
|
||||
"@babel/helper-module-imports/@babel/traverse/@babel/generator": ["@babel/generator@7.27.5", "", { "dependencies": { "@babel/parser": "^7.27.5", "@babel/types": "^7.27.3", "@jridgewell/gen-mapping": "^0.3.5", "@jridgewell/trace-mapping": "^0.3.25", "jsesc": "^3.0.2" } }, "sha512-ZGhA37l0e/g2s1Cnzdix0O3aLYm66eF8aufiVteOgnwxgnRP8GoyMj7VWsgWnQbVKXyge7hqrFh2K2TQM6t1Hw=="],
|
||||
|
||||
"@babel/helper-annotate-as-pure/@babel/types/@babel/helper-validator-identifier": ["@babel/helper-validator-identifier@7.25.9", "", {}, "sha512-Ed61U6XJc3CVRfkERJWDz4dJwKe7iLmmJsbOGu9wSloNSFttHV0I8g6UAgb7qnK5ly5bGLPd4oXZlxCdANBOWQ=="],
|
||||
"@babel/helper-module-imports/@babel/traverse/@babel/parser": ["@babel/parser@7.27.5", "", { "dependencies": { "@babel/types": "^7.27.3" }, "bin": "./bin/babel-parser.js" }, "sha512-OsQd175SxWkGlzbny8J3K8TnnDD0N3lrIUtB92xwyRpzaenGZhxDvxN/JgU00U3CDZNj9tPuDJ5H0WS4Nt3vKg=="],
|
||||
|
||||
"@babel/helper-create-class-features-plugin/@babel/traverse/@babel/code-frame": ["@babel/code-frame@7.26.2", "", { "dependencies": { "@babel/helper-validator-identifier": "^7.25.9", "js-tokens": "^4.0.0", "picocolors": "^1.0.0" } }, "sha512-RJlIHRueQgwWitWgF8OdFYGZX328Ax5BCemNGlqHfplnRT9ESi8JkFlvaVYbS+UubVY6dpv87Fs2u5M29iNFVQ=="],
|
||||
|
||||
"@babel/helper-create-class-features-plugin/@babel/traverse/@babel/generator": ["@babel/generator@7.27.0", "", { "dependencies": { "@babel/parser": "^7.27.0", "@babel/types": "^7.27.0", "@jridgewell/gen-mapping": "^0.3.5", "@jridgewell/trace-mapping": "^0.3.25", "jsesc": "^3.0.2" } }, "sha512-VybsKvpiN1gU1sdMZIp7FcqphVVKEwcuj02x73uvcHE0PTihx1nlBcowYWhDwjpoAXRv43+gDzyggGnn1XZhVw=="],
|
||||
|
||||
"@babel/helper-create-class-features-plugin/@babel/traverse/@babel/template": ["@babel/template@7.27.0", "", { "dependencies": { "@babel/code-frame": "^7.26.2", "@babel/parser": "^7.27.0", "@babel/types": "^7.27.0" } }, "sha512-2ncevenBqXI6qRMukPlXwHKHchC7RyMuu4xv5JBXRfOGVcTy1mXCD12qrp7Jsoxll1EV3+9sE4GugBVRjT2jFA=="],
|
||||
|
||||
"@babel/helper-create-class-features-plugin/@babel/traverse/@babel/types": ["@babel/types@7.27.0", "", { "dependencies": { "@babel/helper-string-parser": "^7.25.9", "@babel/helper-validator-identifier": "^7.25.9" } }, "sha512-H45s8fVLYjbhFH62dIJ3WtmJ6RSPt/3DRO0ZcT2SUiYiQyz3BLVb9ADEnLl91m74aQPS3AzzeajZHYOalWe3bg=="],
|
||||
|
||||
"@babel/helper-create-class-features-plugin/@babel/traverse/globals": ["globals@11.12.0", "", {}, "sha512-WOBp/EEGUiIsJSp7wcv/y6MO+lV9UoncWqxuFfm8eBwzWNgyfBd6Gz+IeKQ9jCmyhoH99g15M3T+QaVHFjizVA=="],
|
||||
|
||||
"@babel/helper-member-expression-to-functions/@babel/traverse/@babel/code-frame": ["@babel/code-frame@7.26.2", "", { "dependencies": { "@babel/helper-validator-identifier": "^7.25.9", "js-tokens": "^4.0.0", "picocolors": "^1.0.0" } }, "sha512-RJlIHRueQgwWitWgF8OdFYGZX328Ax5BCemNGlqHfplnRT9ESi8JkFlvaVYbS+UubVY6dpv87Fs2u5M29iNFVQ=="],
|
||||
|
||||
"@babel/helper-member-expression-to-functions/@babel/traverse/@babel/generator": ["@babel/generator@7.27.0", "", { "dependencies": { "@babel/parser": "^7.27.0", "@babel/types": "^7.27.0", "@jridgewell/gen-mapping": "^0.3.5", "@jridgewell/trace-mapping": "^0.3.25", "jsesc": "^3.0.2" } }, "sha512-VybsKvpiN1gU1sdMZIp7FcqphVVKEwcuj02x73uvcHE0PTihx1nlBcowYWhDwjpoAXRv43+gDzyggGnn1XZhVw=="],
|
||||
|
||||
"@babel/helper-member-expression-to-functions/@babel/traverse/@babel/template": ["@babel/template@7.27.0", "", { "dependencies": { "@babel/code-frame": "^7.26.2", "@babel/parser": "^7.27.0", "@babel/types": "^7.27.0" } }, "sha512-2ncevenBqXI6qRMukPlXwHKHchC7RyMuu4xv5JBXRfOGVcTy1mXCD12qrp7Jsoxll1EV3+9sE4GugBVRjT2jFA=="],
|
||||
|
||||
"@babel/helper-member-expression-to-functions/@babel/traverse/globals": ["globals@11.12.0", "", {}, "sha512-WOBp/EEGUiIsJSp7wcv/y6MO+lV9UoncWqxuFfm8eBwzWNgyfBd6Gz+IeKQ9jCmyhoH99g15M3T+QaVHFjizVA=="],
|
||||
|
||||
"@babel/helper-member-expression-to-functions/@babel/types/@babel/helper-string-parser": ["@babel/helper-string-parser@7.25.9", "", {}, "sha512-4A/SCr/2KLd5jrtOMFzaKjVtAei3+2r/NChoBNoZ3EyP/+GlhoaEGoWOZUmFmoITP7zOJyHIMm+DYRd8o3PvHA=="],
|
||||
|
||||
"@babel/helper-member-expression-to-functions/@babel/types/@babel/helper-validator-identifier": ["@babel/helper-validator-identifier@7.25.9", "", {}, "sha512-Ed61U6XJc3CVRfkERJWDz4dJwKe7iLmmJsbOGu9wSloNSFttHV0I8g6UAgb7qnK5ly5bGLPd4oXZlxCdANBOWQ=="],
|
||||
|
||||
"@babel/helper-optimise-call-expression/@babel/types/@babel/helper-string-parser": ["@babel/helper-string-parser@7.25.9", "", {}, "sha512-4A/SCr/2KLd5jrtOMFzaKjVtAei3+2r/NChoBNoZ3EyP/+GlhoaEGoWOZUmFmoITP7zOJyHIMm+DYRd8o3PvHA=="],
|
||||
|
||||
"@babel/helper-optimise-call-expression/@babel/types/@babel/helper-validator-identifier": ["@babel/helper-validator-identifier@7.25.9", "", {}, "sha512-Ed61U6XJc3CVRfkERJWDz4dJwKe7iLmmJsbOGu9wSloNSFttHV0I8g6UAgb7qnK5ly5bGLPd4oXZlxCdANBOWQ=="],
|
||||
|
||||
"@babel/helper-replace-supers/@babel/traverse/@babel/code-frame": ["@babel/code-frame@7.26.2", "", { "dependencies": { "@babel/helper-validator-identifier": "^7.25.9", "js-tokens": "^4.0.0", "picocolors": "^1.0.0" } }, "sha512-RJlIHRueQgwWitWgF8OdFYGZX328Ax5BCemNGlqHfplnRT9ESi8JkFlvaVYbS+UubVY6dpv87Fs2u5M29iNFVQ=="],
|
||||
|
||||
"@babel/helper-replace-supers/@babel/traverse/@babel/generator": ["@babel/generator@7.27.0", "", { "dependencies": { "@babel/parser": "^7.27.0", "@babel/types": "^7.27.0", "@jridgewell/gen-mapping": "^0.3.5", "@jridgewell/trace-mapping": "^0.3.25", "jsesc": "^3.0.2" } }, "sha512-VybsKvpiN1gU1sdMZIp7FcqphVVKEwcuj02x73uvcHE0PTihx1nlBcowYWhDwjpoAXRv43+gDzyggGnn1XZhVw=="],
|
||||
|
||||
"@babel/helper-replace-supers/@babel/traverse/@babel/template": ["@babel/template@7.27.0", "", { "dependencies": { "@babel/code-frame": "^7.26.2", "@babel/parser": "^7.27.0", "@babel/types": "^7.27.0" } }, "sha512-2ncevenBqXI6qRMukPlXwHKHchC7RyMuu4xv5JBXRfOGVcTy1mXCD12qrp7Jsoxll1EV3+9sE4GugBVRjT2jFA=="],
|
||||
|
||||
"@babel/helper-replace-supers/@babel/traverse/@babel/types": ["@babel/types@7.27.0", "", { "dependencies": { "@babel/helper-string-parser": "^7.25.9", "@babel/helper-validator-identifier": "^7.25.9" } }, "sha512-H45s8fVLYjbhFH62dIJ3WtmJ6RSPt/3DRO0ZcT2SUiYiQyz3BLVb9ADEnLl91m74aQPS3AzzeajZHYOalWe3bg=="],
|
||||
|
||||
"@babel/helper-replace-supers/@babel/traverse/globals": ["globals@11.12.0", "", {}, "sha512-WOBp/EEGUiIsJSp7wcv/y6MO+lV9UoncWqxuFfm8eBwzWNgyfBd6Gz+IeKQ9jCmyhoH99g15M3T+QaVHFjizVA=="],
|
||||
|
||||
"@babel/helper-skip-transparent-expression-wrappers/@babel/traverse/@babel/code-frame": ["@babel/code-frame@7.26.2", "", { "dependencies": { "@babel/helper-validator-identifier": "^7.25.9", "js-tokens": "^4.0.0", "picocolors": "^1.0.0" } }, "sha512-RJlIHRueQgwWitWgF8OdFYGZX328Ax5BCemNGlqHfplnRT9ESi8JkFlvaVYbS+UubVY6dpv87Fs2u5M29iNFVQ=="],
|
||||
|
||||
"@babel/helper-skip-transparent-expression-wrappers/@babel/traverse/@babel/generator": ["@babel/generator@7.27.0", "", { "dependencies": { "@babel/parser": "^7.27.0", "@babel/types": "^7.27.0", "@jridgewell/gen-mapping": "^0.3.5", "@jridgewell/trace-mapping": "^0.3.25", "jsesc": "^3.0.2" } }, "sha512-VybsKvpiN1gU1sdMZIp7FcqphVVKEwcuj02x73uvcHE0PTihx1nlBcowYWhDwjpoAXRv43+gDzyggGnn1XZhVw=="],
|
||||
|
||||
"@babel/helper-skip-transparent-expression-wrappers/@babel/traverse/@babel/template": ["@babel/template@7.27.0", "", { "dependencies": { "@babel/code-frame": "^7.26.2", "@babel/parser": "^7.27.0", "@babel/types": "^7.27.0" } }, "sha512-2ncevenBqXI6qRMukPlXwHKHchC7RyMuu4xv5JBXRfOGVcTy1mXCD12qrp7Jsoxll1EV3+9sE4GugBVRjT2jFA=="],
|
||||
|
||||
"@babel/helper-skip-transparent-expression-wrappers/@babel/traverse/globals": ["globals@11.12.0", "", {}, "sha512-WOBp/EEGUiIsJSp7wcv/y6MO+lV9UoncWqxuFfm8eBwzWNgyfBd6Gz+IeKQ9jCmyhoH99g15M3T+QaVHFjizVA=="],
|
||||
|
||||
"@babel/helper-skip-transparent-expression-wrappers/@babel/types/@babel/helper-string-parser": ["@babel/helper-string-parser@7.25.9", "", {}, "sha512-4A/SCr/2KLd5jrtOMFzaKjVtAei3+2r/NChoBNoZ3EyP/+GlhoaEGoWOZUmFmoITP7zOJyHIMm+DYRd8o3PvHA=="],
|
||||
|
||||
"@babel/helper-skip-transparent-expression-wrappers/@babel/types/@babel/helper-validator-identifier": ["@babel/helper-validator-identifier@7.25.9", "", {}, "sha512-Ed61U6XJc3CVRfkERJWDz4dJwKe7iLmmJsbOGu9wSloNSFttHV0I8g6UAgb7qnK5ly5bGLPd4oXZlxCdANBOWQ=="],
|
||||
|
||||
"@babel/parser/@babel/types/@babel/helper-string-parser": ["@babel/helper-string-parser@7.25.9", "", {}, "sha512-4A/SCr/2KLd5jrtOMFzaKjVtAei3+2r/NChoBNoZ3EyP/+GlhoaEGoWOZUmFmoITP7zOJyHIMm+DYRd8o3PvHA=="],
|
||||
|
||||
"@babel/parser/@babel/types/@babel/helper-validator-identifier": ["@babel/helper-validator-identifier@7.25.9", "", {}, "sha512-Ed61U6XJc3CVRfkERJWDz4dJwKe7iLmmJsbOGu9wSloNSFttHV0I8g6UAgb7qnK5ly5bGLPd4oXZlxCdANBOWQ=="],
|
||||
"@babel/helper-module-imports/@babel/traverse/globals": ["globals@11.12.0", "", {}, "sha512-WOBp/EEGUiIsJSp7wcv/y6MO+lV9UoncWqxuFfm8eBwzWNgyfBd6Gz+IeKQ9jCmyhoH99g15M3T+QaVHFjizVA=="],
|
||||
|
||||
"@eslint/eslintrc/espree/acorn": ["acorn@8.14.1", "", { "bin": { "acorn": "bin/acorn" } }, "sha512-OvQ/2pUDKmgfCg++xsTX1wGxfTaszcHVcTctW4UJB4hibJx2HXxxO5UmVgyjMa+ZDsiaf5wWLXYpRWMmBI0QHg=="],
|
||||
|
||||
@@ -565,52 +487,8 @@
|
||||
|
||||
"@types/babel__traverse/@babel/types/@babel/helper-validator-identifier": ["@babel/helper-validator-identifier@7.25.9", "", {}, "sha512-Ed61U6XJc3CVRfkERJWDz4dJwKe7iLmmJsbOGu9wSloNSFttHV0I8g6UAgb7qnK5ly5bGLPd4oXZlxCdANBOWQ=="],
|
||||
|
||||
"eslint-plugin-react-hooks/@babel/core/@babel/code-frame": ["@babel/code-frame@7.26.2", "", { "dependencies": { "@babel/helper-validator-identifier": "^7.25.9", "js-tokens": "^4.0.0", "picocolors": "^1.0.0" } }, "sha512-RJlIHRueQgwWitWgF8OdFYGZX328Ax5BCemNGlqHfplnRT9ESi8JkFlvaVYbS+UubVY6dpv87Fs2u5M29iNFVQ=="],
|
||||
"@babel/helper-module-imports/@babel/traverse/@babel/generator/@jridgewell/gen-mapping": ["@jridgewell/gen-mapping@0.3.8", "", { "dependencies": { "@jridgewell/set-array": "^1.2.1", "@jridgewell/sourcemap-codec": "^1.4.10", "@jridgewell/trace-mapping": "^0.3.24" } }, "sha512-imAbBGkb+ebQyxKgzv5Hu2nmROxoDOXHh80evxdoXNOrvAnVx7zimzc1Oo5h9RlfV4vPXaE2iM5pOFbvOCClWA=="],
|
||||
|
||||
"eslint-plugin-react-hooks/@babel/core/@babel/generator": ["@babel/generator@7.27.0", "", { "dependencies": { "@babel/parser": "^7.27.0", "@babel/types": "^7.27.0", "@jridgewell/gen-mapping": "^0.3.5", "@jridgewell/trace-mapping": "^0.3.25", "jsesc": "^3.0.2" } }, "sha512-VybsKvpiN1gU1sdMZIp7FcqphVVKEwcuj02x73uvcHE0PTihx1nlBcowYWhDwjpoAXRv43+gDzyggGnn1XZhVw=="],
|
||||
|
||||
"eslint-plugin-react-hooks/@babel/core/@babel/helper-compilation-targets": ["@babel/helper-compilation-targets@7.27.0", "", { "dependencies": { "@babel/compat-data": "^7.26.8", "@babel/helper-validator-option": "^7.25.9", "browserslist": "^4.24.0", "lru-cache": "^5.1.1", "semver": "^6.3.1" } }, "sha512-LVk7fbXml0H2xH34dFzKQ7TDZ2G4/rVTOrq9V+icbbadjbVxxeFeDsNHv2SrZeWoA+6ZiTyWYWtScEIW07EAcA=="],
|
||||
|
||||
"eslint-plugin-react-hooks/@babel/core/@babel/helper-module-transforms": ["@babel/helper-module-transforms@7.26.0", "", { "dependencies": { "@babel/helper-module-imports": "^7.25.9", "@babel/helper-validator-identifier": "^7.25.9", "@babel/traverse": "^7.25.9" }, "peerDependencies": { "@babel/core": "^7.0.0" } }, "sha512-xO+xu6B5K2czEnQye6BHA7DolFFmS3LB7stHZFaOLb1pAwO1HWLS8fXA+eh0A2yIvltPVmx3eNNDBJA2SLHXFw=="],
|
||||
|
||||
"eslint-plugin-react-hooks/@babel/core/@babel/helpers": ["@babel/helpers@7.27.0", "", { "dependencies": { "@babel/template": "^7.27.0", "@babel/types": "^7.27.0" } }, "sha512-U5eyP/CTFPuNE3qk+WZMxFkp/4zUzdceQlfzf7DdGdhp+Fezd7HD+i8Y24ZuTMKX3wQBld449jijbGq6OdGNQg=="],
|
||||
|
||||
"eslint-plugin-react-hooks/@babel/core/@babel/template": ["@babel/template@7.27.0", "", { "dependencies": { "@babel/code-frame": "^7.26.2", "@babel/parser": "^7.27.0", "@babel/types": "^7.27.0" } }, "sha512-2ncevenBqXI6qRMukPlXwHKHchC7RyMuu4xv5JBXRfOGVcTy1mXCD12qrp7Jsoxll1EV3+9sE4GugBVRjT2jFA=="],
|
||||
|
||||
"eslint-plugin-react-hooks/@babel/core/@babel/traverse": ["@babel/traverse@7.27.0", "", { "dependencies": { "@babel/code-frame": "^7.26.2", "@babel/generator": "^7.27.0", "@babel/parser": "^7.27.0", "@babel/template": "^7.27.0", "@babel/types": "^7.27.0", "debug": "^4.3.1", "globals": "^11.1.0" } }, "sha512-19lYZFzYVQkkHkl4Cy4WrAVcqBkgvV2YM2TU3xG6DIwO7O3ecbDPfW3yM3bjAGcqcQHi+CCtjMR3dIEHxsd6bA=="],
|
||||
|
||||
"eslint-plugin-react-hooks/@babel/core/@babel/types": ["@babel/types@7.27.0", "", { "dependencies": { "@babel/helper-string-parser": "^7.25.9", "@babel/helper-validator-identifier": "^7.25.9" } }, "sha512-H45s8fVLYjbhFH62dIJ3WtmJ6RSPt/3DRO0ZcT2SUiYiQyz3BLVb9ADEnLl91m74aQPS3AzzeajZHYOalWe3bg=="],
|
||||
|
||||
"@babel/helper-create-class-features-plugin/@babel/traverse/@babel/code-frame/@babel/helper-validator-identifier": ["@babel/helper-validator-identifier@7.25.9", "", {}, "sha512-Ed61U6XJc3CVRfkERJWDz4dJwKe7iLmmJsbOGu9wSloNSFttHV0I8g6UAgb7qnK5ly5bGLPd4oXZlxCdANBOWQ=="],
|
||||
|
||||
"@babel/helper-create-class-features-plugin/@babel/traverse/@babel/types/@babel/helper-string-parser": ["@babel/helper-string-parser@7.25.9", "", {}, "sha512-4A/SCr/2KLd5jrtOMFzaKjVtAei3+2r/NChoBNoZ3EyP/+GlhoaEGoWOZUmFmoITP7zOJyHIMm+DYRd8o3PvHA=="],
|
||||
|
||||
"@babel/helper-create-class-features-plugin/@babel/traverse/@babel/types/@babel/helper-validator-identifier": ["@babel/helper-validator-identifier@7.25.9", "", {}, "sha512-Ed61U6XJc3CVRfkERJWDz4dJwKe7iLmmJsbOGu9wSloNSFttHV0I8g6UAgb7qnK5ly5bGLPd4oXZlxCdANBOWQ=="],
|
||||
|
||||
"@babel/helper-member-expression-to-functions/@babel/traverse/@babel/code-frame/@babel/helper-validator-identifier": ["@babel/helper-validator-identifier@7.25.9", "", {}, "sha512-Ed61U6XJc3CVRfkERJWDz4dJwKe7iLmmJsbOGu9wSloNSFttHV0I8g6UAgb7qnK5ly5bGLPd4oXZlxCdANBOWQ=="],
|
||||
|
||||
"@babel/helper-replace-supers/@babel/traverse/@babel/code-frame/@babel/helper-validator-identifier": ["@babel/helper-validator-identifier@7.25.9", "", {}, "sha512-Ed61U6XJc3CVRfkERJWDz4dJwKe7iLmmJsbOGu9wSloNSFttHV0I8g6UAgb7qnK5ly5bGLPd4oXZlxCdANBOWQ=="],
|
||||
|
||||
"@babel/helper-replace-supers/@babel/traverse/@babel/types/@babel/helper-string-parser": ["@babel/helper-string-parser@7.25.9", "", {}, "sha512-4A/SCr/2KLd5jrtOMFzaKjVtAei3+2r/NChoBNoZ3EyP/+GlhoaEGoWOZUmFmoITP7zOJyHIMm+DYRd8o3PvHA=="],
|
||||
|
||||
"@babel/helper-replace-supers/@babel/traverse/@babel/types/@babel/helper-validator-identifier": ["@babel/helper-validator-identifier@7.25.9", "", {}, "sha512-Ed61U6XJc3CVRfkERJWDz4dJwKe7iLmmJsbOGu9wSloNSFttHV0I8g6UAgb7qnK5ly5bGLPd4oXZlxCdANBOWQ=="],
|
||||
|
||||
"@babel/helper-skip-transparent-expression-wrappers/@babel/traverse/@babel/code-frame/@babel/helper-validator-identifier": ["@babel/helper-validator-identifier@7.25.9", "", {}, "sha512-Ed61U6XJc3CVRfkERJWDz4dJwKe7iLmmJsbOGu9wSloNSFttHV0I8g6UAgb7qnK5ly5bGLPd4oXZlxCdANBOWQ=="],
|
||||
|
||||
"eslint-plugin-react-hooks/@babel/core/@babel/code-frame/@babel/helper-validator-identifier": ["@babel/helper-validator-identifier@7.25.9", "", {}, "sha512-Ed61U6XJc3CVRfkERJWDz4dJwKe7iLmmJsbOGu9wSloNSFttHV0I8g6UAgb7qnK5ly5bGLPd4oXZlxCdANBOWQ=="],
|
||||
|
||||
"eslint-plugin-react-hooks/@babel/core/@babel/helper-compilation-targets/@babel/compat-data": ["@babel/compat-data@7.26.8", "", {}, "sha512-oH5UPLMWR3L2wEFLnFJ1TZXqHufiTKAiLfqw5zkhS4dKXLJ10yVztfil/twG8EDTA4F/tvVNw9nOl4ZMslB8rQ=="],
|
||||
|
||||
"eslint-plugin-react-hooks/@babel/core/@babel/helper-compilation-targets/@babel/helper-validator-option": ["@babel/helper-validator-option@7.25.9", "", {}, "sha512-e/zv1co8pp55dNdEcCynfj9X7nyUKUXoUEwfXqaZt0omVOmDe9oOTdKStH4GmAw6zxMFs50ZayuMfHDKlO7Tfw=="],
|
||||
|
||||
"eslint-plugin-react-hooks/@babel/core/@babel/helper-module-transforms/@babel/helper-module-imports": ["@babel/helper-module-imports@7.25.9", "", { "dependencies": { "@babel/traverse": "^7.25.9", "@babel/types": "^7.25.9" } }, "sha512-tnUA4RsrmflIM6W6RFTLFSXITtl0wKjgpnLgXyowocVPrbYrLUXSBXDgTs8BlbmIzIdlBySRQjINYs2BAkiLtw=="],
|
||||
|
||||
"eslint-plugin-react-hooks/@babel/core/@babel/helper-module-transforms/@babel/helper-validator-identifier": ["@babel/helper-validator-identifier@7.25.9", "", {}, "sha512-Ed61U6XJc3CVRfkERJWDz4dJwKe7iLmmJsbOGu9wSloNSFttHV0I8g6UAgb7qnK5ly5bGLPd4oXZlxCdANBOWQ=="],
|
||||
|
||||
"eslint-plugin-react-hooks/@babel/core/@babel/traverse/globals": ["globals@11.12.0", "", {}, "sha512-WOBp/EEGUiIsJSp7wcv/y6MO+lV9UoncWqxuFfm8eBwzWNgyfBd6Gz+IeKQ9jCmyhoH99g15M3T+QaVHFjizVA=="],
|
||||
|
||||
"eslint-plugin-react-hooks/@babel/core/@babel/types/@babel/helper-string-parser": ["@babel/helper-string-parser@7.25.9", "", {}, "sha512-4A/SCr/2KLd5jrtOMFzaKjVtAei3+2r/NChoBNoZ3EyP/+GlhoaEGoWOZUmFmoITP7zOJyHIMm+DYRd8o3PvHA=="],
|
||||
|
||||
"eslint-plugin-react-hooks/@babel/core/@babel/types/@babel/helper-validator-identifier": ["@babel/helper-validator-identifier@7.25.9", "", {}, "sha512-Ed61U6XJc3CVRfkERJWDz4dJwKe7iLmmJsbOGu9wSloNSFttHV0I8g6UAgb7qnK5ly5bGLPd4oXZlxCdANBOWQ=="],
|
||||
"@babel/helper-module-imports/@babel/traverse/@babel/generator/@jridgewell/trace-mapping": ["@jridgewell/trace-mapping@0.3.25", "", { "dependencies": { "@jridgewell/resolve-uri": "^3.1.0", "@jridgewell/sourcemap-codec": "^1.4.14" } }, "sha512-vNk6aEwybGtawWmy/PzwnGDOjCkLWSD2wqvjGGAgOAwCGWySYXfYoxt00IJkTF+8Lb57DwOb3Aa0o9CApepiYQ=="],
|
||||
}
|
||||
}
|
||||
|
||||
+12
-12
@@ -10,20 +10,20 @@
|
||||
"preview": "vite preview"
|
||||
},
|
||||
"dependencies": {
|
||||
"react": "^19.1.0",
|
||||
"react-dom": "^19.1.0",
|
||||
"react": "^19.2.0",
|
||||
"react-dom": "^19.2.0",
|
||||
"highlight.js": "^11.11.1"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@eslint/js": "^9.30.1",
|
||||
"@types/react": "^19.1.8",
|
||||
"@types/react-dom": "^19.1.6",
|
||||
"@vitejs/plugin-react": "^4.6.0",
|
||||
"eslint": "^9.30.1",
|
||||
"eslint-plugin-react-hooks": "^6.0.0",
|
||||
"eslint-plugin-react-refresh": "^0.4.20",
|
||||
"globals": "^16.3.0",
|
||||
"react-router-dom": "^7.6.3",
|
||||
"vite": "^7.0.3"
|
||||
"@eslint/js": "^9.39.1",
|
||||
"@types/react": "^19.2.2",
|
||||
"@types/react-dom": "^19.2.2",
|
||||
"@vitejs/plugin-react": "^5.1.0",
|
||||
"eslint": "^9.39.0",
|
||||
"eslint-plugin-react-hooks": "^7.0.1",
|
||||
"eslint-plugin-react-refresh": "^0.4.24",
|
||||
"globals": "^16.5.0",
|
||||
"react-router-dom": "^7.9.5",
|
||||
"vite": "^7.1.12"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -5,7 +5,7 @@ import ConfigForm from './ConfigForm';
|
||||
* ActionForm component for configuring an action
|
||||
* Renders action configuration forms based on field group metadata
|
||||
*/
|
||||
const ActionForm = ({ actions = [], onChange, onRemove, onAdd, fieldGroups = [] }) => {
|
||||
const ActionForm = ({ actions = [], onChange, onRemove, onAdd, onPlay, fieldGroups = [] }) => {
|
||||
const handleActionChange = (index, updatedAction) => {
|
||||
onChange(index, updatedAction);
|
||||
};
|
||||
@@ -17,6 +17,7 @@ const ActionForm = ({ actions = [], onChange, onRemove, onAdd, fieldGroups = []
|
||||
onChange={handleActionChange}
|
||||
onRemove={onRemove}
|
||||
onAdd={onAdd}
|
||||
onPlay={onPlay}
|
||||
itemType="action"
|
||||
typeField="name"
|
||||
addButtonText="Add Action"
|
||||
|
||||
@@ -99,28 +99,28 @@ const AgentForm = ({
|
||||
const handleAddDynamicPrompt = () => {
|
||||
setFormData({
|
||||
...formData,
|
||||
dynamicPrompts: [
|
||||
...(formData.dynamicPrompts || []),
|
||||
dynamic_prompts: [
|
||||
...(formData.dynamic_prompts || []),
|
||||
{ type: '', config: '{}' }
|
||||
]
|
||||
});
|
||||
};
|
||||
|
||||
const handleRemoveDynamicPrompt = (index) => {
|
||||
const updatedDynamicPrompts = [...formData.dynamicPrompts];
|
||||
const updatedDynamicPrompts = [...formData.dynamic_prompts];
|
||||
updatedDynamicPrompts.splice(index, 1);
|
||||
setFormData({
|
||||
...formData,
|
||||
dynamicPrompts: updatedDynamicPrompts,
|
||||
dynamic_prompts: updatedDynamicPrompts,
|
||||
});
|
||||
};
|
||||
|
||||
const handleDynamicPromptChange = (index, updatedPrompt) => {
|
||||
const updatedPrompts = [...formData.dynamicPrompts];
|
||||
const updatedPrompts = [...formData.dynamic_prompts];
|
||||
updatedPrompts[index] = updatedPrompt;
|
||||
setFormData({
|
||||
...formData,
|
||||
dynamicPrompts: updatedPrompts
|
||||
dynamic_prompts: updatedPrompts
|
||||
});
|
||||
};
|
||||
|
||||
|
||||
@@ -10,6 +10,7 @@ import FormFieldDefinition from './common/FormFieldDefinition';
|
||||
* @param {Function} props.onChange - Callback when an item changes
|
||||
* @param {Function} props.onRemove - Callback when an item is removed
|
||||
* @param {Function} props.onAdd - Callback when a new item is added
|
||||
* @param {Function} props.onPlay - Callback when a play button is clicked
|
||||
* @param {String} props.itemType - Type of items being configured ('action', 'connector', etc.)
|
||||
* @param {String} props.typeField - The field name that determines the item's type (e.g., 'name' for actions, 'type' for connectors)
|
||||
* @param {String} props.addButtonText - Text for the add button
|
||||
@@ -19,8 +20,9 @@ const ConfigForm = ({
|
||||
items = [],
|
||||
fieldGroups = [],
|
||||
onChange,
|
||||
onRemove,
|
||||
onRemove,
|
||||
onAdd,
|
||||
onPlay,
|
||||
itemType = 'item',
|
||||
typeField = 'type',
|
||||
addButtonText = 'Add Item',
|
||||
@@ -89,15 +91,25 @@ const ConfigForm = ({
|
||||
|
||||
return (
|
||||
<div key={index} className="config-item mb-4 card">
|
||||
<div className="config-header" style={{ display: 'flex', justifyContent: 'space-between', alignItems: 'center', marginBottom: '1rem' }}>
|
||||
<h4 style={{ margin: 0 }}>{itemTypeLabel} #{index + 1}</h4>
|
||||
<button
|
||||
type="button"
|
||||
className="action-btn delete-btn"
|
||||
onClick={() => onRemove(index)}
|
||||
>
|
||||
<i className="fas fa-times"></i>
|
||||
</button>
|
||||
<div className="config-header" style={{ display: 'flex', justifyContent: 'space-between', alignItems: 'center', marginBottom: '1rem', gap: '1rem' }}>
|
||||
<h4 style={{ margin: 0, flex: 1 }}>{itemTypeLabel} #{index + 1}</h4>
|
||||
{/* Render Play button if onPlay handler is provided */}
|
||||
{onPlay && (
|
||||
<button
|
||||
type="button"
|
||||
className="action-btn"
|
||||
onClick={() => onPlay(index)}
|
||||
>
|
||||
<i className="fas fa-play"></i>
|
||||
</button>
|
||||
)}
|
||||
<button
|
||||
type="button"
|
||||
className="action-btn delete-btn"
|
||||
onClick={() => onRemove(index)}
|
||||
>
|
||||
<i className="fas fa-times"></i>
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<div className="config-type mb-3">
|
||||
|
||||
@@ -35,6 +35,18 @@ const ActionsSection = ({ formData, setFormData, metadata }) => {
|
||||
});
|
||||
};
|
||||
|
||||
// Handle the play button and open the action playground
|
||||
const handleActionPlay = (index) => {
|
||||
const action = formData.actions[index];
|
||||
const searchParams = new URLSearchParams({
|
||||
action: action.name,
|
||||
config: action.config
|
||||
});
|
||||
|
||||
// Open in new tab while staying in React Router context
|
||||
window.open(`/app/actions-playground?${searchParams.toString()}`, '_blank');
|
||||
};
|
||||
|
||||
return (
|
||||
<div className="actions-section">
|
||||
<h3>Actions</h3>
|
||||
@@ -47,6 +59,7 @@ const ActionsSection = ({ formData, setFormData, metadata }) => {
|
||||
onChange={handleActionChange}
|
||||
onRemove={handleActionRemove}
|
||||
onAdd={handleAddAction}
|
||||
onPlay={handleActionPlay}
|
||||
fieldGroups={metadata?.actions || []}
|
||||
/>
|
||||
</div>
|
||||
|
||||
@@ -67,7 +67,7 @@ const PromptsGoalsSection = ({
|
||||
/>
|
||||
|
||||
<DynamicPromptForm
|
||||
prompts={formData.dynamicPrompts || []}
|
||||
prompts={formData.dynamic_prompts || []}
|
||||
onAddPrompt={onAddPrompt}
|
||||
onRemovePrompt={onRemovePrompt}
|
||||
onChange={handleDynamicPromptChange}
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { useState, useEffect } from 'react';
|
||||
import { useOutletContext, useNavigate } from 'react-router-dom';
|
||||
import { useOutletContext, useNavigate, useSearchParams } from 'react-router-dom';
|
||||
import { actionApi, agentApi } from '../utils/api';
|
||||
import FormFieldDefinition from '../components/common/FormFieldDefinition';
|
||||
import hljs from 'highlight.js/lib/core';
|
||||
@@ -10,6 +10,7 @@ hljs.registerLanguage('json', json);
|
||||
function ActionsPlayground() {
|
||||
const { showToast } = useOutletContext();
|
||||
const navigate = useNavigate();
|
||||
const [searchParams] = useSearchParams();
|
||||
const [actions, setActions] = useState([]);
|
||||
const [selectedAction, setSelectedAction] = useState('');
|
||||
const [configJson, setConfigJson] = useState('{}');
|
||||
@@ -47,6 +48,29 @@ function ActionsPlayground() {
|
||||
fetchActions();
|
||||
}, []);
|
||||
|
||||
// If query param 'action' is present, pre-select it once actions are loaded
|
||||
useEffect(() => {
|
||||
if (loadingActions) return;
|
||||
const queryAction = searchParams.get('action');
|
||||
if (!queryAction) return;
|
||||
if (!selectedAction && actions.includes(queryAction)) {
|
||||
setSelectedAction(queryAction);
|
||||
}
|
||||
}, [loadingActions, actions, searchParams, selectedAction]);
|
||||
|
||||
// If query param 'config' is present, pre-fill the configuration JSON
|
||||
useEffect(() => {
|
||||
const queryConfig = searchParams.get('config');
|
||||
if (!queryConfig) return;
|
||||
try {
|
||||
const parsed = JSON.parse(queryConfig);
|
||||
setConfigJson(JSON.stringify(parsed, null, 2));
|
||||
} catch (err) {
|
||||
console.error('Invalid config query parameter:', err);
|
||||
showToast('Invalid config query parameter. Expected JSON.', 'error');
|
||||
}
|
||||
}, [searchParams, showToast]);
|
||||
|
||||
// Fetch agent metadata on mount
|
||||
useEffect(() => {
|
||||
const fetchAgentMetadata = async () => {
|
||||
|
||||
@@ -16,6 +16,14 @@ function ObservableSummary({ observable }) {
|
||||
creationChatMsg = lastMsg?.content || '';
|
||||
}
|
||||
}
|
||||
|
||||
// TODO:: Probably we have an array of two objects: [{type:"text", ...}, {type:"image_url", image_url: {url:"..."}}]
|
||||
// We could display the image and text along with multimedia icon
|
||||
if (typeof creationChatMsg === 'object') {
|
||||
console.log("Multimedia message?", creationChatMsg);
|
||||
creationChatMsg = 'Multimedia message';
|
||||
}
|
||||
|
||||
// FunctionDefinition summary
|
||||
let creationFunctionDef = '';
|
||||
if (creation?.function_definition?.name) {
|
||||
@@ -37,7 +45,6 @@ function ObservableSummary({ observable }) {
|
||||
chatCompletion = { choices: completion.conversation.map(m => {
|
||||
return { message: m }
|
||||
}) }
|
||||
console.log("converted conversation to choices", chatCompletion)
|
||||
}
|
||||
|
||||
if (
|
||||
@@ -106,7 +113,7 @@ function ObservableSummary({ observable }) {
|
||||
!completionAgentState && !completionError && !completionFilter) {
|
||||
return null;
|
||||
}
|
||||
|
||||
|
||||
return (
|
||||
<div style={{ display: 'flex', flexDirection: 'column', gap: 2, margin: '2px 0 0 0' }}>
|
||||
{/* CREATION */}
|
||||
@@ -209,6 +216,7 @@ function AgentStatus() {
|
||||
const [observableMap, setObservableMap] = useState({});
|
||||
const [observableTree, setObservableTree] = useState([]);
|
||||
const [expandedCards, setExpandedCards] = useState(new Map());
|
||||
const [clearLoading, setClearLoading] = useState(false);
|
||||
|
||||
// Update document title
|
||||
useEffect(() => {
|
||||
@@ -282,7 +290,6 @@ function AgentStatus() {
|
||||
|
||||
sse.addEventListener('observable_update', (event) => {
|
||||
const data = JSON.parse(event.data);
|
||||
console.log(data);
|
||||
setObservableMap(prevMap => {
|
||||
const prev = prevMap[data.id] || {};
|
||||
const updated = {
|
||||
@@ -336,6 +343,26 @@ function AgentStatus() {
|
||||
};
|
||||
}, [name]);
|
||||
|
||||
const handleClearObservables = async () => {
|
||||
if (clearLoading) return;
|
||||
setClearLoading(true);
|
||||
try {
|
||||
const resp = await fetch(`/api/agent/${name}/observables`, { method: 'DELETE' });
|
||||
if (!resp.ok) {
|
||||
console.error('Failed to clear observables, status:', resp.status);
|
||||
} else {
|
||||
// Clear local state immediately
|
||||
setObservableMap({});
|
||||
setObservableTree([]);
|
||||
setExpandedCards(new Map());
|
||||
}
|
||||
} catch (e) {
|
||||
console.error('Error clearing observables:', e);
|
||||
} finally {
|
||||
setClearLoading(false);
|
||||
}
|
||||
};
|
||||
|
||||
// Helper function to safely convert any value to a displayable string
|
||||
const formatValue = (value) => {
|
||||
if (value === null || value === undefined) {
|
||||
@@ -429,7 +456,17 @@ function AgentStatus() {
|
||||
</div>
|
||||
{observableTree.length > 0 && (
|
||||
<div>
|
||||
<h2>Observable Updates</h2>
|
||||
<div style={{ display: 'flex', justifyContent: 'space-between', alignItems: 'center' }}>
|
||||
<h2 style={{ margin: 0 }}>Observable Updates</h2>
|
||||
<button
|
||||
className="action-btn delete-btn"
|
||||
onClick={handleClearObservables}
|
||||
disabled={clearLoading}
|
||||
title="Clear observable history"
|
||||
>
|
||||
{clearLoading ? 'Clearing…' : 'Clear history'}
|
||||
</button>
|
||||
</div>
|
||||
<div style={{ color: '#aaa', fontSize: 14, margin: '5px 0 10px 2px' }}>
|
||||
Drill down into what the agent is doing and thinking when activated by a connector
|
||||
</div>
|
||||
|
||||
+14
-14
@@ -22,12 +22,6 @@ import (
|
||||
"github.com/mudler/LocalAGI/services"
|
||||
)
|
||||
|
||||
//go:embed old/views/*
|
||||
var viewsfs embed.FS
|
||||
|
||||
//go:embed old/public/*
|
||||
var embeddedFiles embed.FS
|
||||
|
||||
//go:embed react-ui/dist/*
|
||||
var reactUI embed.FS
|
||||
|
||||
@@ -40,12 +34,6 @@ func (app *App) registerRoutes(pool *state.AgentPool, webapp *fiber.App) {
|
||||
Browse: true,
|
||||
}))
|
||||
|
||||
webapp.Use("/old/public", filesystem.New(filesystem.Config{
|
||||
Root: http.FS(embeddedFiles),
|
||||
PathPrefix: "/old/public",
|
||||
Browse: true,
|
||||
}))
|
||||
|
||||
if len(app.config.ApiKeys) > 0 {
|
||||
kaConfig, err := GetKeyAuthConfig(app.config.ApiKeys)
|
||||
if err != nil || kaConfig == nil {
|
||||
@@ -183,10 +171,10 @@ func (app *App) registerRoutes(pool *state.AgentPool, webapp *fiber.App) {
|
||||
webapp.Put("/api/agent/:name/config", app.UpdateAgentConfig(pool))
|
||||
|
||||
// Metadata endpoint for agent configuration fields
|
||||
webapp.Get("/api/agent/config/metadata", app.GetAgentConfigMeta())
|
||||
webapp.Get("/api/agent/config/metadata", app.GetAgentConfigMeta(app.config.CustomActionsDir))
|
||||
|
||||
// Add endpoint for getting agent config metadata
|
||||
webapp.Get("/api/meta/agent/config", app.GetAgentConfigMeta())
|
||||
webapp.Get("/api/meta/agent/config", app.GetAgentConfigMeta(app.config.CustomActionsDir))
|
||||
|
||||
webapp.Post("/api/action/:name/definition", app.GetActionDefinition(pool))
|
||||
webapp.Post("/api/action/:name/run", app.ExecuteAction(pool))
|
||||
@@ -258,6 +246,7 @@ func (app *App) registerRoutes(pool *state.AgentPool, webapp *fiber.App) {
|
||||
})
|
||||
})
|
||||
|
||||
// API endpoint to retrieve agent observables
|
||||
webapp.Get("/api/agent/:name/observables", func(c *fiber.Ctx) error {
|
||||
name := c.Params("name")
|
||||
agent := pool.GetAgent(name)
|
||||
@@ -273,6 +262,17 @@ func (app *App) registerRoutes(pool *state.AgentPool, webapp *fiber.App) {
|
||||
})
|
||||
})
|
||||
|
||||
// API endpoint to clear agent observables
|
||||
webapp.Delete("/api/agent/:name/observables", func(c *fiber.Ctx) error {
|
||||
name := c.Params("name")
|
||||
agent := pool.GetAgent(name)
|
||||
if agent == nil {
|
||||
return c.Status(fiber.StatusNotFound).JSON(fiber.Map{"error": "Agent not found"})
|
||||
}
|
||||
agent.Observer().ClearHistory()
|
||||
return c.JSON(fiber.Map{"Name": name, "cleared": true})
|
||||
})
|
||||
|
||||
webapp.Post("/settings/import", app.ImportAgent(pool))
|
||||
webapp.Get("/settings/export/:name", app.ExportAgent(pool))
|
||||
|
||||
|
||||
Reference in New Issue
Block a user