Files
chorus/portal/web/web.go
T

115 lines
2.7 KiB
Go

package web
import (
"embed"
"fmt"
"html/template"
"io"
"io/fs"
"strings"
"time"
"unicode/utf8"
"git.ilapage.cn/OPC/chorus/internal/core/model"
"git.ilapage.cn/OPC/chorus/portal/service"
)
//go:embed templates/*.html static/* static/vendor/*
var assets embed.FS
type Page struct {
Title string
DisplayName string
CSRFToken string
ReturnTo string
History []model.Generation
Current *service.Detail
MaxPromptBytes int
MaxImages int
}
type Renderer struct{ templates *template.Template }
func NewRenderer() (*Renderer, error) {
functions := template.FuncMap{
"statusLabel": statusLabel, "statusTone": statusTone, "kindLabel": kindLabel, "kindIcon": kindIcon,
"shortPrompt": shortPrompt, "relativeTime": relativeTime, "terminal": func(status model.GenerationStatus) bool { return status.Terminal() },
"value": func(value *string) string {
if value == nil {
return ""
}
return *value
},
}
templates, err := template.New("portal").Funcs(functions).ParseFS(assets, "templates/*.html")
if err != nil {
return nil, fmt.Errorf("parse portal templates: %w", err)
}
return &Renderer{templates: templates}, nil
}
func (r *Renderer) Render(writer io.Writer, name string, data any) error {
return r.templates.ExecuteTemplate(writer, name, data)
}
func Static() (fs.FS, error) { return fs.Sub(assets, "static") }
func statusLabel(status model.GenerationStatus) string {
switch status {
case model.StatusPending:
return "排队中"
case model.StatusRunning:
return "生成中"
case model.StatusSucceeded:
return "已完成"
case model.StatusFailed:
return "失败"
default:
return "未知"
}
}
func statusTone(status model.GenerationStatus) string {
switch status {
case model.StatusPending:
return "pending"
case model.StatusRunning:
return "running"
case model.StatusFailed:
return "failed"
default:
return "success"
}
}
func kindLabel(kind model.GenerationKind) string {
if kind == model.KindImage {
return "图片"
}
return "文本"
}
func kindIcon(kind model.GenerationKind) string {
if kind == model.KindImage {
return "image"
}
return "file-text"
}
func shortPrompt(value string) string {
value = strings.TrimSpace(value)
if utf8.RuneCountInString(value) <= 28 {
return value
}
return string([]rune(value)[:28]) + "..."
}
func relativeTime(value time.Time) string {
elapsed := time.Since(value)
switch {
case elapsed < time.Minute:
return "刚刚"
case elapsed < time.Hour:
return fmt.Sprintf("%d 分钟前", int(elapsed.Minutes()))
case elapsed < 24*time.Hour:
return fmt.Sprintf("%d 小时前", int(elapsed.Hours()))
default:
return value.Local().Format("01-02 15:04")
}
}