chore: 导入清理前的完整工程状态(含 Capacitor 原生壳,作为回滚备份点)

This commit is contained in:
toor
2026-10-21 11:50:10 +08:00
committed by trae-solo-agent
commit 8ded87a3a8
389 changed files with 52242 additions and 0 deletions
+78
View File
@@ -0,0 +1,78 @@
import type { FeedbackRow, SignalRow } from './lib/feedback'
/**
* 拉取 cook-feedback 云函数里的用户反馈和筛选信号,汇总给 recipe-data-review skill 使用
*
* 需要在 .env 中配置:
* COOK_FEEDBACK_URL cook-feedback 云函数的 HTTP 访问地址
* COOK_FEEDBACK_TOKEN 与云函数环境变量 COOK_FEEDBACK_EXPORT_TOKEN 相同
*
* pnpm feedback:pull
* → .cook/feedback/summary.md 给人和 AI 看的汇总
* → .cook/feedback/export.json 原始数据(不提交)
* → test/fixtures/search-eval.json 筛选评测样例(匿名聚合,可以提交)
*/
import fs from 'node:fs'
import path from 'node:path'
import process from 'node:process'
import { recipeCsvFile, rootDir } from '../packages/cook/src/utils/config'
import { parseRecipeCsv } from '../packages/cook/src/utils/csv'
import { buildEvalCases, summarizeReports } from './lib/feedback'
try {
process.loadEnvFile(path.join(rootDir, '.env'))
}
catch {}
const url = process.env.COOK_FEEDBACK_URL
const token = process.env.COOK_FEEDBACK_TOKEN
if (!url || !token) {
console.error('请先在 .env 中配置 COOK_FEEDBACK_URL 和 COOK_FEEDBACK_TOKEN,见 docs/dev/data.md')
process.exit(1)
}
const res = await fetch(url, { headers: { 'x-cook-token': token }, signal: AbortSignal.timeout(30_000) })
const data = await res.json() as { ok?: boolean, code?: string, feedback?: FeedbackRow[], signals?: SignalRow[] }
if (!res.ok || !data.ok) {
console.error(`拉取失败:${res.status} ${data.code ?? ''}`)
process.exit(1)
}
const outDir = path.join(rootDir, '.cook/feedback')
fs.mkdirSync(outDir, { recursive: true })
fs.writeFileSync(path.join(outDir, 'export.json'), JSON.stringify(data, null, 2))
const recipes = new Map(parseRecipeCsv(fs.readFileSync(recipeCsvFile, 'utf-8')).map(r => [r.id, r]))
const reports = summarizeReports(data.feedback ?? [])
const signals = data.signals ?? []
const empties = signals.filter(s => s.type === 'empty').sort((a, b) => b.count - a.count).slice(0, 30)
const cases = buildEvalCases(signals)
fs.writeFileSync(path.join(rootDir, 'test/fixtures/search-eval.json'), `${JSON.stringify(cases, null, 2)}\n`)
const kindLabel: Record<string, string> = { video: '视频打不开', stuff: '食材或厨具不对', info: '难度或做法不对', other: '其他' }
const lines = [
`# 用户反馈汇总(${new Date().toISOString().slice(0, 10)})`,
'',
`反馈 ${data.feedback?.length ?? 0} 条,涉及 ${reports.length} 道菜;筛选信号 ${signals.length} 组。`,
'',
'## 菜谱反馈(按分数排序,登录用户的反馈算 2 分)',
'',
'| id | 菜名 | 分数 | 人数 | 问题 | 备注 |',
'| --- | --- | --- | --- | --- | --- |',
...reports.map((r) => {
const current = recipes.get(r.recipeId)
const name = current ? current.name : `${r.recipeName}(已不在数据中)`
const kinds = Object.entries(r.kinds).map(([k, n]) => `${kindLabel[k] ?? k}×${n}`).join('、')
return `| ${r.recipeId} | ${name} | ${r.score} | ${r.reporters} | ${kinds} | ${r.notes.slice(0, 3).join(';').replace(/\|/g, '/')} |`
}),
'',
'## 没有结果的食材组合(缺哪些菜)',
'',
'| 食材 | 厨具 | 模式 | 次数 |',
'| --- | --- | --- | --- |',
...empties.map(s => `| ${s.stuff.join('、')} | ${s.tool || '-'} | ${s.mode} | ${s.count} |`),
'',
'筛选排序的评测见 `pnpm eval:search`。',
'',
]
fs.writeFileSync(path.join(outDir, 'summary.md'), lines.join('\n'))
console.log(`已写入 .cook/feedback/summary.md,评测样例 ${cases.length} 组`)