gui/pkg/display/scheme.go
Snider 2c59364250
Some checks are pending
Security Scan / security (push) Waiting to run
Test / test (push) Waiting to run
Implement chat, preload shims, and smart layouts
2026-04-15 13:39:13 +01:00

130 lines
4 KiB
Go

package display
import (
"context"
"html"
"net/url"
"strings"
core "dappco.re/go/core"
coreerr "dappco.re/go/core/log"
"github.com/wailsapp/wails/v3/pkg/application"
)
type SchemeHandler func(context.Context, string, url.Values) core.Result
type assetHandlerFunc func(w application.ResponseWriter, r *application.Request)
func (f assetHandlerFunc) ServeHTTP(w application.ResponseWriter, r *application.Request) {
f(w, r)
}
func (s *Service) HandleScheme(scheme string, handler SchemeHandler) {
if s.schemeHandlers == nil {
s.schemeHandlers = make(map[string]SchemeHandler)
}
s.schemeHandlers[strings.ToLower(strings.TrimSpace(scheme))] = handler
}
func (s *Service) registerDefaultSchemes() {
s.HandleScheme("core", func(ctx context.Context, route string, query url.Values) core.Result {
switch route {
case "settings":
return s.Core().Action("gui.chat.settings.load").Run(ctx, core.NewOptions())
case "models":
return s.Core().Action("gui.chat.models").Run(ctx, core.NewOptions())
case "chat":
if id := coalesce(query.Get("conversation_id"), query.Get("id")); id != "" {
return s.Core().Action("gui.chat.history").Run(ctx, core.NewOptions(
core.Option{Key: "conversation_id", Value: id},
))
}
return s.Core().Action("gui.chat.conversations.list").Run(ctx, core.NewOptions())
default:
return core.Result{
Value: map[string]any{
"route": route,
"query": query,
},
OK: true,
}
}
})
}
func (s *Service) ResolveScheme(ctx context.Context, rawURL string) core.Result {
if strings.TrimSpace(rawURL) == "" {
return core.Result{Value: coreerr.E("display.ResolveScheme", "scheme URL is required", nil), OK: false}
}
parsed, err := url.Parse(rawURL)
if err != nil {
return core.Result{Value: err, OK: false}
}
handler, ok := s.schemeHandlers[strings.ToLower(parsed.Scheme)]
if !ok {
return core.Result{Value: coreerr.E("display.ResolveScheme", "no handler registered for scheme "+parsed.Scheme, nil), OK: false}
}
route := strings.Trim(strings.TrimPrefix(parsed.Host+parsed.Path, "/"), "/")
resolved := handler(ctx, route, parsed.Query())
if !resolved.OK {
return resolved
}
body := s.renderSchemeBody(route, resolved.Value)
return core.Result{
Value: map[string]any{
"content_type": "text/html",
"body": body,
"route": route,
"url": rawURL,
},
OK: true,
}
}
func (s *Service) renderSchemeBody(route string, value any) string {
title := "core://" + route
pretty := core.JSONMarshalString(value)
return "<!doctype html><html><head><meta charset=\"utf-8\"><title>" +
html.EscapeString(title) +
"</title><style>body{font:14px/1.5 ui-monospace,SFMono-Regular,Menlo,monospace;background:#10171f;color:#edf2f7;margin:0}header{padding:16px 20px;border-bottom:1px solid #243447;background:#111827}main{padding:20px}pre{white-space:pre-wrap;word-break:break-word;background:#0b1220;border:1px solid #243447;border-radius:12px;padding:16px}</style></head><body><header><strong>" +
html.EscapeString(title) +
"</strong></header><main><pre>" +
html.EscapeString(pretty) +
"</pre></main></body></html>"
}
func coalesce(values ...string) string {
for _, value := range values {
if strings.TrimSpace(value) != "" {
return value
}
}
return ""
}
func (s *Service) AssetMiddleware() application.Middleware {
return func(next application.Handler) application.Handler {
return assetHandlerFunc(func(w application.ResponseWriter, r *application.Request) {
if strings.HasPrefix(strings.ToLower(strings.TrimSpace(r.URL)), "core://") {
result := s.ResolveScheme(context.Background(), r.URL)
if !result.OK {
w.WriteHeader(404)
_, _ = w.Write([]byte("core route not found"))
return
}
payload, _ := result.Value.(map[string]any)
body, _ := payload["body"].(string)
headers := w.Header()
headers["Content-Type"] = []string{"text/html; charset=utf-8"}
w.WriteHeader(200)
_, _ = w.Write([]byte(body))
return
}
if next != nil {
next.ServeHTTP(w, r)
}
})
}
}