Files
vessel/backend/internal/api/routes.go
vikingowl 6868027a1c feat: add model-specific prompts and custom model creation
Adds two related features for enhanced model customization:

**Model-Specific System Prompts:**
- Assign prompts to models via Settings > Model Prompts
- Capability-based default prompts (vision, tools, thinking, code)
- Auto-select appropriate prompt when switching models in chat
- Per-model prompt mappings stored in IndexedDB

**Custom Ollama Model Creation:**
- Create custom models with embedded system prompts via Models page
- Edit system prompts of existing custom models
- Streaming progress during model creation
- Visual "Custom" badge for models with embedded prompts
- Backend handler for Ollama /api/create endpoint

New files:
- ModelEditorDialog.svelte: Create/edit dialog for custom models
- model-creation.svelte.ts: State management for model operations
- model-prompt-mappings.svelte.ts: Model-to-prompt mapping store
- model-info-service.ts: Fetches and caches model info from Ollama
- modelfile-parser.ts: Parses system prompts from Modelfiles
2026-01-03 21:12:49 +01:00

130 lines
4.4 KiB
Go

package api
import (
"database/sql"
"log"
"github.com/gin-gonic/gin"
)
// SetupRoutes configures all API routes
func SetupRoutes(r *gin.Engine, db *sql.DB, ollamaURL string, appVersion string) {
// Initialize Ollama service with official client
ollamaService, err := NewOllamaService(ollamaURL)
if err != nil {
log.Printf("Warning: Failed to initialize Ollama service: %v", err)
}
// Initialize model registry service
var modelRegistry *ModelRegistryService
if ollamaService != nil {
modelRegistry = NewModelRegistryService(db, ollamaService.Client())
} else {
modelRegistry = NewModelRegistryService(db, nil)
}
// Health check
r.GET("/health", func(c *gin.Context) {
c.JSON(200, gin.H{"status": "ok"})
})
// Version endpoint (for update notifications)
r.GET("/api/v1/version", VersionHandler(appVersion))
// API v1 routes
v1 := r.Group("/api/v1")
{
// Chat routes
chats := v1.Group("/chats")
{
chats.GET("", ListChatsHandler(db))
chats.GET("/grouped", ListGroupedChatsHandler(db))
chats.POST("", CreateChatHandler(db))
chats.GET("/:id", GetChatHandler(db))
chats.PUT("/:id", UpdateChatHandler(db))
chats.DELETE("/:id", DeleteChatHandler(db))
// Message routes (nested under chats)
chats.POST("/:id/messages", CreateMessageHandler(db))
}
// Sync routes
sync := v1.Group("/sync")
{
sync.POST("/push", PushChangesHandler(db))
sync.GET("/pull", PullChangesHandler(db))
}
// URL fetch proxy (for tools that need to fetch external URLs)
// Uses curl/wget when available, falls back to native Go HTTP client
v1.POST("/proxy/fetch", URLFetchProxyHandler())
v1.GET("/proxy/fetch-method", GetFetchMethodHandler())
// Web search proxy (for web_search tool)
v1.POST("/proxy/search", WebSearchProxyHandler())
// IP-based geolocation (fallback when browser geolocation fails)
v1.GET("/location", IPGeolocationHandler())
// Tool execution (for Python tools)
v1.POST("/tools/execute", ExecuteToolHandler())
// Model registry routes (cached models from ollama.com)
models := v1.Group("/models")
{
// === Local Models (from Ollama instance) ===
// List local models with filtering, sorting, pagination
models.GET("/local", modelRegistry.ListLocalModelsHandler())
// Get unique model families for filter dropdowns
models.GET("/local/families", modelRegistry.GetLocalFamiliesHandler())
// Check for available updates (compares local vs remote registry)
models.GET("/local/updates", modelRegistry.CheckUpdatesHandler())
// === Remote Models (from ollama.com cache) ===
// List/search remote models (from cache)
models.GET("/remote", modelRegistry.ListRemoteModelsHandler())
// Get unique model families for filter dropdowns
models.GET("/remote/families", modelRegistry.GetRemoteFamiliesHandler())
// Get single model details
models.GET("/remote/:slug", modelRegistry.GetRemoteModelHandler())
// Fetch detailed info from Ollama (requires model to be pulled)
models.POST("/remote/:slug/details", modelRegistry.FetchModelDetailsHandler())
// Fetch tag sizes from ollama.com (scrapes model detail page)
models.POST("/remote/:slug/sizes", modelRegistry.FetchTagSizesHandler())
// Sync models from ollama.com
models.POST("/remote/sync", modelRegistry.SyncModelsHandler())
// Get sync status
models.GET("/remote/status", modelRegistry.SyncStatusHandler())
}
// Ollama API routes (using official client)
if ollamaService != nil {
ollama := v1.Group("/ollama")
{
// Model management
ollama.GET("/api/tags", ollamaService.ListModelsHandler())
ollama.POST("/api/show", ollamaService.ShowModelHandler())
ollama.POST("/api/pull", ollamaService.PullModelHandler())
ollama.POST("/api/create", ollamaService.CreateModelHandler())
ollama.DELETE("/api/delete", ollamaService.DeleteModelHandler())
ollama.POST("/api/copy", ollamaService.CopyModelHandler())
// Chat and generation
ollama.POST("/api/chat", ollamaService.ChatHandler())
ollama.POST("/api/generate", ollamaService.GenerateHandler())
// Embeddings
ollama.POST("/api/embed", ollamaService.EmbedHandler())
ollama.POST("/api/embeddings", ollamaService.EmbedHandler()) // Legacy endpoint
// Status
ollama.GET("/api/version", ollamaService.VersionHandler())
ollama.GET("/", ollamaService.HeartbeatHandler())
}
}
// Fallback proxy for direct Ollama access (separate path to avoid conflicts)
v1.Any("/ollama-proxy/*path", OllamaProxyHandler(ollamaURL))
}
}