Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
91 commits
Select commit Hold shift + click to select a range
f0828f4
feat(workflow-editor): add real WorkflowRun canvas (#190)
huyanxius Aug 11, 2026
2872598
ci(coverage): integrate Codecov for backend coverage reporting (#168)
xiaocheny214 Aug 11, 2026
1f47667
feat(media): add validated upload adapter (#111)
xyh202131 Aug 11, 2026
1656dcf
feat(workflow-editor): create workflow characters atomically (#198)
huyanxius Aug 11, 2026
fbb86e6
feat(common): 角色卡与动作规格共享 DTO(#152 的前置 · Refs #171) (#172)
johnnyzhang-eng Aug 11, 2026
3d08f76
feat(generation): align validated SSE adapter with backend tasks (#110)
xyh202131 Aug 11, 2026
11b96bf
feat(framework): Provider 抽象 + 主体抠图 + 视频下载重试(#152 的前置 · Refs #171) (#…
johnnyzhang-eng Aug 11, 2026
e91192f
feat(playtest): add asset selection entry (#213)
huyanxius Aug 11, 2026
459bf3e
refactor(quick-start): align with six-node workflow controller (#95)
xyh202131 Aug 11, 2026
302120f
revert(quick-start): remove accidentally merged PR 95 (#214)
xyh202131 Aug 11, 2026
ff8726d
refactor(quick-start): align with six-node workflow controller (#215)
xyh202131 Aug 11, 2026
86b73c8
fix(ui): honor reduced motion changes (#219)
huyanxius Aug 12, 2026
f603f16
feat(workflow-editor): add reference image upload (#220)
huyanxius Aug 12, 2026
2d35976
fix(quick-start): resolve characters across pages (#221)
huyanxius Aug 12, 2026
93c062a
fix(quick-start): fill the viewport canvas (#227)
huyanxius Aug 12, 2026
9a7a911
feat(ai_engine): 出帧工具箱 —— 抽帧 / 选帧 / 后处理 / 提示词(Refs #171) (#180)
johnnyzhang-eng Aug 12, 2026
dd4dafe
feat(landing): add public product landing page (#225)
huyanxius Aug 12, 2026
40cf126
feat(ai_engine): 对外契约 + 动作分流 + 入口预检与出参成色(Refs #171 #53) (#181)
johnnyzhang-eng Aug 12, 2026
10e58f7
docs(workflow-run): align persisted run contract with main (#175)
xyh202131 Aug 12, 2026
3e28a16
feat(quota): 积分模块 ORM 模型、枚举与服务接口 (#230)
xiaocheny214 Aug 12, 2026
15ef635
feat(workspace): add creation workspace (#223)
huyanxius Aug 12, 2026
b8e747e
feat(generation): 任务编排 (#182)
johnnyzhang-eng Aug 12, 2026
63e7639
fix(playtest): address asset entry regressions (#231)
huyanxius Aug 12, 2026
5111f54
feat(character): 添加角色发布状态过滤 (#234)
xiaocheny214 Aug 12, 2026
861b719
fix(workflow): open editor after project creation (#229)
huyanxius Aug 12, 2026
8e52f2f
fix(quick-start): bound generated project names (#238)
xyh202131 Aug 12, 2026
4ca1dd5
feat(header): unify application navigation (#237)
huyanxius Aug 12, 2026
ec09fbc
fix(prompt): 提示词不再断言角色装备,母版说什么算什么 (#232)
johnnyzhang-eng Aug 13, 2026
bf45767
ci: automate new issue metadata (#248)
fennoai[bot] Aug 13, 2026
ec49818
chore: remove naming workflow (#250)
fennoai[bot] Aug 13, 2026
46068f1
fix(assets): 过滤未发布的草稿角色 (#174)
xyh202131 Aug 13, 2026
5c23c6f
chore: remove obsolete gitkeep files (#252)
fennoai[bot] Aug 13, 2026
1e5d1e4
fix(generation): reduce image rate-limit pressure (#254)
xyh202131 Aug 13, 2026
121b981
feat(navigation): add back button and preview-stage naming (#255)
xyh202131 Aug 13, 2026
e0367b6
fix(quick-start): 打通自定义动作完整生成 (#240)
xyh202131 Aug 13, 2026
38ee1f2
fix(storage): reject browser-inaccessible Qiniu S3 download URLs (#260)
xyh202131 Aug 13, 2026
82b5ef6
feat(account): align account center with workspace (#245)
huyanxius Aug 13, 2026
644c657
refactor(prompt): 提示词正文与理由抽离到 markdown (#256)
johnnyzhang-eng Aug 13, 2026
77007f7
fix(workflow-editor): use authenticated generation runtime (#264)
huyanxius Aug 13, 2026
bbd560e
feat(auth): redesign login and registration experience (#242)
huyanxius Aug 13, 2026
2702271
feat(generation): 支持自定义动作生成 (#241)
johnnyzhang-eng Aug 13, 2026
5a72020
feat(export-package): enforce complete game asset packages (#97)
xyh202131 Aug 13, 2026
93ce8dc
fix(quick-start): 使用可读项目名称 (#284)
xyh202131 Aug 13, 2026
cd50a3e
fix(backend): serialize generation and enforce HTTPS media (#291)
xyh202131 Aug 13, 2026
54d26fa
fix(security): 启动时校验 JWT 和数据库硬编码默认凭据 (#261)
xiaocheny214 Aug 13, 2026
33fec3f
fix(providers): 生图重试确定未达上游的 52x (#271)
johnnyzhang-eng Aug 13, 2026
6486576
test(frontend): stabilize auth panel exit timing (#294)
fennoai[bot] Aug 13, 2026
dfa79b2
fix(security): use secrets for verification code and atomic token rot…
xiaocheny214 Aug 13, 2026
308c743
feat(quota): 注册送积分 — 用户注册/自动注册时创建积分账户并赠送初始积分 (#259)
xiaocheny214 Aug 13, 2026
612782c
perf(frontend): prioritize image loading (#286)
xyh202131 Aug 13, 2026
fa10e7a
fix(config): 部署模板改用生效的 AI_ 前缀模型键 (#289)
johnnyzhang-eng Aug 13, 2026
900a4e6
test(auth): stabilize expired-session route test (#287)
huyanxius Aug 13, 2026
f42764a
fix(postprocess): 定标按本体跨度,不按包围盒 (#280)
johnnyzhang-eng Aug 13, 2026
ec1270d
fix(api): add upload size limits, async upload, and magic byte valida…
xiaocheny214 Aug 13, 2026
fa1d12e
chore(history): preserve deferred read-only skeleton (#105)
xyh202131 Aug 13, 2026
a7e5259
fix(frontend): 首帧改为三候选图片生成 (#290)
xyh202131 Aug 13, 2026
5d3db07
refactor(ui): unify product color tokens (#282)
huyanxius Aug 13, 2026
8821b19
fix(frontend): preserve generation expectation during recovery (#300)
xyh202131 Aug 14, 2026
1279360
feat(providers): 三渲二三段能力的 provider 层 (#270)
johnnyzhang-eng Aug 14, 2026
1171fe4
fix(providers): 52x 重发按预算封顶 (#297)
johnnyzhang-eng Aug 14, 2026
10798cb
refactor(account): consolidate form state (#306)
fennoai[bot] Aug 14, 2026
4103106
fix(auth): 移除共享 IP 限流中间件,避免正常登录被误拦截 (#325)
xiaocheny214 Aug 15, 2026
48d8647
test(frontend): wait for profile sync before nickname edits (#339)
fennoai[bot] Aug 17, 2026
d225cc2
ci: warn PR authors about missing issues (#338)
fennoai[bot] Aug 17, 2026
0c003a1
feat: generate OpenAPI contract from backend (#335)
fennoai[bot] Aug 17, 2026
36357a2
feat(projects): build a visual asset workspace (#299)
huyanxius Aug 17, 2026
af6f16e
fix(auth): 内测期间关闭公开注册,仅保留已有账号登录 (#327)
xiaocheny214 Aug 17, 2026
c27fb46
refactor(ui): extract shared kinetic copy (#302)
huyanxius Aug 17, 2026
fb6f96f
docs: 新增协作规范,经 #334 全员确认生效 (#343)
minorcell Aug 17, 2026
a66a558
feat(character): 创建角色时用 LLM 从描述补名称 (#329)
xiaocheny214 Aug 17, 2026
81764fe
fix(security): disable permissive CORS regex default (#340)
fennoai[bot] Aug 17, 2026
8ef0b67
feat(frontend): 在账号中心展示积分余额与流水 (#347)
xyh202131 Aug 17, 2026
2f7cd56
feat(quota): 生成任务调度接入预付费冻结与扣减 (#351)
xiaocheny214 Aug 17, 2026
b482b27
fix(project): 有角色挂载时拒绝删除项目 (#349)
xiaocheny214 Aug 17, 2026
7c4e231
fix(user): 落库接口改为 session-per-call,去掉 NotImplementedError stub (#346)
xiaocheny214 Aug 17, 2026
5f9d615
fix(workflow_run): PATCH 用乐观锁校验 version (#345)
xiaocheny214 Aug 17, 2026
549432a
fix(web): 校验失败的 message 给出可读原因 (#353)
johnnyzhang-eng Aug 17, 2026
e385f09
feat(quick-start): continue creation in one conversation (#312)
huyanxius Aug 17, 2026
879edf5
docs(readme): publish live product and setup guide (#360)
huyanxius Aug 18, 2026
61b4577
feat(quick-start): redesign character creation entry (#303)
huyanxius Aug 18, 2026
63e4d77
fix(frontend): 处理 WorkflowRun 乐观锁与跨资源冲突对账 (#356)
xyh202131 Aug 18, 2026
7e02ae2
feat(prompt): 攻击提示词按运动拓扑分四支 (#320)
johnnyzhang-eng Aug 18, 2026
e07fcce
feat(workspace): add invite reward hint (#363)
huyanxius Aug 18, 2026
f97426c
fix(quick-start): stop sending a self-derived character name (#358)
huyanxius Aug 18, 2026
9666f3e
fix(frontend): remove account menu arrow (#372)
fennoai[bot] Aug 18, 2026
1c8ee09
feat(auth): 开放公开注册,邀请码选填 (#357)
xiaocheny214 Aug 18, 2026
e692f58
feat(render3d): 打通三渲二的生产闭环 (#277)
johnnyzhang-eng Aug 18, 2026
9b77b57
fix(postprocess): 逐帧补偿一段动作内的单调尺度漂移 (#308)
johnnyzhang-eng Aug 18, 2026
0319409
fix(matte): 键控杀伤半径按底噪推导,不再抠穿浅色角色 (#369)
johnnyzhang-eng Aug 18, 2026
28d2c93
fix(tests): 三渲二 fixture 补 flush 与积分账户 (#377)
johnnyzhang-eng Aug 18, 2026
4333035
fix: resolve stale linked issue warnings
minorcell Aug 18, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
The table of contents is too big for display.
Diff view
Diff view
  •  
  •  
  •  
28 changes: 18 additions & 10 deletions .env.example
Original file line number Diff line number Diff line change
Expand Up @@ -18,26 +18,34 @@ REDIS_URL=redis://localhost:6379/0
# 数据持久化目录(默认 ./data/redis)
REDIS_DATA_DIR=./data/redis

# ── LLM 配置 ──
LLM_API_KEY=your-api-key-here
LLM_MODEL_ID=doubao-seed-2.0-mini
LLM_BASE_URL=https://api.qnaigc.com/v1
LLM_IMAGE_MODEL_ID=gemini-3.0-pro-image-preview
LLM_VIDEO_MODEL_ID=

# ── 搜索 API ──
SERPAPI_API_KEY=your-serpapi-key

# ── 七牛云存储 ──
QINIU_ACCESS_KEY=your-access-key
QINIU_SECRET_KEY=your-secret-key
QINIU_BUCKET_NAME=your-bucket
QINIU_BUCKET_DOMAIN=your-domain.com
# Bucket 绑定的浏览器可访问 HTTPS Kodo/CDN 域名,不能填 *.s3.*.qiniucs.com API 端点
QINIU_BUCKET_DOMAIN=https://cdn.example.com
QINIU_PRIVATE_SPACE=false

# ── AI Provider ──
# 键名前缀必须是 AI_,由 framework/config/provider.py 的 env_prefix 决定。
AI_BASE_URL=https://api.qnaigc.com/v1
AI_API_KEY=your-ai-api-key
# 各能力分开配型号:同时在用不同模型,共用一个字段会换一个连带换全部。
# 取值即默认值——不写这几行时跑的就是它们。
AI_CHAT_MODEL=gpt-4o-mini
AI_IMAGE_MODEL=gemini-2.5-flash-image
AI_VIDEO_MODEL=kling-v2-5-turbo

# ── 积分定价 ──
QUOTA_REGISTER_GIFT_AMOUNT=300
QUOTA_INVITE_REWARD_AMOUNT=200
QUOTA_INVITE_REWARD_DAILY_LIMIT=3
QUOTA_INVITE_CODE_TTL_DAYS=30
QUOTA_GENERATE_IMAGE_COST=10
QUOTA_GENERATE_ACTION_COST=50

# ── 服务配置 ──
WINDUP_HOST=127.0.0.1
Expand All @@ -47,5 +55,5 @@ WINDUP_PORT=8000
VITE_API_BASE_URL=/api
# 跨域来源(逗号分隔,默认覆盖 localhost:5173/3000)
WINDUP_CORS_ORIGINS=
# 跨域正则匹配(默认允许 *.vercel.app
WINDUP_CORS_ORIGIN_REGEX=https://.*\.vercel\.app
# 跨域正则匹配(默认关闭;如需 Vercel 预览域名,请限制到自己的项目名前缀
WINDUP_CORS_ORIGIN_REGEX=
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added .github/assets/landing-page/hero-desktop.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added .github/assets/landing-page/pipeline-desktop.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added .github/assets/landing-page/styles-desktop.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added .github/assets/landing-page/workspace-desktop.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added .github/assets/readme/character-journey.webp
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
66 changes: 66 additions & 0 deletions .github/scripts/check-pr-issue.cjs
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
const WARNING_MARKER = '<!-- windup-pr-missing-issue -->'
const RESOLVED_WARNING_MARKER = '<!-- windup-pr-missing-issue-resolved -->'

function isMissingIssueWarning(comment) {
return comment.body?.includes(WARNING_MARKER) && !comment.body.includes(RESOLVED_WARNING_MARKER)
}

async function checkPullRequestIssue({ github, context, core }) {
const pullRequest = context.payload.pull_request
const { owner, repo } = context.repo

const result = await github.graphql(
`query PullRequestClosingIssues($owner: String!, $repo: String!, $number: Int!) {
repository(owner: $owner, name: $repo) {
pullRequest(number: $number) {
closingIssuesReferences(first: 1) {
totalCount
}
}
}
}`,
{ owner, repo, number: pullRequest.number },
)

const comments = await github.paginate(github.rest.issues.listComments, {
owner,
repo,
issue_number: pullRequest.number,
per_page: 100,
})

if (result.repository.pullRequest.closingIssuesReferences.totalCount > 0) {
const warning = comments.find(isMissingIssueWarning)

if (warning) {
await github.rest.issues.updateComment({
owner,
repo,
comment_id: warning.id,
body: `${RESOLVED_WARNING_MARKER}\n✅ 此 PR 已关联 issue,之前的提醒已自动标记为已解决。`,
})
core.info('Resolved stale missing-issue warning.')
}

core.info('Pull request is linked to an issue.')
return
}

if (comments.some(isMissingIssueWarning)) {
core.info('Missing-issue warning has already been posted.')
return
}

await github.rest.issues.createComment({
owner,
repo,
issue_number: pullRequest.number,
body: `${WARNING_MARKER}\n⚠️ @${pullRequest.user.login},此 PR 尚未关联 issue。请在 PR 描述中使用 \`Closes #123\` 等关闭关键字,或通过 Development 侧栏关联对应 issue。`,
})

core.warning('Pull request is not linked to an issue; posted an author warning.')
}

module.exports = checkPullRequestIssue
module.exports.WARNING_MARKER = WARNING_MARKER
module.exports.RESOLVED_WARNING_MARKER = RESOLVED_WARNING_MARKER
153 changes: 153 additions & 0 deletions .github/scripts/check-pr-issue.test.cjs
Original file line number Diff line number Diff line change
@@ -0,0 +1,153 @@
const assert = require('node:assert/strict')
const test = require('node:test')

const checkPullRequestIssue = require('./check-pr-issue.cjs')
const { RESOLVED_WARNING_MARKER, WARNING_MARKER } = checkPullRequestIssue

function createContext() {
return {
repo: { owner: 'owner', repo: 'repo' },
payload: {
pull_request: {
number: 42,
user: { login: 'octocat' },
},
},
}
}

test('marks a stale warning as resolved when the pull request closes an issue', async () => {
const calls = []
const github = {
graphql: async () => ({
repository: {
pullRequest: { closingIssuesReferences: { totalCount: 1 } },
},
}),
paginate: async () => [{ id: 99, body: `${WARNING_MARKER}\nExisting warning` }],
rest: {
issues: {
listComments: () => {},
createComment: async () => {
throw new Error('comment should not be created')
},
updateComment: async (input) => calls.push(input),
},
},
}

await checkPullRequestIssue({
github,
context: createContext(),
core: { info: () => {}, warning: () => {} },
})

assert.deepEqual(calls, [{
owner: 'owner',
repo: 'repo',
comment_id: 99,
body: `${RESOLVED_WARNING_MARKER}\n✅ 此 PR 已关联 issue,之前的提醒已自动标记为已解决。`,
}])
})

test('warns and mentions the author when no issue is linked', async () => {
const calls = []
const github = {
graphql: async (query, variables) => {
assert.match(query, /closingIssuesReferences/)
assert.deepEqual(variables, { owner: 'owner', repo: 'repo', number: 42 })
return {
repository: {
pullRequest: { closingIssuesReferences: { totalCount: 0 } },
},
}
},
paginate: async (method, input) => {
assert.equal(method, github.rest.issues.listComments)
assert.deepEqual(input, {
owner: 'owner',
repo: 'repo',
issue_number: 42,
per_page: 100,
})
return []
},
rest: {
issues: {
listComments: () => {},
createComment: async (input) => calls.push(input),
},
},
}

await checkPullRequestIssue({
github,
context: createContext(),
core: { info: () => {}, warning: () => {} },
})

assert.equal(calls.length, 1)
assert.deepEqual(calls[0], {
owner: 'owner',
repo: 'repo',
issue_number: 42,
body: `${WARNING_MARKER}\n⚠️ @octocat,此 PR 尚未关联 issue。请在 PR 描述中使用 \`Closes #123\` 等关闭关键字,或通过 Development 侧栏关联对应 issue。`,
})
})

test('does not post a duplicate warning', async () => {
const github = {
graphql: async () => ({
repository: {
pullRequest: { closingIssuesReferences: { totalCount: 0 } },
},
}),
paginate: async () => [{ body: `${WARNING_MARKER}\nExisting warning` }],
rest: {
issues: {
listComments: () => {},
createComment: async () => {
throw new Error('duplicate comment should not be created')
},
updateComment: async () => {
throw new Error('resolved comments should not be updated')
},
},
},
}

await checkPullRequestIssue({
github,
context: createContext(),
core: { info: () => {}, warning: () => {} },
})
})

test('posts a new warning when only a resolved warning exists', async () => {
const calls = []
const github = {
graphql: async () => ({
repository: {
pullRequest: { closingIssuesReferences: { totalCount: 0 } },
},
}),
paginate: async () => [{ body: `${RESOLVED_WARNING_MARKER}\nResolved warning` }],
rest: {
issues: {
listComments: () => {},
createComment: async (input) => calls.push(input),
updateComment: async () => {
throw new Error('resolved comments should not be updated')
},
},
},
}

await checkPullRequestIssue({
github,
context: createContext(),
core: { info: () => {}, warning: () => {} },
})

assert.equal(calls.length, 1)
})
Loading
Loading