feat: genasset 离线素材管线(oMLX SVG 生成 + svg2png 打包 + 对比点评回填)
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,435 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"flag"
|
||||
"fmt"
|
||||
"os"
|
||||
"os/exec"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
"sync"
|
||||
|
||||
_ "github.com/gogf/gf/contrib/drivers/sqlite/v2"
|
||||
|
||||
"github.com/gogf/gf/v2/database/gdb"
|
||||
"github.com/gogf/gf/v2/frame/g"
|
||||
"github.com/gogf/gf/v2/os/glog"
|
||||
|
||||
"36wisdom/biz/consts"
|
||||
)
|
||||
|
||||
// genasset:离线生成视觉素材(SVG→PNG 打包入客户端)与对比点评(回填数据库)。
|
||||
// 用法:
|
||||
//
|
||||
// go run ./cmd/genasset --list # 打印生成清单
|
||||
// go run ./cmd/genasset --only=comments # 只生成对比点评
|
||||
// go run ./cmd/genasset --only=visuals --strategy=1 # 只生成第 1 计素材
|
||||
// go run ./cmd/genasset --force # 覆盖已有产物
|
||||
//
|
||||
// 幂等:产物存在即跳过(--force 覆盖);点评按 feedback_pros 是否为空判定。
|
||||
var (
|
||||
ctx = context.Background()
|
||||
genClient omlx
|
||||
assetDir = "workspace/genasset" // SVG 源 + 转换缓存
|
||||
staticDir = "ui-src/static/generated" // 打包入客户端的 PNG
|
||||
)
|
||||
|
||||
type genFlags struct {
|
||||
listOnly bool
|
||||
only string // comments | visuals
|
||||
strategy int // 0 = 全部计策
|
||||
force bool
|
||||
}
|
||||
|
||||
type visualItem struct {
|
||||
kind string // scene | char | prop | strategy
|
||||
key string
|
||||
id int64
|
||||
name string
|
||||
desc string
|
||||
}
|
||||
|
||||
func main() {
|
||||
fl := parseFlags()
|
||||
genClient = omlx{
|
||||
endpoint: g.Cfg().MustGet(ctx, "genasset.endpoint", "http://127.0.0.1:18080").String(),
|
||||
apiKey: g.Cfg().MustGet(ctx, "genasset.api_key", "wenwu901").String(),
|
||||
model: g.Cfg().MustGet(ctx, "genasset.model", "Qwen3.5-9B-MLX-4bit").String(),
|
||||
timeout: g.Cfg().MustGet(ctx, "genasset.timeout", 600).Int(),
|
||||
maxTokens: g.Cfg().MustGet(ctx, "genasset.max_tokens", 24576).Int(),
|
||||
retries: g.Cfg().MustGet(ctx, "genasset.retries", 3).Int(),
|
||||
}
|
||||
if fl.listOnly {
|
||||
printInventory(fl)
|
||||
return
|
||||
}
|
||||
if fl.only == "" || fl.only == "comments" {
|
||||
genComments(fl)
|
||||
}
|
||||
if fl.only == "" || fl.only == "visuals" {
|
||||
genVisuals(fl)
|
||||
}
|
||||
fmt.Println("[genasset] 完成")
|
||||
}
|
||||
|
||||
func parseFlags() genFlags {
|
||||
var fl genFlags
|
||||
flag.BoolVar(&fl.listOnly, "list", false, "只打印生成清单")
|
||||
flag.StringVar(&fl.only, "only", "", "comments | visuals,默认都生成")
|
||||
flag.IntVar(&fl.strategy, "strategy", 0, "只处理指定计策(strategy_id),0=全部")
|
||||
flag.BoolVar(&fl.force, "force", false, "覆盖已存在产物")
|
||||
flag.Parse()
|
||||
return fl
|
||||
}
|
||||
|
||||
// ---------- 清单 ----------
|
||||
|
||||
func printInventory(fl genFlags) {
|
||||
comments := pendingComments(fl)
|
||||
fmt.Printf("待生成点评选项:%d(按节点分组 %d 个请求)\n", len(comments), distinctCount(comments))
|
||||
for _, it := range visualInventory(fl) {
|
||||
fmt.Printf("%-9s %-16s %s\n", it.kind, it.key, it.name)
|
||||
}
|
||||
}
|
||||
|
||||
// ---------- 对比点评 ----------
|
||||
|
||||
func pendingComments(fl genFlags) []gdb.Record {
|
||||
m := g.DB().Model(consts.TableNodeOption).Where("feedback_pros IS NULL OR feedback_pros = ''")
|
||||
if fl.strategy > 0 {
|
||||
_, nodeIDs := strategyScope(fl)
|
||||
m = m.WhereIn("node_id", nodeIDs)
|
||||
}
|
||||
rows, err := m.Ctx(ctx).All()
|
||||
if err != nil {
|
||||
glog.Fatal(ctx, err)
|
||||
}
|
||||
return rows
|
||||
}
|
||||
|
||||
func distinctCount(rows []gdb.Record) int {
|
||||
seen := map[int64]bool{}
|
||||
for _, r := range rows {
|
||||
seen[r["node_id"].Int64()] = true
|
||||
}
|
||||
return len(seen)
|
||||
}
|
||||
|
||||
// strategyScope 返回某计策涉及的 level_ids 与 node_ids(strategy<=0 时返回 nil)
|
||||
func strategyScope(fl genFlags) ([]int64, []int64) {
|
||||
if fl.strategy <= 0 {
|
||||
return nil, nil
|
||||
}
|
||||
var levelIDs []int64
|
||||
arr, err := g.DB().Model(consts.TableLevel).Where("strategy_id", fl.strategy).Array("id")
|
||||
if err != nil {
|
||||
glog.Fatal(ctx, err)
|
||||
}
|
||||
for _, v := range arr {
|
||||
levelIDs = append(levelIDs, v.Int64())
|
||||
}
|
||||
var nodeIDs []int64
|
||||
arr, err = g.DB().Model(consts.TableSceneNode).WhereIn("level_id", levelIDs).Array("id")
|
||||
if err != nil {
|
||||
glog.Fatal(ctx, err)
|
||||
}
|
||||
for _, v := range arr {
|
||||
nodeIDs = append(nodeIDs, v.Int64())
|
||||
}
|
||||
return levelIDs, nodeIDs
|
||||
}
|
||||
|
||||
func genComments(fl genFlags) {
|
||||
rows := pendingComments(fl)
|
||||
if len(rows) == 0 {
|
||||
fmt.Println("[comments] 无待生成选项(全部已回填)")
|
||||
return
|
||||
}
|
||||
byNode := map[int64][]gdb.Record{}
|
||||
var nodeIDs []int64
|
||||
for _, r := range rows {
|
||||
nid := r["node_id"].Int64()
|
||||
if _, ok := byNode[nid]; !ok {
|
||||
nodeIDs = append(nodeIDs, nid)
|
||||
}
|
||||
byNode[nid] = append(byNode[nid], r)
|
||||
}
|
||||
nodeRows, err := g.DB().Model(consts.TableSceneNode).WhereIn("id", nodeIDs).Ctx(ctx).All()
|
||||
if err != nil {
|
||||
glog.Fatal(ctx, err)
|
||||
}
|
||||
nodes := map[int64]gdb.Record{}
|
||||
for _, r := range nodeRows {
|
||||
nodes[r["id"].Int64()] = r
|
||||
}
|
||||
sem := make(chan struct{}, 2) // 本地 9B 模型慢,并发 2 防排队打爆
|
||||
var wg sync.WaitGroup
|
||||
for _, nid := range nodeIDs {
|
||||
wg.Add(1)
|
||||
sem <- struct{}{}
|
||||
go func(nid int64) {
|
||||
defer wg.Done()
|
||||
defer func() { <-sem }()
|
||||
if err := genNodeComments(nid, nodes[nid], byNode[nid]); err != nil {
|
||||
glog.Errorf(ctx, "[comments] node %d 失败: %v", nid, err)
|
||||
}
|
||||
}(nid)
|
||||
}
|
||||
wg.Wait()
|
||||
}
|
||||
|
||||
func genNodeComments(nid int64, node gdb.Record, opts []gdb.Record) error {
|
||||
var lines []string
|
||||
for _, o := range opts {
|
||||
lines = append(lines, fmt.Sprintf("%d. %s", o["id"].Int64(), o["text"].String()))
|
||||
}
|
||||
out, err := genClient.chat(ctx, commentSystem, fmt.Sprintf(commentUser, node["content"].String(), strings.Join(lines, "\n")))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
var resp struct {
|
||||
Comments []struct {
|
||||
OptionID int64 `json:"option_id"`
|
||||
Pros string `json:"pros"`
|
||||
Cons string `json:"cons"`
|
||||
} `json:"comments"`
|
||||
}
|
||||
if err := json.Unmarshal([]byte(out), &resp); err != nil {
|
||||
return fmt.Errorf("点评 JSON 解析失败: %v(输出: %s)", err, truncate(out, 200))
|
||||
}
|
||||
if len(resp.Comments) == 0 {
|
||||
return fmt.Errorf("点评空结果: %s", truncate(out, 200))
|
||||
}
|
||||
for _, c := range resp.Comments {
|
||||
// 注意:Update 的变参是 dataAndWhere,直接 Update(ctx) 会把 ctx 当作 Data 覆盖掉,必须用 .Ctx(ctx)
|
||||
_, err := g.DB().Model(consts.TableNodeOption).
|
||||
Ctx(ctx).
|
||||
Data(g.Map{"feedback_pros": c.Pros, "feedback_cons": c.Cons}).
|
||||
Where("id", c.OptionID).Update()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
fmt.Printf("[comments] ok node %d(%d 个选项)\n", nid, len(resp.Comments))
|
||||
return nil
|
||||
}
|
||||
|
||||
// ---------- 视觉素材 ----------
|
||||
|
||||
func visualInventory(fl genFlags) []visualItem {
|
||||
var items []visualItem
|
||||
items = append(items, elementItems("scene", sceneIDs(fl))...)
|
||||
items = append(items, elementItems("char", charIDs(fl))...)
|
||||
items = append(items, elementItems("prop", propIDs(fl))...)
|
||||
// 计策卡图标
|
||||
m := g.DB().Model(consts.TableStrategy)
|
||||
if fl.strategy > 0 {
|
||||
m = m.Where("id", fl.strategy)
|
||||
}
|
||||
rows, err := m.Ctx(ctx).All()
|
||||
if err != nil {
|
||||
glog.Fatal(ctx, err)
|
||||
}
|
||||
for _, r := range rows {
|
||||
items = append(items, visualItem{
|
||||
kind: "strategy", key: fmt.Sprintf("strategy_%d", r["id"].Int64()),
|
||||
id: r["id"].Int64(), name: r["name"].String(), desc: r["meaning"].String(),
|
||||
})
|
||||
}
|
||||
return items
|
||||
}
|
||||
|
||||
func sceneIDs(fl genFlags) []int64 {
|
||||
m := g.DB().Model(consts.TableLevel).Where("scene_id > 0").Fields("DISTINCT scene_id")
|
||||
if fl.strategy > 0 {
|
||||
m = m.Where("strategy_id", fl.strategy)
|
||||
}
|
||||
var ids []int64
|
||||
arr, err := m.Array("scene_id")
|
||||
if err != nil {
|
||||
glog.Fatal(ctx, err)
|
||||
}
|
||||
for _, v := range arr {
|
||||
ids = append(ids, v.Int64())
|
||||
}
|
||||
return ids
|
||||
}
|
||||
|
||||
func charIDs(fl genFlags) []int64 {
|
||||
m := g.DB().Model(consts.TableSceneNode).Where("character_id > 0").Fields("DISTINCT character_id")
|
||||
if fl.strategy > 0 {
|
||||
levelIDs, _ := strategyScope(fl)
|
||||
m = m.WhereIn("level_id", levelIDs)
|
||||
}
|
||||
var ids []int64
|
||||
arr, err := m.Array("character_id")
|
||||
if err != nil {
|
||||
glog.Fatal(ctx, err)
|
||||
}
|
||||
for _, v := range arr {
|
||||
ids = append(ids, v.Int64())
|
||||
}
|
||||
return ids
|
||||
}
|
||||
|
||||
func propIDs(fl genFlags) []int64 {
|
||||
m := g.DB().Model(consts.TableNodeOption).Where("prop_id > 0").Fields("DISTINCT prop_id")
|
||||
if fl.strategy > 0 {
|
||||
_, nodeIDs := strategyScope(fl)
|
||||
m = m.WhereIn("node_id", nodeIDs)
|
||||
}
|
||||
var ids []int64
|
||||
arr, err := m.Array("prop_id")
|
||||
if err != nil {
|
||||
glog.Fatal(ctx, err)
|
||||
}
|
||||
for _, v := range arr {
|
||||
ids = append(ids, v.Int64())
|
||||
}
|
||||
return ids
|
||||
}
|
||||
|
||||
func elementItems(kind string, ids []int64) []visualItem {
|
||||
if len(ids) == 0 {
|
||||
return nil
|
||||
}
|
||||
rows, err := g.DB().Model(consts.TableElement).WhereIn("id", ids).Ctx(ctx).All()
|
||||
if err != nil {
|
||||
glog.Fatal(ctx, err)
|
||||
}
|
||||
var items []visualItem
|
||||
for _, r := range rows {
|
||||
items = append(items, visualItem{
|
||||
kind: kind, key: fmt.Sprintf("%s_%d", kind, r["id"].Int64()),
|
||||
id: r["id"].Int64(), name: r["name"].String(), desc: r["description"].String(),
|
||||
})
|
||||
}
|
||||
return items
|
||||
}
|
||||
|
||||
func genVisuals(fl genFlags) {
|
||||
items := visualInventory(fl)
|
||||
if len(items) == 0 {
|
||||
fmt.Println("[visuals] 清单为空")
|
||||
return
|
||||
}
|
||||
fmt.Printf("[visuals] 共 %d 个素材\n", len(items))
|
||||
sem := make(chan struct{}, 2)
|
||||
var wg sync.WaitGroup
|
||||
for _, it := range items {
|
||||
wg.Add(1)
|
||||
sem <- struct{}{}
|
||||
go func(it visualItem) {
|
||||
defer wg.Done()
|
||||
defer func() { <-sem }()
|
||||
if err := genOneVisual(fl, it); err != nil {
|
||||
glog.Errorf(ctx, "[visuals] %s %s 失败: %v", it.kind, it.key, err)
|
||||
}
|
||||
}(it)
|
||||
}
|
||||
wg.Wait()
|
||||
// 统一转 PNG(每个 kind 一个目录)
|
||||
for _, kind := range []string{"scene", "char", "prop", "strategy"} {
|
||||
src := filepath.Join(assetDir, kind)
|
||||
if _, err := os.Stat(src); err != nil {
|
||||
continue
|
||||
}
|
||||
if err := svg2png(src, filepath.Join(staticDir, kind)); err != nil {
|
||||
glog.Errorf(ctx, "[visuals] svg2png %s: %v", kind, err)
|
||||
}
|
||||
}
|
||||
// 回填 image 路径(前端按 /static/... 直接引用);PNG 未生成成功的不回填(避免前端 404)
|
||||
for _, it := range items {
|
||||
if !fileExists(filepath.Join(staticDir, it.kind, it.key+".png")) {
|
||||
glog.Errorf(ctx, "[visuals] 回填 %s 跳过:PNG 不存在", it.key)
|
||||
continue
|
||||
}
|
||||
img := pngPath(it)
|
||||
var err error
|
||||
if it.kind == "strategy" {
|
||||
_, err = g.DB().Model(consts.TableStrategy).Ctx(ctx).Data(g.Map{"icon": img}).Where("id", it.id).Update()
|
||||
} else {
|
||||
_, err = g.DB().Model(consts.TableElement).Ctx(ctx).Data(g.Map{"image": img}).Where("id", it.id).Update()
|
||||
}
|
||||
if err != nil {
|
||||
glog.Errorf(ctx, "[visuals] 回填 %s 失败: %v", it.key, err)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func genOneVisual(fl genFlags, it visualItem) error {
|
||||
svgPath := filepath.Join(assetDir, it.kind, it.key+".svg")
|
||||
if !fl.force && fileExists(svgPath) {
|
||||
return nil // 幂等:已有产物跳过
|
||||
}
|
||||
var user string
|
||||
switch it.kind {
|
||||
case "scene":
|
||||
user = fmt.Sprintf("场景名称:%s\n场景说明:%s\n%s", it.name, it.desc, styleRule)
|
||||
case "char":
|
||||
user = fmt.Sprintf("角色名称:%s\n角色说明:%s\n%s", it.name, it.desc, styleRule)
|
||||
case "prop":
|
||||
user = fmt.Sprintf("道具名称:%s\n道具说明:%s\n%s", it.name, it.desc, styleRule)
|
||||
case "strategy":
|
||||
user = fmt.Sprintf("计策名称:%s\n计策释义:%s\n%s", it.name, it.desc, styleRule)
|
||||
}
|
||||
out, err := genClient.chat(ctx, "", user)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
svg, err := extractSVG(out)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if err := os.MkdirAll(filepath.Dir(svgPath), 0o755); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := os.WriteFile(svgPath, []byte(svg), 0o644); err != nil {
|
||||
return err
|
||||
}
|
||||
fmt.Printf("[visuals] ok %s/%s\n", it.kind, it.key)
|
||||
return nil
|
||||
}
|
||||
|
||||
func extractSVG(out string) (string, error) {
|
||||
var box struct {
|
||||
SVG string `json:"svg"`
|
||||
}
|
||||
if err := json.Unmarshal([]byte(out), &box); err != nil {
|
||||
return "", fmt.Errorf("模型输出不是 JSON: %v(输出: %s)", err, truncate(out, 200))
|
||||
}
|
||||
if !strings.Contains(box.SVG, "<svg") {
|
||||
return "", fmt.Errorf("输出缺少 svg 内容: %s", truncate(out, 200))
|
||||
}
|
||||
return box.SVG, nil
|
||||
}
|
||||
|
||||
func pngPath(it visualItem) string {
|
||||
return "/static/generated/" + it.kind + "/" + it.key + ".png"
|
||||
}
|
||||
|
||||
// svg2png 调 ui-src/scripts/svg2png.mjs(sharp)批量转换
|
||||
func svg2png(src, dst string) error {
|
||||
cmd := exec.CommandContext(ctx, "node", "ui-src/scripts/svg2png.mjs", src, dst)
|
||||
out, err := cmd.CombinedOutput()
|
||||
if err != nil {
|
||||
return fmt.Errorf("%s: %v", strings.TrimSpace(string(out)), err)
|
||||
}
|
||||
fmt.Print(string(out))
|
||||
return nil
|
||||
}
|
||||
|
||||
func fileExists(p string) bool {
|
||||
_, err := os.Stat(p)
|
||||
return err == nil
|
||||
}
|
||||
|
||||
func truncate(s string, n int) string {
|
||||
r := []rune(s)
|
||||
if len(r) <= n {
|
||||
return s
|
||||
}
|
||||
return string(r[:n]) + "…"
|
||||
}
|
||||
@@ -0,0 +1,97 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"context"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"io"
|
||||
"net/http"
|
||||
"time"
|
||||
)
|
||||
|
||||
// omlx 调本机 oMLX(OpenAI 兼容 /v1/chat/completions)。
|
||||
// 注意:Qwen3.5 思考链默认开启且思考文本混入 content,必须传 chat_template_kwargs.enable_thinking=false。
|
||||
type omlx struct {
|
||||
endpoint string
|
||||
apiKey string
|
||||
model string
|
||||
timeout int // 秒
|
||||
maxTokens int
|
||||
retries int
|
||||
}
|
||||
|
||||
type chatRequest struct {
|
||||
Model string `json:"model"`
|
||||
Messages []chatMessage `json:"messages"`
|
||||
Stream bool `json:"stream"`
|
||||
MaxTokens int `json:"max_tokens"`
|
||||
ChatTemplateKwargs map[string]any `json:"chat_template_kwargs"`
|
||||
}
|
||||
|
||||
type chatMessage struct {
|
||||
Role string `json:"role"`
|
||||
Content string `json:"content"`
|
||||
}
|
||||
|
||||
type chatResponse struct {
|
||||
Choices []struct {
|
||||
Message struct {
|
||||
Content string `json:"content"`
|
||||
} `json:"message"`
|
||||
} `json:"choices"`
|
||||
}
|
||||
|
||||
func (m *omlx) chat(ctx context.Context, system, user string) (string, error) {
|
||||
payload := chatRequest{
|
||||
Model: m.model,
|
||||
Messages: []chatMessage{
|
||||
{Role: "system", Content: system},
|
||||
{Role: "user", Content: user},
|
||||
},
|
||||
Stream: false,
|
||||
MaxTokens: m.maxTokens,
|
||||
ChatTemplateKwargs: map[string]any{"enable_thinking": false},
|
||||
}
|
||||
buf, err := json.Marshal(payload)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
var lastErr error
|
||||
for i := 0; i <= m.retries; i++ {
|
||||
req, err := http.NewRequestWithContext(ctx, http.MethodPost, m.endpoint+"/v1/chat/completions", bytes.NewReader(buf))
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
req.Header.Set("Content-Type", "application/json")
|
||||
req.Header.Set("Authorization", "Bearer "+m.apiKey)
|
||||
resp, err := (&http.Client{Timeout: time.Duration(m.timeout) * time.Second}).Do(req)
|
||||
if err != nil {
|
||||
lastErr = fmt.Errorf("oMLX 请求失败: %w", err)
|
||||
time.Sleep(2 * time.Second)
|
||||
continue
|
||||
}
|
||||
body, err := io.ReadAll(resp.Body)
|
||||
resp.Body.Close()
|
||||
if err != nil {
|
||||
lastErr = err
|
||||
continue
|
||||
}
|
||||
if resp.StatusCode >= 400 {
|
||||
lastErr = fmt.Errorf("oMLX %d: %s", resp.StatusCode, string(body))
|
||||
time.Sleep(2 * time.Second)
|
||||
continue
|
||||
}
|
||||
var cr chatResponse
|
||||
if err := json.Unmarshal(body, &cr); err != nil {
|
||||
lastErr = fmt.Errorf("oMLX 响应解析失败: %w", err)
|
||||
continue
|
||||
}
|
||||
if len(cr.Choices) == 0 {
|
||||
lastErr = fmt.Errorf("oMLX 空响应")
|
||||
continue
|
||||
}
|
||||
return cr.Choices[0].Message.Content, nil
|
||||
}
|
||||
return "", lastErr
|
||||
}
|
||||
@@ -0,0 +1,35 @@
|
||||
package main
|
||||
|
||||
// styleRule 素材风格约束:简笔卡通、无文字、暖色系、儿童向
|
||||
const styleRule = `要求:简笔卡通风格,粗线条,暖色调(米黄/橙/青绿),画面中禁止出现任何文字,圆润可爱,适合 4-8 岁儿童。
|
||||
只输出 JSON,格式:{"svg": "<svg ...></svg>"}。SVG viewBox="0 0 400 300"(道具图标 0 0 200 200),只用基础形状(rect/circle/path/ellipse/line),不使用外部图片与字体,不用 <text>。`
|
||||
|
||||
const scenePrompt = `你是儿童绘本插画师。为儿童学习游戏绘制一张场景插画。
|
||||
场景名称:%s
|
||||
场景说明:%s
|
||||
%s`
|
||||
|
||||
const charPrompt = `你是儿童绘本插画师。绘制一个 Q 版儿童角色立绘(全身像,头身比 1:2,圆脸大眼)。
|
||||
角色名称:%s
|
||||
角色说明:%s
|
||||
%s`
|
||||
|
||||
const propPrompt = `你是儿童绘本插画师。绘制一个道具图标(单一主体居中,底部浅色圆角底衬)。
|
||||
道具名称:%s
|
||||
道具说明:%s
|
||||
%s`
|
||||
|
||||
const cardPrompt = `你是儿童绘本插画师。绘制一个圆形徽章图标(占满画布,无文字)。
|
||||
计策名称:%s
|
||||
计策释义:%s
|
||||
%s`
|
||||
|
||||
const commentSystem = `你是给 4-8 岁儿童写闯关游戏点评的老师。语言儿童化、口语化、温和,禁止责备,多用"因为/所以"。每条 20-40 字。
|
||||
只输出 JSON:{"comments": [{"option_id": 1, "pros": "选它的好处", "cons": "它的不足,或错过的更好选择"}, ...]},与输入的选项一一对应。`
|
||||
|
||||
const commentUser = `情境:%s
|
||||
|
||||
选项:
|
||||
%s
|
||||
|
||||
请为每个选项写「好处 pros」(选它的收获、为什么好)与「坏处 cons」(它的不足;若它是最佳选择可留空或写小小的代价)。`
|
||||
@@ -15,3 +15,11 @@ pool:
|
||||
auth:
|
||||
secret: "36wisdom-dev-secret-change-in-prod"
|
||||
expire: 604800
|
||||
# 离线素材生成(cmd/genasset,仅开发机运行;本机 oMLX:Qwen3.5-9B-MLX-4bit)
|
||||
genasset:
|
||||
endpoint: http://127.0.0.1:18080
|
||||
api_key: wenwu901 # 本机 oMLX 鉴权 key(rag-local model_config 表)
|
||||
model: Qwen3.5-9B-MLX-4bit
|
||||
timeout: 600 # 本地 ~5 tok/s,单张 SVG 1-3 分钟
|
||||
max_tokens: 24576
|
||||
retries: 3
|
||||
|
||||
Generated
+668
@@ -15,6 +15,7 @@
|
||||
},
|
||||
"devDependencies": {
|
||||
"@dcloudio/vite-plugin-uni": "3.0.0-5020420260813001",
|
||||
"sharp": "^0.35.3",
|
||||
"vite": "5.2.8"
|
||||
}
|
||||
},
|
||||
@@ -2308,6 +2309,17 @@
|
||||
"@jridgewell/sourcemap-codec": "^1.5.5"
|
||||
}
|
||||
},
|
||||
"node_modules/@emnapi/runtime": {
|
||||
"version": "1.11.3",
|
||||
"resolved": "https://registry.npmjs.org/@emnapi/runtime/-/runtime-1.11.3.tgz",
|
||||
"integrity": "sha512-Xz4Tpyki7XyrpbUK1jR1AhdAdaXyhhY4lZ3neLodmhpuWfy2PAQN5B46sAiU4liOXGLkHypn/qU+jvfWSCYYLA==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"dependencies": {
|
||||
"tslib": "^2.4.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@esbuild/aix-ppc64": {
|
||||
"version": "0.20.2",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.20.2.tgz",
|
||||
@@ -2676,6 +2688,581 @@
|
||||
"node": ">=12"
|
||||
}
|
||||
},
|
||||
"node_modules/@img/colour": {
|
||||
"version": "1.1.0",
|
||||
"resolved": "https://registry.npmjs.org/@img/colour/-/colour-1.1.0.tgz",
|
||||
"integrity": "sha512-Td76q7j57o/tLVdgS746cYARfSyxk8iEfRxewL9h4OMzYhbW4TAcppl0mT4eyqXddh6L/jwoM75mo7ixa/pCeQ==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">=18"
|
||||
}
|
||||
},
|
||||
"node_modules/@img/sharp-darwin-arm64": {
|
||||
"version": "0.35.3",
|
||||
"resolved": "https://registry.npmjs.org/@img/sharp-darwin-arm64/-/sharp-darwin-arm64-0.35.3.tgz",
|
||||
"integrity": "sha512-RMnFX7YQsMoh7lWfcM4NEHHymBX/rLuKNPVM84XE9ONPcaSCDgE7CHIHpSgPcO2xcRthgBy1HfNO319mwhIAkg==",
|
||||
"cpu": [
|
||||
"arm64"
|
||||
],
|
||||
"dev": true,
|
||||
"license": "Apache-2.0",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"darwin"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">=20.9.0"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://opencollective.com/libvips"
|
||||
},
|
||||
"optionalDependencies": {
|
||||
"@img/sharp-libvips-darwin-arm64": "1.3.2"
|
||||
}
|
||||
},
|
||||
"node_modules/@img/sharp-darwin-x64": {
|
||||
"version": "0.35.3",
|
||||
"resolved": "https://registry.npmjs.org/@img/sharp-darwin-x64/-/sharp-darwin-x64-0.35.3.tgz",
|
||||
"integrity": "sha512-Xo+5uFBtLN0BKqieTxiFzFPQAUlBbbH5iBKyRX/z1JrbnYsHTfKJnUfL8+p2TPXr1pXqao4eeL4Rl144uDpK9w==",
|
||||
"cpu": [
|
||||
"x64"
|
||||
],
|
||||
"dev": true,
|
||||
"license": "Apache-2.0",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"darwin"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">=20.9.0"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://opencollective.com/libvips"
|
||||
},
|
||||
"optionalDependencies": {
|
||||
"@img/sharp-libvips-darwin-x64": "1.3.2"
|
||||
}
|
||||
},
|
||||
"node_modules/@img/sharp-freebsd-wasm32": {
|
||||
"version": "0.35.3",
|
||||
"resolved": "https://registry.npmjs.org/@img/sharp-freebsd-wasm32/-/sharp-freebsd-wasm32-0.35.3.tgz",
|
||||
"integrity": "sha512-lUxcqWIj2wMQ9BrwNjngcr1gWUr5xgaGThBRqPPalIC2n67Cqj1uPh8NnA/ZhAg8hUbKl+kVHKwgUIwe6ZYPrg==",
|
||||
"dev": true,
|
||||
"license": "Apache-2.0",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"freebsd"
|
||||
],
|
||||
"dependencies": {
|
||||
"@img/sharp-wasm32": "0.35.3"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=20.9.0"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://opencollective.com/libvips"
|
||||
}
|
||||
},
|
||||
"node_modules/@img/sharp-libvips-darwin-arm64": {
|
||||
"version": "1.3.2",
|
||||
"resolved": "https://registry.npmjs.org/@img/sharp-libvips-darwin-arm64/-/sharp-libvips-darwin-arm64-1.3.2.tgz",
|
||||
"integrity": "sha512-9J6ypZFpQBj4YnePGoq/S38w6nz+vqg5WZLrLGY4YuSemdMq47GMLBPO42MzwdGwpg/agZ7xzZcFHa48xlywfg==",
|
||||
"cpu": [
|
||||
"arm64"
|
||||
],
|
||||
"dev": true,
|
||||
"license": "LGPL-3.0-or-later",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"darwin"
|
||||
],
|
||||
"funding": {
|
||||
"url": "https://opencollective.com/libvips"
|
||||
}
|
||||
},
|
||||
"node_modules/@img/sharp-libvips-darwin-x64": {
|
||||
"version": "1.3.2",
|
||||
"resolved": "https://registry.npmjs.org/@img/sharp-libvips-darwin-x64/-/sharp-libvips-darwin-x64-1.3.2.tgz",
|
||||
"integrity": "sha512-m2pW1n6cns9VaubNwsZ+c3CRYjxNQWgJ5gPlnL1nbBcpkBvFm6SCFN5o0psFHI8w9n11NKhFkeEDns98tiqbEw==",
|
||||
"cpu": [
|
||||
"x64"
|
||||
],
|
||||
"dev": true,
|
||||
"license": "LGPL-3.0-or-later",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"darwin"
|
||||
],
|
||||
"funding": {
|
||||
"url": "https://opencollective.com/libvips"
|
||||
}
|
||||
},
|
||||
"node_modules/@img/sharp-libvips-linux-arm": {
|
||||
"version": "1.3.2",
|
||||
"resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-arm/-/sharp-libvips-linux-arm-1.3.2.tgz",
|
||||
"integrity": "sha512-1eMLzy92I4J6rmi4mAT8yC3HxOtniyGELlzGbNMLLeqe052ahFQ0h6LFq+lh5DsDIdYViIDst08abvSbcEdLXQ==",
|
||||
"cpu": [
|
||||
"arm"
|
||||
],
|
||||
"dev": true,
|
||||
"libc": [
|
||||
"glibc"
|
||||
],
|
||||
"license": "LGPL-3.0-or-later",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"linux"
|
||||
],
|
||||
"funding": {
|
||||
"url": "https://opencollective.com/libvips"
|
||||
}
|
||||
},
|
||||
"node_modules/@img/sharp-libvips-linux-arm64": {
|
||||
"version": "1.3.2",
|
||||
"resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-arm64/-/sharp-libvips-linux-arm64-1.3.2.tgz",
|
||||
"integrity": "sha512-dqVSFynCox4C/J8kT16V7SIFAns0IjgLwkvYT7p8LQVmJ5OS5b6tI9IGflxTeuBS//zXeFIUbwt5dwxyZ17cnA==",
|
||||
"cpu": [
|
||||
"arm64"
|
||||
],
|
||||
"dev": true,
|
||||
"libc": [
|
||||
"glibc"
|
||||
],
|
||||
"license": "LGPL-3.0-or-later",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"linux"
|
||||
],
|
||||
"funding": {
|
||||
"url": "https://opencollective.com/libvips"
|
||||
}
|
||||
},
|
||||
"node_modules/@img/sharp-libvips-linux-ppc64": {
|
||||
"version": "1.3.2",
|
||||
"resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-ppc64/-/sharp-libvips-linux-ppc64-1.3.2.tgz",
|
||||
"integrity": "sha512-3z0NHDxD6n5I9gc05U1eW1AyRm+Gznzq3naMrthPNqE6oYykcogW0l/jfpJdjYnuNl8R7yI9pNbE1XiUeyq0Aw==",
|
||||
"cpu": [
|
||||
"ppc64"
|
||||
],
|
||||
"dev": true,
|
||||
"libc": [
|
||||
"glibc"
|
||||
],
|
||||
"license": "LGPL-3.0-or-later",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"linux"
|
||||
],
|
||||
"funding": {
|
||||
"url": "https://opencollective.com/libvips"
|
||||
}
|
||||
},
|
||||
"node_modules/@img/sharp-libvips-linux-riscv64": {
|
||||
"version": "1.3.2",
|
||||
"resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-riscv64/-/sharp-libvips-linux-riscv64-1.3.2.tgz",
|
||||
"integrity": "sha512-bsb4rI+NldGOsXuej2r8OdSS8+zXDVaCWxyWrcv6kneTOlgAHtZABRzBBCwdsPiD90J4myNJuHpg6kA20ImW/w==",
|
||||
"cpu": [
|
||||
"riscv64"
|
||||
],
|
||||
"dev": true,
|
||||
"libc": [
|
||||
"glibc"
|
||||
],
|
||||
"license": "LGPL-3.0-or-later",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"linux"
|
||||
],
|
||||
"funding": {
|
||||
"url": "https://opencollective.com/libvips"
|
||||
}
|
||||
},
|
||||
"node_modules/@img/sharp-libvips-linux-s390x": {
|
||||
"version": "1.3.2",
|
||||
"resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-s390x/-/sharp-libvips-linux-s390x-1.3.2.tgz",
|
||||
"integrity": "sha512-/ABshyj8gCpyIrNXnHn4LorDJ0HHm1VhXPBlxZ8zAtfVPAaSafXPGn+sUSIRiwaSBy0mmFjSjiXI5mkcwdChKQ==",
|
||||
"cpu": [
|
||||
"s390x"
|
||||
],
|
||||
"dev": true,
|
||||
"libc": [
|
||||
"glibc"
|
||||
],
|
||||
"license": "LGPL-3.0-or-later",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"linux"
|
||||
],
|
||||
"funding": {
|
||||
"url": "https://opencollective.com/libvips"
|
||||
}
|
||||
},
|
||||
"node_modules/@img/sharp-libvips-linux-x64": {
|
||||
"version": "1.3.2",
|
||||
"resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-x64/-/sharp-libvips-linux-x64-1.3.2.tgz",
|
||||
"integrity": "sha512-ITPEtgffGJ0S6G9dRyw/366tJQqFRcHWPHhC+Stpg3Z8AEMrDrTr2lhdz4f/Y/HMbRh//7Z5mBzEpVdi62Oc3w==",
|
||||
"cpu": [
|
||||
"x64"
|
||||
],
|
||||
"dev": true,
|
||||
"libc": [
|
||||
"glibc"
|
||||
],
|
||||
"license": "LGPL-3.0-or-later",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"linux"
|
||||
],
|
||||
"funding": {
|
||||
"url": "https://opencollective.com/libvips"
|
||||
}
|
||||
},
|
||||
"node_modules/@img/sharp-libvips-linuxmusl-arm64": {
|
||||
"version": "1.3.2",
|
||||
"resolved": "https://registry.npmjs.org/@img/sharp-libvips-linuxmusl-arm64/-/sharp-libvips-linuxmusl-arm64-1.3.2.tgz",
|
||||
"integrity": "sha512-zE9EdiUzUmg5mDT5a1rk5fYJ6GWPloTwWBYDS14naqHsL+EaMpDj1AWnpLgh3u0YCORv2Tt50wrcrpYqkP97Kw==",
|
||||
"cpu": [
|
||||
"arm64"
|
||||
],
|
||||
"dev": true,
|
||||
"libc": [
|
||||
"musl"
|
||||
],
|
||||
"license": "LGPL-3.0-or-later",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"linux"
|
||||
],
|
||||
"funding": {
|
||||
"url": "https://opencollective.com/libvips"
|
||||
}
|
||||
},
|
||||
"node_modules/@img/sharp-libvips-linuxmusl-x64": {
|
||||
"version": "1.3.2",
|
||||
"resolved": "https://registry.npmjs.org/@img/sharp-libvips-linuxmusl-x64/-/sharp-libvips-linuxmusl-x64-1.3.2.tgz",
|
||||
"integrity": "sha512-m0lrLiUt+lBYnCFr8qV/65yMR4E/c7/wf78I5eKTdkEakFAlZ9QlzEM3QIhhAwVeUhLAHLcCq7a7Vszq/oFNZQ==",
|
||||
"cpu": [
|
||||
"x64"
|
||||
],
|
||||
"dev": true,
|
||||
"libc": [
|
||||
"musl"
|
||||
],
|
||||
"license": "LGPL-3.0-or-later",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"linux"
|
||||
],
|
||||
"funding": {
|
||||
"url": "https://opencollective.com/libvips"
|
||||
}
|
||||
},
|
||||
"node_modules/@img/sharp-linux-arm": {
|
||||
"version": "0.35.3",
|
||||
"resolved": "https://registry.npmjs.org/@img/sharp-linux-arm/-/sharp-linux-arm-0.35.3.tgz",
|
||||
"integrity": "sha512-affVWCTLooy8TSxbDx2qkzuDeaWLNVBA+P//FNBirHsXpP2fuBhk5AuboYUnrDnzoXes8GFjpTx0SBFOCRg+FA==",
|
||||
"cpu": [
|
||||
"arm"
|
||||
],
|
||||
"dev": true,
|
||||
"libc": [
|
||||
"glibc"
|
||||
],
|
||||
"license": "Apache-2.0",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"linux"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">=20.9.0"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://opencollective.com/libvips"
|
||||
},
|
||||
"optionalDependencies": {
|
||||
"@img/sharp-libvips-linux-arm": "1.3.2"
|
||||
}
|
||||
},
|
||||
"node_modules/@img/sharp-linux-arm64": {
|
||||
"version": "0.35.3",
|
||||
"resolved": "https://registry.npmjs.org/@img/sharp-linux-arm64/-/sharp-linux-arm64-0.35.3.tgz",
|
||||
"integrity": "sha512-QgKDspHPnrU+GQ55XPhGwyhC8acLVOOSyAvo1oVfFmrIXLkDNmGWzAfDZ4xK8oSA1qBQrALcHX0G5UZni/SuFQ==",
|
||||
"cpu": [
|
||||
"arm64"
|
||||
],
|
||||
"dev": true,
|
||||
"libc": [
|
||||
"glibc"
|
||||
],
|
||||
"license": "Apache-2.0",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"linux"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">=20.9.0"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://opencollective.com/libvips"
|
||||
},
|
||||
"optionalDependencies": {
|
||||
"@img/sharp-libvips-linux-arm64": "1.3.2"
|
||||
}
|
||||
},
|
||||
"node_modules/@img/sharp-linux-ppc64": {
|
||||
"version": "0.35.3",
|
||||
"resolved": "https://registry.npmjs.org/@img/sharp-linux-ppc64/-/sharp-linux-ppc64-0.35.3.tgz",
|
||||
"integrity": "sha512-sMd8rDxmpLOwv/7N44klFjOD5DUO7FLdjiXDI0hoxYaf7Ar262dQIEkosE98bps+5HPLtp/EvNqeqQtOycP/IA==",
|
||||
"cpu": [
|
||||
"ppc64"
|
||||
],
|
||||
"dev": true,
|
||||
"libc": [
|
||||
"glibc"
|
||||
],
|
||||
"license": "Apache-2.0",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"linux"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">=20.9.0"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://opencollective.com/libvips"
|
||||
},
|
||||
"optionalDependencies": {
|
||||
"@img/sharp-libvips-linux-ppc64": "1.3.2"
|
||||
}
|
||||
},
|
||||
"node_modules/@img/sharp-linux-riscv64": {
|
||||
"version": "0.35.3",
|
||||
"resolved": "https://registry.npmjs.org/@img/sharp-linux-riscv64/-/sharp-linux-riscv64-0.35.3.tgz",
|
||||
"integrity": "sha512-0Eob78yjlYPfL5vMNWAW55l3R9Y6BQS/gOfe0ZcP9mEz9ohhKSt4im1hayiknXgf8AWrFqMvJcKIdmLmEe7yeQ==",
|
||||
"cpu": [
|
||||
"riscv64"
|
||||
],
|
||||
"dev": true,
|
||||
"libc": [
|
||||
"glibc"
|
||||
],
|
||||
"license": "Apache-2.0",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"linux"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">=20.9.0"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://opencollective.com/libvips"
|
||||
},
|
||||
"optionalDependencies": {
|
||||
"@img/sharp-libvips-linux-riscv64": "1.3.2"
|
||||
}
|
||||
},
|
||||
"node_modules/@img/sharp-linux-s390x": {
|
||||
"version": "0.35.3",
|
||||
"resolved": "https://registry.npmjs.org/@img/sharp-linux-s390x/-/sharp-linux-s390x-0.35.3.tgz",
|
||||
"integrity": "sha512-KgAxQ0DxpNOq1rG2t5cgTgShJFGSuU7XO45cqC+1NVOuZnP6tlgZRuSYOfNupGkHID0o3cJOsw4DVeJpMovcGw==",
|
||||
"cpu": [
|
||||
"s390x"
|
||||
],
|
||||
"dev": true,
|
||||
"libc": [
|
||||
"glibc"
|
||||
],
|
||||
"license": "Apache-2.0",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"linux"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">=20.9.0"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://opencollective.com/libvips"
|
||||
},
|
||||
"optionalDependencies": {
|
||||
"@img/sharp-libvips-linux-s390x": "1.3.2"
|
||||
}
|
||||
},
|
||||
"node_modules/@img/sharp-linux-x64": {
|
||||
"version": "0.35.3",
|
||||
"resolved": "https://registry.npmjs.org/@img/sharp-linux-x64/-/sharp-linux-x64-0.35.3.tgz",
|
||||
"integrity": "sha512-8pqvxubL2PGdhlPy6GLqzDYMUjyRmKAwKHYKixpdJYBUK7PJ0C029XdsnpFIdgRZG68fZiGdHVWcKPvtiPB4cA==",
|
||||
"cpu": [
|
||||
"x64"
|
||||
],
|
||||
"dev": true,
|
||||
"libc": [
|
||||
"glibc"
|
||||
],
|
||||
"license": "Apache-2.0",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"linux"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">=20.9.0"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://opencollective.com/libvips"
|
||||
},
|
||||
"optionalDependencies": {
|
||||
"@img/sharp-libvips-linux-x64": "1.3.2"
|
||||
}
|
||||
},
|
||||
"node_modules/@img/sharp-linuxmusl-arm64": {
|
||||
"version": "0.35.3",
|
||||
"resolved": "https://registry.npmjs.org/@img/sharp-linuxmusl-arm64/-/sharp-linuxmusl-arm64-0.35.3.tgz",
|
||||
"integrity": "sha512-Vz0iQjzzcSX3HCbfwFfCSG/9SCIqyO0mH2sXyiHaAYfBk0cRsCWXRyQYX0ovCK/PAQBbTzQ0dsPQHh5MAFL59w==",
|
||||
"cpu": [
|
||||
"arm64"
|
||||
],
|
||||
"dev": true,
|
||||
"libc": [
|
||||
"musl"
|
||||
],
|
||||
"license": "Apache-2.0",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"linux"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">=20.9.0"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://opencollective.com/libvips"
|
||||
},
|
||||
"optionalDependencies": {
|
||||
"@img/sharp-libvips-linuxmusl-arm64": "1.3.2"
|
||||
}
|
||||
},
|
||||
"node_modules/@img/sharp-linuxmusl-x64": {
|
||||
"version": "0.35.3",
|
||||
"resolved": "https://registry.npmjs.org/@img/sharp-linuxmusl-x64/-/sharp-linuxmusl-x64-0.35.3.tgz",
|
||||
"integrity": "sha512-6O1NPKcDVj9QEdg7Hx549EX8U0rp6yXQERqru6yRN7fGBn32UvIRJUlWnk+8xDCiG76hXVBbX82NZ/ZKr0euIg==",
|
||||
"cpu": [
|
||||
"x64"
|
||||
],
|
||||
"dev": true,
|
||||
"libc": [
|
||||
"musl"
|
||||
],
|
||||
"license": "Apache-2.0",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"linux"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">=20.9.0"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://opencollective.com/libvips"
|
||||
},
|
||||
"optionalDependencies": {
|
||||
"@img/sharp-libvips-linuxmusl-x64": "1.3.2"
|
||||
}
|
||||
},
|
||||
"node_modules/@img/sharp-wasm32": {
|
||||
"version": "0.35.3",
|
||||
"resolved": "https://registry.npmjs.org/@img/sharp-wasm32/-/sharp-wasm32-0.35.3.tgz",
|
||||
"integrity": "sha512-cZ0XkcYGpHZkqW6iCkqTcmUC0CD9DhD5d/qeZlZkfRBn6GnHniZXLUo5+9xw8Iv76YE6LQFN9YNBlKREcCG76w==",
|
||||
"dev": true,
|
||||
"license": "Apache-2.0 AND LGPL-3.0-or-later AND MIT",
|
||||
"optional": true,
|
||||
"dependencies": {
|
||||
"@emnapi/runtime": "^1.11.1"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=20.9.0"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://opencollective.com/libvips"
|
||||
}
|
||||
},
|
||||
"node_modules/@img/sharp-webcontainers-wasm32": {
|
||||
"version": "0.35.3",
|
||||
"resolved": "https://registry.npmjs.org/@img/sharp-webcontainers-wasm32/-/sharp-webcontainers-wasm32-0.35.3.tgz",
|
||||
"integrity": "sha512-2rnq7bX3NzeR2T4YWgz8qiG4h3TSdMe+vN1iQXpJleSJ3SM5zQ8Fy2SyyXAWlbxpEZ2Y+Z4u1BePgJEYbSy80Q==",
|
||||
"cpu": [
|
||||
"wasm32"
|
||||
],
|
||||
"dev": true,
|
||||
"license": "Apache-2.0",
|
||||
"optional": true,
|
||||
"dependencies": {
|
||||
"@img/sharp-wasm32": "0.35.3"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=20.9.0"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://opencollective.com/libvips"
|
||||
}
|
||||
},
|
||||
"node_modules/@img/sharp-win32-arm64": {
|
||||
"version": "0.35.3",
|
||||
"resolved": "https://registry.npmjs.org/@img/sharp-win32-arm64/-/sharp-win32-arm64-0.35.3.tgz",
|
||||
"integrity": "sha512-4bPwFdMbeC4JQ8L8LOyWp6nsHcboP5fxkp6iPOXz2Vg49R42TuMs2whkJ5OAP4/Ul035qOzy0AecOF9VOscn4w==",
|
||||
"cpu": [
|
||||
"arm64"
|
||||
],
|
||||
"dev": true,
|
||||
"license": "Apache-2.0 AND LGPL-3.0-or-later",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"win32"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">=20.9.0"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://opencollective.com/libvips"
|
||||
}
|
||||
},
|
||||
"node_modules/@img/sharp-win32-ia32": {
|
||||
"version": "0.35.3",
|
||||
"resolved": "https://registry.npmjs.org/@img/sharp-win32-ia32/-/sharp-win32-ia32-0.35.3.tgz",
|
||||
"integrity": "sha512-r53mXsBN6lFUDiST764SvgwUdHAqM4rPAiDzAmf4fLoB6X/rkfyTrLCg6+g17wJJiCmB3JYgHuUldCWUIRFSXw==",
|
||||
"cpu": [
|
||||
"ia32"
|
||||
],
|
||||
"dev": true,
|
||||
"license": "Apache-2.0 AND LGPL-3.0-or-later",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"win32"
|
||||
],
|
||||
"engines": {
|
||||
"node": "^20.9.0"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://opencollective.com/libvips"
|
||||
}
|
||||
},
|
||||
"node_modules/@img/sharp-win32-x64": {
|
||||
"version": "0.35.3",
|
||||
"resolved": "https://registry.npmjs.org/@img/sharp-win32-x64/-/sharp-win32-x64-0.35.3.tgz",
|
||||
"integrity": "sha512-D4y1vNeZrIIJCN+uHaWVtH86B+aCrdMYYjicy9pXHvbGZeGYLLSd3wdVuC37FxVXlU1ARsk84eKWfWMXGYEqvA==",
|
||||
"cpu": [
|
||||
"x64"
|
||||
],
|
||||
"dev": true,
|
||||
"license": "Apache-2.0 AND LGPL-3.0-or-later",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"win32"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">=20.9.0"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://opencollective.com/libvips"
|
||||
}
|
||||
},
|
||||
"node_modules/@intlify/core-base": {
|
||||
"version": "9.1.9",
|
||||
"resolved": "https://registry.npmjs.org/@intlify/core-base/-/core-base-9.1.9.tgz",
|
||||
@@ -4288,6 +4875,16 @@
|
||||
"npm": "1.2.8000 || >= 1.4.16"
|
||||
}
|
||||
},
|
||||
"node_modules/detect-libc": {
|
||||
"version": "2.1.2",
|
||||
"resolved": "https://registry.npmjs.org/detect-libc/-/detect-libc-2.1.2.tgz",
|
||||
"integrity": "sha512-Btj2BOOO83o3WyH59e8MgXsxEQVcarkUOpEYrubB0urwnN10yQ364rsiByU11nZlqWYZm05i/of7io4mzihBtQ==",
|
||||
"dev": true,
|
||||
"license": "Apache-2.0",
|
||||
"engines": {
|
||||
"node": ">=8"
|
||||
}
|
||||
},
|
||||
"node_modules/dunder-proto": {
|
||||
"version": "1.0.1",
|
||||
"resolved": "https://registry.npmjs.org/dunder-proto/-/dunder-proto-1.0.1.tgz",
|
||||
@@ -6114,6 +6711,69 @@
|
||||
"dev": true,
|
||||
"license": "ISC"
|
||||
},
|
||||
"node_modules/sharp": {
|
||||
"version": "0.35.3",
|
||||
"resolved": "https://registry.npmjs.org/sharp/-/sharp-0.35.3.tgz",
|
||||
"integrity": "sha512-ej0zVHuZGHCiABXcNxeYhpRnPNPAcvbG8RMdBAhDAxLKkCRVSpK3Iyu7qbqw3JMzoj0REeM6f3tJLtVwl0023Q==",
|
||||
"dev": true,
|
||||
"license": "Apache-2.0",
|
||||
"dependencies": {
|
||||
"@img/colour": "^1.1.0",
|
||||
"detect-libc": "^2.1.2",
|
||||
"semver": "^7.8.5"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=20.9.0"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://opencollective.com/libvips"
|
||||
},
|
||||
"optionalDependencies": {
|
||||
"@img/sharp-darwin-arm64": "0.35.3",
|
||||
"@img/sharp-darwin-x64": "0.35.3",
|
||||
"@img/sharp-freebsd-wasm32": "0.35.3",
|
||||
"@img/sharp-libvips-darwin-arm64": "1.3.2",
|
||||
"@img/sharp-libvips-darwin-x64": "1.3.2",
|
||||
"@img/sharp-libvips-linux-arm": "1.3.2",
|
||||
"@img/sharp-libvips-linux-arm64": "1.3.2",
|
||||
"@img/sharp-libvips-linux-ppc64": "1.3.2",
|
||||
"@img/sharp-libvips-linux-riscv64": "1.3.2",
|
||||
"@img/sharp-libvips-linux-s390x": "1.3.2",
|
||||
"@img/sharp-libvips-linux-x64": "1.3.2",
|
||||
"@img/sharp-libvips-linuxmusl-arm64": "1.3.2",
|
||||
"@img/sharp-libvips-linuxmusl-x64": "1.3.2",
|
||||
"@img/sharp-linux-arm": "0.35.3",
|
||||
"@img/sharp-linux-arm64": "0.35.3",
|
||||
"@img/sharp-linux-ppc64": "0.35.3",
|
||||
"@img/sharp-linux-riscv64": "0.35.3",
|
||||
"@img/sharp-linux-s390x": "0.35.3",
|
||||
"@img/sharp-linux-x64": "0.35.3",
|
||||
"@img/sharp-linuxmusl-arm64": "0.35.3",
|
||||
"@img/sharp-linuxmusl-x64": "0.35.3",
|
||||
"@img/sharp-webcontainers-wasm32": "0.35.3",
|
||||
"@img/sharp-win32-arm64": "0.35.3",
|
||||
"@img/sharp-win32-ia32": "0.35.3",
|
||||
"@img/sharp-win32-x64": "0.35.3"
|
||||
},
|
||||
"peerDependenciesMeta": {
|
||||
"@types/node": {
|
||||
"optional": true
|
||||
}
|
||||
}
|
||||
},
|
||||
"node_modules/sharp/node_modules/semver": {
|
||||
"version": "7.8.5",
|
||||
"resolved": "https://registry.npmjs.org/semver/-/semver-7.8.5.tgz",
|
||||
"integrity": "sha512-Y7/KDsb8LjooZpwaqGyulO6DQlksgCncchHGk+sZIY4SBvUocMBEFH5Ur1fI4dV+Jvl0w6cjvucaIi40puRioA==",
|
||||
"dev": true,
|
||||
"license": "ISC",
|
||||
"bin": {
|
||||
"semver": "bin/semver.js"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=10"
|
||||
}
|
||||
},
|
||||
"node_modules/side-channel": {
|
||||
"version": "1.1.1",
|
||||
"resolved": "https://registry.npmjs.org/side-channel/-/side-channel-1.1.1.tgz",
|
||||
@@ -6353,6 +7013,14 @@
|
||||
"node": ">=0.6"
|
||||
}
|
||||
},
|
||||
"node_modules/tslib": {
|
||||
"version": "2.8.1",
|
||||
"resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz",
|
||||
"integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==",
|
||||
"dev": true,
|
||||
"license": "0BSD",
|
||||
"optional": true
|
||||
},
|
||||
"node_modules/type-is": {
|
||||
"version": "1.6.18",
|
||||
"resolved": "https://registry.npmjs.org/type-is/-/type-is-1.6.18.tgz",
|
||||
|
||||
@@ -14,6 +14,7 @@
|
||||
},
|
||||
"devDependencies": {
|
||||
"@dcloudio/vite-plugin-uni": "3.0.0-5020420260813001",
|
||||
"sharp": "^0.35.3",
|
||||
"vite": "5.2.8"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,18 @@
|
||||
// 用法: node svg2png.mjs <srcDir> <outDir> [width]
|
||||
// 把 srcDir 下所有 .svg 转为 outDir 下同名 .png(默认 800 宽,2x 高清)
|
||||
import sharp from 'sharp'
|
||||
import { readdirSync, mkdirSync } from 'fs'
|
||||
import { join } from 'path'
|
||||
|
||||
const [srcDir, outDir, widthArg] = process.argv.slice(2)
|
||||
const width = Number(widthArg) || 800
|
||||
mkdirSync(outDir, { recursive: true })
|
||||
let n = 0
|
||||
for (const f of readdirSync(srcDir)) {
|
||||
if (!f.endsWith('.svg')) continue
|
||||
const out = join(outDir, f.replace(/\.svg$/, '.png'))
|
||||
await sharp(join(srcDir, f)).resize({ width }).png().toFile(out)
|
||||
n++
|
||||
console.log('png:', out)
|
||||
}
|
||||
console.log(`done: ${n} files`)
|
||||
Reference in New Issue
Block a user