diff --git a/.gitignore b/.gitignore index 1ce9c03db4..7841fabf62 100644 --- a/.gitignore +++ b/.gitignore @@ -5,3 +5,7 @@ **/venv/ **/__pycache__/** private.* + +# Build artifacts +tmp/ +*.log diff --git a/Makefile b/Makefile new file mode 100644 index 0000000000..391fecd269 --- /dev/null +++ b/Makefile @@ -0,0 +1,128 @@ +# Makefile for Bifrost + +# Variables +CONFIG_FILE ?= transports/config.example.json +PORT ?= 8080 +POOL_SIZE ?= 300 +PLUGINS ?= maxim +PROMETHEUS_LABELS ?= + +# Colors for output +RED=\033[0;31m +GREEN=\033[0;32m +YELLOW=\033[1;33m +BLUE=\033[0;34m +CYAN=\033[0;36m +NC=\033[0m # No Color + +.PHONY: help dev dev-ui build run install-air clean test ui-dev ui-build ui-install + +# Default target +help: ## Show this help message + @echo "$(BLUE)Bifrost Development - Available Commands:$(NC)" + @echo "" + @grep -E '^[a-zA-Z_-]+:.*?## .*$$' $(MAKEFILE_LIST) | sort | awk 'BEGIN {FS = ":.*?## "}; {printf " $(GREEN)%-15s$(NC) %s\n", $$1, $$2}' + @echo "" + @echo "$(YELLOW)Environment Variables:$(NC)" + @echo " CONFIG_FILE Path to config file (default: transports/config.example.json)" + @echo " PORT Server port (default: 8080)" + @echo " POOL_SIZE Connection pool size (default: 300)" + @echo " PLUGINS Comma-separated plugins to load (default: maxim)" + @echo " PROMETHEUS_LABELS Labels for Prometheus metrics" + +install-air: ## Install air for hot reloading (if not already installed) + @which air > /dev/null || (echo "$(YELLOW)Installing air for hot reloading...$(NC)" && go install github.com/air-verse/air@latest) + @echo "$(GREEN)Air is ready$(NC)" + +dev: ui-install install-air ## Start complete development environment (UI + API with proxy) + @echo "$(GREEN)Starting Bifrost complete development environment...$(NC)" + @echo "$(YELLOW)This will start:$(NC)" + @echo " 1. UI development server (localhost:3000)" + @echo " 2. API server with UI proxy (localhost:$(PORT)/ui)" + @echo "$(CYAN)Access everything at: http://localhost:$(PORT)/ui$(NC)" + @echo "" + @echo "$(YELLOW)Starting UI development server...$(NC)" + @cd ui && npm run dev & + @sleep 3 + @echo "$(YELLOW)Starting API server with UI proxy...$(NC)" + @cd transports/bifrost-http && BIFROST_UI_DEV=true air -c .air.toml -- \ + -config "../../$(CONFIG_FILE)" \ + -port "$(PORT)" \ + -pool-size $(POOL_SIZE) \ + -plugins "$(PLUGINS)" \ + $(if $(PROMETHEUS_LABELS),-prometheus-labels "$(PROMETHEUS_LABELS)") + +build: ## Build bifrost-http binary + @echo "$(GREEN)Building bifrost-http...$(NC)" + @cd transports/bifrost-http && go build -o ../../tmp/bifrost-http . + @echo "$(GREEN)Built: tmp/bifrost-http$(NC)" + +run: build ## Build and run bifrost-http (no hot reload) + @echo "$(GREEN)Running bifrost-http...$(NC)" + @./tmp/bifrost-http \ + -config "$(CONFIG_FILE)" \ + -port "$(PORT)" \ + -pool-size $(POOL_SIZE) \ + -plugins "$(PLUGINS)" \ + $(if $(PROMETHEUS_LABELS),-prometheus-labels "$(PROMETHEUS_LABELS)") + + +clean: ## Clean build artifacts and temporary files + @echo "$(YELLOW)Cleaning build artifacts...$(NC)" + @rm -rf tmp/ + @rm -f transports/bifrost-http/build-errors.log + @rm -rf transports/bifrost-http/tmp/ + @echo "$(GREEN)Clean complete$(NC)" + +test: ## Run tests for bifrost-http + @echo "$(GREEN)Running bifrost-http tests...$(NC)" + @cd transports/bifrost-http && go test -v ./... + +test-core: ## Run core tests + @echo "$(GREEN)Running core tests...$(NC)" + @cd core && go test -v ./... + +test-plugins: ## Run plugin tests + @echo "$(GREEN)Running plugin tests...$(NC)" + @cd plugins && find . -name "*.go" -path "*/tests/*" -o -name "*_test.go" | head -1 > /dev/null && \ + for dir in $$(find . -name "*_test.go" -exec dirname {} \; | sort -u); do \ + echo "Testing $$dir..."; \ + cd $$dir && go test -v ./... && cd - > /dev/null; \ + done || echo "No plugin tests found" + +test-all: test-core test-plugins test ## Run all tests + +# Quick start with example config +quick-start: ## Quick start with example config and maxim plugin + @echo "$(GREEN)Quick starting Bifrost with example configuration...$(NC)" + @$(MAKE) dev CONFIG_FILE=transports/config.example.json PLUGINS=maxim + +# Docker targets +docker-build: ## Build Docker image + @echo "$(GREEN)Building Docker image...$(NC)" + @cd transports && docker build -t bifrost . + @echo "$(GREEN)Docker image built: bifrost$(NC)" + +docker-run: ## Run Docker container + @echo "$(GREEN)Running Docker container...$(NC)" + @docker run -p $(PORT):$(PORT) \ + -v $(PWD)/$(CONFIG_FILE):/app/config/config.json \ + --env-file <(env | grep -E '^(OPENAI|ANTHROPIC|AZURE|AWS|COHERE|VERTEX)_') \ + bifrost + +# Linting and formatting +lint: ## Run linter for Go code + @echo "$(GREEN)Running golangci-lint...$(NC)" + @golangci-lint run ./... + +fmt: ## Format Go code + @echo "$(GREEN)Formatting Go code...$(NC)" + @gofmt -s -w . + @goimports -w . + +# Git hooks and development setup +setup-git-hooks: ## Set up Git hooks for development + @echo "$(GREEN)Setting up Git hooks...$(NC)" + @echo "#!/bin/sh\nmake fmt\nmake lint" > .git/hooks/pre-commit + @chmod +x .git/hooks/pre-commit + @echo "$(GREEN)Git hooks installed$(NC)" \ No newline at end of file diff --git a/README.md b/README.md index 51bdba6bc5..1a6a44bf57 100644 --- a/README.md +++ b/README.md @@ -169,6 +169,7 @@ For additional HTTP server configuration options, read [this](https://github.com - [ii) OR Using Docker](#ii-or-using-docker) - [B. Using Bifrost as a Go Package](#b-using-bifrost-as-a-go-package) - [📑 Table of Contents](#-table-of-contents) + - [🛠️ Development](#️-development) - [🔍 Overview](#-overview) - [✨ Features](#-features) - [🏗️ Repository Structure](#️-repository-structure) @@ -186,6 +187,52 @@ For additional HTTP server configuration options, read [this](https://github.com --- +## 🛠️ Development + +Bifrost includes a comprehensive development environment with hot reloading and build tools. + +### Quick Start + +```bash +# Set up development environment +make dev-full + +# Complete development environment (UI dev server + API with proxy) +make dev-ui + +# Or start API server only with static UI files +make dev +``` + +### Available Commands + +Use `make help` to see all available commands: + +| Command | Description | +|--------------|--------------------------------------------------| +| `dev` | Start bifrost-http with hot reload using Air | +| `dev-ui` | Start complete development environment (UI + API) | +| `build` | Build bifrost-http binary | +| `run` | Build and run bifrost-http (no hot reload) | +| `test-all` | Run all tests (core, plugins, transports) | +| `ui-build` | Build UI for production (static export) | +| `docker-build` | Build Docker image | +| `lint` | Run Go linter | +| `fmt` | Format Go code | + +### Environment Variables + +- `CONFIG_FILE`: Path to config file (default: `transports/config.example.json`) +- `PORT`: Server port (default: `8080`) +- `PLUGINS`: Comma-separated plugins to load (default: `maxim`) + +**Example:** +```bash +make dev CONFIG_FILE=my-config.json PORT=3000 PLUGINS=maxim,other +``` + +--- + ## 🔍 Overview Bifrost acts as a bridge between your applications and multiple AI providers (OpenAI, Anthropic, Amazon Bedrock, Mistral, Ollama, etc.). It provides a consistent API while handling: @@ -212,6 +259,7 @@ With Bifrost, you can focus on building your AI-powered applications without wor - **MCP Integration**: Built-in Model Context Protocol (MCP) support for external tool integration and execution - **Custom Configuration**: Offers granular control over pool sizes, network retry settings, fallback providers, and network proxy configurations - **Built-in Observability**: Native Prometheus metrics out of the box, no wrappers, no sidecars, just drop it in and scrape +- **Web Interface**: Modern React-based UI for configuration management and monitoring --- @@ -236,6 +284,11 @@ bifrost/ │ ├── bifrost-http/ # HTTP transport implementation │ └── ... │ +├── ui/ # Modern React-based web interface +│ ├── app/ # Next.js 15 application with App Router +│ ├── components/ # Reusable UI components +│ └── ... +│ └── plugins/ # Plugin Implementations ├── maxim/ └── ... diff --git a/transports/README.md b/transports/README.md index dfde0f1c45..11c0e55c31 100644 --- a/transports/README.md +++ b/transports/README.md @@ -8,6 +8,7 @@ This package contains clients for various transports that can be used to spin up - [Bifrost Transports](#bifrost-transports) - [📑 Table of Contents](#-table-of-contents) + - [🛠️ Development](#️-development) - [🚀 Setting Up Transports](#-setting-up-transports) - [Prerequisites](#prerequisites) - [Configuration](#configuration) @@ -26,6 +27,22 @@ This package contains clients for various transports that can be used to spin up --- +## 🛠️ Development + +For development with hot reloading and comprehensive tooling, use the main Makefile at the repository root: + +- **HTTP Transport**: See [`bifrost-http/README.md`](bifrost-http/README.md) for development setup details +- **UI Interface**: See [`../ui/README.md`](../ui/README.md) for React UI development + +Quick start for HTTP transport development: +```bash +cd .. # Go to repository root +make dev-full # Set up environment +make dev # Start with hot reload +``` + +--- + ## 🚀 Setting Up Transports ### Prerequisites diff --git a/transports/bifrost-http/.air.toml b/transports/bifrost-http/.air.toml new file mode 100644 index 0000000000..b480673cb3 --- /dev/null +++ b/transports/bifrost-http/.air.toml @@ -0,0 +1,44 @@ +root = "." +testdata_dir = "testdata" +tmp_dir = "tmp" + +[build] + args_bin = [] + bin = "./tmp/main" + cmd = "go build -o ./tmp/main ." + delay = 1000 + exclude_dir = ["assets", "tmp", "vendor", "testdata", "node_modules", "out", ".git", ".next"] + exclude_file = [] + exclude_regex = ["_test.go"] + exclude_unchanged = false + follow_symlink = false + full_bin = "" + include_dir = [".", "../../core", "../../plugins"] + include_ext = ["go", "tpl", "tmpl", "html"] + include_file = [] + kill_delay = "0s" + log = "build-errors.log" + poll = false + poll_interval = 0 + rerun = false + rerun_delay = 500 + send_interrupt = false + stop_on_root = false + +[color] + app = "" + build = "yellow" + main = "magenta" + runner = "green" + watcher = "cyan" + +[log] + main_only = false + time = false + +[misc] + clean_on_exit = false + +[screen] + clear_on_rebuild = false + keep_scroll = true \ No newline at end of file diff --git a/transports/bifrost-http/.gitignore b/transports/bifrost-http/.gitignore new file mode 100644 index 0000000000..64135e10da --- /dev/null +++ b/transports/bifrost-http/.gitignore @@ -0,0 +1,25 @@ +# Air (hot reload) temporary files +tmp/ +build-errors.log + +# Build artifacts +bifrost-http +main + +# OS generated files +.DS_Store +.DS_Store? +._* +.Spotlight-V100 +.Trashes +ehthumbs.db +Thumbs.db + +# IDE +.vscode/ +.idea/ +*.swp +*.swo + +# Logs +*.log \ No newline at end of file diff --git a/transports/bifrost-http/README.md b/transports/bifrost-http/README.md new file mode 100644 index 0000000000..ed12921fa5 --- /dev/null +++ b/transports/bifrost-http/README.md @@ -0,0 +1,167 @@ +# Bifrost HTTP Transport + +A high-performance HTTP transport for the Bifrost AI provider gateway using FastHTTP. + +## 🛠️ Development + +### Quick Start + +**Note:** The main Makefile is located at the repository root. Run commands from the project root directory. + +1. **Set up the development environment:** + ```bash + cd ../../ # Go to repository root + make dev-full + ``` + +2. **Start with hot reload:** + ```bash + make dev + ``` + +3. **Start complete development environment (UI + API):** + ```bash + make dev-ui + ``` + +### Available Commands + +**From the repository root**, use `make help` to see all available commands. Key commands include: + +| Command | Description | +|--------------|--------------------------------------------------| +| `dev` | Start bifrost-http with hot reload using Air | +| `dev-ui` | Start complete development environment (UI + API) | +| `build` | Build bifrost-http binary | +| `run` | Build and run bifrost-http (no hot reload) | +| `test-all` | Run all tests (core, plugins, transports) | +| `ui-build` | Build UI for production (static export) | +| `docker-build` | Build Docker image | +| `lint` | Run Go linter | + +### Environment Variables + +You can customize the development environment using these variables: + +- `CONFIG_FILE`: Path to config file (default: `transports/config.example.json`) +- `PORT`: Server port (default: `8080`) +- `POOL_SIZE`: Connection pool size (default: `300`) +- `PLUGINS`: Comma-separated plugins to load (default: `maxim`) +- `PROMETHEUS_LABELS`: Labels for Prometheus metrics +- `BIFROST_UI_DEV`: Set to `true` to enable UI development proxy mode + +**Example:** +```bash +make dev CONFIG_FILE=my-config.json PORT=3000 PLUGINS=maxim,other +``` + +### Hot Reload Development + +The `make dev` command uses [Air](https://github.com/air-verse/air) to provide hot reloading. It automatically watches for changes in: + +- `./` - Current bifrost-http directory +- `../../core/` - Core Bifrost functionality +- `../../plugins/` - Plugin implementations + +When files change, Air automatically rebuilds and restarts the server, providing a seamless development experience. + +**Features:** +- 🔥 Hot reload on file changes +- 📁 Watches multiple directories +- 🚫 Excludes test files and build artifacts +- 🎨 Colored output for better visibility +- 📝 Build error logging + +## 🎨 UI Integration + +Bifrost HTTP includes a modern React-based web interface for configuration and monitoring. + +### Development Modes + +**1. Complete Development Environment (Recommended)** +```bash +# Starts both UI dev server and API server with proxy +make dev-ui +``` +- Combined: `http://localhost:8080/ui` (proxies to UI dev server) +- API: `http://localhost:8080/v1/*` + +**2. API-Only Development** +```bash +# Start API server with static UI files +make dev +``` +- API: `http://localhost:8080` +- UI: `http://localhost:8080/ui` (static files) + +### Production Deployment + +```bash +# Build both API and UI +make prod-build + +# Run with static UI files +make run +``` + +The UI will be served at `http://localhost:8080/ui` with static files from `ui/out/`. + +### UI Features + +- **Configuration Management**: JSON-based configuration editor with validation +- **Provider Setup**: Configure multiple AI providers (OpenAI, Anthropic, Azure, etc.) +- **Real-time Monitoring**: View server status and metrics +- **Responsive Design**: Mobile-first design using Shadcn UI components + +## 🚀 Manual Usage + +If you prefer to run without the Makefile: + +### Prerequisites + +- Go 1.23 or higher +- Air for hot reloading: `go install github.com/air-verse/air@latest` + +### Running with Air + +```bash +air -c .air.toml -- -config ../config.example.json -port 8080 +``` + +### Building and Running + +```bash +go build -o tmp/bifrost-http . +./tmp/bifrost-http -config ../config.example.json -port 8080 +``` + +## 📡 API Endpoints + +- `POST /v1/text/completions` - Text completion requests +- `POST /v1/chat/completions` - Chat completion requests +- `POST /v1/mcp/tool/execute` - MCP tool execution +- `GET /metrics` - Prometheus metrics +- `GET /ui` - Web interface (serves React UI) +- `GET /ui/*` - UI static assets and routes + +## 🔧 Configuration + +See the parent directory's `config.example.json` for configuration examples. The HTTP transport supports: + +- Multiple AI providers (OpenAI, Anthropic, Azure, etc.) +- Load balancing and failover +- MCP (Model Context Protocol) integration +- Prometheus monitoring +- Plugin system + +## 🏗️ Architecture + +The HTTP transport provides: + +- **FastHTTP Server**: High-performance HTTP server +- **Provider Integrations**: Native API compatibility for major providers +- **Unified Interface**: OpenAI-compatible API surface +- **Hot Reload**: Development-friendly auto-restart +- **Monitoring**: Built-in Prometheus metrics +- **Plugin Support**: Extensible plugin architecture +- **Web Interface**: Modern React-based UI for configuration and monitoring \ No newline at end of file diff --git a/transports/bifrost-http/bifrost-http-server b/transports/bifrost-http/bifrost-http-server new file mode 100755 index 0000000000..1ba13ab2bd Binary files /dev/null and b/transports/bifrost-http/bifrost-http-server differ diff --git a/transports/bifrost-http/lib/ctx.go b/transports/bifrost-http/lib/ctx.go index 7a2579dda7..21df9156bd 100644 --- a/transports/bifrost-http/lib/ctx.go +++ b/transports/bifrost-http/lib/ctx.go @@ -11,7 +11,7 @@ import ( "strings" "github.com/maximhq/bifrost/plugins/maxim" - "github.com/maximhq/bifrost/transports/bifrost-http/tracking" + "github.com/maximhq/bifrost/transports/bifrost-http/telemetry" "github.com/valyala/fasthttp" ) @@ -49,7 +49,7 @@ func ConvertToBifrostContext(ctx *fasthttp.RequestCtx) *context.Context { if strings.HasPrefix(keyStr, "x-bf-prom-") { labelName := strings.TrimPrefix(keyStr, "x-bf-prom-") - bifrostCtx = context.WithValue(bifrostCtx, tracking.PrometheusContextKey(labelName), string(value)) + bifrostCtx = context.WithValue(bifrostCtx, telemetry.PrometheusContextKey(labelName), string(value)) } if strings.HasPrefix(keyStr, "x-bf-maxim-") { diff --git a/transports/bifrost-http/lib/ui.go b/transports/bifrost-http/lib/ui.go new file mode 100644 index 0000000000..922fd66e01 --- /dev/null +++ b/transports/bifrost-http/lib/ui.go @@ -0,0 +1,260 @@ +package lib + +import ( + "log" + "net" + "net/http" + "net/http/httputil" + "net/url" + "os" + "path/filepath" + "strings" + "time" + + "github.com/valyala/fasthttp" +) + +// UIConfig holds configuration for UI serving +type UIConfig struct { + DevMode bool // Whether to use development proxy + DevServerURL string // URL of the development server (default: http://localhost:3000) + StaticDir string // Directory containing static UI files (default: ../../ui/out) + BasePath string // Base path for UI routes (default: /ui) +} + +// UIHandler handles UI requests, either by serving static files or proxying to dev server +type UIHandler struct { + config UIConfig + proxy *httputil.ReverseProxy + staticDir string +} + +// NewUIHandler creates a new UI handler with the given configuration +func NewUIHandler(config UIConfig) *UIHandler { + // Set defaults + if config.DevServerURL == "" { + config.DevServerURL = "http://localhost:3000" + } + if config.StaticDir == "" { + // Default path when running from bifrost-http directory + config.StaticDir = "../../ui/out" + // Check if running from root directory + if _, err := os.Stat("ui/out"); err == nil { + config.StaticDir = "ui/out" + } + } + if config.BasePath == "" { + config.BasePath = "/ui" + } + + handler := &UIHandler{ + config: config, + } + + // Set up proxy for development mode + if config.DevMode { + target, err := url.Parse(config.DevServerURL) + if err != nil { + log.Printf("Warning: Invalid dev server URL %s: %v", config.DevServerURL, err) + config.DevMode = false + } else { + handler.proxy = httputil.NewSingleHostReverseProxy(target) + log.Printf("UI: Development mode enabled, proxying to %s", config.DevServerURL) + log.Printf("UI: Make sure to start the UI dev server with 'make ui-dev' in another terminal") + } + } + + // Set up static file serving + if !config.DevMode { + // Convert relative path to absolute + absPath, err := filepath.Abs(config.StaticDir) + if err != nil { + log.Printf("Warning: Could not resolve static directory %s: %v", config.StaticDir, err) + handler.staticDir = config.StaticDir + } else { + handler.staticDir = absPath + } + + // Check if static directory exists + if _, err := os.Stat(handler.staticDir); os.IsNotExist(err) { + log.Printf("Warning: Static UI directory does not exist: %s", handler.staticDir) + log.Printf("Run 'make ui-build' to generate static files") + } else { + log.Printf("UI: Production mode enabled, serving static files from %s", handler.staticDir) + } + } + + return handler +} + +// HandleUI handles UI requests using FastHTTP +func (h *UIHandler) HandleUI(ctx *fasthttp.RequestCtx) { + if h.config.DevMode && h.proxy != nil { + h.handleDevProxy(ctx) + } else { + h.handleStaticFiles(ctx) + } +} + +// handleDevProxy proxies requests to the development server +func (h *UIHandler) handleDevProxy(ctx *fasthttp.RequestCtx) { + // First, try to connect to the dev server to check if it's running + target, _ := url.Parse(h.config.DevServerURL) + conn, err := net.DialTimeout("tcp", target.Host, 1*time.Second) + if err != nil { + // Dev server is not running, show helpful error page + h.showDevServerError(ctx) + return + } + conn.Close() + + // Convert FastHTTP request to net/http request + req := &http.Request{ + Method: string(ctx.Method()), + URL: &url.URL{ + Path: string(ctx.Path()), + RawQuery: string(ctx.QueryArgs().QueryString()), + }, + Header: make(http.Header), + Body: nil, + } + + // Copy headers + ctx.Request.Header.VisitAll(func(key, value []byte) { + req.Header.Add(string(key), string(value)) + }) + + // Create a response recorder + recorder := &responseRecorder{ + statusCode: 200, + headers: make(http.Header), + } + + // Proxy the request + h.proxy.ServeHTTP(recorder, req) + + // Copy response back to FastHTTP + ctx.SetStatusCode(recorder.statusCode) + for key, values := range recorder.headers { + for _, value := range values { + ctx.Response.Header.Add(key, value) + } + } + ctx.SetBody(recorder.body) +} + +// showDevServerError displays a helpful error page when the dev server is not running +func (h *UIHandler) showDevServerError(ctx *fasthttp.RequestCtx) { + ctx.SetStatusCode(fasthttp.StatusServiceUnavailable) + ctx.SetContentType("text/html; charset=utf-8") + ctx.SetBodyString(` + + + + UI Development Server Not Running + + + +
+

🚧UI Development Server Not Running

+
+ Error: Cannot connect to UI development server at localhost:3000 +
+ +
+

To fix this issue:

+

Use the integrated development command:

+
make dev-ui
+

This will automatically start both the UI dev server and API server with proxy.

+
+ +

Alternative: If you want to use static files instead of development proxy, use:

+
make dev
+ +

The UI development server should start automatically with make dev-ui.

+
+ +`) +} + +// handleStaticFiles serves static files from the build directory +func (h *UIHandler) handleStaticFiles(ctx *fasthttp.RequestCtx) { + // Extract the filepath parameter from the router + routeFilepath := ctx.UserValue("filepath") + + var path string + if routeFilepath != nil { + path = "/" + routeFilepath.(string) + } else { + // This is the root /ui/ route + path = "/" + } + + // Default to index.html for root path or paths without extension + if path == "" || path == "/" { + path = "/index.html" + } + + // Ensure path starts with / + if !strings.HasPrefix(path, "/") { + path = "/" + path + } + + // Security: prevent directory traversal + if strings.Contains(path, "..") { + ctx.SetStatusCode(fasthttp.StatusBadRequest) + ctx.SetBodyString("Invalid path") + return + } + + // Build full file path + filePath := filepath.Join(h.staticDir, path) + + // Check if file exists + if _, err := os.Stat(filePath); os.IsNotExist(err) { + // For SPA routing, serve index.html for non-API routes + if !strings.HasPrefix(path, "/api/") && !strings.Contains(path, ".") { + filePath = filepath.Join(h.staticDir, "index.html") + } else { + ctx.SetStatusCode(fasthttp.StatusNotFound) + ctx.SetBodyString("File not found") + return + } + } + + // Serve the file + fasthttp.ServeFile(ctx, filePath) +} + +// responseRecorder implements http.ResponseWriter for proxy compatibility +type responseRecorder struct { + statusCode int + headers http.Header + body []byte +} + +func (r *responseRecorder) Header() http.Header { + return r.headers +} + +func (r *responseRecorder) Write(data []byte) (int, error) { + r.body = append(r.body, data...) + return len(data), nil +} + +func (r *responseRecorder) WriteHeader(statusCode int) { + r.statusCode = statusCode +} + +// IsDevModeEnabled checks if development mode should be enabled +// It checks for the presence of BIFROST_UI_DEV environment variable +func IsDevModeEnabled() bool { + return os.Getenv("BIFROST_UI_DEV") == "true" +} \ No newline at end of file diff --git a/transports/bifrost-http/main.go b/transports/bifrost-http/main.go index 8941203767..a40412768a 100644 --- a/transports/bifrost-http/main.go +++ b/transports/bifrost-http/main.go @@ -45,13 +45,13 @@ import ( bifrost "github.com/maximhq/bifrost/core" schemas "github.com/maximhq/bifrost/core/schemas" "github.com/maximhq/bifrost/plugins/maxim" - "github.com/maximhq/bifrost/transports/bifrost-http/integrations" - "github.com/maximhq/bifrost/transports/bifrost-http/integrations/anthropic" - "github.com/maximhq/bifrost/transports/bifrost-http/integrations/genai" - "github.com/maximhq/bifrost/transports/bifrost-http/integrations/litellm" - "github.com/maximhq/bifrost/transports/bifrost-http/integrations/openai" "github.com/maximhq/bifrost/transports/bifrost-http/lib" - "github.com/maximhq/bifrost/transports/bifrost-http/tracking" + "github.com/maximhq/bifrost/transports/bifrost-http/providers" + "github.com/maximhq/bifrost/transports/bifrost-http/providers/anthropic" + "github.com/maximhq/bifrost/transports/bifrost-http/providers/genai" + "github.com/maximhq/bifrost/transports/bifrost-http/providers/litellm" + "github.com/maximhq/bifrost/transports/bifrost-http/providers/openai" + "github.com/maximhq/bifrost/transports/bifrost-http/telemetry" "github.com/prometheus/client_golang/prometheus" "github.com/prometheus/client_golang/prometheus/collectors" "github.com/prometheus/client_golang/prometheus/promhttp" @@ -144,7 +144,7 @@ func main() { registerCollectorSafely(collectors.NewGoCollector()) registerCollectorSafely(collectors.NewProcessCollector(collectors.ProcessCollectorOpts{})) - tracking.InitPrometheusMetrics(prometheusLabels) + telemetry.InitPrometheusMetrics(prometheusLabels) log.Println("Prometheus Go/Process collectors registered.") @@ -179,7 +179,7 @@ func main() { } } - promPlugin := tracking.NewPrometheusPlugin() + promPlugin := telemetry.NewPrometheusPlugin() loadedPlugins = append(loadedPlugins, promPlugin) client, err := bifrost.Init(schemas.BifrostConfig{ @@ -193,9 +193,17 @@ func main() { log.Fatalf("failed to initialize bifrost: %v", err) } + // Initialize UI handler + uiHandler := lib.NewUIHandler(lib.UIConfig{ + DevMode: lib.IsDevModeEnabled(), + DevServerURL: "http://localhost:3000", + StaticDir: "../../ui/out", + BasePath: "/ui", + }) + r := router.New() - extensions := []integrations.ExtensionRouter{ + extensions := []providers.Router{ genai.NewGenAIRouter(client), openai.NewOpenAIRouter(client), anthropic.NewAnthropicRouter(client), @@ -214,6 +222,10 @@ func main() { handleMCPToolExecution(ctx, client) }) + // Register UI routes + r.GET("/ui/{filepath:*}", uiHandler.HandleUI) + r.GET("/ui/", uiHandler.HandleUI) + for _, extension := range extensions { extension.RegisterRoutes(r) } @@ -234,7 +246,7 @@ func main() { r.Handler(ctx) return } - tracking.PrometheusMiddleware(r.Handler)(ctx) + telemetry.PrometheusMiddleware(r.Handler)(ctx) }, } diff --git a/transports/bifrost-http/integrations/anthropic/router.go b/transports/bifrost-http/providers/anthropic/router.go similarity index 84% rename from transports/bifrost-http/integrations/anthropic/router.go rename to transports/bifrost-http/providers/anthropic/router.go index 81d2275997..ad7c14dd15 100644 --- a/transports/bifrost-http/integrations/anthropic/router.go +++ b/transports/bifrost-http/providers/anthropic/router.go @@ -5,18 +5,18 @@ import ( bifrost "github.com/maximhq/bifrost/core" "github.com/maximhq/bifrost/core/schemas" - "github.com/maximhq/bifrost/transports/bifrost-http/integrations" + "github.com/maximhq/bifrost/transports/bifrost-http/providers" ) // AnthropicRouter holds route registrations for Anthropic endpoints. // It supports standard chat completions and image-enabled vision capabilities. type AnthropicRouter struct { - *integrations.GenericRouter + *providers.ProviderRouter } // NewAnthropicRouter creates a new AnthropicRouter with the given bifrost client. func NewAnthropicRouter(client *bifrost.Bifrost) *AnthropicRouter { - routes := []integrations.RouteConfig{ + routes := []providers.RouteConfig{ { Path: "/anthropic/v1/messages", Method: "POST", @@ -36,6 +36,6 @@ func NewAnthropicRouter(client *bifrost.Bifrost) *AnthropicRouter { } return &AnthropicRouter{ - GenericRouter: integrations.NewGenericRouter(client, routes), + ProviderRouter: providers.NewProviderRouter(client, routes), } } diff --git a/transports/bifrost-http/integrations/anthropic/types.go b/transports/bifrost-http/providers/anthropic/types.go similarity index 100% rename from transports/bifrost-http/integrations/anthropic/types.go rename to transports/bifrost-http/providers/anthropic/types.go diff --git a/transports/bifrost-http/integrations/genai/router.go b/transports/bifrost-http/providers/genai/router.go similarity index 91% rename from transports/bifrost-http/integrations/genai/router.go rename to transports/bifrost-http/providers/genai/router.go index 8f0b470a9b..9570278b2d 100644 --- a/transports/bifrost-http/integrations/genai/router.go +++ b/transports/bifrost-http/providers/genai/router.go @@ -7,18 +7,18 @@ import ( bifrost "github.com/maximhq/bifrost/core" "github.com/maximhq/bifrost/core/schemas" - "github.com/maximhq/bifrost/transports/bifrost-http/integrations" + "github.com/maximhq/bifrost/transports/bifrost-http/providers" "github.com/valyala/fasthttp" ) // GenAIRouter holds route registrations for genai endpoints. type GenAIRouter struct { - *integrations.GenericRouter + *providers.ProviderRouter } // NewGenAIRouter creates a new GenAIRouter with the given bifrost client. func NewGenAIRouter(client *bifrost.Bifrost) *GenAIRouter { - routes := []integrations.RouteConfig{ + routes := []providers.RouteConfig{ { Path: "/genai/v1beta/models/{model}", Method: "POST", @@ -39,7 +39,7 @@ func NewGenAIRouter(client *bifrost.Bifrost) *GenAIRouter { } return &GenAIRouter{ - GenericRouter: integrations.NewGenericRouter(client, routes), + ProviderRouter: providers.NewProviderRouter(client, routes), } } diff --git a/transports/bifrost-http/integrations/genai/types.go b/transports/bifrost-http/providers/genai/types.go similarity index 100% rename from transports/bifrost-http/integrations/genai/types.go rename to transports/bifrost-http/providers/genai/types.go diff --git a/transports/bifrost-http/integrations/litellm/router.go b/transports/bifrost-http/providers/litellm/router.go similarity index 88% rename from transports/bifrost-http/integrations/litellm/router.go rename to transports/bifrost-http/providers/litellm/router.go index f8d2c25464..674e387e7c 100644 --- a/transports/bifrost-http/integrations/litellm/router.go +++ b/transports/bifrost-http/providers/litellm/router.go @@ -7,10 +7,10 @@ import ( bifrost "github.com/maximhq/bifrost/core" "github.com/maximhq/bifrost/core/schemas" - "github.com/maximhq/bifrost/transports/bifrost-http/integrations" - "github.com/maximhq/bifrost/transports/bifrost-http/integrations/anthropic" - "github.com/maximhq/bifrost/transports/bifrost-http/integrations/genai" - "github.com/maximhq/bifrost/transports/bifrost-http/integrations/openai" + "github.com/maximhq/bifrost/transports/bifrost-http/providers" + "github.com/maximhq/bifrost/transports/bifrost-http/providers/anthropic" + "github.com/maximhq/bifrost/transports/bifrost-http/providers/genai" + "github.com/maximhq/bifrost/transports/bifrost-http/providers/openai" "github.com/valyala/fasthttp" ) @@ -26,7 +26,7 @@ type LiteLLMRequestWrapper struct { // LiteLLM is fully OpenAI-compatible, so we reuse OpenAI types // with aliases for clarity and minimal LiteLLM-specific extensions type LiteLLMRouter struct { - *integrations.GenericRouter + *providers.ProviderRouter } // NewLiteLLMRouter creates a new LiteLLMRouter with the given bifrost client. @@ -59,7 +59,7 @@ func NewLiteLLMRouter(client *bifrost.Bifrost) *LiteLLMRouter { } // Determine provider from model - provider := integrations.GetProviderFromModel(wrapper.Model) + provider := providers.GetProviderFromModel(wrapper.Model) if !slices.Contains(availableProviders, provider) { return errors.New("unsupported provider: " + string(provider)) } @@ -140,9 +140,9 @@ func NewLiteLLMRouter(client *bifrost.Bifrost) *LiteLLMRouter { } } - routes := []integrations.RouteConfig{} + routes := []providers.RouteConfig{} for _, path := range paths { - routes = append(routes, integrations.RouteConfig{ + routes = append(routes, providers.RouteConfig{ Path: "/litellm" + path, Method: "POST", GetRequestTypeInstance: getRequestTypeInstance, @@ -153,6 +153,6 @@ func NewLiteLLMRouter(client *bifrost.Bifrost) *LiteLLMRouter { } return &LiteLLMRouter{ - GenericRouter: integrations.NewGenericRouter(client, routes), + ProviderRouter: providers.NewProviderRouter(client, routes), } } diff --git a/transports/bifrost-http/integrations/openai/router.go b/transports/bifrost-http/providers/openai/router.go similarity index 83% rename from transports/bifrost-http/integrations/openai/router.go rename to transports/bifrost-http/providers/openai/router.go index 7371781f0b..6661503004 100644 --- a/transports/bifrost-http/integrations/openai/router.go +++ b/transports/bifrost-http/providers/openai/router.go @@ -5,18 +5,18 @@ import ( bifrost "github.com/maximhq/bifrost/core" "github.com/maximhq/bifrost/core/schemas" - "github.com/maximhq/bifrost/transports/bifrost-http/integrations" + "github.com/maximhq/bifrost/transports/bifrost-http/providers" ) // OpenAIRouter holds route registrations for OpenAI endpoints. // It supports standard chat completions and image-enabled vision capabilities. type OpenAIRouter struct { - *integrations.GenericRouter + *providers.ProviderRouter } // NewOpenAIRouter creates a new OpenAIRouter with the given bifrost client. func NewOpenAIRouter(client *bifrost.Bifrost) *OpenAIRouter { - routes := []integrations.RouteConfig{ + routes := []providers.RouteConfig{ { Path: "/openai/chat/completions", Method: "POST", @@ -36,6 +36,6 @@ func NewOpenAIRouter(client *bifrost.Bifrost) *OpenAIRouter { } return &OpenAIRouter{ - GenericRouter: integrations.NewGenericRouter(client, routes), + ProviderRouter: providers.NewProviderRouter(client, routes), } } diff --git a/transports/bifrost-http/integrations/openai/types.go b/transports/bifrost-http/providers/openai/types.go similarity index 100% rename from transports/bifrost-http/integrations/openai/types.go rename to transports/bifrost-http/providers/openai/types.go diff --git a/transports/bifrost-http/integrations/utils.go b/transports/bifrost-http/providers/utils.go similarity index 93% rename from transports/bifrost-http/integrations/utils.go rename to transports/bifrost-http/providers/utils.go index de99c4024c..7d73e24786 100644 --- a/transports/bifrost-http/integrations/utils.go +++ b/transports/bifrost-http/providers/utils.go @@ -1,4 +1,4 @@ -package integrations +package providers import ( "encoding/json" @@ -13,9 +13,9 @@ import ( "github.com/valyala/fasthttp" ) -// ExtensionRouter defines the interface that all integration routers must implement +// Router defines the interface that all integration routers must implement // to register their routes with the main HTTP router. -type ExtensionRouter interface { +type Router interface { RegisterRoutes(r *router.Router) } @@ -49,18 +49,18 @@ type RouteConfig struct { PostCallback PostRequestCallback // Optional: called after request processing } -// GenericRouter provides a reusable router implementation for all integrations. +// ProviderRouter provides a reusable router implementation for all integrations. // It handles the common flow of: parse request → convert to Bifrost → execute → convert response. // Integration-specific logic is handled through the RouteConfig callbacks and converters. -type GenericRouter struct { +type ProviderRouter struct { client *bifrost.Bifrost // Bifrost client for executing requests routes []RouteConfig // List of route configurations } -// NewGenericRouter creates a new generic router with the given bifrost client and route configurations. +// NewProviderRouter creates a new generic router with the given bifrost client and route configurations. // Each integration should create their own routes and pass them to this constructor. -func NewGenericRouter(client *bifrost.Bifrost, routes []RouteConfig) *GenericRouter { - return &GenericRouter{ +func NewProviderRouter(client *bifrost.Bifrost, routes []RouteConfig) *ProviderRouter { + return &ProviderRouter{ client: client, routes: routes, } @@ -68,7 +68,7 @@ func NewGenericRouter(client *bifrost.Bifrost, routes []RouteConfig) *GenericRou // RegisterRoutes registers all configured routes on the given fasthttp router. // This method implements the ExtensionRouter interface. -func (g *GenericRouter) RegisterRoutes(r *router.Router) { +func (g *ProviderRouter) RegisterRoutes(r *router.Router) { for _, route := range g.routes { // Validate route configuration at startup to fail fast if route.GetRequestTypeInstance == nil { @@ -114,7 +114,7 @@ func (g *GenericRouter) RegisterRoutes(r *router.Router) { // 4. Execute the request through Bifrost // 5. Execute post-callback (if configured) for response modification // 6. Convert and send the response using the configured response converter -func (g *GenericRouter) createHandler(config RouteConfig) fasthttp.RequestHandler { +func (g *ProviderRouter) createHandler(config RouteConfig) fasthttp.RequestHandler { return func(ctx *fasthttp.RequestCtx) { // Parse request body into the integration-specific request type // Note: config validation is performed at startup in RegisterRoutes @@ -191,7 +191,7 @@ func (g *GenericRouter) createHandler(config RouteConfig) fasthttp.RequestHandle // sendError sends an error response with the appropriate status code and JSON body. // It handles different error types (string, error interface, or arbitrary objects). -func (g *GenericRouter) sendError(ctx *fasthttp.RequestCtx, err *schemas.BifrostError) { +func (g *ProviderRouter) sendError(ctx *fasthttp.RequestCtx, err *schemas.BifrostError) { if err.StatusCode != nil { ctx.SetStatusCode(*err.StatusCode) } else { @@ -206,7 +206,7 @@ func (g *GenericRouter) sendError(ctx *fasthttp.RequestCtx, err *schemas.Bifrost } // sendSuccess sends a successful response with HTTP 200 status and JSON body. -func (g *GenericRouter) sendSuccess(ctx *fasthttp.RequestCtx, response interface{}) { +func (g *ProviderRouter) sendSuccess(ctx *fasthttp.RequestCtx, response interface{}) { ctx.SetStatusCode(fasthttp.StatusOK) ctx.SetContentType("application/json") diff --git a/transports/bifrost-http/tracking/docker-compose.yml b/transports/bifrost-http/telemetry/docker-compose.yml similarity index 100% rename from transports/bifrost-http/tracking/docker-compose.yml rename to transports/bifrost-http/telemetry/docker-compose.yml diff --git a/transports/bifrost-http/tracking/plugin.go b/transports/bifrost-http/telemetry/plugin.go similarity index 97% rename from transports/bifrost-http/tracking/plugin.go rename to transports/bifrost-http/telemetry/plugin.go index 417d4f5cf6..349143a4dd 100644 --- a/transports/bifrost-http/tracking/plugin.go +++ b/transports/bifrost-http/telemetry/plugin.go @@ -1,7 +1,7 @@ -// Package tracking provides Prometheus metrics collection and monitoring functionality +// Package telemetry provides Prometheus metrics collection and monitoring functionality // for the Bifrost HTTP service. It includes middleware for HTTP request tracking // and a plugin for tracking upstream provider metrics. -package tracking +package telemetry import ( "context" diff --git a/transports/bifrost-http/tracking/prometheus.yml b/transports/bifrost-http/telemetry/prometheus.yml similarity index 100% rename from transports/bifrost-http/tracking/prometheus.yml rename to transports/bifrost-http/telemetry/prometheus.yml diff --git a/transports/bifrost-http/tracking/setup.go b/transports/bifrost-http/telemetry/setup.go similarity index 98% rename from transports/bifrost-http/tracking/setup.go rename to transports/bifrost-http/telemetry/setup.go index c65499981e..2dd85bb162 100644 --- a/transports/bifrost-http/tracking/setup.go +++ b/transports/bifrost-http/telemetry/setup.go @@ -1,7 +1,7 @@ -// Package tracking provides Prometheus metrics collection and monitoring functionality +// Package telemetry provides Prometheus metrics collection and monitoring functionality // for the Bifrost HTTP service. This file contains the setup and configuration // for Prometheus metrics collection, including HTTP middleware and metric definitions. -package tracking +package telemetry import ( "log" diff --git a/transports/bifrost-http/test-config.json b/transports/bifrost-http/test-config.json new file mode 100644 index 0000000000..1efd01ca9c --- /dev/null +++ b/transports/bifrost-http/test-config.json @@ -0,0 +1,21 @@ +{ + "providers": { + "openai": { + "keys": [ + { + "value": "test-key", + "models": ["gpt-3.5-turbo"], + "weight": 1.0 + } + ], + "network_config": { + "default_request_timeout_in_seconds": 30, + "max_retries": 1 + }, + "concurrency_and_buffer_size": { + "concurrency": 1, + "buffer_size": 1 + } + } + } +} \ No newline at end of file diff --git a/transports/bifrost-http/test-simple.json b/transports/bifrost-http/test-simple.json new file mode 100644 index 0000000000..ffc7152303 --- /dev/null +++ b/transports/bifrost-http/test-simple.json @@ -0,0 +1,160 @@ +{ + "providers": { + "openai": { + "keys": [ + { + "value": "env.OPENAI_API_KEY", + "models": [ + "gpt-3.5-turbo", + "gpt-3.5-turbo-preview", + "gpt-4", + "gpt-4o", + "gpt-4o-mini", + "gpt-4-turbo", + "gpt-4-turbo-preview", + "gpt-4-vision-preview" + ], + "weight": 1.0 + } + ], + "network_config": { + "extra_headers": { + "X-Organization-ID": "org-123", + "X-Environment": "production" + }, + "default_request_timeout_in_seconds": 30, + "max_retries": 1, + "retry_backoff_initial_ms": 100, + "retry_backoff_max_ms": 2000 + }, + "concurrency_and_buffer_size": { + "concurrency": 3, + "buffer_size": 10 + } + }, + "anthropic": { + "keys": [ + { + "value": "env.ANTHROPIC_API_KEY", + "models": [ + "claude-2.1", + "claude-3-sonnet-20240229", + "claude-3-haiku-20240307", + "claude-3-opus-20240229", + "claude-3-5-sonnet-20240620", + "claude-3-7-sonnet-20250219" + ], + "weight": 1.0 + } + ], + "network_config": { + "default_request_timeout_in_seconds": 30, + "max_retries": 1, + "retry_backoff_initial_ms": 100, + "retry_backoff_max_ms": 2000 + }, + "concurrency_and_buffer_size": { + "concurrency": 3, + "buffer_size": 10 + } + }, + "bedrock": { + "keys": [ + { + "value": "env.BEDROCK_API_KEY", + "models": [ + "anthropic.claude-v2:1", + "mistral.mixtral-8x7b-instruct-v0:1", + "mistral.mistral-large-2402-v1:0", + "anthropic.claude-3-sonnet-20240229-v1:0" + ], + "weight": 1.0 + } + ], + "network_config": { + "default_request_timeout_in_seconds": 30, + "max_retries": 1, + "retry_backoff_initial_ms": 100, + "retry_backoff_max_ms": 2000 + }, + "meta_config": { + "secret_access_key": "env.AWS_SECRET_ACCESS_KEY", + "region": "us-east-1" + }, + "concurrency_and_buffer_size": { + "concurrency": 3, + "buffer_size": 10 + } + }, + "cohere": { + "keys": [ + { + "value": "env.COHERE_API_KEY", + "models": ["command-a-03-2025"], + "weight": 1.0 + } + ], + "network_config": { + "default_request_timeout_in_seconds": 30, + "max_retries": 1, + "retry_backoff_initial_ms": 100, + "retry_backoff_max_ms": 2000 + }, + "concurrency_and_buffer_size": { + "concurrency": 3, + "buffer_size": 10 + } + }, + "azure": { + "keys": [ + { + "value": "env.AZURE_API_KEY", + "models": ["gpt-4o"], + "weight": 1.0 + } + ], + "network_config": { + "default_request_timeout_in_seconds": 30, + "max_retries": 1, + "retry_backoff_initial_ms": 100, + "retry_backoff_max_ms": 2000 + }, + "meta_config": { + "endpoint": "env.AZURE_ENDPOINT", + "deployments": { + "gpt-4o": "gpt-4o-aug" + }, + "api_version": "2024-08-01-preview" + }, + "concurrency_and_buffer_size": { + "concurrency": 3, + "buffer_size": 10 + } + }, + "vertex": { + "keys": [], + "meta_config": { + "project_id": "env.VERTEX_PROJECT_ID", + "region": "us-central1", + "auth_credentials": "env.VERTEX_CREDENTIALS" + }, + "concurrency_and_buffer_size": { + "concurrency": 3, + "buffer_size": 10 + } + } + }, + "mcp": { + "client_configs": [ + { + "name": "your-mcp-server-name", + "connection_type": "stdio", + "stdio_config": { + "command": "npx", + "args": ["-y", "your-mcp-server-name"], + "envs": ["YOUR_MCP_SERVER_ENV_VAR"] + } + } + ] + } +} diff --git a/ui/.eslintrc.json b/ui/.eslintrc.json new file mode 100644 index 0000000000..efefc73e26 --- /dev/null +++ b/ui/.eslintrc.json @@ -0,0 +1,6 @@ +{ + "extends": ["next/core-web-vitals"], + "rules": { + "react/no-unescaped-entities": "off" + } +} \ No newline at end of file diff --git a/ui/.gitignore b/ui/.gitignore new file mode 100644 index 0000000000..86b92f286e --- /dev/null +++ b/ui/.gitignore @@ -0,0 +1,36 @@ +# See https://help.github.com/articles/ignoring-files/ for more about ignoring files. + +# dependencies +/node_modules +/.pnp +.pnp.js +.yarn/install-state.gz + +# testing +/coverage + +# next.js +/.next/ +/out/ + +# production +/build + +# misc +.DS_Store +*.pem + +# debug +npm-debug.log* +yarn-debug.log* +yarn-error.log* + +# local env files +.env*.local + +# vercel +.vercel + +# typescript +*.tsbuildinfo +next-env.d.ts \ No newline at end of file diff --git a/ui/README.md b/ui/README.md new file mode 100644 index 0000000000..5a87f943e9 --- /dev/null +++ b/ui/README.md @@ -0,0 +1,15 @@ +# Bifrost UI + +Web interface to manage the Bifrost AI provider gateway. + + +## Development + +- `npm run dev` - Start development server +- `npm run build` - Build for production +- `npm run start` - Start production server +- `npm run lint` - Run ESLint + +## Integration with Bifrost HTTP + +The static build output can be served by the Bifrost HTTP transport server. Place the contents of the `out` directory in your web server's static file directory. \ No newline at end of file diff --git a/ui/app/config/page.jsx b/ui/app/config/page.jsx new file mode 100644 index 0000000000..1a7b142d8d --- /dev/null +++ b/ui/app/config/page.jsx @@ -0,0 +1,249 @@ +'use client' + +import { Badge } from '@/components/ui/badge' +import { Button } from '@/components/ui/button' +import { Card, CardContent, CardDescription, CardHeader, CardTitle } from '@/components/ui/card' +import { Textarea } from '@/components/ui/textarea' +import { AlertCircle, CheckCircle, Download, Save, Upload } from 'lucide-react' +import { useState } from 'react' + +const defaultConfig = { + providers: { + openai: { + keys: [ + { + value: 'env.OPENAI_API_KEY', + models: ['gpt-3.5-turbo', 'gpt-4', 'gpt-4o', 'gpt-4o-mini'], + weight: 1.0 + } + ], + network_config: { + default_request_timeout_in_seconds: 30, + max_retries: 1, + retry_backoff_initial_ms: 100, + retry_backoff_max_ms: 2000 + }, + concurrency_and_buffer_size: { + concurrency: 3, + buffer_size: 10 + } + }, + anthropic: { + keys: [ + { + value: 'env.ANTHROPIC_API_KEY', + models: ['claude-3-5-sonnet-20240620', 'claude-3-haiku-20240307'], + weight: 1.0 + } + ], + network_config: { + default_request_timeout_in_seconds: 30, + max_retries: 1, + retry_backoff_initial_ms: 100, + retry_backoff_max_ms: 2000 + }, + concurrency_and_buffer_size: { + concurrency: 3, + buffer_size: 10 + } + } + }, + mcp: { + client_configs: [] + } +} + +export default function ConfigPage () { + const [config, setConfig] = useState(JSON.stringify(defaultConfig, null, 2)) + const [isValid, setIsValid] = useState(true) + const [error, setError] = useState('') + const [saveStatus, setSaveStatus] = useState('') + + const validateConfig = (configString) => { + try { + JSON.parse(configString) + setIsValid(true) + setError('') + return true + } catch (err) { + setIsValid(false) + setError(err.message) + return false + } + } + + const handleConfigChange = (value) => { + setConfig(value) + validateConfig(value) + } + + const handleSave = () => { + if (validateConfig(config)) { + // In a real implementation, this would save to a backend + setSaveStatus('Configuration saved successfully!') + setTimeout(() => setSaveStatus(''), 3000) + } + } + + const handleDownload = () => { + if (validateConfig(config)) { + const blob = new Blob([config], { type: 'application/json' }) + const url = URL.createObjectURL(blob) + const a = document.createElement('a') + a.href = url + a.download = 'bifrost-config.json' + document.body.appendChild(a) + a.click() + document.body.removeChild(a) + URL.revokeObjectURL(url) + } + } + + const handleFileUpload = (event) => { + const file = event.target.files[0] + if (file) { + const reader = new FileReader() + reader.onload = (e) => { + const content = e.target.result + setConfig(content) + validateConfig(content) + } + reader.readAsText(file) + } + } + + const handleReset = () => { + setConfig(JSON.stringify(defaultConfig, null, 2)) + setIsValid(true) + setError('') + } + + return ( +
+
+

Configuration

+

+ Configure your AI providers, API keys, and settings +

+
+ +
+ + +
+
+ JSON Configuration + + Edit your Bifrost configuration in JSON format + +
+
+ {isValid ? ( + + + Valid + + ) : ( + + + Invalid + + )} +
+
+
+ +
+