115 lines
2.7 KiB
Go
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")
|
|
}
|
|
}
|