capsule AI-native Unix-like composition layer

src/server/internal/api/components.go

4,281 bytes · 167 lines · capsule://quake0day/[email protected] raw on github

package api

import (
	"net/http"
	"sort"
	"strings"

	"github.com/cyberverse/server/internal/config"
	"gopkg.in/yaml.v3"
)

type componentOption struct {
	ID        string `json:"id"`
	Name      string `json:"name"`
	Model     string `json:"model"`
	Default   bool   `json:"default"`
	Available bool   `json:"available"`
}

type componentsResponse struct {
	LLM []componentOption `json:"llm"`
	ASR []componentOption `json:"asr"`
	TTS []componentOption `json:"tts"`
}

func (r *Router) handleListComponents(w http.ResponseWriter, req *http.Request) {
	if r.configPath == "" {
		writeJSON(w, http.StatusOK, r.defaultComponentsResponse())
		return
	}

	doc, err := config.ReadYAMLNode(r.configPath)
	if err != nil {
		writeJSON(w, http.StatusInternalServerError, ErrorResponse{Error: err.Error()})
		return
	}

	writeJSON(w, http.StatusOK, componentsResponse{
		LLM: r.componentOptions(doc, "llm", r.pipelineDefault("llm")),
		ASR: r.componentOptions(doc, "asr", r.pipelineDefault("asr")),
		TTS: r.componentOptions(doc, "tts", r.pipelineDefault("tts")),
	})
}

func (r *Router) defaultComponentsResponse() componentsResponse {
	return componentsResponse{
		LLM: []componentOption{{ID: r.pipelineDefault("llm"), Name: displayComponentName(r.pipelineDefault("llm")), Default: true, Available: true}},
		ASR: []componentOption{{ID: r.pipelineDefault("asr"), Name: displayComponentName(r.pipelineDefault("asr")), Default: true, Available: true}},
		TTS: []componentOption{{ID: r.pipelineDefault("tts"), Name: displayComponentName(r.pipelineDefault("tts")), Default: true, Available: true}},
	}
}

func (r *Router) pipelineDefault(category string) string {
	if r != nil && r.cfg != nil {
		switch category {
		case "llm":
			if r.cfg.Pipeline.DefaultLLM != "" {
				return r.cfg.Pipeline.DefaultLLM
			}
		case "asr":
			if r.cfg.Pipeline.DefaultASR != "" {
				return r.cfg.Pipeline.DefaultASR
			}
		case "tts":
			if r.cfg.Pipeline.DefaultTTS != "" {
				return r.cfg.Pipeline.DefaultTTS
			}
		}
	}
	return "qwen"
}

func (r *Router) componentOptions(doc *yaml.Node, category string, fallbackDefault string) []componentOption {
	section, err := config.GetNodeAtPath(doc, "inference."+category)
	if err != nil || section.Kind != yaml.MappingNode {
		return []componentOption{{
			ID:        fallbackDefault,
			Name:      displayComponentName(fallbackDefault),
			Default:   true,
			Available: true,
		}}
	}

	defaultID := fallbackDefault
	if n := mappingValue(section, "default"); n != nil {
		if v := strings.TrimSpace(config.NodeScalarValue(n, true)); v != "" {
			defaultID = v
		}
	}

	options := make([]componentOption, 0)
	for i := 0; i < len(section.Content)-1; i += 2 {
		id := section.Content[i].Value
		if id == "default" {
			continue
		}
		node := section.Content[i+1]
		if node.Kind != yaml.MappingNode {
			continue
		}
		pluginClass := scalarAt(node, "plugin_class")
		if pluginClass == "" {
			continue
		}
		options = append(options, componentOption{
			ID:        id,
			Name:      displayComponentName(id),
			Model:     componentModel(node, category),
			Default:   id == defaultID,
			Available: pluginClass != "",
		})
	}

	sort.SliceStable(options, func(i, j int) bool {
		if options[i].Default != options[j].Default {
			return options[i].Default
		}
		return options[i].ID < options[j].ID
	})
	return options
}

func mappingValue(node *yaml.Node, key string) *yaml.Node {
	if node == nil || node.Kind != yaml.MappingNode {
		return nil
	}
	for i := 0; i < len(node.Content)-1; i += 2 {
		if node.Content[i].Value == key {
			return node.Content[i+1]
		}
	}
	return nil
}

func scalarAt(node *yaml.Node, key string) string {
	child := mappingValue(node, key)
	if child == nil {
		return ""
	}
	return strings.TrimSpace(config.NodeScalarValue(child, true))
}

func componentModel(node *yaml.Node, category string) string {
	for _, key := range []string{"model", "model_size"} {
		if value := scalarAt(node, key); value != "" {
			return value
		}
	}
	return category
}

func displayComponentName(id string) string {
	switch strings.ToLower(strings.TrimSpace(id)) {
	case "qwen":
		return "Qwen"
	case "openai":
		return "OpenAI"
	case "whisper":
		return "Whisper"
	default:
		if id == "" {
			return "Qwen"
		}
		return strings.ToUpper(id[:1]) + id[1:]
	}
}