mirror of
https://github.com/lordmathis/llamactl.git
synced 2025-12-22 17:14:22 +00:00
Compare commits
9 Commits
fd9e651e09
...
v0.13.2
| Author | SHA1 | Date | |
|---|---|---|---|
| ec84a7d331 | |||
| b45219a01e | |||
| 463bb561e1 | |||
| ebdb9143c0 | |||
| 4269d04381 | |||
| c734329a62 | |||
| 15fcf7c377 | |||
|
|
795f530956 | ||
|
|
4507358310 |
36
.github/workflows/release.yaml
vendored
36
.github/workflows/release.yaml
vendored
@@ -45,15 +45,23 @@ jobs:
|
|||||||
build:
|
build:
|
||||||
name: Build Binaries
|
name: Build Binaries
|
||||||
needs: build-webui
|
needs: build-webui
|
||||||
runs-on: ubuntu-latest
|
runs-on: ${{ matrix.runner }}
|
||||||
strategy:
|
strategy:
|
||||||
matrix:
|
matrix:
|
||||||
goos: [linux, windows, darwin]
|
include:
|
||||||
goarch: [amd64, arm64]
|
- goos: linux
|
||||||
exclude:
|
goarch: amd64
|
||||||
# Windows ARM64 support is limited
|
runner: ubuntu-latest
|
||||||
- goos: windows
|
- goos: linux
|
||||||
goarch: arm64
|
goarch: arm64
|
||||||
|
runner: ubuntu-latest
|
||||||
|
cc: aarch64-linux-gnu-gcc
|
||||||
|
- goos: darwin
|
||||||
|
goarch: arm64
|
||||||
|
runner: macos-latest
|
||||||
|
- goos: windows
|
||||||
|
goarch: amd64
|
||||||
|
runner: windows-latest
|
||||||
steps:
|
steps:
|
||||||
- name: Checkout repository
|
- name: Checkout repository
|
||||||
uses: actions/checkout@v4
|
uses: actions/checkout@v4
|
||||||
@@ -70,11 +78,19 @@ 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: 0
|
CGO_ENABLED: 1
|
||||||
|
CC: ${{ matrix.cc }}
|
||||||
|
shell: bash
|
||||||
run: |
|
run: |
|
||||||
# Set binary extension for Windows
|
# Set binary extension for Windows
|
||||||
BINARY_NAME="llamactl"
|
BINARY_NAME="llamactl"
|
||||||
@@ -91,8 +107,10 @@ 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
|
||||||
zip "${ARCHIVE_NAME}.zip" "${BINARY_NAME}"
|
# Use 7z on Windows (pre-installed)
|
||||||
|
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}"
|
||||||
@@ -179,4 +197,4 @@ jobs:
|
|||||||
with:
|
with:
|
||||||
files: assets/checksums.txt
|
files: assets/checksums.txt
|
||||||
env:
|
env:
|
||||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||||
|
|||||||
@@ -1,141 +0,0 @@
|
|||||||
package instance
|
|
||||||
|
|
||||||
import (
|
|
||||||
"bytes"
|
|
||||||
"context"
|
|
||||||
"encoding/json"
|
|
||||||
"fmt"
|
|
||||||
"io"
|
|
||||||
"llamactl/pkg/backends"
|
|
||||||
"net/http"
|
|
||||||
"time"
|
|
||||||
)
|
|
||||||
|
|
||||||
// Model represents a model available in a llama.cpp instance
|
|
||||||
type Model struct {
|
|
||||||
ID string `json:"id"`
|
|
||||||
Object string `json:"object"`
|
|
||||||
OwnedBy string `json:"owned_by"`
|
|
||||||
Created int64 `json:"created"`
|
|
||||||
InCache bool `json:"in_cache"`
|
|
||||||
Path string `json:"path"`
|
|
||||||
Status ModelStatus `json:"status"`
|
|
||||||
}
|
|
||||||
|
|
||||||
// ModelStatus represents the status of a model in an instance
|
|
||||||
type ModelStatus struct {
|
|
||||||
Value string `json:"value"` // "loaded" | "loading" | "unloaded"
|
|
||||||
Args []string `json:"args"`
|
|
||||||
}
|
|
||||||
|
|
||||||
// IsLlamaCpp checks if this instance is a llama.cpp instance
|
|
||||||
func (i *Instance) IsLlamaCpp() bool {
|
|
||||||
opts := i.GetOptions()
|
|
||||||
if opts == nil {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
return opts.BackendOptions.BackendType == backends.BackendTypeLlamaCpp
|
|
||||||
}
|
|
||||||
|
|
||||||
// GetModels fetches the models available in this llama.cpp instance
|
|
||||||
func (i *Instance) GetModels() ([]Model, error) {
|
|
||||||
if !i.IsLlamaCpp() {
|
|
||||||
return nil, fmt.Errorf("instance %s is not a llama.cpp instance", i.Name)
|
|
||||||
}
|
|
||||||
|
|
||||||
if !i.IsRunning() {
|
|
||||||
return nil, fmt.Errorf("instance %s is not running", i.Name)
|
|
||||||
}
|
|
||||||
|
|
||||||
var result struct {
|
|
||||||
Data []Model `json:"data"`
|
|
||||||
}
|
|
||||||
if err := i.doRequest("GET", "/models", nil, &result, 10*time.Second); err != nil {
|
|
||||||
return nil, fmt.Errorf("failed to fetch models: %w", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
return result.Data, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// LoadModel loads a model in this llama.cpp instance
|
|
||||||
func (i *Instance) LoadModel(modelName string) error {
|
|
||||||
if !i.IsLlamaCpp() {
|
|
||||||
return fmt.Errorf("instance %s is not a llama.cpp instance", i.Name)
|
|
||||||
}
|
|
||||||
|
|
||||||
if !i.IsRunning() {
|
|
||||||
return fmt.Errorf("instance %s is not running", i.Name)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Make the load request
|
|
||||||
reqBody := map[string]string{"model": modelName}
|
|
||||||
if err := i.doRequest("POST", "/models/load", reqBody, nil, 30*time.Second); err != nil {
|
|
||||||
return fmt.Errorf("failed to load model: %w", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// UnloadModel unloads a model from this llama.cpp instance
|
|
||||||
func (i *Instance) UnloadModel(modelName string) error {
|
|
||||||
if !i.IsLlamaCpp() {
|
|
||||||
return fmt.Errorf("instance %s is not a llama.cpp instance", i.Name)
|
|
||||||
}
|
|
||||||
|
|
||||||
if !i.IsRunning() {
|
|
||||||
return fmt.Errorf("instance %s is not running", i.Name)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Make the unload request
|
|
||||||
reqBody := map[string]string{"model": modelName}
|
|
||||||
if err := i.doRequest("POST", "/models/unload", reqBody, nil, 30*time.Second); err != nil {
|
|
||||||
return fmt.Errorf("failed to unload model: %w", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// doRequest makes an HTTP request to this instance's backend
|
|
||||||
func (i *Instance) doRequest(method, path string, reqBody, respBody any, timeout time.Duration) error {
|
|
||||||
url := fmt.Sprintf("http://%s:%d%s", i.GetHost(), i.GetPort(), path)
|
|
||||||
|
|
||||||
var bodyReader io.Reader
|
|
||||||
if reqBody != nil {
|
|
||||||
bodyBytes, err := json.Marshal(reqBody)
|
|
||||||
if err != nil {
|
|
||||||
return fmt.Errorf("failed to marshal request body: %w", err)
|
|
||||||
}
|
|
||||||
bodyReader = bytes.NewReader(bodyBytes)
|
|
||||||
}
|
|
||||||
|
|
||||||
ctx, cancel := context.WithTimeout(context.Background(), timeout)
|
|
||||||
defer cancel()
|
|
||||||
|
|
||||||
req, err := http.NewRequestWithContext(ctx, method, url, bodyReader)
|
|
||||||
if err != nil {
|
|
||||||
return fmt.Errorf("failed to create request: %w", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
if reqBody != nil {
|
|
||||||
req.Header.Set("Content-Type", "application/json")
|
|
||||||
}
|
|
||||||
|
|
||||||
resp, err := http.DefaultClient.Do(req)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
defer resp.Body.Close()
|
|
||||||
|
|
||||||
if resp.StatusCode != http.StatusOK {
|
|
||||||
bodyBytes, _ := io.ReadAll(resp.Body)
|
|
||||||
return fmt.Errorf("status %d: %s", resp.StatusCode, string(bodyBytes))
|
|
||||||
}
|
|
||||||
|
|
||||||
if respBody != nil {
|
|
||||||
if err := json.NewDecoder(resp.Body).Decode(respBody); err != nil {
|
|
||||||
return fmt.Errorf("failed to decode response: %w", err)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
@@ -24,8 +24,6 @@ type InstanceManager interface {
|
|||||||
EvictLRUInstance() error
|
EvictLRUInstance() error
|
||||||
RestartInstance(name string) (*instance.Instance, error)
|
RestartInstance(name string) (*instance.Instance, error)
|
||||||
GetInstanceLogs(name string, numLines int) (string, error)
|
GetInstanceLogs(name string, numLines int) (string, error)
|
||||||
ResolveInstance(modelName string) (string, error)
|
|
||||||
RefreshModelRegistry(inst *instance.Instance) error
|
|
||||||
Shutdown()
|
Shutdown()
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -36,7 +34,6 @@ type instanceManager struct {
|
|||||||
db database.InstanceStore
|
db database.InstanceStore
|
||||||
remote *remoteManager
|
remote *remoteManager
|
||||||
lifecycle *lifecycleManager
|
lifecycle *lifecycleManager
|
||||||
models *modelRegistry
|
|
||||||
|
|
||||||
// Configuration
|
// Configuration
|
||||||
globalConfig *config.AppConfig
|
globalConfig *config.AppConfig
|
||||||
@@ -63,16 +60,12 @@ func New(globalConfig *config.AppConfig, db database.InstanceStore) InstanceMana
|
|||||||
// Initialize remote manager
|
// Initialize remote manager
|
||||||
remote := newRemoteManager(globalConfig.Nodes, 30*time.Second)
|
remote := newRemoteManager(globalConfig.Nodes, 30*time.Second)
|
||||||
|
|
||||||
// Initialize model registry
|
|
||||||
models := newModelRegistry()
|
|
||||||
|
|
||||||
// Create manager instance
|
// Create manager instance
|
||||||
im := &instanceManager{
|
im := &instanceManager{
|
||||||
registry: registry,
|
registry: registry,
|
||||||
ports: ports,
|
ports: ports,
|
||||||
db: db,
|
db: db,
|
||||||
remote: remote,
|
remote: remote,
|
||||||
models: models,
|
|
||||||
globalConfig: globalConfig,
|
globalConfig: globalConfig,
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -149,27 +142,9 @@ func (im *instanceManager) loadInstances() error {
|
|||||||
// Auto-start instances that have auto-restart enabled
|
// Auto-start instances that have auto-restart enabled
|
||||||
go im.autoStartInstances()
|
go im.autoStartInstances()
|
||||||
|
|
||||||
// Discover models from all running llama.cpp instances
|
|
||||||
go im.discoverAllModels()
|
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// discoverAllModels discovers and registers models for all running llama.cpp instances
|
|
||||||
func (im *instanceManager) discoverAllModels() {
|
|
||||||
instances := im.registry.listRunning()
|
|
||||||
|
|
||||||
for _, inst := range instances {
|
|
||||||
if !inst.IsLlamaCpp() {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
|
|
||||||
if err := im.RefreshModelRegistry(inst); err != nil {
|
|
||||||
log.Printf("Failed to discover models for instance %s: %v", inst.Name, err)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// loadInstance loads a single persisted instance and adds it to the registry
|
// loadInstance loads a single persisted instance and adds it to the registry
|
||||||
func (im *instanceManager) loadInstance(persistedInst *instance.Instance) error {
|
func (im *instanceManager) loadInstance(persistedInst *instance.Instance) error {
|
||||||
name := persistedInst.Name
|
name := persistedInst.Name
|
||||||
|
|||||||
@@ -1,79 +0,0 @@
|
|||||||
package manager
|
|
||||||
|
|
||||||
import (
|
|
||||||
"fmt"
|
|
||||||
"llamactl/pkg/instance"
|
|
||||||
"sync"
|
|
||||||
)
|
|
||||||
|
|
||||||
// modelRegistry maintains a global mapping of model names to instance names
|
|
||||||
// for llama.cpp instances. Model names must be globally unique across all instances.
|
|
||||||
type modelRegistry struct {
|
|
||||||
mu sync.RWMutex
|
|
||||||
modelToInstance map[string]string // model name → instance name
|
|
||||||
instanceModels map[string][]string // instance name → model names
|
|
||||||
}
|
|
||||||
|
|
||||||
// newModelRegistry creates a new model registry
|
|
||||||
func newModelRegistry() *modelRegistry {
|
|
||||||
return &modelRegistry{
|
|
||||||
modelToInstance: make(map[string]string),
|
|
||||||
instanceModels: make(map[string][]string),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// registerModels registers models from an instance to the registry.
|
|
||||||
// Skips models that conflict with other instances and returns a list of conflicts.
|
|
||||||
func (mr *modelRegistry) registerModels(instanceName string, models []instance.Model) []string {
|
|
||||||
mr.mu.Lock()
|
|
||||||
defer mr.mu.Unlock()
|
|
||||||
|
|
||||||
// Unregister any existing models for this instance first
|
|
||||||
mr.removeModels(instanceName)
|
|
||||||
|
|
||||||
// Register models, skipping conflicts
|
|
||||||
var modelNames []string
|
|
||||||
var conflicts []string
|
|
||||||
|
|
||||||
for _, model := range models {
|
|
||||||
// Check if this model conflicts with another instance
|
|
||||||
if existingInstance, exists := mr.modelToInstance[model.ID]; exists && existingInstance != instanceName {
|
|
||||||
conflicts = append(conflicts, fmt.Sprintf("%s (already in %s)", model.ID, existingInstance))
|
|
||||||
continue // Skip this model
|
|
||||||
}
|
|
||||||
|
|
||||||
// Register the model
|
|
||||||
mr.modelToInstance[model.ID] = instanceName
|
|
||||||
modelNames = append(modelNames, model.ID)
|
|
||||||
}
|
|
||||||
|
|
||||||
mr.instanceModels[instanceName] = modelNames
|
|
||||||
|
|
||||||
return conflicts
|
|
||||||
}
|
|
||||||
|
|
||||||
// unregisterModels removes all models for an instance
|
|
||||||
func (mr *modelRegistry) unregisterModels(instanceName string) {
|
|
||||||
mr.mu.Lock()
|
|
||||||
defer mr.mu.Unlock()
|
|
||||||
mr.removeModels(instanceName)
|
|
||||||
}
|
|
||||||
|
|
||||||
// removeModels removes all models for an instance (caller must hold lock)
|
|
||||||
func (mr *modelRegistry) removeModels(instanceName string) {
|
|
||||||
if models, exists := mr.instanceModels[instanceName]; exists {
|
|
||||||
for _, modelName := range models {
|
|
||||||
delete(mr.modelToInstance, modelName)
|
|
||||||
}
|
|
||||||
delete(mr.instanceModels, instanceName)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// getModelInstance returns the instance name that hosts the given model
|
|
||||||
func (mr *modelRegistry) getModelInstance(modelName string) (string, bool) {
|
|
||||||
mr.mu.RLock()
|
|
||||||
defer mr.mu.RUnlock()
|
|
||||||
|
|
||||||
instanceName, exists := mr.modelToInstance[modelName]
|
|
||||||
return instanceName, exists
|
|
||||||
}
|
|
||||||
@@ -337,9 +337,6 @@ func (im *instanceManager) DeleteInstance(name string) error {
|
|||||||
// Release port (use ReleaseByInstance for proper cleanup)
|
// Release port (use ReleaseByInstance for proper cleanup)
|
||||||
im.ports.releaseByInstance(name)
|
im.ports.releaseByInstance(name)
|
||||||
|
|
||||||
// Unregister models when instance is deleted
|
|
||||||
im.onInstanceStopped(name)
|
|
||||||
|
|
||||||
// Remove from registry
|
// Remove from registry
|
||||||
if err := im.registry.remove(name); err != nil {
|
if err := im.registry.remove(name); err != nil {
|
||||||
return fmt.Errorf("failed to remove instance from registry: %w", err)
|
return fmt.Errorf("failed to remove instance from registry: %w", err)
|
||||||
@@ -399,9 +396,6 @@ func (im *instanceManager) StartInstance(name string) (*instance.Instance, error
|
|||||||
log.Printf("Warning: failed to persist instance %s: %v", name, err)
|
log.Printf("Warning: failed to persist instance %s: %v", name, err)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Discover and register models for llama.cpp instances
|
|
||||||
go im.onInstanceStarted(name)
|
|
||||||
|
|
||||||
return inst, nil
|
return inst, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -461,9 +455,6 @@ func (im *instanceManager) StopInstance(name string) (*instance.Instance, error)
|
|||||||
log.Printf("Warning: failed to persist instance %s: %v", name, err)
|
log.Printf("Warning: failed to persist instance %s: %v", name, err)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Unregister models when instance stops
|
|
||||||
im.onInstanceStopped(name)
|
|
||||||
|
|
||||||
return inst, nil
|
return inst, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -544,73 +535,3 @@ func (im *instanceManager) setPortInOptions(options *instance.Options, port int)
|
|||||||
func (im *instanceManager) EvictLRUInstance() error {
|
func (im *instanceManager) EvictLRUInstance() error {
|
||||||
return im.lifecycle.evictLRU()
|
return im.lifecycle.evictLRU()
|
||||||
}
|
}
|
||||||
|
|
||||||
// ResolveInstance resolves a model name to an instance name.
|
|
||||||
// Precedence: instance name > model registry
|
|
||||||
func (im *instanceManager) ResolveInstance(modelName string) (string, error) {
|
|
||||||
// Check if it's an instance name first
|
|
||||||
if _, err := im.GetInstance(modelName); err == nil {
|
|
||||||
return modelName, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// Check if it's a model name in the registry
|
|
||||||
if instanceName, exists := im.models.getModelInstance(modelName); exists {
|
|
||||||
return instanceName, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
return "", fmt.Errorf("model or instance '%s' not found", modelName)
|
|
||||||
}
|
|
||||||
|
|
||||||
// RefreshModelRegistry refreshes the model registry for the given instance
|
|
||||||
func (im *instanceManager) RefreshModelRegistry(inst *instance.Instance) error {
|
|
||||||
if !inst.IsRunning() {
|
|
||||||
return fmt.Errorf("instance %s is not running", inst.Name)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Fetch models from instance and register them
|
|
||||||
models, err := inst.GetModels()
|
|
||||||
if err != nil {
|
|
||||||
return fmt.Errorf("failed to fetch models: %w", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Register models, skipping conflicts
|
|
||||||
conflicts := im.models.registerModels(inst.Name, models)
|
|
||||||
if len(conflicts) > 0 {
|
|
||||||
log.Printf("Warning: Model name conflicts for instance %s (skipped): %v", inst.Name, conflicts)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Check if instance name shadows any model names
|
|
||||||
if otherInstance, exists := im.models.getModelInstance(inst.Name); exists && otherInstance != inst.Name {
|
|
||||||
log.Printf("Warning: Instance name '%s' shadows model name from instance '%s'", inst.Name, otherInstance)
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// onInstanceStarted is called when an instance successfully starts and becomes healthy
|
|
||||||
func (im *instanceManager) onInstanceStarted(name string) {
|
|
||||||
inst, err := im.GetInstance(name)
|
|
||||||
if err != nil {
|
|
||||||
log.Printf("Failed to get instance %s for model discovery: %v", name, err)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
// Only discover models for llama.cpp instances
|
|
||||||
if !inst.IsLlamaCpp() {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
if err := inst.WaitForHealthy(30); err != nil {
|
|
||||||
log.Printf("Instance %s not healthy, skipping model discovery: %v", name, err)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
if err := im.RefreshModelRegistry(inst); err != nil {
|
|
||||||
log.Printf("Failed to discover models for instance %s: %v", name, err)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// onInstanceStopped is called when an instance stops or is deleted
|
|
||||||
func (im *instanceManager) onInstanceStopped(name string) {
|
|
||||||
im.models.unregisterModels(name)
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -5,12 +5,9 @@ import (
|
|||||||
"fmt"
|
"fmt"
|
||||||
"llamactl/pkg/backends"
|
"llamactl/pkg/backends"
|
||||||
"llamactl/pkg/instance"
|
"llamactl/pkg/instance"
|
||||||
"log"
|
|
||||||
"net/http"
|
"net/http"
|
||||||
"os/exec"
|
"os/exec"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
"github.com/go-chi/chi/v5"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
// ParseCommandRequest represents the request body for backend command parsing
|
// ParseCommandRequest represents the request body for backend command parsing
|
||||||
@@ -309,115 +306,3 @@ func (h *Handler) LlamaServerVersionHandler() http.HandlerFunc {
|
|||||||
func (h *Handler) LlamaServerListDevicesHandler() http.HandlerFunc {
|
func (h *Handler) LlamaServerListDevicesHandler() http.HandlerFunc {
|
||||||
return h.executeLlamaServerCommand("--list-devices", "Failed to list devices")
|
return h.executeLlamaServerCommand("--list-devices", "Failed to list devices")
|
||||||
}
|
}
|
||||||
|
|
||||||
// LlamaCppListModels godoc
|
|
||||||
// @Summary List models in a llama.cpp instance
|
|
||||||
// @Description Returns a list of models available in the specified llama.cpp instance
|
|
||||||
// @Tags Llama.cpp
|
|
||||||
// @Security ApiKeyAuth
|
|
||||||
// @Produces json
|
|
||||||
// @Param name path string true "Instance Name"
|
|
||||||
// @Success 200 {object} map[string]any "Models list response"
|
|
||||||
// @Failure 400 {string} string "Invalid instance"
|
|
||||||
// @Failure 500 {string} string "Internal Server Error"
|
|
||||||
// @Router /api/v1/llama-cpp/{name}/models [get]
|
|
||||||
func (h *Handler) LlamaCppListModels() http.HandlerFunc {
|
|
||||||
return func(w http.ResponseWriter, r *http.Request) {
|
|
||||||
inst, err := h.getInstance(r)
|
|
||||||
if err != nil {
|
|
||||||
writeError(w, http.StatusBadRequest, "invalid_instance", err.Error())
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
models, err := inst.GetModels()
|
|
||||||
if err != nil {
|
|
||||||
writeError(w, http.StatusBadRequest, "get_models_failed", err.Error())
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
response := map[string]any{
|
|
||||||
"object": "list",
|
|
||||||
"data": models,
|
|
||||||
}
|
|
||||||
|
|
||||||
writeJSON(w, http.StatusOK, response)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// LlamaCppLoadModel godoc
|
|
||||||
// @Summary Load a model in a llama.cpp instance
|
|
||||||
// @Description Loads the specified model in the given llama.cpp instance
|
|
||||||
// @Tags Llama.cpp
|
|
||||||
// @Security ApiKeyAuth
|
|
||||||
// @Produces json
|
|
||||||
// @Param name path string true "Instance Name"
|
|
||||||
// @Param model path string true "Model Name"
|
|
||||||
// @Success 200 {object} map[string]string "Success message"
|
|
||||||
// @Failure 400 {string} string "Invalid request"
|
|
||||||
// @Failure 500 {string} string "Internal Server Error"
|
|
||||||
// @Router /api/v1/llama-cpp/{name}/models/{model}/load [post]
|
|
||||||
func (h *Handler) LlamaCppLoadModel() http.HandlerFunc {
|
|
||||||
return func(w http.ResponseWriter, r *http.Request) {
|
|
||||||
inst, err := h.getInstance(r)
|
|
||||||
if err != nil {
|
|
||||||
writeError(w, http.StatusBadRequest, "invalid_instance", err.Error())
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
modelName := chi.URLParam(r, "model")
|
|
||||||
|
|
||||||
if err := inst.LoadModel(modelName); err != nil {
|
|
||||||
writeError(w, http.StatusBadRequest, "load_model_failed", err.Error())
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
// Refresh the model registry
|
|
||||||
if err := h.InstanceManager.RefreshModelRegistry(inst); err != nil {
|
|
||||||
log.Printf("Warning: failed to refresh model registry after load: %v", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
writeJSON(w, http.StatusOK, map[string]string{
|
|
||||||
"status": "success",
|
|
||||||
"message": fmt.Sprintf("Model %s loaded successfully", modelName),
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// LlamaCppUnloadModel godoc
|
|
||||||
// @Summary Unload a model in a llama.cpp instance
|
|
||||||
// @Description Unloads the specified model in the given llama.cpp instance
|
|
||||||
// @Tags Llama.cpp
|
|
||||||
// @Security ApiKeyAuth
|
|
||||||
// @Produces json
|
|
||||||
// @Param name path string true "Instance Name"
|
|
||||||
// @Param model path string true "Model Name"
|
|
||||||
// @Success 200 {object} map[string]string "Success message"
|
|
||||||
// @Failure 400 {string} string "Invalid request"
|
|
||||||
// @Failure 500 {string} string "Internal Server Error"
|
|
||||||
// @Router /api/v1/llama-cpp/{name}/models/{model}/unload [post]
|
|
||||||
func (h *Handler) LlamaCppUnloadModel() http.HandlerFunc {
|
|
||||||
return func(w http.ResponseWriter, r *http.Request) {
|
|
||||||
inst, err := h.getInstance(r)
|
|
||||||
if err != nil {
|
|
||||||
writeError(w, http.StatusBadRequest, "invalid_instance", err.Error())
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
modelName := chi.URLParam(r, "model")
|
|
||||||
|
|
||||||
if err := inst.UnloadModel(modelName); err != nil {
|
|
||||||
writeError(w, http.StatusBadRequest, "unload_model_failed", err.Error())
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
// Refresh the model registry
|
|
||||||
if err := h.InstanceManager.RefreshModelRegistry(inst); err != nil {
|
|
||||||
log.Printf("Warning: failed to refresh model registry after unload: %v", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
writeJSON(w, http.StatusOK, map[string]string{
|
|
||||||
"status": "success",
|
|
||||||
"message": fmt.Sprintf("Model %s unloaded successfully", modelName),
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -3,7 +3,6 @@ package server
|
|||||||
import (
|
import (
|
||||||
"bytes"
|
"bytes"
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
"fmt"
|
|
||||||
"io"
|
"io"
|
||||||
"llamactl/pkg/instance"
|
"llamactl/pkg/instance"
|
||||||
"llamactl/pkg/validation"
|
"llamactl/pkg/validation"
|
||||||
@@ -41,41 +40,14 @@ func (h *Handler) OpenAIListInstances() http.HandlerFunc {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
var openaiInstances []OpenAIInstance
|
openaiInstances := make([]OpenAIInstance, len(instances))
|
||||||
|
for i, inst := range instances {
|
||||||
// For each llama.cpp instance, try to fetch models and add them as separate entries
|
openaiInstances[i] = OpenAIInstance{
|
||||||
for _, inst := range instances {
|
|
||||||
|
|
||||||
if inst.IsLlamaCpp() && inst.IsRunning() {
|
|
||||||
// Try to fetch models from the instance
|
|
||||||
models, err := inst.GetModels()
|
|
||||||
if err != nil {
|
|
||||||
fmt.Printf("Failed to fetch models from instance %s: %v", inst.Name, err)
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
|
|
||||||
for _, model := range models {
|
|
||||||
openaiInstances = append(openaiInstances, OpenAIInstance{
|
|
||||||
ID: model.ID,
|
|
||||||
Object: "model",
|
|
||||||
Created: model.Created,
|
|
||||||
OwnedBy: inst.Name,
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
if len(models) > 1 {
|
|
||||||
// Skip adding the instance name if multiple models are present
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Add instance name as single entry (for non-llama.cpp or if model fetch failed)
|
|
||||||
openaiInstances = append(openaiInstances, OpenAIInstance{
|
|
||||||
ID: inst.Name,
|
ID: inst.Name,
|
||||||
Object: "model",
|
Object: "model",
|
||||||
Created: inst.Created,
|
Created: inst.Created,
|
||||||
OwnedBy: "llamactl",
|
OwnedBy: "llamactl",
|
||||||
})
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
openaiResponse := OpenAIListInstancesResponse{
|
openaiResponse := OpenAIListInstancesResponse{
|
||||||
@@ -117,19 +89,12 @@ func (h *Handler) OpenAIProxy() http.HandlerFunc {
|
|||||||
|
|
||||||
modelName, ok := requestBody["model"].(string)
|
modelName, ok := requestBody["model"].(string)
|
||||||
if !ok || modelName == "" {
|
if !ok || modelName == "" {
|
||||||
writeError(w, http.StatusBadRequest, "invalid_request", "Model name is required")
|
writeError(w, http.StatusBadRequest, "invalid_request", "Instance name is required")
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
// Resolve model name to instance name (checks instance names first, then model registry)
|
|
||||||
instanceName, err := h.InstanceManager.ResolveInstance(modelName)
|
|
||||||
if err != nil {
|
|
||||||
writeError(w, http.StatusBadRequest, "model_not_found", err.Error())
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
// Validate instance name at the entry point
|
// Validate instance name at the entry point
|
||||||
validatedName, err := validation.ValidateInstanceName(instanceName)
|
validatedName, err := validation.ValidateInstanceName(modelName)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
writeError(w, http.StatusBadRequest, "invalid_instance_name", err.Error())
|
writeError(w, http.StatusBadRequest, "invalid_instance_name", err.Error())
|
||||||
return
|
return
|
||||||
|
|||||||
@@ -26,9 +26,6 @@ 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"),
|
||||||
@@ -38,8 +35,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 authMiddleware != nil && handler.cfg.Auth.RequireManagementAuth {
|
if handler.authMiddleware != nil && handler.cfg.Auth.RequireManagementAuth {
|
||||||
r.Use(authMiddleware.ManagementAuthMiddleware())
|
r.Use(handler.authMiddleware.ManagementAuthMiddleware())
|
||||||
}
|
}
|
||||||
|
|
||||||
r.Get("/version", handler.VersionHandler())
|
r.Get("/version", handler.VersionHandler())
|
||||||
@@ -73,13 +70,6 @@ func SetupRouter(handler *Handler) *chi.Mux {
|
|||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
// Llama.cpp instance-specific endpoints
|
|
||||||
r.Route("/llama-cpp/{name}", func(r chi.Router) {
|
|
||||||
r.Get("/models", handler.LlamaCppListModels())
|
|
||||||
r.Post("/models/{model}/load", handler.LlamaCppLoadModel())
|
|
||||||
r.Post("/models/{model}/unload", handler.LlamaCppUnloadModel())
|
|
||||||
})
|
|
||||||
|
|
||||||
// Node management endpoints
|
// Node management endpoints
|
||||||
r.Route("/nodes", func(r chi.Router) {
|
r.Route("/nodes", func(r chi.Router) {
|
||||||
r.Get("/", handler.ListNodes()) // List all nodes
|
r.Get("/", handler.ListNodes()) // List all nodes
|
||||||
@@ -114,8 +104,8 @@ func SetupRouter(handler *Handler) *chi.Mux {
|
|||||||
|
|
||||||
r.Route("/v1", func(r chi.Router) {
|
r.Route("/v1", func(r chi.Router) {
|
||||||
|
|
||||||
if authMiddleware != nil && handler.cfg.Auth.RequireInferenceAuth {
|
if handler.authMiddleware != nil && handler.cfg.Auth.RequireInferenceAuth {
|
||||||
r.Use(authMiddleware.InferenceAuthMiddleware())
|
r.Use(handler.authMiddleware.InferenceAuthMiddleware())
|
||||||
}
|
}
|
||||||
|
|
||||||
r.Get("/models", handler.OpenAIListInstances()) // List instances in OpenAI-compatible format
|
r.Get("/models", handler.OpenAIListInstances()) // List instances in OpenAI-compatible format
|
||||||
@@ -142,8 +132,8 @@ func SetupRouter(handler *Handler) *chi.Mux {
|
|||||||
// Private Routes
|
// Private Routes
|
||||||
r.Group(func(r chi.Router) {
|
r.Group(func(r chi.Router) {
|
||||||
|
|
||||||
if authMiddleware != nil && handler.cfg.Auth.RequireInferenceAuth {
|
if handler.authMiddleware != nil && handler.cfg.Auth.RequireInferenceAuth {
|
||||||
r.Use(authMiddleware.InferenceAuthMiddleware())
|
r.Use(handler.authMiddleware.InferenceAuthMiddleware())
|
||||||
}
|
}
|
||||||
|
|
||||||
// This handler auto starts the server if it's not running
|
// This handler auto starts the server if it's not running
|
||||||
|
|||||||
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.560.0",
|
"lucide-react": "^0.561.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.1.12"
|
"zod": "^4.2.0"
|
||||||
},
|
},
|
||||||
"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": "^24.10.1",
|
"@types/node": "^25.0.2",
|
||||||
"@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.49.0",
|
"typescript-eslint": "^8.50.0",
|
||||||
"vite": "^7.2.2",
|
"vite": "^7.3.0",
|
||||||
"vitest": "^4.0.8"
|
"vitest": "^4.0.8"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
@@ -564,9 +564,9 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@esbuild/aix-ppc64": {
|
"node_modules/@esbuild/aix-ppc64": {
|
||||||
"version": "0.25.8",
|
"version": "0.27.1",
|
||||||
"resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.25.8.tgz",
|
"resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.27.1.tgz",
|
||||||
"integrity": "sha512-urAvrUedIqEiFR3FYSLTWQgLu5tb+m0qZw0NBEasUeo6wuqatkMDaRT+1uABiGXEu5vqgPd7FGE1BhsAIy9QVA==",
|
"integrity": "sha512-HHB50pdsBX6k47S4u5g/CaLjqS3qwaOVE5ILsq64jyzgMhLuCuZ8rGzM9yhsAjfjkbgUPMzZEPa7DAp7yz6vuA==",
|
||||||
"cpu": [
|
"cpu": [
|
||||||
"ppc64"
|
"ppc64"
|
||||||
],
|
],
|
||||||
@@ -580,9 +580,9 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@esbuild/android-arm": {
|
"node_modules/@esbuild/android-arm": {
|
||||||
"version": "0.25.8",
|
"version": "0.27.1",
|
||||||
"resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.25.8.tgz",
|
"resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.27.1.tgz",
|
||||||
"integrity": "sha512-RONsAvGCz5oWyePVnLdZY/HHwA++nxYWIX1atInlaW6SEkwq6XkP3+cb825EUcRs5Vss/lGh/2YxAb5xqc07Uw==",
|
"integrity": "sha512-kFqa6/UcaTbGm/NncN9kzVOODjhZW8e+FRdSeypWe6j33gzclHtwlANs26JrupOntlcWmB0u8+8HZo8s7thHvg==",
|
||||||
"cpu": [
|
"cpu": [
|
||||||
"arm"
|
"arm"
|
||||||
],
|
],
|
||||||
@@ -596,9 +596,9 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@esbuild/android-arm64": {
|
"node_modules/@esbuild/android-arm64": {
|
||||||
"version": "0.25.8",
|
"version": "0.27.1",
|
||||||
"resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.25.8.tgz",
|
"resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.27.1.tgz",
|
||||||
"integrity": "sha512-OD3p7LYzWpLhZEyATcTSJ67qB5D+20vbtr6vHlHWSQYhKtzUYrETuWThmzFpZtFsBIxRvhO07+UgVA9m0i/O1w==",
|
"integrity": "sha512-45fuKmAJpxnQWixOGCrS+ro4Uvb4Re9+UTieUY2f8AEc+t7d4AaZ6eUJ3Hva7dtrxAAWHtlEFsXFMAgNnGU9uQ==",
|
||||||
"cpu": [
|
"cpu": [
|
||||||
"arm64"
|
"arm64"
|
||||||
],
|
],
|
||||||
@@ -612,9 +612,9 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@esbuild/android-x64": {
|
"node_modules/@esbuild/android-x64": {
|
||||||
"version": "0.25.8",
|
"version": "0.27.1",
|
||||||
"resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.25.8.tgz",
|
"resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.27.1.tgz",
|
||||||
"integrity": "sha512-yJAVPklM5+4+9dTeKwHOaA+LQkmrKFX96BM0A/2zQrbS6ENCmxc4OVoBs5dPkCCak2roAD+jKCdnmOqKszPkjA==",
|
"integrity": "sha512-LBEpOz0BsgMEeHgenf5aqmn/lLNTFXVfoWMUox8CtWWYK9X4jmQzWjoGoNb8lmAYml/tQ/Ysvm8q7szu7BoxRQ==",
|
||||||
"cpu": [
|
"cpu": [
|
||||||
"x64"
|
"x64"
|
||||||
],
|
],
|
||||||
@@ -628,9 +628,9 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@esbuild/darwin-arm64": {
|
"node_modules/@esbuild/darwin-arm64": {
|
||||||
"version": "0.25.8",
|
"version": "0.27.1",
|
||||||
"resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.25.8.tgz",
|
"resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.27.1.tgz",
|
||||||
"integrity": "sha512-Jw0mxgIaYX6R8ODrdkLLPwBqHTtYHJSmzzd+QeytSugzQ0Vg4c5rDky5VgkoowbZQahCbsv1rT1KW72MPIkevw==",
|
"integrity": "sha512-veg7fL8eMSCVKL7IW4pxb54QERtedFDfY/ASrumK/SbFsXnRazxY4YykN/THYqFnFwJ0aVjiUrVG2PwcdAEqQQ==",
|
||||||
"cpu": [
|
"cpu": [
|
||||||
"arm64"
|
"arm64"
|
||||||
],
|
],
|
||||||
@@ -644,9 +644,9 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@esbuild/darwin-x64": {
|
"node_modules/@esbuild/darwin-x64": {
|
||||||
"version": "0.25.8",
|
"version": "0.27.1",
|
||||||
"resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.25.8.tgz",
|
"resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.27.1.tgz",
|
||||||
"integrity": "sha512-Vh2gLxxHnuoQ+GjPNvDSDRpoBCUzY4Pu0kBqMBDlK4fuWbKgGtmDIeEC081xi26PPjn+1tct+Bh8FjyLlw1Zlg==",
|
"integrity": "sha512-+3ELd+nTzhfWb07Vol7EZ+5PTbJ/u74nC6iv4/lwIU99Ip5uuY6QoIf0Hn4m2HoV0qcnRivN3KSqc+FyCHjoVQ==",
|
||||||
"cpu": [
|
"cpu": [
|
||||||
"x64"
|
"x64"
|
||||||
],
|
],
|
||||||
@@ -660,9 +660,9 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@esbuild/freebsd-arm64": {
|
"node_modules/@esbuild/freebsd-arm64": {
|
||||||
"version": "0.25.8",
|
"version": "0.27.1",
|
||||||
"resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.25.8.tgz",
|
"resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.27.1.tgz",
|
||||||
"integrity": "sha512-YPJ7hDQ9DnNe5vxOm6jaie9QsTwcKedPvizTVlqWG9GBSq+BuyWEDazlGaDTC5NGU4QJd666V0yqCBL2oWKPfA==",
|
"integrity": "sha512-/8Rfgns4XD9XOSXlzUDepG8PX+AVWHliYlUkFI3K3GB6tqbdjYqdhcb4BKRd7C0BhZSoaCxhv8kTcBrcZWP+xg==",
|
||||||
"cpu": [
|
"cpu": [
|
||||||
"arm64"
|
"arm64"
|
||||||
],
|
],
|
||||||
@@ -676,9 +676,9 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@esbuild/freebsd-x64": {
|
"node_modules/@esbuild/freebsd-x64": {
|
||||||
"version": "0.25.8",
|
"version": "0.27.1",
|
||||||
"resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.25.8.tgz",
|
"resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.27.1.tgz",
|
||||||
"integrity": "sha512-MmaEXxQRdXNFsRN/KcIimLnSJrk2r5H8v+WVafRWz5xdSVmWLoITZQXcgehI2ZE6gioE6HirAEToM/RvFBeuhw==",
|
"integrity": "sha512-GITpD8dK9C+r+5yRT/UKVT36h/DQLOHdwGVwwoHidlnA168oD3uxA878XloXebK4Ul3gDBBIvEdL7go9gCUFzQ==",
|
||||||
"cpu": [
|
"cpu": [
|
||||||
"x64"
|
"x64"
|
||||||
],
|
],
|
||||||
@@ -692,9 +692,9 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@esbuild/linux-arm": {
|
"node_modules/@esbuild/linux-arm": {
|
||||||
"version": "0.25.8",
|
"version": "0.27.1",
|
||||||
"resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.25.8.tgz",
|
"resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.27.1.tgz",
|
||||||
"integrity": "sha512-FuzEP9BixzZohl1kLf76KEVOsxtIBFwCaLupVuk4eFVnOZfU+Wsn+x5Ryam7nILV2pkq2TqQM9EZPsOBuMC+kg==",
|
"integrity": "sha512-ieMID0JRZY/ZeCrsFQ3Y3NlHNCqIhTprJfDgSB3/lv5jJZ8FX3hqPyXWhe+gvS5ARMBJ242PM+VNz/ctNj//eA==",
|
||||||
"cpu": [
|
"cpu": [
|
||||||
"arm"
|
"arm"
|
||||||
],
|
],
|
||||||
@@ -708,9 +708,9 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@esbuild/linux-arm64": {
|
"node_modules/@esbuild/linux-arm64": {
|
||||||
"version": "0.25.8",
|
"version": "0.27.1",
|
||||||
"resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.25.8.tgz",
|
"resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.27.1.tgz",
|
||||||
"integrity": "sha512-WIgg00ARWv/uYLU7lsuDK00d/hHSfES5BzdWAdAig1ioV5kaFNrtK8EqGcUBJhYqotlUByUKz5Qo6u8tt7iD/w==",
|
"integrity": "sha512-W9//kCrh/6in9rWIBdKaMtuTTzNj6jSeG/haWBADqLLa9P8O5YSRDzgD5y9QBok4AYlzS6ARHifAb75V6G670Q==",
|
||||||
"cpu": [
|
"cpu": [
|
||||||
"arm64"
|
"arm64"
|
||||||
],
|
],
|
||||||
@@ -724,9 +724,9 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@esbuild/linux-ia32": {
|
"node_modules/@esbuild/linux-ia32": {
|
||||||
"version": "0.25.8",
|
"version": "0.27.1",
|
||||||
"resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.25.8.tgz",
|
"resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.27.1.tgz",
|
||||||
"integrity": "sha512-A1D9YzRX1i+1AJZuFFUMP1E9fMaYY+GnSQil9Tlw05utlE86EKTUA7RjwHDkEitmLYiFsRd9HwKBPEftNdBfjg==",
|
"integrity": "sha512-VIUV4z8GD8rtSVMfAj1aXFahsi/+tcoXXNYmXgzISL+KB381vbSTNdeZHHHIYqFyXcoEhu9n5cT+05tRv13rlw==",
|
||||||
"cpu": [
|
"cpu": [
|
||||||
"ia32"
|
"ia32"
|
||||||
],
|
],
|
||||||
@@ -740,9 +740,9 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@esbuild/linux-loong64": {
|
"node_modules/@esbuild/linux-loong64": {
|
||||||
"version": "0.25.8",
|
"version": "0.27.1",
|
||||||
"resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.25.8.tgz",
|
"resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.27.1.tgz",
|
||||||
"integrity": "sha512-O7k1J/dwHkY1RMVvglFHl1HzutGEFFZ3kNiDMSOyUrB7WcoHGf96Sh+64nTRT26l3GMbCW01Ekh/ThKM5iI7hQ==",
|
"integrity": "sha512-l4rfiiJRN7sTNI//ff65zJ9z8U+k6zcCg0LALU5iEWzY+a1mVZ8iWC1k5EsNKThZ7XCQ6YWtsZ8EWYm7r1UEsg==",
|
||||||
"cpu": [
|
"cpu": [
|
||||||
"loong64"
|
"loong64"
|
||||||
],
|
],
|
||||||
@@ -756,9 +756,9 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@esbuild/linux-mips64el": {
|
"node_modules/@esbuild/linux-mips64el": {
|
||||||
"version": "0.25.8",
|
"version": "0.27.1",
|
||||||
"resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.25.8.tgz",
|
"resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.27.1.tgz",
|
||||||
"integrity": "sha512-uv+dqfRazte3BzfMp8PAQXmdGHQt2oC/y2ovwpTteqrMx2lwaksiFZ/bdkXJC19ttTvNXBuWH53zy/aTj1FgGw==",
|
"integrity": "sha512-U0bEuAOLvO/DWFdygTHWY8C067FXz+UbzKgxYhXC0fDieFa0kDIra1FAhsAARRJbvEyso8aAqvPdNxzWuStBnA==",
|
||||||
"cpu": [
|
"cpu": [
|
||||||
"mips64el"
|
"mips64el"
|
||||||
],
|
],
|
||||||
@@ -772,9 +772,9 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@esbuild/linux-ppc64": {
|
"node_modules/@esbuild/linux-ppc64": {
|
||||||
"version": "0.25.8",
|
"version": "0.27.1",
|
||||||
"resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.25.8.tgz",
|
"resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.27.1.tgz",
|
||||||
"integrity": "sha512-GyG0KcMi1GBavP5JgAkkstMGyMholMDybAf8wF5A70CALlDM2p/f7YFE7H92eDeH/VBtFJA5MT4nRPDGg4JuzQ==",
|
"integrity": "sha512-NzdQ/Xwu6vPSf/GkdmRNsOfIeSGnh7muundsWItmBsVpMoNPVpM61qNzAVY3pZ1glzzAxLR40UyYM23eaDDbYQ==",
|
||||||
"cpu": [
|
"cpu": [
|
||||||
"ppc64"
|
"ppc64"
|
||||||
],
|
],
|
||||||
@@ -788,9 +788,9 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@esbuild/linux-riscv64": {
|
"node_modules/@esbuild/linux-riscv64": {
|
||||||
"version": "0.25.8",
|
"version": "0.27.1",
|
||||||
"resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.25.8.tgz",
|
"resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.27.1.tgz",
|
||||||
"integrity": "sha512-rAqDYFv3yzMrq7GIcen3XP7TUEG/4LK86LUPMIz6RT8A6pRIDn0sDcvjudVZBiiTcZCY9y2SgYX2lgK3AF+1eg==",
|
"integrity": "sha512-7zlw8p3IApcsN7mFw0O1Z1PyEk6PlKMu18roImfl3iQHTnr/yAfYv6s4hXPidbDoI2Q0pW+5xeoM4eTCC0UdrQ==",
|
||||||
"cpu": [
|
"cpu": [
|
||||||
"riscv64"
|
"riscv64"
|
||||||
],
|
],
|
||||||
@@ -804,9 +804,9 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@esbuild/linux-s390x": {
|
"node_modules/@esbuild/linux-s390x": {
|
||||||
"version": "0.25.8",
|
"version": "0.27.1",
|
||||||
"resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.25.8.tgz",
|
"resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.27.1.tgz",
|
||||||
"integrity": "sha512-Xutvh6VjlbcHpsIIbwY8GVRbwoviWT19tFhgdA7DlenLGC/mbc3lBoVb7jxj9Z+eyGqvcnSyIltYUrkKzWqSvg==",
|
"integrity": "sha512-cGj5wli+G+nkVQdZo3+7FDKC25Uh4ZVwOAK6A06Hsvgr8WqBBuOy/1s+PUEd/6Je+vjfm6stX0kmib5b/O2Ykw==",
|
||||||
"cpu": [
|
"cpu": [
|
||||||
"s390x"
|
"s390x"
|
||||||
],
|
],
|
||||||
@@ -820,9 +820,9 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@esbuild/linux-x64": {
|
"node_modules/@esbuild/linux-x64": {
|
||||||
"version": "0.25.8",
|
"version": "0.27.1",
|
||||||
"resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.25.8.tgz",
|
"resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.27.1.tgz",
|
||||||
"integrity": "sha512-ASFQhgY4ElXh3nDcOMTkQero4b1lgubskNlhIfJrsH5OKZXDpUAKBlNS0Kx81jwOBp+HCeZqmoJuihTv57/jvQ==",
|
"integrity": "sha512-z3H/HYI9MM0HTv3hQZ81f+AKb+yEoCRlUby1F80vbQ5XdzEMyY/9iNlAmhqiBKw4MJXwfgsh7ERGEOhrM1niMA==",
|
||||||
"cpu": [
|
"cpu": [
|
||||||
"x64"
|
"x64"
|
||||||
],
|
],
|
||||||
@@ -836,9 +836,9 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@esbuild/netbsd-arm64": {
|
"node_modules/@esbuild/netbsd-arm64": {
|
||||||
"version": "0.25.8",
|
"version": "0.27.1",
|
||||||
"resolved": "https://registry.npmjs.org/@esbuild/netbsd-arm64/-/netbsd-arm64-0.25.8.tgz",
|
"resolved": "https://registry.npmjs.org/@esbuild/netbsd-arm64/-/netbsd-arm64-0.27.1.tgz",
|
||||||
"integrity": "sha512-d1KfruIeohqAi6SA+gENMuObDbEjn22olAR7egqnkCD9DGBG0wsEARotkLgXDu6c4ncgWTZJtN5vcgxzWRMzcw==",
|
"integrity": "sha512-wzC24DxAvk8Em01YmVXyjl96Mr+ecTPyOuADAvjGg+fyBpGmxmcr2E5ttf7Im8D0sXZihpxzO1isus8MdjMCXQ==",
|
||||||
"cpu": [
|
"cpu": [
|
||||||
"arm64"
|
"arm64"
|
||||||
],
|
],
|
||||||
@@ -852,9 +852,9 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@esbuild/netbsd-x64": {
|
"node_modules/@esbuild/netbsd-x64": {
|
||||||
"version": "0.25.8",
|
"version": "0.27.1",
|
||||||
"resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.25.8.tgz",
|
"resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.27.1.tgz",
|
||||||
"integrity": "sha512-nVDCkrvx2ua+XQNyfrujIG38+YGyuy2Ru9kKVNyh5jAys6n+l44tTtToqHjino2My8VAY6Lw9H7RI73XFi66Cg==",
|
"integrity": "sha512-1YQ8ybGi2yIXswu6eNzJsrYIGFpnlzEWRl6iR5gMgmsrR0FcNoV1m9k9sc3PuP5rUBLshOZylc9nqSgymI+TYg==",
|
||||||
"cpu": [
|
"cpu": [
|
||||||
"x64"
|
"x64"
|
||||||
],
|
],
|
||||||
@@ -868,9 +868,9 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@esbuild/openbsd-arm64": {
|
"node_modules/@esbuild/openbsd-arm64": {
|
||||||
"version": "0.25.8",
|
"version": "0.27.1",
|
||||||
"resolved": "https://registry.npmjs.org/@esbuild/openbsd-arm64/-/openbsd-arm64-0.25.8.tgz",
|
"resolved": "https://registry.npmjs.org/@esbuild/openbsd-arm64/-/openbsd-arm64-0.27.1.tgz",
|
||||||
"integrity": "sha512-j8HgrDuSJFAujkivSMSfPQSAa5Fxbvk4rgNAS5i3K+r8s1X0p1uOO2Hl2xNsGFppOeHOLAVgYwDVlmxhq5h+SQ==",
|
"integrity": "sha512-5Z+DzLCrq5wmU7RDaMDe2DVXMRm2tTDvX2KU14JJVBN2CT/qov7XVix85QoJqHltpvAOZUAc3ndU56HSMWrv8g==",
|
||||||
"cpu": [
|
"cpu": [
|
||||||
"arm64"
|
"arm64"
|
||||||
],
|
],
|
||||||
@@ -884,9 +884,9 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@esbuild/openbsd-x64": {
|
"node_modules/@esbuild/openbsd-x64": {
|
||||||
"version": "0.25.8",
|
"version": "0.27.1",
|
||||||
"resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.25.8.tgz",
|
"resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.27.1.tgz",
|
||||||
"integrity": "sha512-1h8MUAwa0VhNCDp6Af0HToI2TJFAn1uqT9Al6DJVzdIBAd21m/G0Yfc77KDM3uF3T/YaOgQq3qTJHPbTOInaIQ==",
|
"integrity": "sha512-Q73ENzIdPF5jap4wqLtsfh8YbYSZ8Q0wnxplOlZUOyZy7B4ZKW8DXGWgTCZmF8VWD7Tciwv5F4NsRf6vYlZtqg==",
|
||||||
"cpu": [
|
"cpu": [
|
||||||
"x64"
|
"x64"
|
||||||
],
|
],
|
||||||
@@ -900,9 +900,9 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@esbuild/openharmony-arm64": {
|
"node_modules/@esbuild/openharmony-arm64": {
|
||||||
"version": "0.25.8",
|
"version": "0.27.1",
|
||||||
"resolved": "https://registry.npmjs.org/@esbuild/openharmony-arm64/-/openharmony-arm64-0.25.8.tgz",
|
"resolved": "https://registry.npmjs.org/@esbuild/openharmony-arm64/-/openharmony-arm64-0.27.1.tgz",
|
||||||
"integrity": "sha512-r2nVa5SIK9tSWd0kJd9HCffnDHKchTGikb//9c7HX+r+wHYCpQrSgxhlY6KWV1nFo1l4KFbsMlHk+L6fekLsUg==",
|
"integrity": "sha512-ajbHrGM/XiK+sXM0JzEbJAen+0E+JMQZ2l4RR4VFwvV9JEERx+oxtgkpoKv1SevhjavK2z2ReHk32pjzktWbGg==",
|
||||||
"cpu": [
|
"cpu": [
|
||||||
"arm64"
|
"arm64"
|
||||||
],
|
],
|
||||||
@@ -916,9 +916,9 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@esbuild/sunos-x64": {
|
"node_modules/@esbuild/sunos-x64": {
|
||||||
"version": "0.25.8",
|
"version": "0.27.1",
|
||||||
"resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.25.8.tgz",
|
"resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.27.1.tgz",
|
||||||
"integrity": "sha512-zUlaP2S12YhQ2UzUfcCuMDHQFJyKABkAjvO5YSndMiIkMimPmxA+BYSBikWgsRpvyxuRnow4nS5NPnf9fpv41w==",
|
"integrity": "sha512-IPUW+y4VIjuDVn+OMzHc5FV4GubIwPnsz6ubkvN8cuhEqH81NovB53IUlrlBkPMEPxvNnf79MGBoz8rZ2iW8HA==",
|
||||||
"cpu": [
|
"cpu": [
|
||||||
"x64"
|
"x64"
|
||||||
],
|
],
|
||||||
@@ -932,9 +932,9 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@esbuild/win32-arm64": {
|
"node_modules/@esbuild/win32-arm64": {
|
||||||
"version": "0.25.8",
|
"version": "0.27.1",
|
||||||
"resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.25.8.tgz",
|
"resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.27.1.tgz",
|
||||||
"integrity": "sha512-YEGFFWESlPva8hGL+zvj2z/SaK+pH0SwOM0Nc/d+rVnW7GSTFlLBGzZkuSU9kFIGIo8q9X3ucpZhu8PDN5A2sQ==",
|
"integrity": "sha512-RIVRWiljWA6CdVu8zkWcRmGP7iRRIIwvhDKem8UMBjPql2TXM5PkDVvvrzMtj1V+WFPB4K7zkIGM7VzRtFkjdg==",
|
||||||
"cpu": [
|
"cpu": [
|
||||||
"arm64"
|
"arm64"
|
||||||
],
|
],
|
||||||
@@ -948,9 +948,9 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@esbuild/win32-ia32": {
|
"node_modules/@esbuild/win32-ia32": {
|
||||||
"version": "0.25.8",
|
"version": "0.27.1",
|
||||||
"resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.25.8.tgz",
|
"resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.27.1.tgz",
|
||||||
"integrity": "sha512-hiGgGC6KZ5LZz58OL/+qVVoZiuZlUYlYHNAmczOm7bs2oE1XriPFi5ZHHrS8ACpV5EjySrnoCKmcbQMN+ojnHg==",
|
"integrity": "sha512-2BR5M8CPbptC1AK5JbJT1fWrHLvejwZidKx3UMSF0ecHMa+smhi16drIrCEggkgviBwLYd5nwrFLSl5Kho96RQ==",
|
||||||
"cpu": [
|
"cpu": [
|
||||||
"ia32"
|
"ia32"
|
||||||
],
|
],
|
||||||
@@ -964,9 +964,9 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@esbuild/win32-x64": {
|
"node_modules/@esbuild/win32-x64": {
|
||||||
"version": "0.25.8",
|
"version": "0.27.1",
|
||||||
"resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.25.8.tgz",
|
"resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.27.1.tgz",
|
||||||
"integrity": "sha512-cn3Yr7+OaaZq1c+2pe+8yxC8E144SReCQjN6/2ynubzYjvyqZjTXfQJpAcQpsdJq3My7XADANiYGHoFC69pLQw==",
|
"integrity": "sha512-d5X6RMYv6taIymSk8JBP+nxv8DQAMY6A51GPgusqLdK9wBz5wWIXy1KjTck6HnjE9hqJzJRdk+1p/t5soSbCtw==",
|
||||||
"cpu": [
|
"cpu": [
|
||||||
"x64"
|
"x64"
|
||||||
],
|
],
|
||||||
@@ -2616,9 +2616,9 @@
|
|||||||
"license": "MIT"
|
"license": "MIT"
|
||||||
},
|
},
|
||||||
"node_modules/@types/node": {
|
"node_modules/@types/node": {
|
||||||
"version": "24.10.1",
|
"version": "25.0.2",
|
||||||
"resolved": "https://registry.npmjs.org/@types/node/-/node-24.10.1.tgz",
|
"resolved": "https://registry.npmjs.org/@types/node/-/node-25.0.2.tgz",
|
||||||
"integrity": "sha512-GNWcUTRBgIRJD5zj+Tq0fKOJ5XZajIiBroOF0yvj2bSU1WvNdYS/dn9UxwsujGW4JX06dnHyjV2y9rRaybH0iQ==",
|
"integrity": "sha512-gWEkeiyYE4vqjON/+Obqcoeffmk0NF15WSBwSs7zwVA2bAbTaE0SJ7P0WNGoJn8uE7fiaV5a7dKYIJriEqOrmA==",
|
||||||
"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.49.0",
|
"version": "8.50.0",
|
||||||
"resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-8.49.0.tgz",
|
"resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-8.50.0.tgz",
|
||||||
"integrity": "sha512-JXij0vzIaTtCwu6SxTh8qBc66kmf1xs7pI4UOiMDFVct6q86G0Zs7KRcEoJgY3Cav3x5Tq0MF5jwgpgLqgKG3A==",
|
"integrity": "sha512-O7QnmOXYKVtPrfYzMolrCTfkezCJS9+ljLdKW/+DCvRsc3UAz+sbH6Xcsv7p30+0OwUbeWfUDAQE0vpabZ3QLg==",
|
||||||
"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.49.0",
|
"@typescript-eslint/scope-manager": "8.50.0",
|
||||||
"@typescript-eslint/type-utils": "8.49.0",
|
"@typescript-eslint/type-utils": "8.50.0",
|
||||||
"@typescript-eslint/utils": "8.49.0",
|
"@typescript-eslint/utils": "8.50.0",
|
||||||
"@typescript-eslint/visitor-keys": "8.49.0",
|
"@typescript-eslint/visitor-keys": "8.50.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.49.0",
|
"@typescript-eslint/parser": "^8.50.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.49.0",
|
"version": "8.50.0",
|
||||||
"resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-8.49.0.tgz",
|
"resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-8.50.0.tgz",
|
||||||
"integrity": "sha512-N9lBGA9o9aqb1hVMc9hzySbhKibHmB+N3IpoShyV6HyQYRGIhlrO5rQgttypi+yEeKsKI4idxC8Jw6gXKD4THA==",
|
"integrity": "sha512-6/cmF2piao+f6wSxUsJLZjck7OQsYyRtcOZS02k7XINSNlz93v6emM8WutDQSXnroG2xwYlEVHJI+cPA7CPM3Q==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"peer": true,
|
"peer": true,
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@typescript-eslint/scope-manager": "8.49.0",
|
"@typescript-eslint/scope-manager": "8.50.0",
|
||||||
"@typescript-eslint/types": "8.49.0",
|
"@typescript-eslint/types": "8.50.0",
|
||||||
"@typescript-eslint/typescript-estree": "8.49.0",
|
"@typescript-eslint/typescript-estree": "8.50.0",
|
||||||
"@typescript-eslint/visitor-keys": "8.49.0",
|
"@typescript-eslint/visitor-keys": "8.50.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.49.0",
|
"version": "8.50.0",
|
||||||
"resolved": "https://registry.npmjs.org/@typescript-eslint/project-service/-/project-service-8.49.0.tgz",
|
"resolved": "https://registry.npmjs.org/@typescript-eslint/project-service/-/project-service-8.50.0.tgz",
|
||||||
"integrity": "sha512-/wJN0/DKkmRUMXjZUXYZpD1NEQzQAAn9QWfGwo+Ai8gnzqH7tvqS7oNVdTjKqOcPyVIdZdyCMoqN66Ia789e7g==",
|
"integrity": "sha512-Cg/nQcL1BcoTijEWyx4mkVC56r8dj44bFDvBdygifuS20f3OZCHmFbjF34DPSi07kwlFvqfv/xOLnJ5DquxSGQ==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@typescript-eslint/tsconfig-utils": "^8.49.0",
|
"@typescript-eslint/tsconfig-utils": "^8.50.0",
|
||||||
"@typescript-eslint/types": "^8.49.0",
|
"@typescript-eslint/types": "^8.50.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.49.0",
|
"version": "8.50.0",
|
||||||
"resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-8.49.0.tgz",
|
"resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-8.50.0.tgz",
|
||||||
"integrity": "sha512-npgS3zi+/30KSOkXNs0LQXtsg9ekZ8OISAOLGWA/ZOEn0ZH74Ginfl7foziV8DT+D98WfQ5Kopwqb/PZOaIJGg==",
|
"integrity": "sha512-xCwfuCZjhIqy7+HKxBLrDVT5q/iq7XBVBXLn57RTIIpelLtEIZHXAF/Upa3+gaCpeV1NNS5Z9A+ID6jn50VD4A==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@typescript-eslint/types": "8.49.0",
|
"@typescript-eslint/types": "8.50.0",
|
||||||
"@typescript-eslint/visitor-keys": "8.49.0"
|
"@typescript-eslint/visitor-keys": "8.50.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.49.0",
|
"version": "8.50.0",
|
||||||
"resolved": "https://registry.npmjs.org/@typescript-eslint/tsconfig-utils/-/tsconfig-utils-8.49.0.tgz",
|
"resolved": "https://registry.npmjs.org/@typescript-eslint/tsconfig-utils/-/tsconfig-utils-8.50.0.tgz",
|
||||||
"integrity": "sha512-8prixNi1/6nawsRYxet4YOhnbW+W9FK/bQPxsGB1D3ZrDzbJ5FXw5XmzxZv82X3B+ZccuSxo/X8q9nQ+mFecWA==",
|
"integrity": "sha512-vxd3G/ybKTSlm31MOA96gqvrRGv9RJ7LGtZCn2Vrc5htA0zCDvcMqUkifcjrWNNKXHUU3WCkYOzzVSFBd0wa2w==",
|
||||||
"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.49.0",
|
"version": "8.50.0",
|
||||||
"resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-8.49.0.tgz",
|
"resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-8.50.0.tgz",
|
||||||
"integrity": "sha512-KTExJfQ+svY8I10P4HdxKzWsvtVnsuCifU5MvXrRwoP2KOlNZ9ADNEWWsQTJgMxLzS5VLQKDjkCT/YzgsnqmZg==",
|
"integrity": "sha512-7OciHT2lKCewR0mFoBrvZJ4AXTMe/sYOe87289WAViOocEmDjjv8MvIOT2XESuKj9jp8u3SZYUSh89QA4S1kQw==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@typescript-eslint/types": "8.49.0",
|
"@typescript-eslint/types": "8.50.0",
|
||||||
"@typescript-eslint/typescript-estree": "8.49.0",
|
"@typescript-eslint/typescript-estree": "8.50.0",
|
||||||
"@typescript-eslint/utils": "8.49.0",
|
"@typescript-eslint/utils": "8.50.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.49.0",
|
"version": "8.50.0",
|
||||||
"resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-8.49.0.tgz",
|
"resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-8.50.0.tgz",
|
||||||
"integrity": "sha512-e9k/fneezorUo6WShlQpMxXh8/8wfyc+biu6tnAqA81oWrEic0k21RHzP9uqqpyBBeBKu4T+Bsjy9/b8u7obXQ==",
|
"integrity": "sha512-iX1mgmGrXdANhhITbpp2QQM2fGehBse9LbTf0sidWK6yg/NE+uhV5dfU1g6EYPlcReYmkE9QLPq/2irKAmtS9w==",
|
||||||
"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.49.0",
|
"version": "8.50.0",
|
||||||
"resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-8.49.0.tgz",
|
"resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-8.50.0.tgz",
|
||||||
"integrity": "sha512-jrLdRuAbPfPIdYNppHJ/D0wN+wwNfJ32YTAm10eJVsFmrVpXQnDWBn8niCSMlWjvml8jsce5E/O+86IQtTbJWA==",
|
"integrity": "sha512-W7SVAGBR/IX7zm1t70Yujpbk+zdPq/u4soeFSknWFdXIFuWsBGBOUu/Tn/I6KHSKvSh91OiMuaSnYp3mtPt5IQ==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@typescript-eslint/project-service": "8.49.0",
|
"@typescript-eslint/project-service": "8.50.0",
|
||||||
"@typescript-eslint/tsconfig-utils": "8.49.0",
|
"@typescript-eslint/tsconfig-utils": "8.50.0",
|
||||||
"@typescript-eslint/types": "8.49.0",
|
"@typescript-eslint/types": "8.50.0",
|
||||||
"@typescript-eslint/visitor-keys": "8.49.0",
|
"@typescript-eslint/visitor-keys": "8.50.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.49.0",
|
"version": "8.50.0",
|
||||||
"resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-8.49.0.tgz",
|
"resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-8.50.0.tgz",
|
||||||
"integrity": "sha512-N3W7rJw7Rw+z1tRsHZbK395TWSYvufBXumYtEGzypgMUthlg0/hmCImeA8hgO2d2G4pd7ftpxxul2J8OdtdaFA==",
|
"integrity": "sha512-87KgUXET09CRjGCi2Ejxy3PULXna63/bMYv72tCAlDJC3Yqwln0HiFJ3VJMst2+mEtNtZu5oFvX4qJGjKsnAgg==",
|
||||||
"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.49.0",
|
"@typescript-eslint/scope-manager": "8.50.0",
|
||||||
"@typescript-eslint/types": "8.49.0",
|
"@typescript-eslint/types": "8.50.0",
|
||||||
"@typescript-eslint/typescript-estree": "8.49.0"
|
"@typescript-eslint/typescript-estree": "8.50.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.49.0",
|
"version": "8.50.0",
|
||||||
"resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-8.49.0.tgz",
|
"resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-8.50.0.tgz",
|
||||||
"integrity": "sha512-LlKaciDe3GmZFphXIc79THF/YYBugZ7FS1pO581E/edlVVNbZKDy93evqmrfQ9/Y4uN0vVhX4iuchq26mK/iiA==",
|
"integrity": "sha512-Xzmnb58+Db78gT/CCj/PVCvK+zxbnsw6F+O1oheYszJbBSdEjVhQi3C/Xttzxgi/GLmpvOggRs1RFpiJ8+c34Q==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@typescript-eslint/types": "8.49.0",
|
"@typescript-eslint/types": "8.50.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.25.8",
|
"version": "0.27.1",
|
||||||
"resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.25.8.tgz",
|
"resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.27.1.tgz",
|
||||||
"integrity": "sha512-vVC0USHGtMi8+R4Kz8rt6JhEWLxsv9Rnu/lGYbPR8u47B+DCBksq9JarW0zOO7bs37hyOK1l2/oqtbciutL5+Q==",
|
"integrity": "sha512-yY35KZckJJuVVPXpvjgxiCuVEJT67F6zDeVTv4rizyPrfGBUpZQsvmxnN+C371c2esD/hNMjj4tpBhuueLN7aA==",
|
||||||
"hasInstallScript": true,
|
"hasInstallScript": true,
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"bin": {
|
"bin": {
|
||||||
@@ -4081,32 +4081,32 @@
|
|||||||
"node": ">=18"
|
"node": ">=18"
|
||||||
},
|
},
|
||||||
"optionalDependencies": {
|
"optionalDependencies": {
|
||||||
"@esbuild/aix-ppc64": "0.25.8",
|
"@esbuild/aix-ppc64": "0.27.1",
|
||||||
"@esbuild/android-arm": "0.25.8",
|
"@esbuild/android-arm": "0.27.1",
|
||||||
"@esbuild/android-arm64": "0.25.8",
|
"@esbuild/android-arm64": "0.27.1",
|
||||||
"@esbuild/android-x64": "0.25.8",
|
"@esbuild/android-x64": "0.27.1",
|
||||||
"@esbuild/darwin-arm64": "0.25.8",
|
"@esbuild/darwin-arm64": "0.27.1",
|
||||||
"@esbuild/darwin-x64": "0.25.8",
|
"@esbuild/darwin-x64": "0.27.1",
|
||||||
"@esbuild/freebsd-arm64": "0.25.8",
|
"@esbuild/freebsd-arm64": "0.27.1",
|
||||||
"@esbuild/freebsd-x64": "0.25.8",
|
"@esbuild/freebsd-x64": "0.27.1",
|
||||||
"@esbuild/linux-arm": "0.25.8",
|
"@esbuild/linux-arm": "0.27.1",
|
||||||
"@esbuild/linux-arm64": "0.25.8",
|
"@esbuild/linux-arm64": "0.27.1",
|
||||||
"@esbuild/linux-ia32": "0.25.8",
|
"@esbuild/linux-ia32": "0.27.1",
|
||||||
"@esbuild/linux-loong64": "0.25.8",
|
"@esbuild/linux-loong64": "0.27.1",
|
||||||
"@esbuild/linux-mips64el": "0.25.8",
|
"@esbuild/linux-mips64el": "0.27.1",
|
||||||
"@esbuild/linux-ppc64": "0.25.8",
|
"@esbuild/linux-ppc64": "0.27.1",
|
||||||
"@esbuild/linux-riscv64": "0.25.8",
|
"@esbuild/linux-riscv64": "0.27.1",
|
||||||
"@esbuild/linux-s390x": "0.25.8",
|
"@esbuild/linux-s390x": "0.27.1",
|
||||||
"@esbuild/linux-x64": "0.25.8",
|
"@esbuild/linux-x64": "0.27.1",
|
||||||
"@esbuild/netbsd-arm64": "0.25.8",
|
"@esbuild/netbsd-arm64": "0.27.1",
|
||||||
"@esbuild/netbsd-x64": "0.25.8",
|
"@esbuild/netbsd-x64": "0.27.1",
|
||||||
"@esbuild/openbsd-arm64": "0.25.8",
|
"@esbuild/openbsd-arm64": "0.27.1",
|
||||||
"@esbuild/openbsd-x64": "0.25.8",
|
"@esbuild/openbsd-x64": "0.27.1",
|
||||||
"@esbuild/openharmony-arm64": "0.25.8",
|
"@esbuild/openharmony-arm64": "0.27.1",
|
||||||
"@esbuild/sunos-x64": "0.25.8",
|
"@esbuild/sunos-x64": "0.27.1",
|
||||||
"@esbuild/win32-arm64": "0.25.8",
|
"@esbuild/win32-arm64": "0.27.1",
|
||||||
"@esbuild/win32-ia32": "0.25.8",
|
"@esbuild/win32-ia32": "0.27.1",
|
||||||
"@esbuild/win32-x64": "0.25.8"
|
"@esbuild/win32-x64": "0.27.1"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/escalade": {
|
"node_modules/escalade": {
|
||||||
@@ -5754,9 +5754,9 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/lucide-react": {
|
"node_modules/lucide-react": {
|
||||||
"version": "0.560.0",
|
"version": "0.561.0",
|
||||||
"resolved": "https://registry.npmjs.org/lucide-react/-/lucide-react-0.560.0.tgz",
|
"resolved": "https://registry.npmjs.org/lucide-react/-/lucide-react-0.561.0.tgz",
|
||||||
"integrity": "sha512-NwKoUA/aBShsdL8WE5lukV2F/tjHzQRlonQs7fkNGI1sCT0Ay4a9Ap3ST2clUUkcY+9eQ0pBe2hybTQd2fmyDA==",
|
"integrity": "sha512-Y59gMY38tl4/i0qewcqohPdEbieBy7SovpBL9IFebhc2mDd8x4PZSOsiFRkpPcOq6bj1r/mjH/Rk73gSlIJP2A==",
|
||||||
"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.49.0",
|
"version": "8.50.0",
|
||||||
"resolved": "https://registry.npmjs.org/typescript-eslint/-/typescript-eslint-8.49.0.tgz",
|
"resolved": "https://registry.npmjs.org/typescript-eslint/-/typescript-eslint-8.50.0.tgz",
|
||||||
"integrity": "sha512-zRSVH1WXD0uXczCXw+nsdjGPUdx4dfrs5VQoHnUWmv1U3oNlAKv4FUNdLDhVUg+gYn+a5hUESqch//Rv5wVhrg==",
|
"integrity": "sha512-Q1/6yNUmCpH94fbgMUMg2/BSAr/6U7GBk61kZTv1/asghQOWOjTlp9K8mixS5NcJmm2creY+UFfGeW/+OcA64A==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@typescript-eslint/eslint-plugin": "8.49.0",
|
"@typescript-eslint/eslint-plugin": "8.50.0",
|
||||||
"@typescript-eslint/parser": "8.49.0",
|
"@typescript-eslint/parser": "8.50.0",
|
||||||
"@typescript-eslint/typescript-estree": "8.49.0",
|
"@typescript-eslint/typescript-estree": "8.50.0",
|
||||||
"@typescript-eslint/utils": "8.49.0"
|
"@typescript-eslint/utils": "8.50.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.2.2",
|
"version": "7.3.0",
|
||||||
"resolved": "https://registry.npmjs.org/vite/-/vite-7.2.2.tgz",
|
"resolved": "https://registry.npmjs.org/vite/-/vite-7.3.0.tgz",
|
||||||
"integrity": "sha512-BxAKBWmIbrDgrokdGZH1IgkIk/5mMHDreLDmCJ0qpyJaAteP8NvMhkwr/ZCQNqNH97bw/dANTE9PDzqwJghfMQ==",
|
"integrity": "sha512-dZwN5L1VlUBewiP6H9s2+B3e3Jg96D0vzN+Ry73sOefebhYr9f94wwkMNN/9ouoU8pV1BqA1d1zGk8928cx0rg==",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"peer": true,
|
"peer": true,
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"esbuild": "^0.25.0",
|
"esbuild": "^0.27.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.1.12",
|
"version": "4.2.0",
|
||||||
"resolved": "https://registry.npmjs.org/zod/-/zod-4.1.12.tgz",
|
"resolved": "https://registry.npmjs.org/zod/-/zod-4.2.0.tgz",
|
||||||
"integrity": "sha512-JInaHOamG8pt5+Ey8kGmdcAcg3OL9reK8ltczgHTAwNhMys/6ThXHityHxVV2p3fkw/c+MAvBHFVYHFZDmjMCQ==",
|
"integrity": "sha512-Bd5fw9wlIhtqCCxotZgdTOMwGm1a0u75wARVEY9HMs1X17trvA/lMi4+MGK5EUfYkXVTbX8UDiDKW4OgzHVUZw==",
|
||||||
"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.560.0",
|
"lucide-react": "^0.561.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.1.12"
|
"zod": "^4.2.0"
|
||||||
},
|
},
|
||||||
"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": "^24.10.1",
|
"@types/node": "^25.0.2",
|
||||||
"@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.49.0",
|
"typescript-eslint": "^8.50.0",
|
||||||
"vite": "^7.2.2",
|
"vite": "^7.3.0",
|
||||||
"vitest": "^4.0.8"
|
"vitest": "^4.0.8"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,14 +2,13 @@
|
|||||||
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 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 } from "lucide-react";
|
||||||
import LogsDialog from "@/components/LogDialog";
|
import LogsDialog from "@/components/LogDialog";
|
||||||
import ModelsDialog from "@/components/ModelsDialog";
|
|
||||||
import HealthBadge from "@/components/HealthBadge";
|
import HealthBadge from "@/components/HealthBadge";
|
||||||
import BackendBadge from "@/components/BackendBadge";
|
import BackendBadge from "@/components/BackendBadge";
|
||||||
import { useState, useEffect } from "react";
|
import { useState } from "react";
|
||||||
import { useInstanceHealth } from "@/hooks/useInstanceHealth";
|
import { useInstanceHealth } from "@/hooks/useInstanceHealth";
|
||||||
import { instancesApi, llamaCppApi } from "@/lib/api";
|
import { instancesApi } from "@/lib/api";
|
||||||
|
|
||||||
interface InstanceCardProps {
|
interface InstanceCardProps {
|
||||||
instance: Instance;
|
instance: Instance;
|
||||||
@@ -27,31 +26,9 @@ function InstanceCard({
|
|||||||
editInstance,
|
editInstance,
|
||||||
}: InstanceCardProps) {
|
}: InstanceCardProps) {
|
||||||
const [isLogsOpen, setIsLogsOpen] = useState(false);
|
const [isLogsOpen, setIsLogsOpen] = useState(false);
|
||||||
const [isModelsOpen, setIsModelsOpen] = useState(false);
|
|
||||||
const [showAllActions, setShowAllActions] = useState(false);
|
const [showAllActions, setShowAllActions] = useState(false);
|
||||||
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 isLlamaCpp = instance.options?.backend_type === "llama_cpp";
|
|
||||||
|
|
||||||
// Fetch model count for llama.cpp instances
|
|
||||||
useEffect(() => {
|
|
||||||
if (!isLlamaCpp || !running) {
|
|
||||||
setModelCount(0);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
void (async () => {
|
|
||||||
try {
|
|
||||||
const models = await llamaCppApi.getModels(instance.name);
|
|
||||||
setModelCount(models.length);
|
|
||||||
} catch {
|
|
||||||
setModelCount(0);
|
|
||||||
}
|
|
||||||
})();
|
|
||||||
}, [instance.name, isLlamaCpp, running]);
|
|
||||||
|
|
||||||
const handleStart = () => {
|
const handleStart = () => {
|
||||||
startInstance(instance.name);
|
startInstance(instance.name);
|
||||||
};
|
};
|
||||||
@@ -76,10 +53,6 @@ function InstanceCard({
|
|||||||
setIsLogsOpen(true);
|
setIsLogsOpen(true);
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleModels = () => {
|
|
||||||
setIsModelsOpen(true);
|
|
||||||
};
|
|
||||||
|
|
||||||
const handleExport = () => {
|
const handleExport = () => {
|
||||||
void (async () => {
|
void (async () => {
|
||||||
try {
|
try {
|
||||||
@@ -110,6 +83,8 @@ function InstanceCard({
|
|||||||
})();
|
})();
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const running = instance.status === "running";
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<Card className="hover:shadow-md transition-shadow">
|
<Card className="hover:shadow-md transition-shadow">
|
||||||
@@ -187,20 +162,6 @@ function InstanceCard({
|
|||||||
Logs
|
Logs
|
||||||
</Button>
|
</Button>
|
||||||
|
|
||||||
{isLlamaCpp && modelCount > 1 && (
|
|
||||||
<Button
|
|
||||||
size="sm"
|
|
||||||
variant="outline"
|
|
||||||
onClick={handleModels}
|
|
||||||
title="Manage models"
|
|
||||||
data-testid="manage-models-button"
|
|
||||||
className="flex-1"
|
|
||||||
>
|
|
||||||
<Boxes className="h-4 w-4 mr-1" />
|
|
||||||
Models ({modelCount})
|
|
||||||
</Button>
|
|
||||||
)}
|
|
||||||
|
|
||||||
<Button
|
<Button
|
||||||
size="sm"
|
size="sm"
|
||||||
variant="outline"
|
variant="outline"
|
||||||
@@ -234,13 +195,6 @@ function InstanceCard({
|
|||||||
instanceName={instance.name}
|
instanceName={instance.name}
|
||||||
isRunning={running}
|
isRunning={running}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<ModelsDialog
|
|
||||||
open={isModelsOpen}
|
|
||||||
onOpenChange={setIsModelsOpen}
|
|
||||||
instanceName={instance.name}
|
|
||||||
isRunning={running}
|
|
||||||
/>
|
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,287 +0,0 @@
|
|||||||
import React, { useState, useEffect } from 'react'
|
|
||||||
import { Button } from '@/components/ui/button'
|
|
||||||
import {
|
|
||||||
Dialog,
|
|
||||||
DialogContent,
|
|
||||||
DialogDescription,
|
|
||||||
DialogHeader,
|
|
||||||
DialogTitle,
|
|
||||||
} from '@/components/ui/dialog'
|
|
||||||
import {
|
|
||||||
Table,
|
|
||||||
TableBody,
|
|
||||||
TableCell,
|
|
||||||
TableHead,
|
|
||||||
TableHeader,
|
|
||||||
TableRow,
|
|
||||||
} from '@/components/ui/table'
|
|
||||||
import { Badge } from '@/components/ui/badge'
|
|
||||||
import { llamaCppApi } from '@/lib/api'
|
|
||||||
import { RefreshCw, Loader2, AlertCircle } from 'lucide-react'
|
|
||||||
|
|
||||||
interface ModelsDialogProps {
|
|
||||||
open: boolean
|
|
||||||
onOpenChange: (open: boolean) => void
|
|
||||||
instanceName: string
|
|
||||||
isRunning: boolean
|
|
||||||
}
|
|
||||||
|
|
||||||
interface Model {
|
|
||||||
id: string
|
|
||||||
object: string
|
|
||||||
owned_by: string
|
|
||||||
created: number
|
|
||||||
in_cache: boolean
|
|
||||||
path: string
|
|
||||||
status: {
|
|
||||||
value: string // "loaded" | "loading" | "unloaded"
|
|
||||||
args: string[]
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const StatusIcon: React.FC<{ status: string }> = ({ status }) => {
|
|
||||||
switch (status) {
|
|
||||||
case 'loaded':
|
|
||||||
return (
|
|
||||||
<div className="h-2 w-2 rounded-full bg-green-500" />
|
|
||||||
)
|
|
||||||
case 'loading':
|
|
||||||
return (
|
|
||||||
<Loader2
|
|
||||||
className="h-3 w-3 animate-spin text-yellow-500"
|
|
||||||
/>
|
|
||||||
)
|
|
||||||
case 'unloaded':
|
|
||||||
return (
|
|
||||||
<div className="h-2 w-2 rounded-full bg-gray-400" />
|
|
||||||
)
|
|
||||||
default:
|
|
||||||
return null
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const ModelsDialog: React.FC<ModelsDialogProps> = ({
|
|
||||||
open,
|
|
||||||
onOpenChange,
|
|
||||||
instanceName,
|
|
||||||
isRunning,
|
|
||||||
}) => {
|
|
||||||
const [models, setModels] = useState<Model[]>([])
|
|
||||||
const [loading, setLoading] = useState(false)
|
|
||||||
const [error, setError] = useState<string | null>(null)
|
|
||||||
const [loadingModels, setLoadingModels] = useState<Set<string>>(new Set())
|
|
||||||
|
|
||||||
// Fetch models function
|
|
||||||
const fetchModels = React.useCallback(async () => {
|
|
||||||
if (!instanceName || !isRunning) return
|
|
||||||
|
|
||||||
setLoading(true)
|
|
||||||
setError(null)
|
|
||||||
|
|
||||||
try {
|
|
||||||
const response = await llamaCppApi.getModels(instanceName)
|
|
||||||
setModels(response)
|
|
||||||
} catch (err) {
|
|
||||||
setError(err instanceof Error ? err.message : 'Failed to fetch models')
|
|
||||||
} finally {
|
|
||||||
setLoading(false)
|
|
||||||
}
|
|
||||||
}, [instanceName, isRunning])
|
|
||||||
|
|
||||||
// Poll for models while dialog is open
|
|
||||||
useEffect(() => {
|
|
||||||
if (!open || !isRunning) return
|
|
||||||
|
|
||||||
// Initial fetch
|
|
||||||
void fetchModels()
|
|
||||||
|
|
||||||
// Poll every 2 seconds
|
|
||||||
const interval = setInterval(() => {
|
|
||||||
void fetchModels()
|
|
||||||
}, 2000)
|
|
||||||
|
|
||||||
return () => clearInterval(interval)
|
|
||||||
}, [open, isRunning, fetchModels])
|
|
||||||
|
|
||||||
// Load model
|
|
||||||
const loadModel = async (modelName: string) => {
|
|
||||||
setLoadingModels((prev) => new Set(prev).add(modelName))
|
|
||||||
setError(null)
|
|
||||||
|
|
||||||
try {
|
|
||||||
await llamaCppApi.loadModel(instanceName, modelName)
|
|
||||||
// Polling will pick up the change
|
|
||||||
} catch (err) {
|
|
||||||
setError(err instanceof Error ? err.message : 'Failed to load model')
|
|
||||||
} finally {
|
|
||||||
setLoadingModels((prev) => {
|
|
||||||
const newSet = new Set(prev)
|
|
||||||
newSet.delete(modelName)
|
|
||||||
return newSet
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Unload model
|
|
||||||
const unloadModel = async (modelName: string) => {
|
|
||||||
setLoadingModels((prev) => new Set(prev).add(modelName))
|
|
||||||
setError(null)
|
|
||||||
|
|
||||||
try {
|
|
||||||
await llamaCppApi.unloadModel(instanceName, modelName)
|
|
||||||
// Polling will pick up the change
|
|
||||||
} catch (err) {
|
|
||||||
setError(err instanceof Error ? err.message : 'Failed to unload model')
|
|
||||||
} finally {
|
|
||||||
setLoadingModels((prev) => {
|
|
||||||
const newSet = new Set(prev)
|
|
||||||
newSet.delete(modelName)
|
|
||||||
return newSet
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return (
|
|
||||||
<Dialog open={open} onOpenChange={onOpenChange}>
|
|
||||||
<DialogContent className="sm:max-w-4xl max-w-[calc(100%-2rem)] max-h-[80vh] flex flex-col">
|
|
||||||
<DialogHeader>
|
|
||||||
<div className="flex items-center justify-between">
|
|
||||||
<div>
|
|
||||||
<DialogTitle className="flex items-center gap-2">
|
|
||||||
Models: {instanceName}
|
|
||||||
<Badge variant={isRunning ? 'default' : 'secondary'}>
|
|
||||||
{isRunning ? 'Running' : 'Stopped'}
|
|
||||||
</Badge>
|
|
||||||
</DialogTitle>
|
|
||||||
<DialogDescription>
|
|
||||||
Manage models in this llama.cpp instance
|
|
||||||
</DialogDescription>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<Button
|
|
||||||
variant="outline"
|
|
||||||
size="sm"
|
|
||||||
onClick={() => void fetchModels()}
|
|
||||||
disabled={loading || !isRunning}
|
|
||||||
>
|
|
||||||
{loading ? (
|
|
||||||
<Loader2 className="h-4 w-4 animate-spin" />
|
|
||||||
) : (
|
|
||||||
<RefreshCw className="h-4 w-4" />
|
|
||||||
)}
|
|
||||||
</Button>
|
|
||||||
</div>
|
|
||||||
</DialogHeader>
|
|
||||||
|
|
||||||
{/* Error Display */}
|
|
||||||
{error && (
|
|
||||||
<div className="flex items-center gap-2 p-3 bg-destructive/10 border border-destructive/20 rounded-lg">
|
|
||||||
<AlertCircle className="h-4 w-4 text-destructive" />
|
|
||||||
<span className="text-sm text-destructive">{error}</span>
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
|
|
||||||
{/* Models Table */}
|
|
||||||
<div className="flex-1 flex flex-col min-h-0 overflow-auto">
|
|
||||||
{!isRunning ? (
|
|
||||||
<div className="flex items-center justify-center h-full text-muted-foreground">
|
|
||||||
Instance is not running
|
|
||||||
</div>
|
|
||||||
) : loading && models.length === 0 ? (
|
|
||||||
<div className="flex items-center justify-center h-full">
|
|
||||||
<Loader2 className="h-6 w-6 animate-spin text-muted-foreground" />
|
|
||||||
<span className="ml-2 text-muted-foreground">
|
|
||||||
Loading models...
|
|
||||||
</span>
|
|
||||||
</div>
|
|
||||||
) : models.length === 0 ? (
|
|
||||||
<div className="flex items-center justify-center h-full text-muted-foreground">
|
|
||||||
No models found
|
|
||||||
</div>
|
|
||||||
) : (
|
|
||||||
<Table>
|
|
||||||
<TableHeader>
|
|
||||||
<TableRow>
|
|
||||||
<TableHead>Model</TableHead>
|
|
||||||
<TableHead>Status</TableHead>
|
|
||||||
<TableHead className="text-right">Actions</TableHead>
|
|
||||||
</TableRow>
|
|
||||||
</TableHeader>
|
|
||||||
<TableBody>
|
|
||||||
{models.map((model) => {
|
|
||||||
const isLoading = loadingModels.has(model.id)
|
|
||||||
const isModelLoading = model.status.value === 'loading'
|
|
||||||
|
|
||||||
return (
|
|
||||||
<TableRow key={model.id}>
|
|
||||||
<TableCell className="font-mono text-sm">
|
|
||||||
{model.id}
|
|
||||||
</TableCell>
|
|
||||||
<TableCell>
|
|
||||||
<div className="flex items-center gap-2">
|
|
||||||
<StatusIcon status={model.status.value} />
|
|
||||||
<span className="text-sm capitalize">
|
|
||||||
{model.status.value}
|
|
||||||
</span>
|
|
||||||
</div>
|
|
||||||
</TableCell>
|
|
||||||
<TableCell className="text-right">
|
|
||||||
{model.status.value === 'loaded' ? (
|
|
||||||
<Button
|
|
||||||
size="sm"
|
|
||||||
variant="outline"
|
|
||||||
onClick={() => unloadModel(model.id)}
|
|
||||||
disabled={!isRunning || isLoading || isModelLoading}
|
|
||||||
>
|
|
||||||
{isLoading ? (
|
|
||||||
<>
|
|
||||||
<Loader2 className="h-3 w-3 animate-spin mr-1" />
|
|
||||||
Unloading...
|
|
||||||
</>
|
|
||||||
) : (
|
|
||||||
'Unload'
|
|
||||||
)}
|
|
||||||
</Button>
|
|
||||||
) : model.status.value === 'unloaded' ? (
|
|
||||||
<Button
|
|
||||||
size="sm"
|
|
||||||
variant="default"
|
|
||||||
onClick={() => loadModel(model.id)}
|
|
||||||
disabled={!isRunning || isLoading || isModelLoading}
|
|
||||||
>
|
|
||||||
{isLoading ? (
|
|
||||||
<>
|
|
||||||
<Loader2 className="h-3 w-3 animate-spin mr-1" />
|
|
||||||
Loading...
|
|
||||||
</>
|
|
||||||
) : (
|
|
||||||
'Load'
|
|
||||||
)}
|
|
||||||
</Button>
|
|
||||||
) : (
|
|
||||||
<Button size="sm" variant="ghost" disabled>
|
|
||||||
Loading...
|
|
||||||
</Button>
|
|
||||||
)}
|
|
||||||
</TableCell>
|
|
||||||
</TableRow>
|
|
||||||
)
|
|
||||||
})}
|
|
||||||
</TableBody>
|
|
||||||
</Table>
|
|
||||||
)}
|
|
||||||
</div>
|
|
||||||
|
|
||||||
{/* Auto-refresh indicator */}
|
|
||||||
{isRunning && (
|
|
||||||
<div className="flex items-center gap-2 text-sm text-muted-foreground">
|
|
||||||
<div className="w-2 h-2 bg-green-500 rounded-full animate-pulse"></div>
|
|
||||||
Auto-refreshing every 2 seconds
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
</DialogContent>
|
|
||||||
</Dialog>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
export default ModelsDialog
|
|
||||||
@@ -1,117 +0,0 @@
|
|||||||
import * as React from "react"
|
|
||||||
|
|
||||||
import { cn } from "@/lib/utils"
|
|
||||||
|
|
||||||
const Table = React.forwardRef<
|
|
||||||
HTMLTableElement,
|
|
||||||
React.HTMLAttributes<HTMLTableElement>
|
|
||||||
>(({ className, ...props }, ref) => (
|
|
||||||
<div className="relative w-full overflow-auto">
|
|
||||||
<table
|
|
||||||
ref={ref}
|
|
||||||
className={cn("w-full caption-bottom text-sm", className)}
|
|
||||||
{...props}
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
))
|
|
||||||
Table.displayName = "Table"
|
|
||||||
|
|
||||||
const TableHeader = React.forwardRef<
|
|
||||||
HTMLTableSectionElement,
|
|
||||||
React.HTMLAttributes<HTMLTableSectionElement>
|
|
||||||
>(({ className, ...props }, ref) => (
|
|
||||||
<thead ref={ref} className={cn("[&_tr]:border-b", className)} {...props} />
|
|
||||||
))
|
|
||||||
TableHeader.displayName = "TableHeader"
|
|
||||||
|
|
||||||
const TableBody = React.forwardRef<
|
|
||||||
HTMLTableSectionElement,
|
|
||||||
React.HTMLAttributes<HTMLTableSectionElement>
|
|
||||||
>(({ className, ...props }, ref) => (
|
|
||||||
<tbody
|
|
||||||
ref={ref}
|
|
||||||
className={cn("[&_tr:last-child]:border-0", className)}
|
|
||||||
{...props}
|
|
||||||
/>
|
|
||||||
))
|
|
||||||
TableBody.displayName = "TableBody"
|
|
||||||
|
|
||||||
const TableFooter = React.forwardRef<
|
|
||||||
HTMLTableSectionElement,
|
|
||||||
React.HTMLAttributes<HTMLTableSectionElement>
|
|
||||||
>(({ className, ...props }, ref) => (
|
|
||||||
<tfoot
|
|
||||||
ref={ref}
|
|
||||||
className={cn(
|
|
||||||
"border-t bg-muted/50 font-medium [&>tr]:last:border-b-0",
|
|
||||||
className
|
|
||||||
)}
|
|
||||||
{...props}
|
|
||||||
/>
|
|
||||||
))
|
|
||||||
TableFooter.displayName = "TableFooter"
|
|
||||||
|
|
||||||
const TableRow = React.forwardRef<
|
|
||||||
HTMLTableRowElement,
|
|
||||||
React.HTMLAttributes<HTMLTableRowElement>
|
|
||||||
>(({ className, ...props }, ref) => (
|
|
||||||
<tr
|
|
||||||
ref={ref}
|
|
||||||
className={cn(
|
|
||||||
"border-b transition-colors hover:bg-muted/50 data-[state=selected]:bg-muted",
|
|
||||||
className
|
|
||||||
)}
|
|
||||||
{...props}
|
|
||||||
/>
|
|
||||||
))
|
|
||||||
TableRow.displayName = "TableRow"
|
|
||||||
|
|
||||||
const TableHead = React.forwardRef<
|
|
||||||
HTMLTableCellElement,
|
|
||||||
React.ThHTMLAttributes<HTMLTableCellElement>
|
|
||||||
>(({ className, ...props }, ref) => (
|
|
||||||
<th
|
|
||||||
ref={ref}
|
|
||||||
className={cn(
|
|
||||||
"h-12 px-4 text-left align-middle font-medium text-muted-foreground [&:has([role=checkbox])]:pr-0",
|
|
||||||
className
|
|
||||||
)}
|
|
||||||
{...props}
|
|
||||||
/>
|
|
||||||
))
|
|
||||||
TableHead.displayName = "TableHead"
|
|
||||||
|
|
||||||
const TableCell = React.forwardRef<
|
|
||||||
HTMLTableCellElement,
|
|
||||||
React.TdHTMLAttributes<HTMLTableCellElement>
|
|
||||||
>(({ className, ...props }, ref) => (
|
|
||||||
<td
|
|
||||||
ref={ref}
|
|
||||||
className={cn("p-4 align-middle [&:has([role=checkbox])]:pr-0", className)}
|
|
||||||
{...props}
|
|
||||||
/>
|
|
||||||
))
|
|
||||||
TableCell.displayName = "TableCell"
|
|
||||||
|
|
||||||
const TableCaption = React.forwardRef<
|
|
||||||
HTMLTableCaptionElement,
|
|
||||||
React.HTMLAttributes<HTMLTableCaptionElement>
|
|
||||||
>(({ className, ...props }, ref) => (
|
|
||||||
<caption
|
|
||||||
ref={ref}
|
|
||||||
className={cn("mt-4 text-sm text-muted-foreground", className)}
|
|
||||||
{...props}
|
|
||||||
/>
|
|
||||||
))
|
|
||||||
TableCaption.displayName = "TableCaption"
|
|
||||||
|
|
||||||
export {
|
|
||||||
Table,
|
|
||||||
TableHeader,
|
|
||||||
TableBody,
|
|
||||||
TableFooter,
|
|
||||||
TableHead,
|
|
||||||
TableRow,
|
|
||||||
TableCell,
|
|
||||||
TableCaption,
|
|
||||||
}
|
|
||||||
@@ -205,51 +205,3 @@ export const apiKeysApi = {
|
|||||||
getPermissions: (id: number) =>
|
getPermissions: (id: number) =>
|
||||||
apiCall<KeyPermissionResponse[]>(`/auth/keys/${id}/permissions`),
|
apiCall<KeyPermissionResponse[]>(`/auth/keys/${id}/permissions`),
|
||||||
};
|
};
|
||||||
|
|
||||||
// Llama.cpp model management types
|
|
||||||
export interface Model {
|
|
||||||
id: string;
|
|
||||||
object: string;
|
|
||||||
owned_by: string;
|
|
||||||
created: number;
|
|
||||||
in_cache: boolean;
|
|
||||||
path: string;
|
|
||||||
status: {
|
|
||||||
value: string; // "loaded" | "loading" | "unloaded"
|
|
||||||
args: string[];
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface ModelsListResponse {
|
|
||||||
object: string;
|
|
||||||
data: Model[];
|
|
||||||
}
|
|
||||||
|
|
||||||
// Llama.cpp model management API functions
|
|
||||||
export const llamaCppApi = {
|
|
||||||
// GET /llama-cpp/{name}/models
|
|
||||||
getModels: async (instanceName: string): Promise<Model[]> => {
|
|
||||||
const response = await apiCall<ModelsListResponse>(
|
|
||||||
`/llama-cpp/${encodeURIComponent(instanceName)}/models`
|
|
||||||
);
|
|
||||||
return response.data;
|
|
||||||
},
|
|
||||||
|
|
||||||
// POST /llama-cpp/{name}/models/{model}/load
|
|
||||||
loadModel: (instanceName: string, modelName: string) =>
|
|
||||||
apiCall<{ status: string; message: string }>(
|
|
||||||
`/llama-cpp/${encodeURIComponent(instanceName)}/models/${encodeURIComponent(modelName)}/load`,
|
|
||||||
{
|
|
||||||
method: "POST",
|
|
||||||
}
|
|
||||||
),
|
|
||||||
|
|
||||||
// POST /llama-cpp/{name}/models/{model}/unload
|
|
||||||
unloadModel: (instanceName: string, modelName: string) =>
|
|
||||||
apiCall<{ status: string; message: string }>(
|
|
||||||
`/llama-cpp/${encodeURIComponent(instanceName)}/models/${encodeURIComponent(modelName)}/unload`,
|
|
||||||
{
|
|
||||||
method: "POST",
|
|
||||||
}
|
|
||||||
),
|
|
||||||
};
|
|
||||||
|
|||||||
Reference in New Issue
Block a user