mirror of
https://github.com/lordmathis/llamactl.git
synced 2025-12-23 09:34:23 +00:00
Compare commits
3 Commits
main
...
38790aa507
| Author | SHA1 | Date | |
|---|---|---|---|
| 38790aa507 | |||
| faf026aa54 | |||
| fd9e651e09 |
36
.github/workflows/release.yaml
vendored
36
.github/workflows/release.yaml
vendored
@@ -45,23 +45,15 @@ jobs:
|
|||||||
build:
|
build:
|
||||||
name: Build Binaries
|
name: Build Binaries
|
||||||
needs: build-webui
|
needs: build-webui
|
||||||
runs-on: ${{ matrix.runner }}
|
runs-on: ubuntu-latest
|
||||||
strategy:
|
strategy:
|
||||||
matrix:
|
matrix:
|
||||||
include:
|
goos: [linux, windows, darwin]
|
||||||
- goos: linux
|
goarch: [amd64, arm64]
|
||||||
goarch: amd64
|
exclude:
|
||||||
runner: ubuntu-latest
|
# Windows ARM64 support is limited
|
||||||
- goos: linux
|
|
||||||
goarch: arm64
|
|
||||||
runner: ubuntu-latest
|
|
||||||
cc: aarch64-linux-gnu-gcc
|
|
||||||
- goos: darwin
|
|
||||||
goarch: arm64
|
|
||||||
runner: macos-latest
|
|
||||||
- goos: windows
|
- goos: windows
|
||||||
goarch: amd64
|
goarch: arm64
|
||||||
runner: windows-latest
|
|
||||||
steps:
|
steps:
|
||||||
- name: Checkout repository
|
- name: Checkout repository
|
||||||
uses: actions/checkout@v4
|
uses: actions/checkout@v4
|
||||||
@@ -78,19 +70,11 @@ jobs:
|
|||||||
name: webui-dist
|
name: webui-dist
|
||||||
path: webui/dist/
|
path: webui/dist/
|
||||||
|
|
||||||
- name: Install cross-compilation tools (Linux ARM64 only)
|
|
||||||
if: matrix.cc != ''
|
|
||||||
run: |
|
|
||||||
sudo apt-get update
|
|
||||||
sudo apt-get install -y gcc-aarch64-linux-gnu
|
|
||||||
|
|
||||||
- name: Build binary
|
- name: Build binary
|
||||||
env:
|
env:
|
||||||
GOOS: ${{ matrix.goos }}
|
GOOS: ${{ matrix.goos }}
|
||||||
GOARCH: ${{ matrix.goarch }}
|
GOARCH: ${{ matrix.goarch }}
|
||||||
CGO_ENABLED: 1
|
CGO_ENABLED: 0
|
||||||
CC: ${{ matrix.cc }}
|
|
||||||
shell: bash
|
|
||||||
run: |
|
run: |
|
||||||
# Set binary extension for Windows
|
# Set binary extension for Windows
|
||||||
BINARY_NAME="llamactl"
|
BINARY_NAME="llamactl"
|
||||||
@@ -107,10 +91,8 @@ jobs:
|
|||||||
ARCHIVE_OS="macos"
|
ARCHIVE_OS="macos"
|
||||||
fi
|
fi
|
||||||
ARCHIVE_NAME="llamactl-${{ github.ref_name }}-${ARCHIVE_OS}-${{ matrix.goarch }}"
|
ARCHIVE_NAME="llamactl-${{ github.ref_name }}-${ARCHIVE_OS}-${{ matrix.goarch }}"
|
||||||
|
|
||||||
if [ "${{ matrix.goos }}" = "windows" ]; then
|
if [ "${{ matrix.goos }}" = "windows" ]; then
|
||||||
# Use 7z on Windows (pre-installed)
|
zip "${ARCHIVE_NAME}.zip" "${BINARY_NAME}"
|
||||||
7z a "${ARCHIVE_NAME}.zip" "${BINARY_NAME}"
|
|
||||||
echo "ASSET_PATH=${ARCHIVE_NAME}.zip" >> $GITHUB_ENV
|
echo "ASSET_PATH=${ARCHIVE_NAME}.zip" >> $GITHUB_ENV
|
||||||
else
|
else
|
||||||
tar -czf "${ARCHIVE_NAME}.tar.gz" "${BINARY_NAME}"
|
tar -czf "${ARCHIVE_NAME}.tar.gz" "${BINARY_NAME}"
|
||||||
@@ -197,4 +179,4 @@ jobs:
|
|||||||
with:
|
with:
|
||||||
files: assets/checksums.txt
|
files: assets/checksums.txt
|
||||||
env:
|
env:
|
||||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||||
@@ -12,7 +12,6 @@
|
|||||||
|
|
||||||
**🚀 Easy Model Management**
|
**🚀 Easy Model Management**
|
||||||
- **Multiple Models Simultaneously**: Run different models at the same time (7B for speed, 70B for quality)
|
- **Multiple Models Simultaneously**: Run different models at the same time (7B for speed, 70B for quality)
|
||||||
- **Dynamic Multi-Model Instances**: llama.cpp router mode - serve multiple models from a single instance with on-demand loading
|
|
||||||
- **Smart Resource Management**: Automatic idle timeout, LRU eviction, and configurable instance limits
|
- **Smart Resource Management**: Automatic idle timeout, LRU eviction, and configurable instance limits
|
||||||
- **Web Dashboard**: Modern React UI for managing instances, monitoring health, and viewing logs
|
- **Web Dashboard**: Modern React UI for managing instances, monitoring health, and viewing logs
|
||||||
|
|
||||||
@@ -184,6 +183,7 @@ data_dir: ~/.local/share/llamactl # Main data directory (database, instances, l
|
|||||||
|
|
||||||
instances:
|
instances:
|
||||||
port_range: [8000, 9000] # Port range for instances
|
port_range: [8000, 9000] # Port range for instances
|
||||||
|
configs_dir: ~/.local/share/llamactl/instances # Instance configs directory (platform dependent) [deprecated]
|
||||||
logs_dir: ~/.local/share/llamactl/logs # Logs directory (platform dependent)
|
logs_dir: ~/.local/share/llamactl/logs # Logs directory (platform dependent)
|
||||||
auto_create_dirs: true # Auto-create data/config/logs dirs if missing
|
auto_create_dirs: true # Auto-create data/config/logs dirs if missing
|
||||||
max_instances: -1 # Max instances (-1 = unlimited)
|
max_instances: -1 # Max instances (-1 = unlimited)
|
||||||
|
|||||||
@@ -57,6 +57,11 @@ func main() {
|
|||||||
log.Printf("Error creating data directory %s: %v\nData persistence may not be available.", cfg.DataDir, err)
|
log.Printf("Error creating data directory %s: %v\nData persistence may not be available.", cfg.DataDir, err)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Create instances directory
|
||||||
|
if err := os.MkdirAll(cfg.Instances.InstancesDir, 0755); err != nil {
|
||||||
|
log.Printf("Error creating instances directory %s: %v\nPersistence will not be available.", cfg.Instances.InstancesDir, err)
|
||||||
|
}
|
||||||
|
|
||||||
// Create logs directory
|
// Create logs directory
|
||||||
if err := os.MkdirAll(cfg.Instances.LogsDir, 0755); err != nil {
|
if err := os.MkdirAll(cfg.Instances.LogsDir, 0755); err != nil {
|
||||||
log.Printf("Error creating log directory %s: %v\nInstance logs will not be available.", cfg.Instances.LogsDir, err)
|
log.Printf("Error creating log directory %s: %v\nInstance logs will not be available.", cfg.Instances.LogsDir, err)
|
||||||
@@ -79,6 +84,11 @@ func main() {
|
|||||||
log.Fatalf("Failed to run database migrations: %v", err)
|
log.Fatalf("Failed to run database migrations: %v", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Migrate from JSON files if needed (one-time migration)
|
||||||
|
if err := migrateFromJSON(&cfg, db); err != nil {
|
||||||
|
log.Printf("Warning: Failed to migrate from JSON: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
// Initialize the instance manager with dependency injection
|
// Initialize the instance manager with dependency injection
|
||||||
instanceManager := manager.New(&cfg, db)
|
instanceManager := manager.New(&cfg, db)
|
||||||
|
|
||||||
|
|||||||
87
cmd/server/migrate_json.go
Normal file
87
cmd/server/migrate_json.go
Normal file
@@ -0,0 +1,87 @@
|
|||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/json"
|
||||||
|
"fmt"
|
||||||
|
"llamactl/pkg/config"
|
||||||
|
"llamactl/pkg/database"
|
||||||
|
"llamactl/pkg/instance"
|
||||||
|
"log"
|
||||||
|
"os"
|
||||||
|
"path/filepath"
|
||||||
|
)
|
||||||
|
|
||||||
|
// migrateFromJSON migrates instances from JSON files to SQLite database
|
||||||
|
// This is a one-time migration that runs on first startup with existing JSON files.
|
||||||
|
// Migrated files are moved to a migrated subdirectory to avoid re-importing.
|
||||||
|
func migrateFromJSON(cfg *config.AppConfig, db database.InstanceStore) error {
|
||||||
|
instancesDir := cfg.Instances.InstancesDir
|
||||||
|
if instancesDir == "" {
|
||||||
|
return nil // No instances directory configured
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check if instances directory exists
|
||||||
|
if _, err := os.Stat(instancesDir); os.IsNotExist(err) {
|
||||||
|
return nil // No instances directory, nothing to migrate
|
||||||
|
}
|
||||||
|
|
||||||
|
// Find all JSON files
|
||||||
|
files, err := filepath.Glob(filepath.Join(instancesDir, "*.json"))
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("failed to list instance files: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(files) == 0 {
|
||||||
|
return nil // No JSON files to migrate
|
||||||
|
}
|
||||||
|
|
||||||
|
log.Printf("Migrating %d instances from JSON to SQLite...", len(files))
|
||||||
|
|
||||||
|
// Create migrated directory
|
||||||
|
migratedDir := filepath.Join(instancesDir, "migrated")
|
||||||
|
if err := os.MkdirAll(migratedDir, 0755); err != nil {
|
||||||
|
return fmt.Errorf("failed to create migrated directory: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Migrate each JSON file
|
||||||
|
var migrated int
|
||||||
|
for _, file := range files {
|
||||||
|
if err := migrateJSONFile(file, db); err != nil {
|
||||||
|
log.Printf("Failed to migrate %s: %v", file, err)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
// Move the file to the migrated directory
|
||||||
|
destPath := filepath.Join(migratedDir, filepath.Base(file))
|
||||||
|
if err := os.Rename(file, destPath); err != nil {
|
||||||
|
log.Printf("Warning: Failed to move %s to migrated directory: %v", file, err)
|
||||||
|
// Don't fail the migration if we can't move the file
|
||||||
|
}
|
||||||
|
|
||||||
|
migrated++
|
||||||
|
}
|
||||||
|
|
||||||
|
log.Printf("Successfully migrated %d/%d instances to SQLite", migrated, len(files))
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// migrateJSONFile migrates a single JSON file to the database
|
||||||
|
func migrateJSONFile(filename string, db database.InstanceStore) error {
|
||||||
|
data, err := os.ReadFile(filename)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("failed to read file: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
var inst instance.Instance
|
||||||
|
if err := json.Unmarshal(data, &inst); err != nil {
|
||||||
|
return fmt.Errorf("failed to unmarshal instance: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := db.Save(&inst); err != nil {
|
||||||
|
return fmt.Errorf("failed to save instance to database: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
log.Printf("Migrated instance %s from JSON to SQLite", inst.Name)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
176
docs/docs.go
176
docs/docs.go
@@ -999,156 +999,6 @@ const docTemplate = `{
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"/api/v1/llama-cpp/{name}/models": {
|
|
||||||
"get": {
|
|
||||||
"security": [
|
|
||||||
{
|
|
||||||
"ApiKeyAuth": []
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"description": "Returns a list of models available in the specified llama.cpp instance",
|
|
||||||
"tags": [
|
|
||||||
"Llama.cpp"
|
|
||||||
],
|
|
||||||
"summary": "List models in a llama.cpp instance",
|
|
||||||
"parameters": [
|
|
||||||
{
|
|
||||||
"type": "string",
|
|
||||||
"description": "Instance Name",
|
|
||||||
"name": "name",
|
|
||||||
"in": "path",
|
|
||||||
"required": true
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"responses": {
|
|
||||||
"200": {
|
|
||||||
"description": "Models list response",
|
|
||||||
"schema": {
|
|
||||||
"type": "object",
|
|
||||||
"additionalProperties": true
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"400": {
|
|
||||||
"description": "Invalid instance",
|
|
||||||
"schema": {
|
|
||||||
"type": "string"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"500": {
|
|
||||||
"description": "Internal Server Error",
|
|
||||||
"schema": {
|
|
||||||
"type": "string"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"/api/v1/llama-cpp/{name}/models/{model}/load": {
|
|
||||||
"post": {
|
|
||||||
"security": [
|
|
||||||
{
|
|
||||||
"ApiKeyAuth": []
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"description": "Loads the specified model in the given llama.cpp instance",
|
|
||||||
"tags": [
|
|
||||||
"Llama.cpp"
|
|
||||||
],
|
|
||||||
"summary": "Load a model in a llama.cpp instance",
|
|
||||||
"parameters": [
|
|
||||||
{
|
|
||||||
"type": "string",
|
|
||||||
"description": "Instance Name",
|
|
||||||
"name": "name",
|
|
||||||
"in": "path",
|
|
||||||
"required": true
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"type": "string",
|
|
||||||
"description": "Model Name",
|
|
||||||
"name": "model",
|
|
||||||
"in": "path",
|
|
||||||
"required": true
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"responses": {
|
|
||||||
"200": {
|
|
||||||
"description": "Success message",
|
|
||||||
"schema": {
|
|
||||||
"type": "object",
|
|
||||||
"additionalProperties": {
|
|
||||||
"type": "string"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"400": {
|
|
||||||
"description": "Invalid request",
|
|
||||||
"schema": {
|
|
||||||
"type": "string"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"500": {
|
|
||||||
"description": "Internal Server Error",
|
|
||||||
"schema": {
|
|
||||||
"type": "string"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"/api/v1/llama-cpp/{name}/models/{model}/unload": {
|
|
||||||
"post": {
|
|
||||||
"security": [
|
|
||||||
{
|
|
||||||
"ApiKeyAuth": []
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"description": "Unloads the specified model in the given llama.cpp instance",
|
|
||||||
"tags": [
|
|
||||||
"Llama.cpp"
|
|
||||||
],
|
|
||||||
"summary": "Unload a model in a llama.cpp instance",
|
|
||||||
"parameters": [
|
|
||||||
{
|
|
||||||
"type": "string",
|
|
||||||
"description": "Instance Name",
|
|
||||||
"name": "name",
|
|
||||||
"in": "path",
|
|
||||||
"required": true
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"type": "string",
|
|
||||||
"description": "Model Name",
|
|
||||||
"name": "model",
|
|
||||||
"in": "path",
|
|
||||||
"required": true
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"responses": {
|
|
||||||
"200": {
|
|
||||||
"description": "Success message",
|
|
||||||
"schema": {
|
|
||||||
"type": "object",
|
|
||||||
"additionalProperties": {
|
|
||||||
"type": "string"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"400": {
|
|
||||||
"description": "Invalid request",
|
|
||||||
"schema": {
|
|
||||||
"type": "string"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"500": {
|
|
||||||
"description": "Internal Server Error",
|
|
||||||
"schema": {
|
|
||||||
"type": "string"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"/api/v1/nodes": {
|
"/api/v1/nodes": {
|
||||||
"get": {
|
"get": {
|
||||||
"security": [
|
"security": [
|
||||||
@@ -1938,6 +1788,13 @@ const docTemplate = `{
|
|||||||
"config.AuthConfig": {
|
"config.AuthConfig": {
|
||||||
"type": "object",
|
"type": "object",
|
||||||
"properties": {
|
"properties": {
|
||||||
|
"inference_keys": {
|
||||||
|
"description": "List of keys for OpenAI compatible inference endpoints",
|
||||||
|
"type": "array",
|
||||||
|
"items": {
|
||||||
|
"type": "string"
|
||||||
|
}
|
||||||
|
},
|
||||||
"management_keys": {
|
"management_keys": {
|
||||||
"description": "List of keys for management endpoints",
|
"description": "List of keys for management endpoints",
|
||||||
"type": "array",
|
"type": "array",
|
||||||
@@ -2048,6 +1905,10 @@ const docTemplate = `{
|
|||||||
"description": "Automatically create the data directory if it doesn't exist",
|
"description": "Automatically create the data directory if it doesn't exist",
|
||||||
"type": "boolean"
|
"type": "boolean"
|
||||||
},
|
},
|
||||||
|
"configs_dir": {
|
||||||
|
"description": "Instance config directory override (relative to data_dir if not absolute)",
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
"default_auto_restart": {
|
"default_auto_restart": {
|
||||||
"description": "Default auto-restart setting for new instances",
|
"description": "Default auto-restart setting for new instances",
|
||||||
"type": "boolean"
|
"type": "boolean"
|
||||||
@@ -2068,21 +1929,6 @@ const docTemplate = `{
|
|||||||
"description": "Enable LRU eviction for instance logs",
|
"description": "Enable LRU eviction for instance logs",
|
||||||
"type": "boolean"
|
"type": "boolean"
|
||||||
},
|
},
|
||||||
"logRotationCompress": {
|
|
||||||
"description": "Whether to compress rotated log files",
|
|
||||||
"type": "boolean",
|
|
||||||
"default": false
|
|
||||||
},
|
|
||||||
"logRotationEnabled": {
|
|
||||||
"description": "Log rotation enabled",
|
|
||||||
"type": "boolean",
|
|
||||||
"default": true
|
|
||||||
},
|
|
||||||
"logRotationMaxSize": {
|
|
||||||
"description": "Maximum log file size in MB before rotation",
|
|
||||||
"type": "integer",
|
|
||||||
"default": 100
|
|
||||||
},
|
|
||||||
"logs_dir": {
|
"logs_dir": {
|
||||||
"description": "Logs directory override (relative to data_dir if not absolute)",
|
"description": "Logs directory override (relative to data_dir if not absolute)",
|
||||||
"type": "string"
|
"type": "string"
|
||||||
|
|||||||
@@ -222,100 +222,6 @@ curl -X DELETE http://localhost:8080/api/v1/instances/{name} \
|
|||||||
-H "Authorization: Bearer <token>"
|
-H "Authorization: Bearer <token>"
|
||||||
```
|
```
|
||||||
|
|
||||||
## Multi-Model llama.cpp Instances
|
|
||||||
|
|
||||||
!!! info "llama.cpp Router Mode"
|
|
||||||
llama.cpp instances support [**router mode**](https://huggingface.co/blog/ggml-org/model-management-in-llamacpp), allowing a single instance to serve multiple models dynamically. Models are loaded on-demand from the llama.cpp cache without restarting the instance.
|
|
||||||
|
|
||||||
### Creating a Multi-Model Instance
|
|
||||||
|
|
||||||
**Via Web UI**
|
|
||||||
|
|
||||||
1. Click **"Create Instance"**
|
|
||||||
2. Select **Backend Type**: "Llama Server"
|
|
||||||
3. Leave **Backend Options** empty `{}` or omit the model field
|
|
||||||
4. Create the instance
|
|
||||||
|
|
||||||
**Via API**
|
|
||||||
|
|
||||||
```bash
|
|
||||||
# Create instance without specifying a model (router mode)
|
|
||||||
curl -X POST http://localhost:8080/api/v1/instances/my-router \
|
|
||||||
-H "Content-Type: application/json" \
|
|
||||||
-H "Authorization: Bearer <token>" \
|
|
||||||
-d '{
|
|
||||||
"backend_type": "llama_cpp",
|
|
||||||
"backend_options": {},
|
|
||||||
"nodes": ["main"]
|
|
||||||
}'
|
|
||||||
```
|
|
||||||
|
|
||||||
### Managing Models
|
|
||||||
|
|
||||||
**Via Web UI**
|
|
||||||
|
|
||||||
1. Start the router mode instance
|
|
||||||
2. Instance card displays a badge showing loaded/total models (e.g., "2/5 models")
|
|
||||||
3. Click the **"Models"** button on the instance card
|
|
||||||
4. Models dialog opens showing:
|
|
||||||
- All available models from llama.cpp instance
|
|
||||||
- Status indicator (loaded, loading, or unloaded)
|
|
||||||
- Load/Unload buttons for each model
|
|
||||||
5. Click **"Load"** to load a model into memory
|
|
||||||
6. Click **"Unload"** to free up memory
|
|
||||||
|
|
||||||
**Via API**
|
|
||||||
|
|
||||||
```bash
|
|
||||||
# List available models
|
|
||||||
curl http://localhost:8080/api/v1/llama-cpp/my-router/models \
|
|
||||||
-H "Authorization: Bearer <token>"
|
|
||||||
|
|
||||||
# Load a model
|
|
||||||
curl -X POST http://localhost:8080/api/v1/llama-cpp/my-router/models/Mistral-7B-Instruct-v0.3.Q4_K_M.gguf/load \
|
|
||||||
-H "Content-Type: application/json" \
|
|
||||||
-H "Authorization: Bearer <token>" \
|
|
||||||
-d '{"model": "Mistral-7B-Instruct-v0.3.Q4_K_M.gguf"}'
|
|
||||||
|
|
||||||
# Unload a model
|
|
||||||
curl -X POST http://localhost:8080/api/v1/llama-cpp/my-router/models/Mistral-7B-Instruct-v0.3.Q4_K_M.gguf/unload \
|
|
||||||
-H "Content-Type: application/json" \
|
|
||||||
-H "Authorization: Bearer <token>" \
|
|
||||||
-d '{"model": "Mistral-7B-Instruct-v0.3.Q4_K_M.gguf"}'
|
|
||||||
```
|
|
||||||
|
|
||||||
### Using Multi-Model Instances
|
|
||||||
|
|
||||||
When making inference requests to a multi-model instance, specify the model using the format `instance_name/model_name`:
|
|
||||||
|
|
||||||
```bash
|
|
||||||
# OpenAI-compatible chat completion with specific model
|
|
||||||
curl -X POST http://localhost:8080/v1/chat/completions \
|
|
||||||
-H "Content-Type: application/json" \
|
|
||||||
-H "Authorization: Bearer <inference-key>" \
|
|
||||||
-d '{
|
|
||||||
"model": "my-router/Mistral-7B-Instruct-v0.3.Q4_K_M.gguf",
|
|
||||||
"messages": [
|
|
||||||
{"role": "user", "content": "Hello!"}
|
|
||||||
]
|
|
||||||
}'
|
|
||||||
|
|
||||||
# List all available models (includes multi-model instances)
|
|
||||||
curl http://localhost:8080/v1/models \
|
|
||||||
-H "Authorization: Bearer <inference-key>"
|
|
||||||
```
|
|
||||||
|
|
||||||
The response from `/v1/models` will include each model from multi-model instances as separate entries in the format `instance_name/model_name`.
|
|
||||||
|
|
||||||
### Model Discovery
|
|
||||||
|
|
||||||
Models are automatically discovered from the llama.cpp cache directory. The default cache locations are:
|
|
||||||
|
|
||||||
- **Linux/macOS**: `~/.cache/llama.cpp/`
|
|
||||||
- **Windows**: `%LOCALAPPDATA%\llama.cpp\`
|
|
||||||
|
|
||||||
Place your GGUF model files in the cache directory, and they will appear in the models list when you start a router mode instance.
|
|
||||||
|
|
||||||
## Instance Proxy
|
## Instance Proxy
|
||||||
|
|
||||||
Llamactl proxies all requests to the underlying backend instances (llama-server, MLX, or vLLM).
|
Llamactl proxies all requests to the underlying backend instances (llama-server, MLX, or vLLM).
|
||||||
|
|||||||
@@ -992,156 +992,6 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"/api/v1/llama-cpp/{name}/models": {
|
|
||||||
"get": {
|
|
||||||
"security": [
|
|
||||||
{
|
|
||||||
"ApiKeyAuth": []
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"description": "Returns a list of models available in the specified llama.cpp instance",
|
|
||||||
"tags": [
|
|
||||||
"Llama.cpp"
|
|
||||||
],
|
|
||||||
"summary": "List models in a llama.cpp instance",
|
|
||||||
"parameters": [
|
|
||||||
{
|
|
||||||
"type": "string",
|
|
||||||
"description": "Instance Name",
|
|
||||||
"name": "name",
|
|
||||||
"in": "path",
|
|
||||||
"required": true
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"responses": {
|
|
||||||
"200": {
|
|
||||||
"description": "Models list response",
|
|
||||||
"schema": {
|
|
||||||
"type": "object",
|
|
||||||
"additionalProperties": true
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"400": {
|
|
||||||
"description": "Invalid instance",
|
|
||||||
"schema": {
|
|
||||||
"type": "string"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"500": {
|
|
||||||
"description": "Internal Server Error",
|
|
||||||
"schema": {
|
|
||||||
"type": "string"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"/api/v1/llama-cpp/{name}/models/{model}/load": {
|
|
||||||
"post": {
|
|
||||||
"security": [
|
|
||||||
{
|
|
||||||
"ApiKeyAuth": []
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"description": "Loads the specified model in the given llama.cpp instance",
|
|
||||||
"tags": [
|
|
||||||
"Llama.cpp"
|
|
||||||
],
|
|
||||||
"summary": "Load a model in a llama.cpp instance",
|
|
||||||
"parameters": [
|
|
||||||
{
|
|
||||||
"type": "string",
|
|
||||||
"description": "Instance Name",
|
|
||||||
"name": "name",
|
|
||||||
"in": "path",
|
|
||||||
"required": true
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"type": "string",
|
|
||||||
"description": "Model Name",
|
|
||||||
"name": "model",
|
|
||||||
"in": "path",
|
|
||||||
"required": true
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"responses": {
|
|
||||||
"200": {
|
|
||||||
"description": "Success message",
|
|
||||||
"schema": {
|
|
||||||
"type": "object",
|
|
||||||
"additionalProperties": {
|
|
||||||
"type": "string"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"400": {
|
|
||||||
"description": "Invalid request",
|
|
||||||
"schema": {
|
|
||||||
"type": "string"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"500": {
|
|
||||||
"description": "Internal Server Error",
|
|
||||||
"schema": {
|
|
||||||
"type": "string"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"/api/v1/llama-cpp/{name}/models/{model}/unload": {
|
|
||||||
"post": {
|
|
||||||
"security": [
|
|
||||||
{
|
|
||||||
"ApiKeyAuth": []
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"description": "Unloads the specified model in the given llama.cpp instance",
|
|
||||||
"tags": [
|
|
||||||
"Llama.cpp"
|
|
||||||
],
|
|
||||||
"summary": "Unload a model in a llama.cpp instance",
|
|
||||||
"parameters": [
|
|
||||||
{
|
|
||||||
"type": "string",
|
|
||||||
"description": "Instance Name",
|
|
||||||
"name": "name",
|
|
||||||
"in": "path",
|
|
||||||
"required": true
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"type": "string",
|
|
||||||
"description": "Model Name",
|
|
||||||
"name": "model",
|
|
||||||
"in": "path",
|
|
||||||
"required": true
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"responses": {
|
|
||||||
"200": {
|
|
||||||
"description": "Success message",
|
|
||||||
"schema": {
|
|
||||||
"type": "object",
|
|
||||||
"additionalProperties": {
|
|
||||||
"type": "string"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"400": {
|
|
||||||
"description": "Invalid request",
|
|
||||||
"schema": {
|
|
||||||
"type": "string"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"500": {
|
|
||||||
"description": "Internal Server Error",
|
|
||||||
"schema": {
|
|
||||||
"type": "string"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"/api/v1/nodes": {
|
"/api/v1/nodes": {
|
||||||
"get": {
|
"get": {
|
||||||
"security": [
|
"security": [
|
||||||
@@ -1931,6 +1781,13 @@
|
|||||||
"config.AuthConfig": {
|
"config.AuthConfig": {
|
||||||
"type": "object",
|
"type": "object",
|
||||||
"properties": {
|
"properties": {
|
||||||
|
"inference_keys": {
|
||||||
|
"description": "List of keys for OpenAI compatible inference endpoints",
|
||||||
|
"type": "array",
|
||||||
|
"items": {
|
||||||
|
"type": "string"
|
||||||
|
}
|
||||||
|
},
|
||||||
"management_keys": {
|
"management_keys": {
|
||||||
"description": "List of keys for management endpoints",
|
"description": "List of keys for management endpoints",
|
||||||
"type": "array",
|
"type": "array",
|
||||||
@@ -2041,6 +1898,10 @@
|
|||||||
"description": "Automatically create the data directory if it doesn't exist",
|
"description": "Automatically create the data directory if it doesn't exist",
|
||||||
"type": "boolean"
|
"type": "boolean"
|
||||||
},
|
},
|
||||||
|
"configs_dir": {
|
||||||
|
"description": "Instance config directory override (relative to data_dir if not absolute)",
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
"default_auto_restart": {
|
"default_auto_restart": {
|
||||||
"description": "Default auto-restart setting for new instances",
|
"description": "Default auto-restart setting for new instances",
|
||||||
"type": "boolean"
|
"type": "boolean"
|
||||||
@@ -2061,21 +1922,6 @@
|
|||||||
"description": "Enable LRU eviction for instance logs",
|
"description": "Enable LRU eviction for instance logs",
|
||||||
"type": "boolean"
|
"type": "boolean"
|
||||||
},
|
},
|
||||||
"logRotationCompress": {
|
|
||||||
"description": "Whether to compress rotated log files",
|
|
||||||
"type": "boolean",
|
|
||||||
"default": false
|
|
||||||
},
|
|
||||||
"logRotationEnabled": {
|
|
||||||
"description": "Log rotation enabled",
|
|
||||||
"type": "boolean",
|
|
||||||
"default": true
|
|
||||||
},
|
|
||||||
"logRotationMaxSize": {
|
|
||||||
"description": "Maximum log file size in MB before rotation",
|
|
||||||
"type": "integer",
|
|
||||||
"default": 100
|
|
||||||
},
|
|
||||||
"logs_dir": {
|
"logs_dir": {
|
||||||
"description": "Logs directory override (relative to data_dir if not absolute)",
|
"description": "Logs directory override (relative to data_dir if not absolute)",
|
||||||
"type": "string"
|
"type": "string"
|
||||||
|
|||||||
@@ -39,6 +39,11 @@ definitions:
|
|||||||
type: object
|
type: object
|
||||||
config.AuthConfig:
|
config.AuthConfig:
|
||||||
properties:
|
properties:
|
||||||
|
inference_keys:
|
||||||
|
description: List of keys for OpenAI compatible inference endpoints
|
||||||
|
items:
|
||||||
|
type: string
|
||||||
|
type: array
|
||||||
management_keys:
|
management_keys:
|
||||||
description: List of keys for management endpoints
|
description: List of keys for management endpoints
|
||||||
items:
|
items:
|
||||||
@@ -113,6 +118,10 @@ definitions:
|
|||||||
auto_create_dirs:
|
auto_create_dirs:
|
||||||
description: Automatically create the data directory if it doesn't exist
|
description: Automatically create the data directory if it doesn't exist
|
||||||
type: boolean
|
type: boolean
|
||||||
|
configs_dir:
|
||||||
|
description: Instance config directory override (relative to data_dir if not
|
||||||
|
absolute)
|
||||||
|
type: string
|
||||||
default_auto_restart:
|
default_auto_restart:
|
||||||
description: Default auto-restart setting for new instances
|
description: Default auto-restart setting for new instances
|
||||||
type: boolean
|
type: boolean
|
||||||
@@ -128,18 +137,6 @@ definitions:
|
|||||||
enable_lru_eviction:
|
enable_lru_eviction:
|
||||||
description: Enable LRU eviction for instance logs
|
description: Enable LRU eviction for instance logs
|
||||||
type: boolean
|
type: boolean
|
||||||
logRotationCompress:
|
|
||||||
default: false
|
|
||||||
description: Whether to compress rotated log files
|
|
||||||
type: boolean
|
|
||||||
logRotationEnabled:
|
|
||||||
default: true
|
|
||||||
description: Log rotation enabled
|
|
||||||
type: boolean
|
|
||||||
logRotationMaxSize:
|
|
||||||
default: 100
|
|
||||||
description: Maximum log file size in MB before rotation
|
|
||||||
type: integer
|
|
||||||
logs_dir:
|
logs_dir:
|
||||||
description: Logs directory override (relative to data_dir if not absolute)
|
description: Logs directory override (relative to data_dir if not absolute)
|
||||||
type: string
|
type: string
|
||||||
@@ -958,102 +955,6 @@ paths:
|
|||||||
summary: Stop a running instance
|
summary: Stop a running instance
|
||||||
tags:
|
tags:
|
||||||
- Instances
|
- Instances
|
||||||
/api/v1/llama-cpp/{name}/models:
|
|
||||||
get:
|
|
||||||
description: Returns a list of models available in the specified llama.cpp instance
|
|
||||||
parameters:
|
|
||||||
- description: Instance Name
|
|
||||||
in: path
|
|
||||||
name: name
|
|
||||||
required: true
|
|
||||||
type: string
|
|
||||||
responses:
|
|
||||||
"200":
|
|
||||||
description: Models list response
|
|
||||||
schema:
|
|
||||||
additionalProperties: true
|
|
||||||
type: object
|
|
||||||
"400":
|
|
||||||
description: Invalid instance
|
|
||||||
schema:
|
|
||||||
type: string
|
|
||||||
"500":
|
|
||||||
description: Internal Server Error
|
|
||||||
schema:
|
|
||||||
type: string
|
|
||||||
security:
|
|
||||||
- ApiKeyAuth: []
|
|
||||||
summary: List models in a llama.cpp instance
|
|
||||||
tags:
|
|
||||||
- Llama.cpp
|
|
||||||
/api/v1/llama-cpp/{name}/models/{model}/load:
|
|
||||||
post:
|
|
||||||
description: Loads the specified model in the given llama.cpp instance
|
|
||||||
parameters:
|
|
||||||
- description: Instance Name
|
|
||||||
in: path
|
|
||||||
name: name
|
|
||||||
required: true
|
|
||||||
type: string
|
|
||||||
- description: Model Name
|
|
||||||
in: path
|
|
||||||
name: model
|
|
||||||
required: true
|
|
||||||
type: string
|
|
||||||
responses:
|
|
||||||
"200":
|
|
||||||
description: Success message
|
|
||||||
schema:
|
|
||||||
additionalProperties:
|
|
||||||
type: string
|
|
||||||
type: object
|
|
||||||
"400":
|
|
||||||
description: Invalid request
|
|
||||||
schema:
|
|
||||||
type: string
|
|
||||||
"500":
|
|
||||||
description: Internal Server Error
|
|
||||||
schema:
|
|
||||||
type: string
|
|
||||||
security:
|
|
||||||
- ApiKeyAuth: []
|
|
||||||
summary: Load a model in a llama.cpp instance
|
|
||||||
tags:
|
|
||||||
- Llama.cpp
|
|
||||||
/api/v1/llama-cpp/{name}/models/{model}/unload:
|
|
||||||
post:
|
|
||||||
description: Unloads the specified model in the given llama.cpp instance
|
|
||||||
parameters:
|
|
||||||
- description: Instance Name
|
|
||||||
in: path
|
|
||||||
name: name
|
|
||||||
required: true
|
|
||||||
type: string
|
|
||||||
- description: Model Name
|
|
||||||
in: path
|
|
||||||
name: model
|
|
||||||
required: true
|
|
||||||
type: string
|
|
||||||
responses:
|
|
||||||
"200":
|
|
||||||
description: Success message
|
|
||||||
schema:
|
|
||||||
additionalProperties:
|
|
||||||
type: string
|
|
||||||
type: object
|
|
||||||
"400":
|
|
||||||
description: Invalid request
|
|
||||||
schema:
|
|
||||||
type: string
|
|
||||||
"500":
|
|
||||||
description: Internal Server Error
|
|
||||||
schema:
|
|
||||||
type: string
|
|
||||||
security:
|
|
||||||
- ApiKeyAuth: []
|
|
||||||
summary: Unload a model in a llama.cpp instance
|
|
||||||
tags:
|
|
||||||
- Llama.cpp
|
|
||||||
/api/v1/nodes:
|
/api/v1/nodes:
|
||||||
get:
|
get:
|
||||||
description: Returns a map of all nodes configured in the server (node name
|
description: Returns a map of all nodes configured in the server (node name
|
||||||
|
|||||||
@@ -56,15 +56,13 @@ func (o *Options) UnmarshalJSON(data []byte) error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Create backend from constructor map
|
// Create backend from constructor map
|
||||||
constructor, exists := backendConstructors[o.BackendType]
|
|
||||||
if !exists {
|
|
||||||
return fmt.Errorf("unsupported backend type: %s", o.BackendType)
|
|
||||||
}
|
|
||||||
|
|
||||||
backend := constructor()
|
|
||||||
|
|
||||||
// If backend_options is provided, unmarshal into the backend
|
|
||||||
if o.BackendOptions != nil {
|
if o.BackendOptions != nil {
|
||||||
|
constructor, exists := backendConstructors[o.BackendType]
|
||||||
|
if !exists {
|
||||||
|
return fmt.Errorf("unsupported backend type: %s", o.BackendType)
|
||||||
|
}
|
||||||
|
|
||||||
|
backend := constructor()
|
||||||
optionsData, err := json.Marshal(o.BackendOptions)
|
optionsData, err := json.Marshal(o.BackendOptions)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("failed to marshal backend options: %w", err)
|
return fmt.Errorf("failed to marshal backend options: %w", err)
|
||||||
@@ -73,11 +71,10 @@ func (o *Options) UnmarshalJSON(data []byte) error {
|
|||||||
if err := json.Unmarshal(optionsData, backend); err != nil {
|
if err := json.Unmarshal(optionsData, backend); err != nil {
|
||||||
return fmt.Errorf("failed to unmarshal backend options: %w", err)
|
return fmt.Errorf("failed to unmarshal backend options: %w", err)
|
||||||
}
|
}
|
||||||
}
|
|
||||||
// If backend_options is nil or empty, backend remains as empty struct (for router mode)
|
|
||||||
|
|
||||||
// Store in the appropriate typed field
|
// Store in the appropriate typed field for backward compatibility
|
||||||
o.setBackendOptions(backend)
|
o.setBackendOptions(backend)
|
||||||
|
}
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -327,30 +327,20 @@ func (o *LlamaServerOptions) UnmarshalJSON(data []byte) error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (o *LlamaServerOptions) GetPort() int {
|
func (o *LlamaServerOptions) GetPort() int {
|
||||||
if o == nil {
|
|
||||||
return 0
|
|
||||||
}
|
|
||||||
return o.Port
|
return o.Port
|
||||||
}
|
}
|
||||||
|
|
||||||
func (o *LlamaServerOptions) SetPort(port int) {
|
func (o *LlamaServerOptions) SetPort(port int) {
|
||||||
if o == nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
o.Port = port
|
o.Port = port
|
||||||
}
|
}
|
||||||
|
|
||||||
func (o *LlamaServerOptions) GetHost() string {
|
func (o *LlamaServerOptions) GetHost() string {
|
||||||
if o == nil {
|
|
||||||
return "localhost"
|
|
||||||
}
|
|
||||||
return o.Host
|
return o.Host
|
||||||
}
|
}
|
||||||
|
|
||||||
func (o *LlamaServerOptions) Validate() error {
|
func (o *LlamaServerOptions) Validate() error {
|
||||||
// Allow nil options for router mode where llama.cpp manages models dynamically
|
|
||||||
if o == nil {
|
if o == nil {
|
||||||
return nil
|
return validation.ValidationError(fmt.Errorf("llama server options cannot be nil for llama.cpp backend"))
|
||||||
}
|
}
|
||||||
|
|
||||||
// Use reflection to check all string fields for injection patterns
|
// Use reflection to check all string fields for injection patterns
|
||||||
@@ -380,9 +370,6 @@ func (o *LlamaServerOptions) Validate() error {
|
|||||||
|
|
||||||
// BuildCommandArgs converts InstanceOptions to command line arguments
|
// BuildCommandArgs converts InstanceOptions to command line arguments
|
||||||
func (o *LlamaServerOptions) BuildCommandArgs() []string {
|
func (o *LlamaServerOptions) BuildCommandArgs() []string {
|
||||||
if o == nil {
|
|
||||||
return []string{}
|
|
||||||
}
|
|
||||||
// Llama uses multiple flags for arrays by default (not comma-separated)
|
// Llama uses multiple flags for arrays by default (not comma-separated)
|
||||||
// Use package-level llamaMultiValuedFlags variable
|
// Use package-level llamaMultiValuedFlags variable
|
||||||
args := BuildCommandArgs(o, llamaMultiValuedFlags)
|
args := BuildCommandArgs(o, llamaMultiValuedFlags)
|
||||||
@@ -394,9 +381,6 @@ func (o *LlamaServerOptions) BuildCommandArgs() []string {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (o *LlamaServerOptions) BuildDockerArgs() []string {
|
func (o *LlamaServerOptions) BuildDockerArgs() []string {
|
||||||
if o == nil {
|
|
||||||
return []string{}
|
|
||||||
}
|
|
||||||
// For llama, Docker args are the same as normal args
|
// For llama, Docker args are the same as normal args
|
||||||
return o.BuildCommandArgs()
|
return o.BuildCommandArgs()
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -32,7 +32,19 @@ func LoadConfig(configPath string) (AppConfig, error) {
|
|||||||
// 3. Override with environment variables
|
// 3. Override with environment variables
|
||||||
loadEnvVars(&cfg)
|
loadEnvVars(&cfg)
|
||||||
|
|
||||||
|
// Log warning if deprecated inference keys are present
|
||||||
|
if len(cfg.Auth.InferenceKeys) > 0 {
|
||||||
|
log.Println("⚠️ Config-based inference keys are no longer supported and will be ignored.")
|
||||||
|
log.Println(" Please create inference keys in web UI or via management API.")
|
||||||
|
}
|
||||||
|
|
||||||
// Set default directories if not specified
|
// Set default directories if not specified
|
||||||
|
if cfg.Instances.InstancesDir == "" {
|
||||||
|
cfg.Instances.InstancesDir = filepath.Join(cfg.DataDir, "instances")
|
||||||
|
} else {
|
||||||
|
// Log deprecation warning if using custom instances dir
|
||||||
|
log.Println("⚠️ Instances directory is deprecated and will be removed in future versions. Instances are persisted in the database.")
|
||||||
|
}
|
||||||
if cfg.Instances.LogsDir == "" {
|
if cfg.Instances.LogsDir == "" {
|
||||||
cfg.Instances.LogsDir = filepath.Join(cfg.DataDir, "logs")
|
cfg.Instances.LogsDir = filepath.Join(cfg.DataDir, "logs")
|
||||||
}
|
}
|
||||||
@@ -89,6 +101,7 @@ func (cfg *AppConfig) SanitizedCopy() (AppConfig, error) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Clear sensitive information
|
// Clear sensitive information
|
||||||
|
sanitized.Auth.InferenceKeys = []string{}
|
||||||
sanitized.Auth.ManagementKeys = []string{}
|
sanitized.Auth.ManagementKeys = []string{}
|
||||||
|
|
||||||
// Clear API keys from nodes
|
// Clear API keys from nodes
|
||||||
|
|||||||
@@ -41,6 +41,9 @@ func TestLoadConfig_Defaults(t *testing.T) {
|
|||||||
t.Fatalf("Failed to get user home directory: %v", err)
|
t.Fatalf("Failed to get user home directory: %v", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if cfg.Instances.InstancesDir != filepath.Join(homedir, ".local", "share", "llamactl", "instances") {
|
||||||
|
t.Errorf("Expected default instances directory '%s', got %q", filepath.Join(homedir, ".local", "share", "llamactl", "instances"), cfg.Instances.InstancesDir)
|
||||||
|
}
|
||||||
if cfg.Instances.LogsDir != filepath.Join(homedir, ".local", "share", "llamactl", "logs") {
|
if cfg.Instances.LogsDir != filepath.Join(homedir, ".local", "share", "llamactl", "logs") {
|
||||||
t.Errorf("Expected default logs directory '%s', got %q", filepath.Join(homedir, ".local", "share", "llamactl", "logs"), cfg.Instances.LogsDir)
|
t.Errorf("Expected default logs directory '%s', got %q", filepath.Join(homedir, ".local", "share", "llamactl", "logs"), cfg.Instances.LogsDir)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -53,7 +53,10 @@ func getDefaultConfig(dataDir string) AppConfig {
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
Instances: InstancesConfig{
|
Instances: InstancesConfig{
|
||||||
PortRange: [2]int{8000, 9000},
|
PortRange: [2]int{8000, 9000},
|
||||||
|
// NOTE: empty string is set as placeholder value since InstancesDir
|
||||||
|
// should be relative path to DataDir if not explicitly set.
|
||||||
|
InstancesDir: "",
|
||||||
AutoCreateDirs: true,
|
AutoCreateDirs: true,
|
||||||
MaxInstances: -1, // -1 means unlimited
|
MaxInstances: -1, // -1 means unlimited
|
||||||
MaxRunningInstances: -1, // -1 means unlimited
|
MaxRunningInstances: -1, // -1 means unlimited
|
||||||
@@ -77,6 +80,7 @@ func getDefaultConfig(dataDir string) AppConfig {
|
|||||||
},
|
},
|
||||||
Auth: AuthConfig{
|
Auth: AuthConfig{
|
||||||
RequireInferenceAuth: true,
|
RequireInferenceAuth: true,
|
||||||
|
InferenceKeys: []string{},
|
||||||
RequireManagementAuth: true,
|
RequireManagementAuth: true,
|
||||||
ManagementKeys: []string{},
|
ManagementKeys: []string{},
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -31,6 +31,9 @@ func loadEnvVars(cfg *AppConfig) {
|
|||||||
if dataDir := os.Getenv("LLAMACTL_DATA_DIRECTORY"); dataDir != "" {
|
if dataDir := os.Getenv("LLAMACTL_DATA_DIRECTORY"); dataDir != "" {
|
||||||
cfg.DataDir = dataDir
|
cfg.DataDir = dataDir
|
||||||
}
|
}
|
||||||
|
if instancesDir := os.Getenv("LLAMACTL_INSTANCES_DIR"); instancesDir != "" {
|
||||||
|
cfg.Instances.InstancesDir = instancesDir
|
||||||
|
}
|
||||||
if logsDir := os.Getenv("LLAMACTL_LOGS_DIR"); logsDir != "" {
|
if logsDir := os.Getenv("LLAMACTL_LOGS_DIR"); logsDir != "" {
|
||||||
cfg.Instances.LogsDir = logsDir
|
cfg.Instances.LogsDir = logsDir
|
||||||
}
|
}
|
||||||
@@ -217,6 +220,9 @@ func loadEnvVars(cfg *AppConfig) {
|
|||||||
cfg.Auth.RequireInferenceAuth = b
|
cfg.Auth.RequireInferenceAuth = b
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
if inferenceKeys := os.Getenv("LLAMACTL_INFERENCE_KEYS"); inferenceKeys != "" {
|
||||||
|
cfg.Auth.InferenceKeys = strings.Split(inferenceKeys, ",")
|
||||||
|
}
|
||||||
if requireManagementAuth := os.Getenv("LLAMACTL_REQUIRE_MANAGEMENT_AUTH"); requireManagementAuth != "" {
|
if requireManagementAuth := os.Getenv("LLAMACTL_REQUIRE_MANAGEMENT_AUTH"); requireManagementAuth != "" {
|
||||||
if b, err := strconv.ParseBool(requireManagementAuth); err == nil {
|
if b, err := strconv.ParseBool(requireManagementAuth); err == nil {
|
||||||
cfg.Auth.RequireManagementAuth = b
|
cfg.Auth.RequireManagementAuth = b
|
||||||
|
|||||||
@@ -81,6 +81,9 @@ type InstancesConfig struct {
|
|||||||
// Port range for instances (e.g., 8000,9000)
|
// Port range for instances (e.g., 8000,9000)
|
||||||
PortRange [2]int `yaml:"port_range" json:"port_range"`
|
PortRange [2]int `yaml:"port_range" json:"port_range"`
|
||||||
|
|
||||||
|
// Instance config directory override (relative to data_dir if not absolute)
|
||||||
|
InstancesDir string `yaml:"configs_dir" json:"configs_dir"`
|
||||||
|
|
||||||
// Automatically create the data directory if it doesn't exist
|
// Automatically create the data directory if it doesn't exist
|
||||||
AutoCreateDirs bool `yaml:"auto_create_dirs" json:"auto_create_dirs"`
|
AutoCreateDirs bool `yaml:"auto_create_dirs" json:"auto_create_dirs"`
|
||||||
|
|
||||||
@@ -130,6 +133,9 @@ type AuthConfig struct {
|
|||||||
// Require authentication for OpenAI compatible inference endpoints
|
// Require authentication for OpenAI compatible inference endpoints
|
||||||
RequireInferenceAuth bool `yaml:"require_inference_auth" json:"require_inference_auth"`
|
RequireInferenceAuth bool `yaml:"require_inference_auth" json:"require_inference_auth"`
|
||||||
|
|
||||||
|
// List of keys for OpenAI compatible inference endpoints
|
||||||
|
InferenceKeys []string `yaml:"inference_keys" json:"inference_keys"`
|
||||||
|
|
||||||
// Require authentication for management endpoints
|
// Require authentication for management endpoints
|
||||||
RequireManagementAuth bool `yaml:"require_management_auth" json:"require_management_auth"`
|
RequireManagementAuth bool `yaml:"require_management_auth" json:"require_management_auth"`
|
||||||
|
|
||||||
|
|||||||
@@ -202,6 +202,7 @@ func createTestAppConfig(instancesDir string) *config.AppConfig {
|
|||||||
},
|
},
|
||||||
Instances: config.InstancesConfig{
|
Instances: config.InstancesConfig{
|
||||||
PortRange: [2]int{8000, 9000},
|
PortRange: [2]int{8000, 9000},
|
||||||
|
InstancesDir: instancesDir,
|
||||||
MaxInstances: 10,
|
MaxInstances: 10,
|
||||||
MaxRunningInstances: 10,
|
MaxRunningInstances: 10,
|
||||||
DefaultAutoRestart: true,
|
DefaultAutoRestart: true,
|
||||||
|
|||||||
@@ -38,6 +38,7 @@ func TestCreateInstance_FailsWithDuplicateName(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func TestCreateInstance_FailsWhenMaxInstancesReached(t *testing.T) {
|
func TestCreateInstance_FailsWhenMaxInstancesReached(t *testing.T) {
|
||||||
|
tempDir := t.TempDir()
|
||||||
appConfig := &config.AppConfig{
|
appConfig := &config.AppConfig{
|
||||||
Backends: config.BackendConfig{
|
Backends: config.BackendConfig{
|
||||||
LlamaCpp: config.BackendSettings{
|
LlamaCpp: config.BackendSettings{
|
||||||
@@ -46,6 +47,7 @@ func TestCreateInstance_FailsWhenMaxInstancesReached(t *testing.T) {
|
|||||||
},
|
},
|
||||||
Instances: config.InstancesConfig{
|
Instances: config.InstancesConfig{
|
||||||
PortRange: [2]int{8000, 9000},
|
PortRange: [2]int{8000, 9000},
|
||||||
|
InstancesDir: tempDir,
|
||||||
MaxInstances: 1, // Very low limit for testing
|
MaxInstances: 1, // Very low limit for testing
|
||||||
TimeoutCheckInterval: 5,
|
TimeoutCheckInterval: 5,
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -275,3 +275,16 @@ func TestAutoGenerationScenarios(t *testing.T) {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestConfigBasedInferenceKeysDeprecationWarning(t *testing.T) {
|
||||||
|
// Test that config-based inference keys trigger a warning (captured in logs)
|
||||||
|
cfg := config.AuthConfig{
|
||||||
|
InferenceKeys: []string{"sk-inference-old"},
|
||||||
|
}
|
||||||
|
|
||||||
|
// Creating middleware should log a warning, but shouldn't fail
|
||||||
|
_ = server.NewAPIAuthMiddleware(cfg, nil)
|
||||||
|
|
||||||
|
// If we get here without panic, the test passes
|
||||||
|
// The warning is logged but not returned as an error
|
||||||
|
}
|
||||||
|
|||||||
@@ -26,6 +26,9 @@ func SetupRouter(handler *Handler) *chi.Mux {
|
|||||||
MaxAge: 300,
|
MaxAge: 300,
|
||||||
}))
|
}))
|
||||||
|
|
||||||
|
// Add API authentication middleware
|
||||||
|
authMiddleware := NewAPIAuthMiddleware(handler.cfg.Auth, handler.authStore)
|
||||||
|
|
||||||
if handler.cfg.Server.EnableSwagger {
|
if handler.cfg.Server.EnableSwagger {
|
||||||
r.Get("/swagger/*", httpSwagger.Handler(
|
r.Get("/swagger/*", httpSwagger.Handler(
|
||||||
httpSwagger.URL("/swagger/doc.json"),
|
httpSwagger.URL("/swagger/doc.json"),
|
||||||
@@ -35,8 +38,8 @@ func SetupRouter(handler *Handler) *chi.Mux {
|
|||||||
// Define routes
|
// Define routes
|
||||||
r.Route("/api/v1", func(r chi.Router) {
|
r.Route("/api/v1", func(r chi.Router) {
|
||||||
|
|
||||||
if handler.authMiddleware != nil && handler.cfg.Auth.RequireManagementAuth {
|
if authMiddleware != nil && handler.cfg.Auth.RequireManagementAuth {
|
||||||
r.Use(handler.authMiddleware.ManagementAuthMiddleware())
|
r.Use(authMiddleware.ManagementAuthMiddleware())
|
||||||
}
|
}
|
||||||
|
|
||||||
r.Get("/version", handler.VersionHandler())
|
r.Get("/version", handler.VersionHandler())
|
||||||
@@ -111,8 +114,8 @@ func SetupRouter(handler *Handler) *chi.Mux {
|
|||||||
|
|
||||||
r.Route("/v1", func(r chi.Router) {
|
r.Route("/v1", func(r chi.Router) {
|
||||||
|
|
||||||
if handler.authMiddleware != nil && handler.cfg.Auth.RequireInferenceAuth {
|
if authMiddleware != nil && handler.cfg.Auth.RequireInferenceAuth {
|
||||||
r.Use(handler.authMiddleware.InferenceAuthMiddleware())
|
r.Use(authMiddleware.InferenceAuthMiddleware())
|
||||||
}
|
}
|
||||||
|
|
||||||
r.Get("/models", handler.OpenAIListInstances()) // List instances in OpenAI-compatible format
|
r.Get("/models", handler.OpenAIListInstances()) // List instances in OpenAI-compatible format
|
||||||
@@ -139,8 +142,8 @@ func SetupRouter(handler *Handler) *chi.Mux {
|
|||||||
// Private Routes
|
// Private Routes
|
||||||
r.Group(func(r chi.Router) {
|
r.Group(func(r chi.Router) {
|
||||||
|
|
||||||
if handler.authMiddleware != nil && handler.cfg.Auth.RequireInferenceAuth {
|
if authMiddleware != nil && handler.cfg.Auth.RequireInferenceAuth {
|
||||||
r.Use(handler.authMiddleware.InferenceAuthMiddleware())
|
r.Use(authMiddleware.InferenceAuthMiddleware())
|
||||||
}
|
}
|
||||||
|
|
||||||
// This handler auto starts the server if it's not running
|
// This handler auto starts the server if it's not running
|
||||||
|
|||||||
@@ -4,6 +4,7 @@ Simple Python script to interact with local LLM server's OpenAI-compatible API
|
|||||||
"""
|
"""
|
||||||
|
|
||||||
import requests
|
import requests
|
||||||
|
import json
|
||||||
import sys
|
import sys
|
||||||
|
|
||||||
# Local LLM server configuration
|
# Local LLM server configuration
|
||||||
|
|||||||
74
test_llm.py
Normal file
74
test_llm.py
Normal file
@@ -0,0 +1,74 @@
|
|||||||
|
#!/usr/bin/env python3
|
||||||
|
"""
|
||||||
|
Simple Python script to interact with local LLM server's OpenAI-compatible API
|
||||||
|
"""
|
||||||
|
|
||||||
|
import requests
|
||||||
|
|
||||||
|
# Local LLM server configuration
|
||||||
|
LLM_SERVER_URL = "http://localhost:8080/v1/chat/completions"
|
||||||
|
MODEL_NAME = "proxy-test" # Default model name, can be changed based on your setup
|
||||||
|
|
||||||
|
def send_message(message, model=MODEL_NAME, temperature=0.7, max_tokens=1000):
|
||||||
|
"""
|
||||||
|
Send a message to local LLM server API
|
||||||
|
|
||||||
|
Args:
|
||||||
|
message (str): The message to send
|
||||||
|
model (str): Model name (depends on your LLM server setup)
|
||||||
|
temperature (float): Controls randomness (0.0 to 1.0)
|
||||||
|
max_tokens (int): Maximum tokens in response
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
str: The AI response or error message
|
||||||
|
"""
|
||||||
|
|
||||||
|
headers = {
|
||||||
|
"Content-Type": "application/json",
|
||||||
|
"Authorization": "Bearer test-inf"
|
||||||
|
}
|
||||||
|
|
||||||
|
data = {
|
||||||
|
"model": model,
|
||||||
|
"messages": [
|
||||||
|
{
|
||||||
|
"role": "user",
|
||||||
|
"content": message
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"temperature": temperature,
|
||||||
|
"max_tokens": max_tokens,
|
||||||
|
"stream": False
|
||||||
|
}
|
||||||
|
|
||||||
|
response = requests.post(LLM_SERVER_URL, headers=headers, json=data, timeout=60)
|
||||||
|
response.raise_for_status()
|
||||||
|
|
||||||
|
result = response.json()
|
||||||
|
return result["choices"][0]["message"]["content"]
|
||||||
|
|
||||||
|
def main():
|
||||||
|
"""Run in interactive mode for continuous conversation"""
|
||||||
|
print("Local LLM Chat Client")
|
||||||
|
print("-" * 40)
|
||||||
|
|
||||||
|
while True:
|
||||||
|
try:
|
||||||
|
user_input = input("\nYou: ").strip()
|
||||||
|
|
||||||
|
if not user_input:
|
||||||
|
continue
|
||||||
|
|
||||||
|
print("AI: ", end="", flush=True)
|
||||||
|
response = send_message(user_input)
|
||||||
|
print(response)
|
||||||
|
|
||||||
|
except KeyboardInterrupt:
|
||||||
|
print("\nGoodbye!")
|
||||||
|
break
|
||||||
|
except EOFError:
|
||||||
|
print("\nGoodbye!")
|
||||||
|
break
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
main()
|
||||||
372
webui/package-lock.json
generated
372
webui/package-lock.json
generated
@@ -18,20 +18,20 @@
|
|||||||
"class-variance-authority": "^0.7.1",
|
"class-variance-authority": "^0.7.1",
|
||||||
"clsx": "^2.1.1",
|
"clsx": "^2.1.1",
|
||||||
"date-fns": "^4.1.0",
|
"date-fns": "^4.1.0",
|
||||||
"lucide-react": "^0.562.0",
|
"lucide-react": "^0.560.0",
|
||||||
"react": "^19.2.0",
|
"react": "^19.2.0",
|
||||||
"react-dom": "^19.2.0",
|
"react-dom": "^19.2.0",
|
||||||
"sonner": "^2.0.7",
|
"sonner": "^2.0.7",
|
||||||
"tailwind-merge": "^3.4.0",
|
"tailwind-merge": "^3.4.0",
|
||||||
"tailwindcss": "^4.1.11",
|
"tailwindcss": "^4.1.11",
|
||||||
"zod": "^4.2.0"
|
"zod": "^4.1.12"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@eslint/js": "^9.39.1",
|
"@eslint/js": "^9.39.1",
|
||||||
"@testing-library/jest-dom": "^6.9.1",
|
"@testing-library/jest-dom": "^6.9.1",
|
||||||
"@testing-library/react": "^16.3.0",
|
"@testing-library/react": "^16.3.0",
|
||||||
"@testing-library/user-event": "^14.6.1",
|
"@testing-library/user-event": "^14.6.1",
|
||||||
"@types/node": "^25.0.2",
|
"@types/node": "^24.10.1",
|
||||||
"@types/react": "^19.2.4",
|
"@types/react": "^19.2.4",
|
||||||
"@types/react-dom": "^19.2.3",
|
"@types/react-dom": "^19.2.3",
|
||||||
"@vitejs/plugin-react": "^5.1.1",
|
"@vitejs/plugin-react": "^5.1.1",
|
||||||
@@ -43,8 +43,8 @@
|
|||||||
"jsdom": "^27.3.0",
|
"jsdom": "^27.3.0",
|
||||||
"tw-animate-css": "^1.4.0",
|
"tw-animate-css": "^1.4.0",
|
||||||
"typescript": "^5.9.3",
|
"typescript": "^5.9.3",
|
||||||
"typescript-eslint": "^8.50.0",
|
"typescript-eslint": "^8.49.0",
|
||||||
"vite": "^7.3.0",
|
"vite": "^7.2.2",
|
||||||
"vitest": "^4.0.8"
|
"vitest": "^4.0.8"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
@@ -564,9 +564,9 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@esbuild/aix-ppc64": {
|
"node_modules/@esbuild/aix-ppc64": {
|
||||||
"version": "0.27.1",
|
"version": "0.25.8",
|
||||||
"resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.27.1.tgz",
|
"resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.25.8.tgz",
|
||||||
"integrity": "sha512-HHB50pdsBX6k47S4u5g/CaLjqS3qwaOVE5ILsq64jyzgMhLuCuZ8rGzM9yhsAjfjkbgUPMzZEPa7DAp7yz6vuA==",
|
"integrity": "sha512-urAvrUedIqEiFR3FYSLTWQgLu5tb+m0qZw0NBEasUeo6wuqatkMDaRT+1uABiGXEu5vqgPd7FGE1BhsAIy9QVA==",
|
||||||
"cpu": [
|
"cpu": [
|
||||||
"ppc64"
|
"ppc64"
|
||||||
],
|
],
|
||||||
@@ -580,9 +580,9 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@esbuild/android-arm": {
|
"node_modules/@esbuild/android-arm": {
|
||||||
"version": "0.27.1",
|
"version": "0.25.8",
|
||||||
"resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.27.1.tgz",
|
"resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.25.8.tgz",
|
||||||
"integrity": "sha512-kFqa6/UcaTbGm/NncN9kzVOODjhZW8e+FRdSeypWe6j33gzclHtwlANs26JrupOntlcWmB0u8+8HZo8s7thHvg==",
|
"integrity": "sha512-RONsAvGCz5oWyePVnLdZY/HHwA++nxYWIX1atInlaW6SEkwq6XkP3+cb825EUcRs5Vss/lGh/2YxAb5xqc07Uw==",
|
||||||
"cpu": [
|
"cpu": [
|
||||||
"arm"
|
"arm"
|
||||||
],
|
],
|
||||||
@@ -596,9 +596,9 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@esbuild/android-arm64": {
|
"node_modules/@esbuild/android-arm64": {
|
||||||
"version": "0.27.1",
|
"version": "0.25.8",
|
||||||
"resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.27.1.tgz",
|
"resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.25.8.tgz",
|
||||||
"integrity": "sha512-45fuKmAJpxnQWixOGCrS+ro4Uvb4Re9+UTieUY2f8AEc+t7d4AaZ6eUJ3Hva7dtrxAAWHtlEFsXFMAgNnGU9uQ==",
|
"integrity": "sha512-OD3p7LYzWpLhZEyATcTSJ67qB5D+20vbtr6vHlHWSQYhKtzUYrETuWThmzFpZtFsBIxRvhO07+UgVA9m0i/O1w==",
|
||||||
"cpu": [
|
"cpu": [
|
||||||
"arm64"
|
"arm64"
|
||||||
],
|
],
|
||||||
@@ -612,9 +612,9 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@esbuild/android-x64": {
|
"node_modules/@esbuild/android-x64": {
|
||||||
"version": "0.27.1",
|
"version": "0.25.8",
|
||||||
"resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.27.1.tgz",
|
"resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.25.8.tgz",
|
||||||
"integrity": "sha512-LBEpOz0BsgMEeHgenf5aqmn/lLNTFXVfoWMUox8CtWWYK9X4jmQzWjoGoNb8lmAYml/tQ/Ysvm8q7szu7BoxRQ==",
|
"integrity": "sha512-yJAVPklM5+4+9dTeKwHOaA+LQkmrKFX96BM0A/2zQrbS6ENCmxc4OVoBs5dPkCCak2roAD+jKCdnmOqKszPkjA==",
|
||||||
"cpu": [
|
"cpu": [
|
||||||
"x64"
|
"x64"
|
||||||
],
|
],
|
||||||
@@ -628,9 +628,9 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@esbuild/darwin-arm64": {
|
"node_modules/@esbuild/darwin-arm64": {
|
||||||
"version": "0.27.1",
|
"version": "0.25.8",
|
||||||
"resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.27.1.tgz",
|
"resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.25.8.tgz",
|
||||||
"integrity": "sha512-veg7fL8eMSCVKL7IW4pxb54QERtedFDfY/ASrumK/SbFsXnRazxY4YykN/THYqFnFwJ0aVjiUrVG2PwcdAEqQQ==",
|
"integrity": "sha512-Jw0mxgIaYX6R8ODrdkLLPwBqHTtYHJSmzzd+QeytSugzQ0Vg4c5rDky5VgkoowbZQahCbsv1rT1KW72MPIkevw==",
|
||||||
"cpu": [
|
"cpu": [
|
||||||
"arm64"
|
"arm64"
|
||||||
],
|
],
|
||||||
@@ -644,9 +644,9 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@esbuild/darwin-x64": {
|
"node_modules/@esbuild/darwin-x64": {
|
||||||
"version": "0.27.1",
|
"version": "0.25.8",
|
||||||
"resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.27.1.tgz",
|
"resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.25.8.tgz",
|
||||||
"integrity": "sha512-+3ELd+nTzhfWb07Vol7EZ+5PTbJ/u74nC6iv4/lwIU99Ip5uuY6QoIf0Hn4m2HoV0qcnRivN3KSqc+FyCHjoVQ==",
|
"integrity": "sha512-Vh2gLxxHnuoQ+GjPNvDSDRpoBCUzY4Pu0kBqMBDlK4fuWbKgGtmDIeEC081xi26PPjn+1tct+Bh8FjyLlw1Zlg==",
|
||||||
"cpu": [
|
"cpu": [
|
||||||
"x64"
|
"x64"
|
||||||
],
|
],
|
||||||
@@ -660,9 +660,9 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@esbuild/freebsd-arm64": {
|
"node_modules/@esbuild/freebsd-arm64": {
|
||||||
"version": "0.27.1",
|
"version": "0.25.8",
|
||||||
"resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.27.1.tgz",
|
"resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.25.8.tgz",
|
||||||
"integrity": "sha512-/8Rfgns4XD9XOSXlzUDepG8PX+AVWHliYlUkFI3K3GB6tqbdjYqdhcb4BKRd7C0BhZSoaCxhv8kTcBrcZWP+xg==",
|
"integrity": "sha512-YPJ7hDQ9DnNe5vxOm6jaie9QsTwcKedPvizTVlqWG9GBSq+BuyWEDazlGaDTC5NGU4QJd666V0yqCBL2oWKPfA==",
|
||||||
"cpu": [
|
"cpu": [
|
||||||
"arm64"
|
"arm64"
|
||||||
],
|
],
|
||||||
@@ -676,9 +676,9 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@esbuild/freebsd-x64": {
|
"node_modules/@esbuild/freebsd-x64": {
|
||||||
"version": "0.27.1",
|
"version": "0.25.8",
|
||||||
"resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.27.1.tgz",
|
"resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.25.8.tgz",
|
||||||
"integrity": "sha512-GITpD8dK9C+r+5yRT/UKVT36h/DQLOHdwGVwwoHidlnA168oD3uxA878XloXebK4Ul3gDBBIvEdL7go9gCUFzQ==",
|
"integrity": "sha512-MmaEXxQRdXNFsRN/KcIimLnSJrk2r5H8v+WVafRWz5xdSVmWLoITZQXcgehI2ZE6gioE6HirAEToM/RvFBeuhw==",
|
||||||
"cpu": [
|
"cpu": [
|
||||||
"x64"
|
"x64"
|
||||||
],
|
],
|
||||||
@@ -692,9 +692,9 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@esbuild/linux-arm": {
|
"node_modules/@esbuild/linux-arm": {
|
||||||
"version": "0.27.1",
|
"version": "0.25.8",
|
||||||
"resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.27.1.tgz",
|
"resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.25.8.tgz",
|
||||||
"integrity": "sha512-ieMID0JRZY/ZeCrsFQ3Y3NlHNCqIhTprJfDgSB3/lv5jJZ8FX3hqPyXWhe+gvS5ARMBJ242PM+VNz/ctNj//eA==",
|
"integrity": "sha512-FuzEP9BixzZohl1kLf76KEVOsxtIBFwCaLupVuk4eFVnOZfU+Wsn+x5Ryam7nILV2pkq2TqQM9EZPsOBuMC+kg==",
|
||||||
"cpu": [
|
"cpu": [
|
||||||
"arm"
|
"arm"
|
||||||
],
|
],
|
||||||
@@ -708,9 +708,9 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@esbuild/linux-arm64": {
|
"node_modules/@esbuild/linux-arm64": {
|
||||||
"version": "0.27.1",
|
"version": "0.25.8",
|
||||||
"resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.27.1.tgz",
|
"resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.25.8.tgz",
|
||||||
"integrity": "sha512-W9//kCrh/6in9rWIBdKaMtuTTzNj6jSeG/haWBADqLLa9P8O5YSRDzgD5y9QBok4AYlzS6ARHifAb75V6G670Q==",
|
"integrity": "sha512-WIgg00ARWv/uYLU7lsuDK00d/hHSfES5BzdWAdAig1ioV5kaFNrtK8EqGcUBJhYqotlUByUKz5Qo6u8tt7iD/w==",
|
||||||
"cpu": [
|
"cpu": [
|
||||||
"arm64"
|
"arm64"
|
||||||
],
|
],
|
||||||
@@ -724,9 +724,9 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@esbuild/linux-ia32": {
|
"node_modules/@esbuild/linux-ia32": {
|
||||||
"version": "0.27.1",
|
"version": "0.25.8",
|
||||||
"resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.27.1.tgz",
|
"resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.25.8.tgz",
|
||||||
"integrity": "sha512-VIUV4z8GD8rtSVMfAj1aXFahsi/+tcoXXNYmXgzISL+KB381vbSTNdeZHHHIYqFyXcoEhu9n5cT+05tRv13rlw==",
|
"integrity": "sha512-A1D9YzRX1i+1AJZuFFUMP1E9fMaYY+GnSQil9Tlw05utlE86EKTUA7RjwHDkEitmLYiFsRd9HwKBPEftNdBfjg==",
|
||||||
"cpu": [
|
"cpu": [
|
||||||
"ia32"
|
"ia32"
|
||||||
],
|
],
|
||||||
@@ -740,9 +740,9 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@esbuild/linux-loong64": {
|
"node_modules/@esbuild/linux-loong64": {
|
||||||
"version": "0.27.1",
|
"version": "0.25.8",
|
||||||
"resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.27.1.tgz",
|
"resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.25.8.tgz",
|
||||||
"integrity": "sha512-l4rfiiJRN7sTNI//ff65zJ9z8U+k6zcCg0LALU5iEWzY+a1mVZ8iWC1k5EsNKThZ7XCQ6YWtsZ8EWYm7r1UEsg==",
|
"integrity": "sha512-O7k1J/dwHkY1RMVvglFHl1HzutGEFFZ3kNiDMSOyUrB7WcoHGf96Sh+64nTRT26l3GMbCW01Ekh/ThKM5iI7hQ==",
|
||||||
"cpu": [
|
"cpu": [
|
||||||
"loong64"
|
"loong64"
|
||||||
],
|
],
|
||||||
@@ -756,9 +756,9 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@esbuild/linux-mips64el": {
|
"node_modules/@esbuild/linux-mips64el": {
|
||||||
"version": "0.27.1",
|
"version": "0.25.8",
|
||||||
"resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.27.1.tgz",
|
"resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.25.8.tgz",
|
||||||
"integrity": "sha512-U0bEuAOLvO/DWFdygTHWY8C067FXz+UbzKgxYhXC0fDieFa0kDIra1FAhsAARRJbvEyso8aAqvPdNxzWuStBnA==",
|
"integrity": "sha512-uv+dqfRazte3BzfMp8PAQXmdGHQt2oC/y2ovwpTteqrMx2lwaksiFZ/bdkXJC19ttTvNXBuWH53zy/aTj1FgGw==",
|
||||||
"cpu": [
|
"cpu": [
|
||||||
"mips64el"
|
"mips64el"
|
||||||
],
|
],
|
||||||
@@ -772,9 +772,9 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@esbuild/linux-ppc64": {
|
"node_modules/@esbuild/linux-ppc64": {
|
||||||
"version": "0.27.1",
|
"version": "0.25.8",
|
||||||
"resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.27.1.tgz",
|
"resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.25.8.tgz",
|
||||||
"integrity": "sha512-NzdQ/Xwu6vPSf/GkdmRNsOfIeSGnh7muundsWItmBsVpMoNPVpM61qNzAVY3pZ1glzzAxLR40UyYM23eaDDbYQ==",
|
"integrity": "sha512-GyG0KcMi1GBavP5JgAkkstMGyMholMDybAf8wF5A70CALlDM2p/f7YFE7H92eDeH/VBtFJA5MT4nRPDGg4JuzQ==",
|
||||||
"cpu": [
|
"cpu": [
|
||||||
"ppc64"
|
"ppc64"
|
||||||
],
|
],
|
||||||
@@ -788,9 +788,9 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@esbuild/linux-riscv64": {
|
"node_modules/@esbuild/linux-riscv64": {
|
||||||
"version": "0.27.1",
|
"version": "0.25.8",
|
||||||
"resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.27.1.tgz",
|
"resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.25.8.tgz",
|
||||||
"integrity": "sha512-7zlw8p3IApcsN7mFw0O1Z1PyEk6PlKMu18roImfl3iQHTnr/yAfYv6s4hXPidbDoI2Q0pW+5xeoM4eTCC0UdrQ==",
|
"integrity": "sha512-rAqDYFv3yzMrq7GIcen3XP7TUEG/4LK86LUPMIz6RT8A6pRIDn0sDcvjudVZBiiTcZCY9y2SgYX2lgK3AF+1eg==",
|
||||||
"cpu": [
|
"cpu": [
|
||||||
"riscv64"
|
"riscv64"
|
||||||
],
|
],
|
||||||
@@ -804,9 +804,9 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@esbuild/linux-s390x": {
|
"node_modules/@esbuild/linux-s390x": {
|
||||||
"version": "0.27.1",
|
"version": "0.25.8",
|
||||||
"resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.27.1.tgz",
|
"resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.25.8.tgz",
|
||||||
"integrity": "sha512-cGj5wli+G+nkVQdZo3+7FDKC25Uh4ZVwOAK6A06Hsvgr8WqBBuOy/1s+PUEd/6Je+vjfm6stX0kmib5b/O2Ykw==",
|
"integrity": "sha512-Xutvh6VjlbcHpsIIbwY8GVRbwoviWT19tFhgdA7DlenLGC/mbc3lBoVb7jxj9Z+eyGqvcnSyIltYUrkKzWqSvg==",
|
||||||
"cpu": [
|
"cpu": [
|
||||||
"s390x"
|
"s390x"
|
||||||
],
|
],
|
||||||
@@ -820,9 +820,9 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@esbuild/linux-x64": {
|
"node_modules/@esbuild/linux-x64": {
|
||||||
"version": "0.27.1",
|
"version": "0.25.8",
|
||||||
"resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.27.1.tgz",
|
"resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.25.8.tgz",
|
||||||
"integrity": "sha512-z3H/HYI9MM0HTv3hQZ81f+AKb+yEoCRlUby1F80vbQ5XdzEMyY/9iNlAmhqiBKw4MJXwfgsh7ERGEOhrM1niMA==",
|
"integrity": "sha512-ASFQhgY4ElXh3nDcOMTkQero4b1lgubskNlhIfJrsH5OKZXDpUAKBlNS0Kx81jwOBp+HCeZqmoJuihTv57/jvQ==",
|
||||||
"cpu": [
|
"cpu": [
|
||||||
"x64"
|
"x64"
|
||||||
],
|
],
|
||||||
@@ -836,9 +836,9 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@esbuild/netbsd-arm64": {
|
"node_modules/@esbuild/netbsd-arm64": {
|
||||||
"version": "0.27.1",
|
"version": "0.25.8",
|
||||||
"resolved": "https://registry.npmjs.org/@esbuild/netbsd-arm64/-/netbsd-arm64-0.27.1.tgz",
|
"resolved": "https://registry.npmjs.org/@esbuild/netbsd-arm64/-/netbsd-arm64-0.25.8.tgz",
|
||||||
"integrity": "sha512-wzC24DxAvk8Em01YmVXyjl96Mr+ecTPyOuADAvjGg+fyBpGmxmcr2E5ttf7Im8D0sXZihpxzO1isus8MdjMCXQ==",
|
"integrity": "sha512-d1KfruIeohqAi6SA+gENMuObDbEjn22olAR7egqnkCD9DGBG0wsEARotkLgXDu6c4ncgWTZJtN5vcgxzWRMzcw==",
|
||||||
"cpu": [
|
"cpu": [
|
||||||
"arm64"
|
"arm64"
|
||||||
],
|
],
|
||||||
@@ -852,9 +852,9 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@esbuild/netbsd-x64": {
|
"node_modules/@esbuild/netbsd-x64": {
|
||||||
"version": "0.27.1",
|
"version": "0.25.8",
|
||||||
"resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.27.1.tgz",
|
"resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.25.8.tgz",
|
||||||
"integrity": "sha512-1YQ8ybGi2yIXswu6eNzJsrYIGFpnlzEWRl6iR5gMgmsrR0FcNoV1m9k9sc3PuP5rUBLshOZylc9nqSgymI+TYg==",
|
"integrity": "sha512-nVDCkrvx2ua+XQNyfrujIG38+YGyuy2Ru9kKVNyh5jAys6n+l44tTtToqHjino2My8VAY6Lw9H7RI73XFi66Cg==",
|
||||||
"cpu": [
|
"cpu": [
|
||||||
"x64"
|
"x64"
|
||||||
],
|
],
|
||||||
@@ -868,9 +868,9 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@esbuild/openbsd-arm64": {
|
"node_modules/@esbuild/openbsd-arm64": {
|
||||||
"version": "0.27.1",
|
"version": "0.25.8",
|
||||||
"resolved": "https://registry.npmjs.org/@esbuild/openbsd-arm64/-/openbsd-arm64-0.27.1.tgz",
|
"resolved": "https://registry.npmjs.org/@esbuild/openbsd-arm64/-/openbsd-arm64-0.25.8.tgz",
|
||||||
"integrity": "sha512-5Z+DzLCrq5wmU7RDaMDe2DVXMRm2tTDvX2KU14JJVBN2CT/qov7XVix85QoJqHltpvAOZUAc3ndU56HSMWrv8g==",
|
"integrity": "sha512-j8HgrDuSJFAujkivSMSfPQSAa5Fxbvk4rgNAS5i3K+r8s1X0p1uOO2Hl2xNsGFppOeHOLAVgYwDVlmxhq5h+SQ==",
|
||||||
"cpu": [
|
"cpu": [
|
||||||
"arm64"
|
"arm64"
|
||||||
],
|
],
|
||||||
@@ -884,9 +884,9 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@esbuild/openbsd-x64": {
|
"node_modules/@esbuild/openbsd-x64": {
|
||||||
"version": "0.27.1",
|
"version": "0.25.8",
|
||||||
"resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.27.1.tgz",
|
"resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.25.8.tgz",
|
||||||
"integrity": "sha512-Q73ENzIdPF5jap4wqLtsfh8YbYSZ8Q0wnxplOlZUOyZy7B4ZKW8DXGWgTCZmF8VWD7Tciwv5F4NsRf6vYlZtqg==",
|
"integrity": "sha512-1h8MUAwa0VhNCDp6Af0HToI2TJFAn1uqT9Al6DJVzdIBAd21m/G0Yfc77KDM3uF3T/YaOgQq3qTJHPbTOInaIQ==",
|
||||||
"cpu": [
|
"cpu": [
|
||||||
"x64"
|
"x64"
|
||||||
],
|
],
|
||||||
@@ -900,9 +900,9 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@esbuild/openharmony-arm64": {
|
"node_modules/@esbuild/openharmony-arm64": {
|
||||||
"version": "0.27.1",
|
"version": "0.25.8",
|
||||||
"resolved": "https://registry.npmjs.org/@esbuild/openharmony-arm64/-/openharmony-arm64-0.27.1.tgz",
|
"resolved": "https://registry.npmjs.org/@esbuild/openharmony-arm64/-/openharmony-arm64-0.25.8.tgz",
|
||||||
"integrity": "sha512-ajbHrGM/XiK+sXM0JzEbJAen+0E+JMQZ2l4RR4VFwvV9JEERx+oxtgkpoKv1SevhjavK2z2ReHk32pjzktWbGg==",
|
"integrity": "sha512-r2nVa5SIK9tSWd0kJd9HCffnDHKchTGikb//9c7HX+r+wHYCpQrSgxhlY6KWV1nFo1l4KFbsMlHk+L6fekLsUg==",
|
||||||
"cpu": [
|
"cpu": [
|
||||||
"arm64"
|
"arm64"
|
||||||
],
|
],
|
||||||
@@ -916,9 +916,9 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@esbuild/sunos-x64": {
|
"node_modules/@esbuild/sunos-x64": {
|
||||||
"version": "0.27.1",
|
"version": "0.25.8",
|
||||||
"resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.27.1.tgz",
|
"resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.25.8.tgz",
|
||||||
"integrity": "sha512-IPUW+y4VIjuDVn+OMzHc5FV4GubIwPnsz6ubkvN8cuhEqH81NovB53IUlrlBkPMEPxvNnf79MGBoz8rZ2iW8HA==",
|
"integrity": "sha512-zUlaP2S12YhQ2UzUfcCuMDHQFJyKABkAjvO5YSndMiIkMimPmxA+BYSBikWgsRpvyxuRnow4nS5NPnf9fpv41w==",
|
||||||
"cpu": [
|
"cpu": [
|
||||||
"x64"
|
"x64"
|
||||||
],
|
],
|
||||||
@@ -932,9 +932,9 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@esbuild/win32-arm64": {
|
"node_modules/@esbuild/win32-arm64": {
|
||||||
"version": "0.27.1",
|
"version": "0.25.8",
|
||||||
"resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.27.1.tgz",
|
"resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.25.8.tgz",
|
||||||
"integrity": "sha512-RIVRWiljWA6CdVu8zkWcRmGP7iRRIIwvhDKem8UMBjPql2TXM5PkDVvvrzMtj1V+WFPB4K7zkIGM7VzRtFkjdg==",
|
"integrity": "sha512-YEGFFWESlPva8hGL+zvj2z/SaK+pH0SwOM0Nc/d+rVnW7GSTFlLBGzZkuSU9kFIGIo8q9X3ucpZhu8PDN5A2sQ==",
|
||||||
"cpu": [
|
"cpu": [
|
||||||
"arm64"
|
"arm64"
|
||||||
],
|
],
|
||||||
@@ -948,9 +948,9 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@esbuild/win32-ia32": {
|
"node_modules/@esbuild/win32-ia32": {
|
||||||
"version": "0.27.1",
|
"version": "0.25.8",
|
||||||
"resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.27.1.tgz",
|
"resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.25.8.tgz",
|
||||||
"integrity": "sha512-2BR5M8CPbptC1AK5JbJT1fWrHLvejwZidKx3UMSF0ecHMa+smhi16drIrCEggkgviBwLYd5nwrFLSl5Kho96RQ==",
|
"integrity": "sha512-hiGgGC6KZ5LZz58OL/+qVVoZiuZlUYlYHNAmczOm7bs2oE1XriPFi5ZHHrS8ACpV5EjySrnoCKmcbQMN+ojnHg==",
|
||||||
"cpu": [
|
"cpu": [
|
||||||
"ia32"
|
"ia32"
|
||||||
],
|
],
|
||||||
@@ -964,9 +964,9 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@esbuild/win32-x64": {
|
"node_modules/@esbuild/win32-x64": {
|
||||||
"version": "0.27.1",
|
"version": "0.25.8",
|
||||||
"resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.27.1.tgz",
|
"resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.25.8.tgz",
|
||||||
"integrity": "sha512-d5X6RMYv6taIymSk8JBP+nxv8DQAMY6A51GPgusqLdK9wBz5wWIXy1KjTck6HnjE9hqJzJRdk+1p/t5soSbCtw==",
|
"integrity": "sha512-cn3Yr7+OaaZq1c+2pe+8yxC8E144SReCQjN6/2ynubzYjvyqZjTXfQJpAcQpsdJq3My7XADANiYGHoFC69pLQw==",
|
||||||
"cpu": [
|
"cpu": [
|
||||||
"x64"
|
"x64"
|
||||||
],
|
],
|
||||||
@@ -2616,9 +2616,9 @@
|
|||||||
"license": "MIT"
|
"license": "MIT"
|
||||||
},
|
},
|
||||||
"node_modules/@types/node": {
|
"node_modules/@types/node": {
|
||||||
"version": "25.0.2",
|
"version": "24.10.1",
|
||||||
"resolved": "https://registry.npmjs.org/@types/node/-/node-25.0.2.tgz",
|
"resolved": "https://registry.npmjs.org/@types/node/-/node-24.10.1.tgz",
|
||||||
"integrity": "sha512-gWEkeiyYE4vqjON/+Obqcoeffmk0NF15WSBwSs7zwVA2bAbTaE0SJ7P0WNGoJn8uE7fiaV5a7dKYIJriEqOrmA==",
|
"integrity": "sha512-GNWcUTRBgIRJD5zj+Tq0fKOJ5XZajIiBroOF0yvj2bSU1WvNdYS/dn9UxwsujGW4JX06dnHyjV2y9rRaybH0iQ==",
|
||||||
"devOptional": true,
|
"devOptional": true,
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"peer": true,
|
"peer": true,
|
||||||
@@ -2649,17 +2649,17 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@typescript-eslint/eslint-plugin": {
|
"node_modules/@typescript-eslint/eslint-plugin": {
|
||||||
"version": "8.50.0",
|
"version": "8.49.0",
|
||||||
"resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-8.50.0.tgz",
|
"resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-8.49.0.tgz",
|
||||||
"integrity": "sha512-O7QnmOXYKVtPrfYzMolrCTfkezCJS9+ljLdKW/+DCvRsc3UAz+sbH6Xcsv7p30+0OwUbeWfUDAQE0vpabZ3QLg==",
|
"integrity": "sha512-JXij0vzIaTtCwu6SxTh8qBc66kmf1xs7pI4UOiMDFVct6q86G0Zs7KRcEoJgY3Cav3x5Tq0MF5jwgpgLqgKG3A==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@eslint-community/regexpp": "^4.10.0",
|
"@eslint-community/regexpp": "^4.10.0",
|
||||||
"@typescript-eslint/scope-manager": "8.50.0",
|
"@typescript-eslint/scope-manager": "8.49.0",
|
||||||
"@typescript-eslint/type-utils": "8.50.0",
|
"@typescript-eslint/type-utils": "8.49.0",
|
||||||
"@typescript-eslint/utils": "8.50.0",
|
"@typescript-eslint/utils": "8.49.0",
|
||||||
"@typescript-eslint/visitor-keys": "8.50.0",
|
"@typescript-eslint/visitor-keys": "8.49.0",
|
||||||
"ignore": "^7.0.0",
|
"ignore": "^7.0.0",
|
||||||
"natural-compare": "^1.4.0",
|
"natural-compare": "^1.4.0",
|
||||||
"ts-api-utils": "^2.1.0"
|
"ts-api-utils": "^2.1.0"
|
||||||
@@ -2672,7 +2672,7 @@
|
|||||||
"url": "https://opencollective.com/typescript-eslint"
|
"url": "https://opencollective.com/typescript-eslint"
|
||||||
},
|
},
|
||||||
"peerDependencies": {
|
"peerDependencies": {
|
||||||
"@typescript-eslint/parser": "^8.50.0",
|
"@typescript-eslint/parser": "^8.49.0",
|
||||||
"eslint": "^8.57.0 || ^9.0.0",
|
"eslint": "^8.57.0 || ^9.0.0",
|
||||||
"typescript": ">=4.8.4 <6.0.0"
|
"typescript": ">=4.8.4 <6.0.0"
|
||||||
}
|
}
|
||||||
@@ -2688,17 +2688,17 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@typescript-eslint/parser": {
|
"node_modules/@typescript-eslint/parser": {
|
||||||
"version": "8.50.0",
|
"version": "8.49.0",
|
||||||
"resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-8.50.0.tgz",
|
"resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-8.49.0.tgz",
|
||||||
"integrity": "sha512-6/cmF2piao+f6wSxUsJLZjck7OQsYyRtcOZS02k7XINSNlz93v6emM8WutDQSXnroG2xwYlEVHJI+cPA7CPM3Q==",
|
"integrity": "sha512-N9lBGA9o9aqb1hVMc9hzySbhKibHmB+N3IpoShyV6HyQYRGIhlrO5rQgttypi+yEeKsKI4idxC8Jw6gXKD4THA==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"peer": true,
|
"peer": true,
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@typescript-eslint/scope-manager": "8.50.0",
|
"@typescript-eslint/scope-manager": "8.49.0",
|
||||||
"@typescript-eslint/types": "8.50.0",
|
"@typescript-eslint/types": "8.49.0",
|
||||||
"@typescript-eslint/typescript-estree": "8.50.0",
|
"@typescript-eslint/typescript-estree": "8.49.0",
|
||||||
"@typescript-eslint/visitor-keys": "8.50.0",
|
"@typescript-eslint/visitor-keys": "8.49.0",
|
||||||
"debug": "^4.3.4"
|
"debug": "^4.3.4"
|
||||||
},
|
},
|
||||||
"engines": {
|
"engines": {
|
||||||
@@ -2714,14 +2714,14 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@typescript-eslint/project-service": {
|
"node_modules/@typescript-eslint/project-service": {
|
||||||
"version": "8.50.0",
|
"version": "8.49.0",
|
||||||
"resolved": "https://registry.npmjs.org/@typescript-eslint/project-service/-/project-service-8.50.0.tgz",
|
"resolved": "https://registry.npmjs.org/@typescript-eslint/project-service/-/project-service-8.49.0.tgz",
|
||||||
"integrity": "sha512-Cg/nQcL1BcoTijEWyx4mkVC56r8dj44bFDvBdygifuS20f3OZCHmFbjF34DPSi07kwlFvqfv/xOLnJ5DquxSGQ==",
|
"integrity": "sha512-/wJN0/DKkmRUMXjZUXYZpD1NEQzQAAn9QWfGwo+Ai8gnzqH7tvqS7oNVdTjKqOcPyVIdZdyCMoqN66Ia789e7g==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@typescript-eslint/tsconfig-utils": "^8.50.0",
|
"@typescript-eslint/tsconfig-utils": "^8.49.0",
|
||||||
"@typescript-eslint/types": "^8.50.0",
|
"@typescript-eslint/types": "^8.49.0",
|
||||||
"debug": "^4.3.4"
|
"debug": "^4.3.4"
|
||||||
},
|
},
|
||||||
"engines": {
|
"engines": {
|
||||||
@@ -2736,14 +2736,14 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@typescript-eslint/scope-manager": {
|
"node_modules/@typescript-eslint/scope-manager": {
|
||||||
"version": "8.50.0",
|
"version": "8.49.0",
|
||||||
"resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-8.50.0.tgz",
|
"resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-8.49.0.tgz",
|
||||||
"integrity": "sha512-xCwfuCZjhIqy7+HKxBLrDVT5q/iq7XBVBXLn57RTIIpelLtEIZHXAF/Upa3+gaCpeV1NNS5Z9A+ID6jn50VD4A==",
|
"integrity": "sha512-npgS3zi+/30KSOkXNs0LQXtsg9ekZ8OISAOLGWA/ZOEn0ZH74Ginfl7foziV8DT+D98WfQ5Kopwqb/PZOaIJGg==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@typescript-eslint/types": "8.50.0",
|
"@typescript-eslint/types": "8.49.0",
|
||||||
"@typescript-eslint/visitor-keys": "8.50.0"
|
"@typescript-eslint/visitor-keys": "8.49.0"
|
||||||
},
|
},
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": "^18.18.0 || ^20.9.0 || >=21.1.0"
|
"node": "^18.18.0 || ^20.9.0 || >=21.1.0"
|
||||||
@@ -2754,9 +2754,9 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@typescript-eslint/tsconfig-utils": {
|
"node_modules/@typescript-eslint/tsconfig-utils": {
|
||||||
"version": "8.50.0",
|
"version": "8.49.0",
|
||||||
"resolved": "https://registry.npmjs.org/@typescript-eslint/tsconfig-utils/-/tsconfig-utils-8.50.0.tgz",
|
"resolved": "https://registry.npmjs.org/@typescript-eslint/tsconfig-utils/-/tsconfig-utils-8.49.0.tgz",
|
||||||
"integrity": "sha512-vxd3G/ybKTSlm31MOA96gqvrRGv9RJ7LGtZCn2Vrc5htA0zCDvcMqUkifcjrWNNKXHUU3WCkYOzzVSFBd0wa2w==",
|
"integrity": "sha512-8prixNi1/6nawsRYxet4YOhnbW+W9FK/bQPxsGB1D3ZrDzbJ5FXw5XmzxZv82X3B+ZccuSxo/X8q9nQ+mFecWA==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"engines": {
|
"engines": {
|
||||||
@@ -2771,15 +2771,15 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@typescript-eslint/type-utils": {
|
"node_modules/@typescript-eslint/type-utils": {
|
||||||
"version": "8.50.0",
|
"version": "8.49.0",
|
||||||
"resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-8.50.0.tgz",
|
"resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-8.49.0.tgz",
|
||||||
"integrity": "sha512-7OciHT2lKCewR0mFoBrvZJ4AXTMe/sYOe87289WAViOocEmDjjv8MvIOT2XESuKj9jp8u3SZYUSh89QA4S1kQw==",
|
"integrity": "sha512-KTExJfQ+svY8I10P4HdxKzWsvtVnsuCifU5MvXrRwoP2KOlNZ9ADNEWWsQTJgMxLzS5VLQKDjkCT/YzgsnqmZg==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@typescript-eslint/types": "8.50.0",
|
"@typescript-eslint/types": "8.49.0",
|
||||||
"@typescript-eslint/typescript-estree": "8.50.0",
|
"@typescript-eslint/typescript-estree": "8.49.0",
|
||||||
"@typescript-eslint/utils": "8.50.0",
|
"@typescript-eslint/utils": "8.49.0",
|
||||||
"debug": "^4.3.4",
|
"debug": "^4.3.4",
|
||||||
"ts-api-utils": "^2.1.0"
|
"ts-api-utils": "^2.1.0"
|
||||||
},
|
},
|
||||||
@@ -2796,9 +2796,9 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@typescript-eslint/types": {
|
"node_modules/@typescript-eslint/types": {
|
||||||
"version": "8.50.0",
|
"version": "8.49.0",
|
||||||
"resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-8.50.0.tgz",
|
"resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-8.49.0.tgz",
|
||||||
"integrity": "sha512-iX1mgmGrXdANhhITbpp2QQM2fGehBse9LbTf0sidWK6yg/NE+uhV5dfU1g6EYPlcReYmkE9QLPq/2irKAmtS9w==",
|
"integrity": "sha512-e9k/fneezorUo6WShlQpMxXh8/8wfyc+biu6tnAqA81oWrEic0k21RHzP9uqqpyBBeBKu4T+Bsjy9/b8u7obXQ==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"engines": {
|
"engines": {
|
||||||
@@ -2810,16 +2810,16 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@typescript-eslint/typescript-estree": {
|
"node_modules/@typescript-eslint/typescript-estree": {
|
||||||
"version": "8.50.0",
|
"version": "8.49.0",
|
||||||
"resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-8.50.0.tgz",
|
"resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-8.49.0.tgz",
|
||||||
"integrity": "sha512-W7SVAGBR/IX7zm1t70Yujpbk+zdPq/u4soeFSknWFdXIFuWsBGBOUu/Tn/I6KHSKvSh91OiMuaSnYp3mtPt5IQ==",
|
"integrity": "sha512-jrLdRuAbPfPIdYNppHJ/D0wN+wwNfJ32YTAm10eJVsFmrVpXQnDWBn8niCSMlWjvml8jsce5E/O+86IQtTbJWA==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@typescript-eslint/project-service": "8.50.0",
|
"@typescript-eslint/project-service": "8.49.0",
|
||||||
"@typescript-eslint/tsconfig-utils": "8.50.0",
|
"@typescript-eslint/tsconfig-utils": "8.49.0",
|
||||||
"@typescript-eslint/types": "8.50.0",
|
"@typescript-eslint/types": "8.49.0",
|
||||||
"@typescript-eslint/visitor-keys": "8.50.0",
|
"@typescript-eslint/visitor-keys": "8.49.0",
|
||||||
"debug": "^4.3.4",
|
"debug": "^4.3.4",
|
||||||
"minimatch": "^9.0.4",
|
"minimatch": "^9.0.4",
|
||||||
"semver": "^7.6.0",
|
"semver": "^7.6.0",
|
||||||
@@ -2877,16 +2877,16 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@typescript-eslint/utils": {
|
"node_modules/@typescript-eslint/utils": {
|
||||||
"version": "8.50.0",
|
"version": "8.49.0",
|
||||||
"resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-8.50.0.tgz",
|
"resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-8.49.0.tgz",
|
||||||
"integrity": "sha512-87KgUXET09CRjGCi2Ejxy3PULXna63/bMYv72tCAlDJC3Yqwln0HiFJ3VJMst2+mEtNtZu5oFvX4qJGjKsnAgg==",
|
"integrity": "sha512-N3W7rJw7Rw+z1tRsHZbK395TWSYvufBXumYtEGzypgMUthlg0/hmCImeA8hgO2d2G4pd7ftpxxul2J8OdtdaFA==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@eslint-community/eslint-utils": "^4.7.0",
|
"@eslint-community/eslint-utils": "^4.7.0",
|
||||||
"@typescript-eslint/scope-manager": "8.50.0",
|
"@typescript-eslint/scope-manager": "8.49.0",
|
||||||
"@typescript-eslint/types": "8.50.0",
|
"@typescript-eslint/types": "8.49.0",
|
||||||
"@typescript-eslint/typescript-estree": "8.50.0"
|
"@typescript-eslint/typescript-estree": "8.49.0"
|
||||||
},
|
},
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": "^18.18.0 || ^20.9.0 || >=21.1.0"
|
"node": "^18.18.0 || ^20.9.0 || >=21.1.0"
|
||||||
@@ -2901,13 +2901,13 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@typescript-eslint/visitor-keys": {
|
"node_modules/@typescript-eslint/visitor-keys": {
|
||||||
"version": "8.50.0",
|
"version": "8.49.0",
|
||||||
"resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-8.50.0.tgz",
|
"resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-8.49.0.tgz",
|
||||||
"integrity": "sha512-Xzmnb58+Db78gT/CCj/PVCvK+zxbnsw6F+O1oheYszJbBSdEjVhQi3C/Xttzxgi/GLmpvOggRs1RFpiJ8+c34Q==",
|
"integrity": "sha512-LlKaciDe3GmZFphXIc79THF/YYBugZ7FS1pO581E/edlVVNbZKDy93evqmrfQ9/Y4uN0vVhX4iuchq26mK/iiA==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@typescript-eslint/types": "8.50.0",
|
"@typescript-eslint/types": "8.49.0",
|
||||||
"eslint-visitor-keys": "^4.2.1"
|
"eslint-visitor-keys": "^4.2.1"
|
||||||
},
|
},
|
||||||
"engines": {
|
"engines": {
|
||||||
@@ -4069,9 +4069,9 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/esbuild": {
|
"node_modules/esbuild": {
|
||||||
"version": "0.27.1",
|
"version": "0.25.8",
|
||||||
"resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.27.1.tgz",
|
"resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.25.8.tgz",
|
||||||
"integrity": "sha512-yY35KZckJJuVVPXpvjgxiCuVEJT67F6zDeVTv4rizyPrfGBUpZQsvmxnN+C371c2esD/hNMjj4tpBhuueLN7aA==",
|
"integrity": "sha512-vVC0USHGtMi8+R4Kz8rt6JhEWLxsv9Rnu/lGYbPR8u47B+DCBksq9JarW0zOO7bs37hyOK1l2/oqtbciutL5+Q==",
|
||||||
"hasInstallScript": true,
|
"hasInstallScript": true,
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"bin": {
|
"bin": {
|
||||||
@@ -4081,32 +4081,32 @@
|
|||||||
"node": ">=18"
|
"node": ">=18"
|
||||||
},
|
},
|
||||||
"optionalDependencies": {
|
"optionalDependencies": {
|
||||||
"@esbuild/aix-ppc64": "0.27.1",
|
"@esbuild/aix-ppc64": "0.25.8",
|
||||||
"@esbuild/android-arm": "0.27.1",
|
"@esbuild/android-arm": "0.25.8",
|
||||||
"@esbuild/android-arm64": "0.27.1",
|
"@esbuild/android-arm64": "0.25.8",
|
||||||
"@esbuild/android-x64": "0.27.1",
|
"@esbuild/android-x64": "0.25.8",
|
||||||
"@esbuild/darwin-arm64": "0.27.1",
|
"@esbuild/darwin-arm64": "0.25.8",
|
||||||
"@esbuild/darwin-x64": "0.27.1",
|
"@esbuild/darwin-x64": "0.25.8",
|
||||||
"@esbuild/freebsd-arm64": "0.27.1",
|
"@esbuild/freebsd-arm64": "0.25.8",
|
||||||
"@esbuild/freebsd-x64": "0.27.1",
|
"@esbuild/freebsd-x64": "0.25.8",
|
||||||
"@esbuild/linux-arm": "0.27.1",
|
"@esbuild/linux-arm": "0.25.8",
|
||||||
"@esbuild/linux-arm64": "0.27.1",
|
"@esbuild/linux-arm64": "0.25.8",
|
||||||
"@esbuild/linux-ia32": "0.27.1",
|
"@esbuild/linux-ia32": "0.25.8",
|
||||||
"@esbuild/linux-loong64": "0.27.1",
|
"@esbuild/linux-loong64": "0.25.8",
|
||||||
"@esbuild/linux-mips64el": "0.27.1",
|
"@esbuild/linux-mips64el": "0.25.8",
|
||||||
"@esbuild/linux-ppc64": "0.27.1",
|
"@esbuild/linux-ppc64": "0.25.8",
|
||||||
"@esbuild/linux-riscv64": "0.27.1",
|
"@esbuild/linux-riscv64": "0.25.8",
|
||||||
"@esbuild/linux-s390x": "0.27.1",
|
"@esbuild/linux-s390x": "0.25.8",
|
||||||
"@esbuild/linux-x64": "0.27.1",
|
"@esbuild/linux-x64": "0.25.8",
|
||||||
"@esbuild/netbsd-arm64": "0.27.1",
|
"@esbuild/netbsd-arm64": "0.25.8",
|
||||||
"@esbuild/netbsd-x64": "0.27.1",
|
"@esbuild/netbsd-x64": "0.25.8",
|
||||||
"@esbuild/openbsd-arm64": "0.27.1",
|
"@esbuild/openbsd-arm64": "0.25.8",
|
||||||
"@esbuild/openbsd-x64": "0.27.1",
|
"@esbuild/openbsd-x64": "0.25.8",
|
||||||
"@esbuild/openharmony-arm64": "0.27.1",
|
"@esbuild/openharmony-arm64": "0.25.8",
|
||||||
"@esbuild/sunos-x64": "0.27.1",
|
"@esbuild/sunos-x64": "0.25.8",
|
||||||
"@esbuild/win32-arm64": "0.27.1",
|
"@esbuild/win32-arm64": "0.25.8",
|
||||||
"@esbuild/win32-ia32": "0.27.1",
|
"@esbuild/win32-ia32": "0.25.8",
|
||||||
"@esbuild/win32-x64": "0.27.1"
|
"@esbuild/win32-x64": "0.25.8"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/escalade": {
|
"node_modules/escalade": {
|
||||||
@@ -5754,9 +5754,9 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/lucide-react": {
|
"node_modules/lucide-react": {
|
||||||
"version": "0.562.0",
|
"version": "0.560.0",
|
||||||
"resolved": "https://registry.npmjs.org/lucide-react/-/lucide-react-0.562.0.tgz",
|
"resolved": "https://registry.npmjs.org/lucide-react/-/lucide-react-0.560.0.tgz",
|
||||||
"integrity": "sha512-82hOAu7y0dbVuFfmO4bYF1XEwYk/mEbM5E+b1jgci/udUBEE/R7LF5Ip0CCEmXe8AybRM8L+04eP+LGZeDvkiw==",
|
"integrity": "sha512-NwKoUA/aBShsdL8WE5lukV2F/tjHzQRlonQs7fkNGI1sCT0Ay4a9Ap3ST2clUUkcY+9eQ0pBe2hybTQd2fmyDA==",
|
||||||
"license": "ISC",
|
"license": "ISC",
|
||||||
"peerDependencies": {
|
"peerDependencies": {
|
||||||
"react": "^16.5.1 || ^17.0.0 || ^18.0.0 || ^19.0.0"
|
"react": "^16.5.1 || ^17.0.0 || ^18.0.0 || ^19.0.0"
|
||||||
@@ -7259,16 +7259,16 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/typescript-eslint": {
|
"node_modules/typescript-eslint": {
|
||||||
"version": "8.50.0",
|
"version": "8.49.0",
|
||||||
"resolved": "https://registry.npmjs.org/typescript-eslint/-/typescript-eslint-8.50.0.tgz",
|
"resolved": "https://registry.npmjs.org/typescript-eslint/-/typescript-eslint-8.49.0.tgz",
|
||||||
"integrity": "sha512-Q1/6yNUmCpH94fbgMUMg2/BSAr/6U7GBk61kZTv1/asghQOWOjTlp9K8mixS5NcJmm2creY+UFfGeW/+OcA64A==",
|
"integrity": "sha512-zRSVH1WXD0uXczCXw+nsdjGPUdx4dfrs5VQoHnUWmv1U3oNlAKv4FUNdLDhVUg+gYn+a5hUESqch//Rv5wVhrg==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@typescript-eslint/eslint-plugin": "8.50.0",
|
"@typescript-eslint/eslint-plugin": "8.49.0",
|
||||||
"@typescript-eslint/parser": "8.50.0",
|
"@typescript-eslint/parser": "8.49.0",
|
||||||
"@typescript-eslint/typescript-estree": "8.50.0",
|
"@typescript-eslint/typescript-estree": "8.49.0",
|
||||||
"@typescript-eslint/utils": "8.50.0"
|
"@typescript-eslint/utils": "8.49.0"
|
||||||
},
|
},
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": "^18.18.0 || ^20.9.0 || >=21.1.0"
|
"node": "^18.18.0 || ^20.9.0 || >=21.1.0"
|
||||||
@@ -7393,13 +7393,13 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/vite": {
|
"node_modules/vite": {
|
||||||
"version": "7.3.0",
|
"version": "7.2.2",
|
||||||
"resolved": "https://registry.npmjs.org/vite/-/vite-7.3.0.tgz",
|
"resolved": "https://registry.npmjs.org/vite/-/vite-7.2.2.tgz",
|
||||||
"integrity": "sha512-dZwN5L1VlUBewiP6H9s2+B3e3Jg96D0vzN+Ry73sOefebhYr9f94wwkMNN/9ouoU8pV1BqA1d1zGk8928cx0rg==",
|
"integrity": "sha512-BxAKBWmIbrDgrokdGZH1IgkIk/5mMHDreLDmCJ0qpyJaAteP8NvMhkwr/ZCQNqNH97bw/dANTE9PDzqwJghfMQ==",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"peer": true,
|
"peer": true,
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"esbuild": "^0.27.0",
|
"esbuild": "^0.25.0",
|
||||||
"fdir": "^6.5.0",
|
"fdir": "^6.5.0",
|
||||||
"picomatch": "^4.0.3",
|
"picomatch": "^4.0.3",
|
||||||
"postcss": "^8.5.6",
|
"postcss": "^8.5.6",
|
||||||
@@ -7798,9 +7798,9 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/zod": {
|
"node_modules/zod": {
|
||||||
"version": "4.2.0",
|
"version": "4.1.12",
|
||||||
"resolved": "https://registry.npmjs.org/zod/-/zod-4.2.0.tgz",
|
"resolved": "https://registry.npmjs.org/zod/-/zod-4.1.12.tgz",
|
||||||
"integrity": "sha512-Bd5fw9wlIhtqCCxotZgdTOMwGm1a0u75wARVEY9HMs1X17trvA/lMi4+MGK5EUfYkXVTbX8UDiDKW4OgzHVUZw==",
|
"integrity": "sha512-JInaHOamG8pt5+Ey8kGmdcAcg3OL9reK8ltczgHTAwNhMys/6ThXHityHxVV2p3fkw/c+MAvBHFVYHFZDmjMCQ==",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"peer": true,
|
"peer": true,
|
||||||
"funding": {
|
"funding": {
|
||||||
|
|||||||
@@ -27,20 +27,20 @@
|
|||||||
"class-variance-authority": "^0.7.1",
|
"class-variance-authority": "^0.7.1",
|
||||||
"clsx": "^2.1.1",
|
"clsx": "^2.1.1",
|
||||||
"date-fns": "^4.1.0",
|
"date-fns": "^4.1.0",
|
||||||
"lucide-react": "^0.562.0",
|
"lucide-react": "^0.560.0",
|
||||||
"react": "^19.2.0",
|
"react": "^19.2.0",
|
||||||
"react-dom": "^19.2.0",
|
"react-dom": "^19.2.0",
|
||||||
"sonner": "^2.0.7",
|
"sonner": "^2.0.7",
|
||||||
"tailwind-merge": "^3.4.0",
|
"tailwind-merge": "^3.4.0",
|
||||||
"tailwindcss": "^4.1.11",
|
"tailwindcss": "^4.1.11",
|
||||||
"zod": "^4.2.0"
|
"zod": "^4.1.12"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@eslint/js": "^9.39.1",
|
"@eslint/js": "^9.39.1",
|
||||||
"@testing-library/jest-dom": "^6.9.1",
|
"@testing-library/jest-dom": "^6.9.1",
|
||||||
"@testing-library/react": "^16.3.0",
|
"@testing-library/react": "^16.3.0",
|
||||||
"@testing-library/user-event": "^14.6.1",
|
"@testing-library/user-event": "^14.6.1",
|
||||||
"@types/node": "^25.0.2",
|
"@types/node": "^24.10.1",
|
||||||
"@types/react": "^19.2.4",
|
"@types/react": "^19.2.4",
|
||||||
"@types/react-dom": "^19.2.3",
|
"@types/react-dom": "^19.2.3",
|
||||||
"@vitejs/plugin-react": "^5.1.1",
|
"@vitejs/plugin-react": "^5.1.1",
|
||||||
@@ -52,8 +52,8 @@
|
|||||||
"jsdom": "^27.3.0",
|
"jsdom": "^27.3.0",
|
||||||
"tw-animate-css": "^1.4.0",
|
"tw-animate-css": "^1.4.0",
|
||||||
"typescript": "^5.9.3",
|
"typescript": "^5.9.3",
|
||||||
"typescript-eslint": "^8.50.0",
|
"typescript-eslint": "^8.49.0",
|
||||||
"vite": "^7.3.0",
|
"vite": "^7.2.2",
|
||||||
"vitest": "^4.0.8"
|
"vitest": "^4.0.8"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,7 +1,6 @@
|
|||||||
// ui/src/components/InstanceCard.tsx
|
// ui/src/components/InstanceCard.tsx
|
||||||
import { Button } from "@/components/ui/button";
|
import { Button } from "@/components/ui/button";
|
||||||
import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card";
|
import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card";
|
||||||
import { Badge } from "@/components/ui/badge";
|
|
||||||
import type { Instance } from "@/types/instance";
|
import type { Instance } from "@/types/instance";
|
||||||
import { Edit, FileText, Play, Square, Trash2, MoreHorizontal, Download, Boxes } from "lucide-react";
|
import { Edit, FileText, Play, Square, Trash2, MoreHorizontal, Download, Boxes } from "lucide-react";
|
||||||
import LogsDialog from "@/components/LogDialog";
|
import LogsDialog from "@/components/LogDialog";
|
||||||
@@ -10,7 +9,7 @@ import HealthBadge from "@/components/HealthBadge";
|
|||||||
import BackendBadge from "@/components/BackendBadge";
|
import BackendBadge from "@/components/BackendBadge";
|
||||||
import { useState, useEffect } from "react";
|
import { useState, useEffect } from "react";
|
||||||
import { useInstanceHealth } from "@/hooks/useInstanceHealth";
|
import { useInstanceHealth } from "@/hooks/useInstanceHealth";
|
||||||
import { instancesApi, llamaCppApi, type Model } from "@/lib/api";
|
import { instancesApi, llamaCppApi } from "@/lib/api";
|
||||||
|
|
||||||
interface InstanceCardProps {
|
interface InstanceCardProps {
|
||||||
instance: Instance;
|
instance: Instance;
|
||||||
@@ -30,33 +29,29 @@ function InstanceCard({
|
|||||||
const [isLogsOpen, setIsLogsOpen] = useState(false);
|
const [isLogsOpen, setIsLogsOpen] = useState(false);
|
||||||
const [isModelsOpen, setIsModelsOpen] = useState(false);
|
const [isModelsOpen, setIsModelsOpen] = useState(false);
|
||||||
const [showAllActions, setShowAllActions] = useState(false);
|
const [showAllActions, setShowAllActions] = useState(false);
|
||||||
const [models, setModels] = useState<Model[]>([]);
|
const [modelCount, setModelCount] = useState(0);
|
||||||
const health = useInstanceHealth(instance.name, instance.status);
|
const health = useInstanceHealth(instance.name, instance.status);
|
||||||
|
|
||||||
const running = instance.status === "running";
|
const running = instance.status === "running";
|
||||||
const isLlamaCpp = instance.options?.backend_type === "llama_cpp";
|
const isLlamaCpp = instance.options?.backend_type === "llama_cpp";
|
||||||
|
|
||||||
// Fetch models for llama.cpp instances
|
// Fetch model count for llama.cpp instances
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (!isLlamaCpp || !running) {
|
if (!isLlamaCpp || !running) {
|
||||||
setModels([]);
|
setModelCount(0);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
void (async () => {
|
void (async () => {
|
||||||
try {
|
try {
|
||||||
const fetchedModels = await llamaCppApi.getModels(instance.name);
|
const models = await llamaCppApi.getModels(instance.name);
|
||||||
setModels(fetchedModels);
|
setModelCount(models.length);
|
||||||
} catch {
|
} catch {
|
||||||
setModels([]);
|
setModelCount(0);
|
||||||
}
|
}
|
||||||
})();
|
})();
|
||||||
}, [instance.name, isLlamaCpp, running]);
|
}, [instance.name, isLlamaCpp, running]);
|
||||||
|
|
||||||
// Calculate model counts
|
|
||||||
const totalModels = models.length;
|
|
||||||
const loadedModels = models.filter(m => m.status.value === "loaded").length;
|
|
||||||
|
|
||||||
const handleStart = () => {
|
const handleStart = () => {
|
||||||
startInstance(instance.name);
|
startInstance(instance.name);
|
||||||
};
|
};
|
||||||
@@ -129,12 +124,6 @@ function InstanceCard({
|
|||||||
<div className="flex items-center gap-2 flex-wrap">
|
<div className="flex items-center gap-2 flex-wrap">
|
||||||
<BackendBadge backend={instance.options?.backend_type} docker={instance.options?.docker_enabled} />
|
<BackendBadge backend={instance.options?.backend_type} docker={instance.options?.docker_enabled} />
|
||||||
{running && <HealthBadge health={health} />}
|
{running && <HealthBadge health={health} />}
|
||||||
{isLlamaCpp && running && totalModels > 0 && (
|
|
||||||
<Badge variant="secondary" className="text-xs">
|
|
||||||
<Boxes className="h-3 w-3 mr-1" />
|
|
||||||
{loadedModels}/{totalModels} models
|
|
||||||
</Badge>
|
|
||||||
)}
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</CardHeader>
|
</CardHeader>
|
||||||
@@ -185,28 +174,30 @@ function InstanceCard({
|
|||||||
|
|
||||||
{/* Secondary actions - collapsible */}
|
{/* Secondary actions - collapsible */}
|
||||||
{showAllActions && (
|
{showAllActions && (
|
||||||
<div className="flex items-center gap-2 pt-2 border-t border-border flex-wrap">
|
<div className="flex items-center gap-2 pt-2 border-t border-border">
|
||||||
<Button
|
<Button
|
||||||
size="sm"
|
size="sm"
|
||||||
variant="outline"
|
variant="outline"
|
||||||
onClick={handleLogs}
|
onClick={handleLogs}
|
||||||
title="View logs"
|
title="View logs"
|
||||||
data-testid="view-logs-button"
|
data-testid="view-logs-button"
|
||||||
|
className="flex-1"
|
||||||
>
|
>
|
||||||
<FileText className="h-4 w-4 mr-1" />
|
<FileText className="h-4 w-4 mr-1" />
|
||||||
Logs
|
Logs
|
||||||
</Button>
|
</Button>
|
||||||
|
|
||||||
{isLlamaCpp && totalModels > 1 && (
|
{isLlamaCpp && modelCount > 1 && (
|
||||||
<Button
|
<Button
|
||||||
size="sm"
|
size="sm"
|
||||||
variant="outline"
|
variant="outline"
|
||||||
onClick={handleModels}
|
onClick={handleModels}
|
||||||
title="Manage models"
|
title="Manage models"
|
||||||
data-testid="manage-models-button"
|
data-testid="manage-models-button"
|
||||||
|
className="flex-1"
|
||||||
>
|
>
|
||||||
<Boxes className="h-4 w-4 mr-1" />
|
<Boxes className="h-4 w-4 mr-1" />
|
||||||
Models
|
Models ({modelCount})
|
||||||
</Button>
|
</Button>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
@@ -216,6 +207,7 @@ function InstanceCard({
|
|||||||
onClick={handleExport}
|
onClick={handleExport}
|
||||||
title="Export instance"
|
title="Export instance"
|
||||||
data-testid="export-instance-button"
|
data-testid="export-instance-button"
|
||||||
|
className="flex-1"
|
||||||
>
|
>
|
||||||
<Download className="h-4 w-4 mr-1" />
|
<Download className="h-4 w-4 mr-1" />
|
||||||
Export
|
Export
|
||||||
|
|||||||
@@ -88,30 +88,20 @@ const ModelsDialog: React.FC<ModelsDialogProps> = ({
|
|||||||
}
|
}
|
||||||
}, [instanceName, isRunning])
|
}, [instanceName, isRunning])
|
||||||
|
|
||||||
// Fetch models when dialog opens
|
// Poll for models while dialog is open
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (!open || !isRunning) return
|
if (!open || !isRunning) return
|
||||||
|
|
||||||
// Initial fetch
|
// Initial fetch
|
||||||
void fetchModels()
|
void fetchModels()
|
||||||
}, [open, isRunning, fetchModels])
|
|
||||||
|
|
||||||
// Auto-refresh only when models are loading
|
// Poll every 2 seconds
|
||||||
useEffect(() => {
|
|
||||||
if (!open || !isRunning) return
|
|
||||||
|
|
||||||
// Check if any model is in loading state
|
|
||||||
const hasLoadingModel = models.some(m => m.status.value === 'loading')
|
|
||||||
|
|
||||||
if (!hasLoadingModel) return
|
|
||||||
|
|
||||||
// Poll every 2 seconds when there's a loading model
|
|
||||||
const interval = setInterval(() => {
|
const interval = setInterval(() => {
|
||||||
void fetchModels()
|
void fetchModels()
|
||||||
}, 2000)
|
}, 2000)
|
||||||
|
|
||||||
return () => clearInterval(interval)
|
return () => clearInterval(interval)
|
||||||
}, [open, isRunning, models, fetchModels])
|
}, [open, isRunning, fetchModels])
|
||||||
|
|
||||||
// Load model
|
// Load model
|
||||||
const loadModel = async (modelName: string) => {
|
const loadModel = async (modelName: string) => {
|
||||||
@@ -120,10 +110,7 @@ const ModelsDialog: React.FC<ModelsDialogProps> = ({
|
|||||||
|
|
||||||
try {
|
try {
|
||||||
await llamaCppApi.loadModel(instanceName, modelName)
|
await llamaCppApi.loadModel(instanceName, modelName)
|
||||||
// Wait a bit for the backend to process the load
|
// Polling will pick up the change
|
||||||
await new Promise(resolve => setTimeout(resolve, 500))
|
|
||||||
// Refresh models list after loading
|
|
||||||
await fetchModels()
|
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
setError(err instanceof Error ? err.message : 'Failed to load model')
|
setError(err instanceof Error ? err.message : 'Failed to load model')
|
||||||
} finally {
|
} finally {
|
||||||
@@ -142,10 +129,7 @@ const ModelsDialog: React.FC<ModelsDialogProps> = ({
|
|||||||
|
|
||||||
try {
|
try {
|
||||||
await llamaCppApi.unloadModel(instanceName, modelName)
|
await llamaCppApi.unloadModel(instanceName, modelName)
|
||||||
// Wait a bit for the backend to process the unload
|
// Polling will pick up the change
|
||||||
await new Promise(resolve => setTimeout(resolve, 500))
|
|
||||||
// Refresh models list after unloading
|
|
||||||
await fetchModels()
|
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
setError(err instanceof Error ? err.message : 'Failed to unload model')
|
setError(err instanceof Error ? err.message : 'Failed to unload model')
|
||||||
} finally {
|
} finally {
|
||||||
@@ -246,7 +230,7 @@ const ModelsDialog: React.FC<ModelsDialogProps> = ({
|
|||||||
<Button
|
<Button
|
||||||
size="sm"
|
size="sm"
|
||||||
variant="outline"
|
variant="outline"
|
||||||
onClick={() => { void unloadModel(model.id) }}
|
onClick={() => unloadModel(model.id)}
|
||||||
disabled={!isRunning || isLoading || isModelLoading}
|
disabled={!isRunning || isLoading || isModelLoading}
|
||||||
>
|
>
|
||||||
{isLoading ? (
|
{isLoading ? (
|
||||||
@@ -262,7 +246,7 @@ const ModelsDialog: React.FC<ModelsDialogProps> = ({
|
|||||||
<Button
|
<Button
|
||||||
size="sm"
|
size="sm"
|
||||||
variant="default"
|
variant="default"
|
||||||
onClick={() => { void loadModel(model.id) }}
|
onClick={() => loadModel(model.id)}
|
||||||
disabled={!isRunning || isLoading || isModelLoading}
|
disabled={!isRunning || isLoading || isModelLoading}
|
||||||
>
|
>
|
||||||
{isLoading ? (
|
{isLoading ? (
|
||||||
@@ -288,11 +272,11 @@ const ModelsDialog: React.FC<ModelsDialogProps> = ({
|
|||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* Auto-refresh indicator - only shown when models are loading */}
|
{/* Auto-refresh indicator */}
|
||||||
{isRunning && models.some(m => m.status.value === 'loading') && (
|
{isRunning && (
|
||||||
<div className="flex items-center gap-2 text-sm text-muted-foreground">
|
<div className="flex items-center gap-2 text-sm text-muted-foreground">
|
||||||
<div className="w-2 h-2 bg-yellow-500 rounded-full animate-pulse"></div>
|
<div className="w-2 h-2 bg-green-500 rounded-full animate-pulse"></div>
|
||||||
Auto-refreshing while models are loading
|
Auto-refreshing every 2 seconds
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
</DialogContent>
|
</DialogContent>
|
||||||
|
|||||||
@@ -237,21 +237,19 @@ export const llamaCppApi = {
|
|||||||
|
|
||||||
// POST /llama-cpp/{name}/models/{model}/load
|
// POST /llama-cpp/{name}/models/{model}/load
|
||||||
loadModel: (instanceName: string, modelName: string) =>
|
loadModel: (instanceName: string, modelName: string) =>
|
||||||
apiCall<{ success: boolean }>(
|
apiCall<{ status: string; message: string }>(
|
||||||
`/llama-cpp/${encodeURIComponent(instanceName)}/models/${encodeURIComponent(modelName)}/load`,
|
`/llama-cpp/${encodeURIComponent(instanceName)}/models/${encodeURIComponent(modelName)}/load`,
|
||||||
{
|
{
|
||||||
method: "POST",
|
method: "POST",
|
||||||
body: JSON.stringify({ model: modelName }),
|
|
||||||
}
|
}
|
||||||
),
|
),
|
||||||
|
|
||||||
// POST /llama-cpp/{name}/models/{model}/unload
|
// POST /llama-cpp/{name}/models/{model}/unload
|
||||||
unloadModel: (instanceName: string, modelName: string) =>
|
unloadModel: (instanceName: string, modelName: string) =>
|
||||||
apiCall<{ success: boolean }>(
|
apiCall<{ status: string; message: string }>(
|
||||||
`/llama-cpp/${encodeURIComponent(instanceName)}/models/${encodeURIComponent(modelName)}/unload`,
|
`/llama-cpp/${encodeURIComponent(instanceName)}/models/${encodeURIComponent(modelName)}/unload`,
|
||||||
{
|
{
|
||||||
method: "POST",
|
method: "POST",
|
||||||
body: JSON.stringify({ model: modelName }),
|
|
||||||
}
|
}
|
||||||
),
|
),
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -30,6 +30,7 @@ export interface ServerConfig {
|
|||||||
|
|
||||||
export interface InstancesConfig {
|
export interface InstancesConfig {
|
||||||
port_range: [number, number]
|
port_range: [number, number]
|
||||||
|
configs_dir: string
|
||||||
logs_dir: string
|
logs_dir: string
|
||||||
auto_create_dirs: boolean
|
auto_create_dirs: boolean
|
||||||
max_instances: number
|
max_instances: number
|
||||||
@@ -52,6 +53,7 @@ export interface DatabaseConfig {
|
|||||||
|
|
||||||
export interface AuthConfig {
|
export interface AuthConfig {
|
||||||
require_inference_auth: boolean
|
require_inference_auth: boolean
|
||||||
|
inference_keys: string[] // Will be empty in sanitized response
|
||||||
require_management_auth: boolean
|
require_management_auth: boolean
|
||||||
management_keys: string[] // Will be empty in sanitized response
|
management_keys: string[] // Will be empty in sanitized response
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user