diff --git a/.github/ISSUE_TEMPLATE/bug_report.yml b/.github/ISSUE_TEMPLATE/bug_report.yml index c8e0427..30cd836 100644 --- a/.github/ISSUE_TEMPLATE/bug_report.yml +++ b/.github/ISSUE_TEMPLATE/bug_report.yml @@ -1,7 +1,7 @@ name: Bug Report description: Report a reproducible problem with ADI (build, inference, packaging) title: "[Bug]: " -labels: ["bug"] +labels: ["type/bug", "status/needs-investigation"] body: - type: markdown attributes: diff --git a/.github/ISSUE_TEMPLATE/config.yml b/.github/ISSUE_TEMPLATE/config.yml new file mode 100644 index 0000000..fc9497a --- /dev/null +++ b/.github/ISSUE_TEMPLATE/config.yml @@ -0,0 +1,11 @@ +blank_issues_enabled: false +contact_links: + - name: 💬 技术问答(Discussions) + url: https://github.com/Windsander/ADI-Stable-Diffusion/discussions + about: 一般性使用 / 技术问题请优先在 Discussions 提问,可获得 Copilot 与社区的上下文应答 + - name: 📖 项目文档(README) + url: https://github.com/Windsander/ADI-Stable-Diffusion/blob/main/README.md + about: 构建、部署与模型支持的权威说明 + - name: 🗺️ 路线图(ROADMAP) + url: https://github.com/Windsander/ADI-Stable-Diffusion/blob/main/ROADMAP.md + about: 提需求前请先确认是否已在规划中 diff --git a/.github/ISSUE_TEMPLATE/feature_request.yml b/.github/ISSUE_TEMPLATE/feature_request.yml index 2a32173..06089b4 100644 --- a/.github/ISSUE_TEMPLATE/feature_request.yml +++ b/.github/ISSUE_TEMPLATE/feature_request.yml @@ -1,7 +1,7 @@ name: Feature Request description: Suggest a new model, scheduler, provider, or engineering improvement title: "[Feature]: " -labels: ["enhancement"] +labels: ["type/enhancement"] body: - type: textarea id: summary diff --git a/.github/ISSUE_TEMPLATE/model_support_request.yml b/.github/ISSUE_TEMPLATE/model_support_request.yml new file mode 100644 index 0000000..8371ff4 --- /dev/null +++ b/.github/ISSUE_TEMPLATE/model_support_request.yml @@ -0,0 +1,43 @@ +name: Model Support Request +description: Request support for a new Stable Diffusion model family or checkpoint +title: "[Model]: " +labels: ["area/model-support", "type/enhancement", "status/needs-design"] +body: + - type: markdown + attributes: + value: | + Thanks for the request! Please include enough detail to judge feasibility. + - type: textarea + id: model-info + attributes: + label: 模型信息 + description: 模型名称、来源、下载方式 + placeholder: | + 模型名称: ... + 来源/许可: ... + 类型: txt2img / img2img / img2vid / ... + 预期输入输出分辨率: ... + validations: + required: true + - type: textarea + id: use-case + attributes: + label: 期望用途 + description: 你打算用这个模型做什么,典型输入/输出是什么 + - type: textarea + id: compatibility + attributes: + label: 已知兼容性问题 + description: 该模型有没有已知的架构差异、tokenizer 差异、onnx 不兼容等 + - type: textarea + id: priority + attributes: + label: 优先级理由 + description: 为什么该模型值得优先支持(用户量、项目方向、重要性) + - type: checkboxes + id: checks + attributes: + label: Pre-flight checks + options: + - label: 我已查阅 ROADMAP.md 并确认尚未规划 + required: true diff --git a/.github/ISSUE_TEMPLATE/tech_question.yml b/.github/ISSUE_TEMPLATE/tech_question.yml new file mode 100644 index 0000000..b1c255e --- /dev/null +++ b/.github/ISSUE_TEMPLATE/tech_question.yml @@ -0,0 +1,39 @@ +name: Technical Question +description: Ask a technical question about Adi Stable Diffusion +title: "[Question]: " +labels: ["type/question"] +body: + - type: markdown + attributes: + value: | + Feel free to ask anything about ADI — models, build, ONNX Runtime, webbridge, etc. + - type: textarea + id: question + attributes: + label: 问题描述 + description: 尽可能具体地描述你的问题 + placeholder: "我在 ... 上遇到了问题,期望是 ..." + validations: + required: true + - type: textarea + id: tried + attributes: + label: 已尝试方案 + description: 你已经查了什么,试了什么 + - type: input + id: platform + attributes: + label: 平台 + description: macOS / Linux / Windows / Android + - type: input + id: version + attributes: + label: ADI 版本 + description: 版本号或 commit hash + - type: checkboxes + id: checks + attributes: + label: Pre-flight checks + options: + - label: 我已搜索现有 Issues / Discussions 并确认尚未回答 + required: true diff --git a/.github/ISSUE_TEMPLATE/webbridge_enhancement.yml b/.github/ISSUE_TEMPLATE/webbridge_enhancement.yml new file mode 100644 index 0000000..6bfcc9f --- /dev/null +++ b/.github/ISSUE_TEMPLATE/webbridge_enhancement.yml @@ -0,0 +1,43 @@ +name: Webbridge Enhancement +description: Propose a new feature or improvement for the Webbridge (Web UI/API) layer +title: "[Webbridge]: " +labels: ["area/webbridge", "type/enhancement", "status/needs-design"] +body: + - type: markdown + attributes: + value: | + Thanks for the suggestion! Please describe the current pain point and the desired behaviour. + - type: textarea + id: pain-point + attributes: + label: 当前痛点 + description: 现有 Webbridge 功能中哪方面不够用,或Blocking了什么场景 + - type: textarea + id: desired + attributes: + label: 期望功能 + description: 具体想要的新功能、改进点、接口变化 + placeholder: | + 例如: + - 增加 REST 接口 ... + - 支持流式输出 + - 提供指定分辨率的参数 + validations: + required: true + - type: textarea + id: scenario + attributes: + label: 使用场景 + description: 谁会用这个功能,在什么情况下用 + - type: textarea + id: technical + attributes: + label: 技术考量 + description: 相关的技术备注,例如依赖、性能影响、与 engine/CMake 的关系 + - type: checkboxes + id: checks + attributes: + label: Pre-flight checks + options: + - label: 我已查阅 ROADMAP.md 并确认尚未规划 + required: true diff --git a/.github/README.md b/.github/README.md new file mode 100644 index 0000000..5d88ba0 --- /dev/null +++ b/.github/README.md @@ -0,0 +1,145 @@ +# ADI 项目助手配置说明 + +本目录包含 ADI-Stable-Diffusion 项目的「项目助手代理」配置,旨在利用 GitHub 原生能力(减少额外 token 消耗)实现: + +1. **技术问题应答**(Discussions + Copilot) +2. **建议 / Bug 收集**(Issue 模板 + 自动分类 + 关联 Issue 创建) +3. **每周汇总周报**(Discussions,季度一份、期间追加) + +## 目录结构 + +``` +.github/ +├── ISSUE_TEMPLATE/ # Issue 创建表单 (YAML format) +│ ├── config.yml # 关闭空白 Issue,引导至 Discussions / 文档 +│ ├── bug_report.yml # 🐛 Bug 报告 +│ ├── feature_request.yml # ✨ 特性请求(新模型、工程改进等) +│ ├── model_support_request.yml # 🤖 新模型支持请求 +│ ├── webbridge_enhancement.yml # 🌐 Webbridge 增强 +│ └── tech_question.yml # ❓ 技术问题 +├── copilot-instructions.md # Copilot 仓库级自定义提示词(GitHub 约定的标准文件名) +├── workflows/ # GitHub Actions +│ ├── issue-classifier.yml # Issue 自动分类器 +│ ├── weekly-report.yml # 周报生成(Discussions,GraphQL API) +│ ├── auto-deploy.yml # (已有)自动部署 +│ └── ... +└── CODEOWNERS # (已有)负责人配置 +``` + +## 使用方式 + +### 1. 创建 Issue + +访问 `https://github.com/Windsander/ADI-Stable-Diffusion/issues/new/choose` 选择合适的模板: + +- **Bug Report** — 报告错误 +- **Feature Request** — 请求新功能(新模型、工程改进等) +- **Model Support Request** — 请求支持特定模型 +- **Webbridge Enhancement** — 提出 Webbridge 改进建议 +- **Technical Question** — 提问技术问题 + +### 2. 技术问答 + +在 **Discussions** 中提问,Copilot 可以基于 `.github/copilot-instructions.md` 中的上下文回答。你也可以直接在 Issue/Discussions 中 @GitHub Copilot。 + +### 3. 周报 + +每周六上午 9:00 北京时间(UTC 1:00),如果本周有新增内容,工作流自动在 Discussions 中创建或追加周报。季度内使用同一个 Discussion 帖,季度末自然归档。 + +周报位置示例:`[周报] ADI 项目 2026-Q3 周报汇总` + +## 自动分类 + +新创建的 Issue 会自动打上类型和领域标签,例如: + +- `type/bug` / `type/enhancement` / `type/question` +- `area/model-support` / `area/webbridge` / `area/build-system` 等 + +分类逻辑见 `.github/workflows/issue-classifier.yml`。 + +## Label 配置 + +项目助手需要以下 Labels(共 14 个)。可通过下列方式导入: + +### 方法一:gh CLI(推荐) + +```bash +# 确保 gh 已认证 +gh auth status + +# 运行导入脚本 +bash scripts/import-labels.sh +``` + +### 方法二:手动创建 + +在 GitHub 仓库 → Settings → Labels 中手动创建以下 Labels: + +| Label | 颜色 | 说明 | +|-------|------|------| +| `type/bug` | `#d73a4a` | Bug 报告 | +| `type/enhancement` | `#a8d692` | 新功能/增强 | +| `type/question` | `#0066cc` | 技术问题 | +| `area/model-support` | `#9b59b6` | 新模型支持相关 | +| `area/webbridge` | `#f39c12` | Webbridge 增强相关 | +| `area/build-system` | `#7f8c8d` | CMake/跨平台构建 | +| `area/cli-tools` | `#1abc9c` | CLI 工具改进 | +| `area/python-binding` | `#e91e63` | Python 绑定 | +| `area/engine` | `#f1c40f` | 推理引擎核心 | +| `status/needs-investigation` | `#f39c12` | 待调查 | +| `status/needs-design` | `#e67e22` | 待设计决策 | +| `status/ready-for-dev` | `#27ae60` | 可开发 | +| `status/in-progress` | `#3498db` | 开发中 | +| `status/blocked` | `#c0392b` | 阻塞 | + +## 周报工作流详情 + +### 触发方式 + +- **定时**: 每周六 UTC 1:00(北京时间 9:00) +- **手动**: GitHub Actions 页面 → Run workflow + +### 逻辑 + +1. 收集过去 7 天新建的 Issues(按 `created_at` 过滤,排除 PR)、合并的 PRs(按 `merged_at` 过滤)、新建的 Discussions +2. 若无内容 → 跳过,不生成空周报 +3. 若有内容: + - 通过 GraphQL 查找当前季度的周报 Discussion 帖(标题 `[周报] ADI 项目 YYYY-QX 周报汇总`) + - 若不存在 → 创建新帖(自动选择 `general` 分类,无则退到第一个可用分类) + - 若存在 → 在原帖追加本周内容 + +> 注意:GitHub Discussions **只有 GraphQL API**(`github.rest.repos.*Discussion*` 并不存在), +> 本工作流统一使用 `github.graphql` 读写 Discussions。 + +### 周报内容 + +- 本周概览(Issues、PRs、Discussions 数量) +- Bug 报告列表 +- 建议 & 增强列表 +- 技术问题列表 +- 模型支持 / Webbridge 进展 +- 合并 PR 列表 +- 待跟进事项 + +## Copilot 提示词 + +`.github/copilot-instructions.md` 是 GitHub Copilot 约定的仓库级自定义指令文件名(旧名 `COPILOT_INSTRUCTIONS.md` 不会被识别)。内容涵盖: + +- 项目概述(ADI 是什么) +- 核心组件结构 +- 已支持模型列表 +- 架构关键点 +- 回答准则和反馈处理准则 + +## 注意事项 + +1. **Discussions 需要开启**: 周报依赖 Discussions 功能。如未开启,请在仓库 Settings → General → Discussions 启用;未开启时工作流会给出 warning 并跳过发布。 +2. **Labels 需先导入**: 分类器在标签不存在时不会失败(仅 warning),但建议先运行 `scripts/import-labels.sh` 保证 `type/*`、`area/*`、`status/*` 全套标签可用。 +3. **Discussion 分类**: 周报自动选择 `general` 分类(无则取第一个可用分类),无需再手工调整 `category_id`。 +4. **模板格式**: 本配置使用 YAML format Issue forms;`config.yml` 已关闭空白 Issue,并将一般性问题引导至 Discussions。 + +## 维护 + +- 如需新增领域标签,在 `scripts/github-labels.json` 中添加,并重新运行导入脚本(幂等,可重复执行) +- 如需调整分类逻辑,编辑 `.github/workflows/issue-classifier.yml` +- 如需修改 Copilot 行为,编辑 `.github/copilot-instructions.md` diff --git a/.github/copilot-instructions.md b/.github/copilot-instructions.md new file mode 100644 index 0000000..8f17337 --- /dev/null +++ b/.github/copilot-instructions.md @@ -0,0 +1,37 @@ +# ADI-Stable-Diffusion 项目助手 + +## 项目概述 +ADI (All Device Inference) Stable Diffusion 是一个本地 Stable Diffusion 推理引擎,使用 ONNX Runtime 实现跨平台部署(macOS aarch64/arm64、Android arm64-v8a/x86_64、Linux、Windows)。 + +## 核心组件 +- **engine/**: 推理核心,使用 ONNX Runtime +- **clitools/**: 命令行工具,用于图像生成 +- **webbridge/**: Web 桥接接口 +- **ort_sd_py_imp.py**: Python 绑定 +- **CMakeLists.txt**: 跨平台 CMake 构建系统 + +## 已支持模型(参见 showcase/) +- flux-schnell (1024x1024) +- sd-turbo (512x512) +- sd21 (768x768) +- sd35-turbo (1024x1024) +- sdxl-turbo (512x512) +- svd-img2vid (图像到视频) + +## 架构关键点 +- 使用 ONNX Runtime 进行推理 +- CMake 跨平台构建 +- 支持多种输出格式和分辨率 +- Webbridge 提供 Web 接口 +- 采用 C++ 主要实现,Python 作为辅助绑定 + +## 回答准则 +1. 优先引用项目代码或文档 +2. 技术问题尽量给出具体建议 +3. 不确定时明确说明 +4. 涉及模型支持时,参考现有模型架构 + +## 反馈处理准则 +- **Bug**: 确认复现步骤、环境信息,提取关键信息 +- **建议**: 评估可行性,记录 rationale +- **问题**: 提供答案或指引到相关资源 diff --git a/.github/workflows/issue-classifier.yml b/.github/workflows/issue-classifier.yml new file mode 100644 index 0000000..211c2ee --- /dev/null +++ b/.github/workflows/issue-classifier.yml @@ -0,0 +1,111 @@ +name: Issue 分类器 +on: + issues: + types: [opened, reopened] + +permissions: + issues: write + +jobs: + classify: + runs-on: ubuntu-latest + steps: + - name: 自动分类 Issue + uses: actions/github-script@v7 + with: + script: | + const issue = context.issue; + const title = (issue.title || '').toLowerCase(); + const body = ((issue.body || '') + ' ' + (issue.title || '')).toLowerCase(); + const labels = []; + + // --- 类型分类 --- + + // Bug + const bugKeywords = ['bug', '错误', '崩溃', '失败', '报错', 'crash', 'error', + 'exception', '无法', '不能', '出错', '异常', '闪退']; + if (bugKeywords.some(k => title.includes(k) || body.includes(k))) { + labels.push('type/bug'); + } + + // 技术问题 + const questionKeywords = ['question', '?', '?', '问题', '怎么', '如何', '求助', + '疑问', '咨询', '请教', '不懂', '不明白', 'help']; + if (questionKeywords.some(k => title.includes(k) || body.includes(k))) { + labels.push('type/question'); + } + + // 增强/建议 + const enhancementKeywords = ['增强', '改进', '优化', '增加', '支持', 'feature', 'request', + 'enhancement', 'improvement', 'suggestion', 'feature request', + '新功能', '新增', '希望', '可以', '添加', '扩展']; + if (enhancementKeywords.some(k => title.includes(k) || body.includes(k))) { + labels.push('type/enhancement'); + } + + // --- 领域分类 --- + + // 模型支持 + const modelKeywords = ['模型', 'model', 'sd2', 'sd3', 'sdxl', 'flux', 'stable diffusion', + '支持', 'new model', 'lora', 'checkpoint', 'diffusion']; + if (modelKeywords.some(k => title.includes(k) || body.includes(k))) { + labels.push('area/model-support'); + } + + // Webbridge + const wbKeywords = ['webbridge', 'web 桥接', 'web 接口', 'web bridge', 'web api', + '网页', '网页版', '界面', 'ui', '可视化']; + if (wbKeywords.some(k => title.includes(k) || body.includes(k))) { + labels.push('area/webbridge'); + } + + // 构建系统 + const buildKeywords = ['build', 'cmake', '编译', '构建', '安装', '依赖', 'link', + 'make', 'cross-compile', 'android', 'ios', 'macos', 'windows']; + if (buildKeywords.some(k => title.includes(k) || body.includes(k))) { + labels.push('area/build-system'); + } + + // CLI 工具 + const cliKeywords = ['cli', '命令行', '命令行工具', '命令行接口', 'terminal', + 'cmd', '命令', '控制台', 'script']; + if (cliKeywords.some(k => title.includes(k) || body.includes(k))) { + labels.push('area/cli-tools'); + } + + // Python 绑定 + const pyKeywords = ['python', 'py', '绑定', 'binding', 'ort_sd_py_imp', 'pip', + 'virtualenv', 'import adi']; + if (pyKeywords.some(k => title.includes(k) || body.includes(k))) { + labels.push('area/python-binding'); + } + + // 引擎 + const engineKeywords = ['engine', '推理', 'inference', 'onnx', 'runtime', + 'kernel', '运算', '性能', '速度', '内存', '显存']; + if (engineKeywords.some(k => title.includes(k) || body.includes(k))) { + labels.push('area/engine'); + } + + // 去重并添加(表单模板已带的标签除外) + const existing = new Set( + (context.payload.issue.labels || []).map(l => typeof l === 'string' ? l : l.name) + ); + const uniqueLabels = [...new Set(labels)].filter(l => !existing.includes(l)); + if (uniqueLabels.length > 0) { + try { + await github.rest.issues.addLabels({ + owner: context.repo.owner, + repo: context.repo.repo, + issue_number: issue.number, + labels: uniqueLabels + }); + console.log(`Added labels: ${uniqueLabels.join(', ')}`); + } catch (e) { + // 标签不存在时会返回 422 —— 不阻塞 Issue,仅提醒维护者导入标签 + core.warning( + `打标签失败 (${e.status}): ${uniqueLabels.join(', ')}。` + + `请运行 scripts/import-labels.sh 导入标签后重试。` + ); + } + } \ No newline at end of file diff --git a/.github/workflows/weekly-report.yml b/.github/workflows/weekly-report.yml new file mode 100644 index 0000000..dc39054 --- /dev/null +++ b/.github/workflows/weekly-report.yml @@ -0,0 +1,270 @@ +name: 周报生成 + +on: + schedule: + - cron: '0 1 * * 6' # 每周六 UTC 1:00 = 北京时间 9:00 + workflow_dispatch: # 支持手动触发 + +permissions: + discussions: write + issues: read + pull-requests: read + +concurrency: + group: weekly-report + cancel-in-progress: false + +jobs: + collect: + runs-on: ubuntu-latest + outputs: + hasContent: ${{ steps.report.outputs.hasContent }} + discussionTitle: ${{ steps.report.outputs.discussionTitle }} + reportBody: ${{ steps.report.outputs.reportBody }} + repoId: ${{ steps.report.outputs.repoId }} + categoryId: ${{ steps.report.outputs.categoryId }} + existingDiscussionId: ${{ steps.report.outputs.existingDiscussionId }} + steps: + - name: 收集本周数据并生成周报正文 + id: report + uses: actions/github-script@v7 + with: + script: | + const { owner, repo } = context.repo; + + // ---------- 时间窗口(最近 7 天) ---------- + const now = new Date(); + const weekStart = new Date(now.getTime() - 7 * 24 * 3600 * 1000); + const fmt = d => d.toISOString().split('T')[0]; + const weekStartStr = fmt(weekStart); + const weekEndStr = fmt(now); + + // ---------- 季度 / ISO-8601 周数 ---------- + const year = now.getFullYear(); + const quarter = `Q${Math.ceil((now.getMonth() + 1) / 3)}`; + const isoDate = new Date(Date.UTC(year, now.getMonth(), now.getDate())); + const dayNum = (isoDate.getUTCDay() + 6) % 7; // 周一 = 0 + isoDate.setUTCDate(isoDate.getUTCDate() - dayNum + 3); // 本周周四 + const firstThursday = new Date(Date.UTC(isoDate.getUTCFullYear(), 0, 4)); + const week = 1 + Math.round((isoDate - firstThursday) / (7 * 24 * 3600 * 1000)); + + const discussionTitle = `[周报] ADI 项目 ${year}-${quarter} 周报汇总`; + + // ---------- 本周新建 Issues ---------- + // 注意:issues API 会把 PR 一并返回,需排除; + // since 参数按 updated_at 过滤,因此再按 created_at 精确过滤。 + const candidates = await github.paginate(github.rest.issues.listForRepo, { + owner, repo, state: 'all', + since: weekStart.toISOString(), + per_page: 100 + }); + const newIssues = candidates.filter(i => + !i.pull_request && new Date(i.created_at) >= weekStart + ); + + // ---------- 本周合并的 PR ---------- + // 注意:pulls.list 不支持 since 参数,需按 merged_at 客户端过滤。 + const closedPRs = await github.rest.pulls.list({ + owner, repo, state: 'closed', + sort: 'updated', direction: 'desc', per_page: 100 + }); + const mergedPRs = closedPRs.data.filter(p => + p.merged_at && new Date(p.merged_at) >= weekStart + ); + + // ---------- Discussions(仅 GraphQL API 可用) ---------- + let repoId = ''; + let categoryId = ''; + let existingDiscussionId = ''; + let newDiscussionCount = 0; + try { + const gql = await github.graphql( + `query($owner: String!, $name: String!) { + repository(owner: $owner, name: $name) { + id + discussionCategories(first: 25) { nodes { id name slug } } + discussions(first: 50, orderBy: {field: CREATED_AT, direction: DESC}) { + nodes { id number title createdAt } + } + } + }`, + { owner, name: repo } + ); + repoId = gql.repository.id; + const categories = gql.repository.discussionCategories.nodes; + // 优先使用 general 分类,否则退到第一个可用分类 + const general = categories.find(c => c.slug === 'general') || categories[0]; + categoryId = general ? general.id : ''; + const discussions = gql.repository.discussions.nodes; + newDiscussionCount = discussions.filter(d => + new Date(d.createdAt) >= weekStart + ).length; + const existing = discussions.find(d => d.title === discussionTitle); + if (existing) { + existingDiscussionId = existing.id; + console.log(`找到已有周报 Discussion: #${existing.number}`); + } + } catch (e) { + core.warning( + `无法访问 Discussions (${e.message})。` + + `请在仓库 Settings → General 中启用 Discussions,否则周报无法发布。` + ); + } + + const hasContent = + newIssues.length > 0 || mergedPRs.length > 0 || newDiscussionCount > 0; + + if (!hasContent) { + console.log('本周无新增内容,跳过周报生成。'); + core.setOutput('hasContent', 'false'); + core.setOutput('reportBody', ''); + core.setOutput('discussionTitle', discussionTitle); + core.setOutput('repoId', repoId); + core.setOutput('categoryId', categoryId); + core.setOutput('existingDiscussionId', existingDiscussionId); + return; + } + + // ---------- 生成报告正文 ---------- + const labelName = l => (typeof l === 'string' ? l : l.name); + const hasLabel = (i, name) => i.labels.some(l => labelName(l) === name); + + const newBugs = newIssues.filter(i => hasLabel(i, 'type/bug')); + const newSuggestions = newIssues.filter(i => + hasLabel(i, 'type/enhancement') || hasLabel(i, 'type/discussion') + ); + const newQuestions = newIssues.filter(i => hasLabel(i, 'type/question')); + const areaItems = newIssues.filter(i => + hasLabel(i, 'area/model-support') || hasLabel(i, 'area/webbridge') + ); + + function getPriority(labels) { + const p = labels.map(labelName).find(n => n.startsWith('priority/')); + return p ? p.split('/')[1] : '未指定'; + } + function getQuestionStatus(issue) { + if (issue.comments > 0) return '已回复'; + return issue.state === 'open' ? '待解答' : '已关闭'; + } + function getAreaLabel(labels) { + const a = labels.map(labelName).find(n => n.startsWith('area/')); + return a ? a.replace('area/', '') : '未分类'; + } + + const lines = []; + lines.push(`## ${year} 第 ${quarter} 季 第 ${week} 周(${weekStartStr} ~ ${weekEndStr})`, ''); + lines.push('### 本周概览', '', '| 指标 | 数量 |', '|------|------|'); + lines.push(`| 新增 Issues | ${newIssues.length} |`); + lines.push(`| 合并 PRs | ${mergedPRs.length} |`); + lines.push(`| 新增 Discussions | ${newDiscussionCount} |`, ''); + + lines.push('### Bug 报告'); + if (newBugs.length) { + newBugs.forEach(i => + lines.push(`- #${i.number} ${i.title}(优先级: ${getPriority(i.labels)})`)); + } else { + lines.push('(本周无新增 Bug 报告)'); + } + lines.push(''); + + lines.push('### 建议 & 增强'); + if (newSuggestions.length) { + newSuggestions.forEach(i => lines.push(`- #${i.number} ${i.title}`)); + } else { + lines.push('(本周无新增建议)'); + } + lines.push(''); + + lines.push('### 技术问题'); + if (newQuestions.length) { + newQuestions.forEach(i => + lines.push(`- #${i.number} ${i.title} — 状态: ${getQuestionStatus(i)}`)); + } else { + lines.push('(本周无新增技术问题)'); + } + lines.push(''); + + lines.push('### 模型支持 / Webbridge 进展'); + if (areaItems.length) { + areaItems.forEach(i => + lines.push(`- #${i.number} ${i.title}(${getAreaLabel(i.labels)})`)); + } else { + lines.push('(本周无模型支持或 Webbridge 相关更新)'); + } + lines.push(''); + + lines.push('### PR 合并'); + if (mergedPRs.length) { + mergedPRs.forEach(p => + lines.push(`- #${p.number} ${p.title}(作者: ${p.user.login})`)); + } else { + lines.push('(本周无合并 PR)'); + } + lines.push(''); + + lines.push('### 待跟进事项', '(如有需要手动跟进的事项,请在此列出)', ''); + lines.push('*周报由 GitHub Actions 自动生成,详情请查看对应 Issue / PR*'); + + core.setOutput('hasContent', 'true'); + core.setOutput('reportBody', lines.join('\n')); + core.setOutput('discussionTitle', discussionTitle); + core.setOutput('repoId', repoId); + core.setOutput('categoryId', categoryId); + core.setOutput('existingDiscussionId', existingDiscussionId); + + publish: + needs: collect + runs-on: ubuntu-latest + if: needs.collect.outputs.hasContent == 'true' + steps: + - name: 创建或追加周报 Discussion + uses: actions/github-script@v7 + env: + REPORT_BODY: ${{ needs.collect.outputs.reportBody }} + DISCUSSION_TITLE: ${{ needs.collect.outputs.discussionTitle }} + REPO_ID: ${{ needs.collect.outputs.repoId }} + CATEGORY_ID: ${{ needs.collect.outputs.categoryId }} + EXISTING_DISCUSSION_ID: ${{ needs.collect.outputs.existingDiscussionId }} + with: + script: | + const body = process.env.REPORT_BODY; + const title = process.env.DISCUSSION_TITLE; + const existingId = process.env.EXISTING_DISCUSSION_ID; + + if (existingId) { + // 追加到已有的季度周报 Discussion + const res = await github.graphql( + `mutation($id: ID!, $body: String!) { + addDiscussionComment(input: {discussionId: $id, body: $body}) { + comment { url } + } + }`, + { id: existingId, body } + ); + console.log(`已追加周报内容: ${res.addDiscussionComment.comment.url}`); + } else { + // 创建新的季度周报 Discussion + const repoId = process.env.REPO_ID; + const categoryId = process.env.CATEGORY_ID; + if (!repoId || !categoryId) { + core.setFailed( + '缺少 repositoryId 或 categoryId —— 请确认 Discussions 已启用且存在分类。' + ); + return; + } + const res = await github.graphql( + `mutation($repoId: ID!, $categoryId: ID!, $title: String!, $body: String!) { + createDiscussion(input: { + repositoryId: $repoId, categoryId: $categoryId, + title: $title, body: $body + }) { + discussion { number url } + } + }`, + { repoId, categoryId, title, body } + ); + console.log( + `已创建周报 Discussion #${res.createDiscussion.discussion.number}: ` + + res.createDiscussion.discussion.url + ); + } diff --git a/scripts/github-labels.json b/scripts/github-labels.json new file mode 100644 index 0000000..65f5f91 --- /dev/null +++ b/scripts/github-labels.json @@ -0,0 +1,16 @@ +[ + {"name": "type/bug", "color": "d73a4a", "description": "Bug 报告"}, + {"name": "type/enhancement", "color": "a8d692", "description": "新功能/增强"}, + {"name": "type/question", "color": "0066cc", "description": "技术问题"}, + {"name": "area/model-support", "color": "9b59b6", "description": "新模型支持相关"}, + {"name": "area/webbridge", "color": "f39c12", "description": "Webbridge 增强相关"}, + {"name": "area/build-system", "color": "7f8c8d", "description": "CMake/跨平台构建相关"}, + {"name": "area/cli-tools", "color": "1abc9c", "description": "CLI 工具改进相关"}, + {"name": "area/python-binding", "color": "e91e63", "description": "Python 绑定 (ort_sd_py_imp) 相关"}, + {"name": "area/engine", "color": "f1c40f", "description": "推理引擎核心相关"}, + {"name": "status/needs-investigation", "color": "f39c12", "description": "待调查"}, + {"name": "status/needs-design", "color": "e67e22", "description": "待设计决策"}, + {"name": "status/ready-for-dev", "color": "27ae60", "description": "可开发"}, + {"name": "status/in-progress", "color": "3498db", "description": "开发中"}, + {"name": "status/blocked", "color": "c0392b", "description": "阻塞"} +] diff --git a/scripts/import-labels.sh b/scripts/import-labels.sh new file mode 100644 index 0000000..c21ba40 --- /dev/null +++ b/scripts/import-labels.sh @@ -0,0 +1,61 @@ +#!/usr/bin/env bash +# 导入项目助手所需的 GitHub Labels +# 用法: bash scripts/import-labels.sh [owner/repo] +# 未提供参数时,依次尝试:git remote origin → 默认仓库 +# 要求: gh CLI 已认证 (gh auth status),且安装了 jq +# +# 幂等:gh label create --force 会在标签已存在时直接更新颜色与描述。 + +set -euo pipefail + +DEFAULT_REPO="Windsander/ADI-Stable-Diffusion" +LABELS_FILE="$(cd "$(dirname "$0")" && pwd)/github-labels.json" + +if ! command -v gh &> /dev/null; then + echo "错误: 需要 GitHub CLI (gh)。请先安装: https://cli.github.com/" + exit 1 +fi + +if ! command -v jq &> /dev/null; then + echo "错误: 需要 jq 解析 $LABELS_FILE。请先安装(如 brew install jq)。" + exit 1 +fi + +if [ ! -f "$LABELS_FILE" ]; then + echo "错误: 未找到 $LABELS_FILE" + exit 1 +fi + +# 仓库解析优先级:命令行参数 > git remote origin > 默认值 +REPO="${1:-}" +if [ -z "$REPO" ]; then + remote_url="$(git remote get-url origin 2>/dev/null || true)" + if [[ "$remote_url" =~ github\.com[:/]([^/]+/[^/.]+)(\.git)?$ ]]; then + REPO="${BASH_REMATCH[1]}" + else + REPO="$DEFAULT_REPO" + fi +fi + +echo "正在为 $REPO 创建/更新 Labels..." + +fail_count=0 +while read -r label; do + name="$(jq -r '.name' <<< "$label")" + color="$(jq -r '.color' <<< "$label")" + description="$(jq -r '.description' <<< "$label")" + + # --force:已存在则更新,不存在则创建 + if gh label create "$name" --repo "$REPO" --color "$color" --description "$description" --force; then + echo " ✓ $name" + else + echo " ✗ $name 创建/更新失败" >&2 + fail_count=$((fail_count + 1)) + fi +done < <(jq -c '.[]' "$LABELS_FILE") + +if [ "$fail_count" -gt 0 ]; then + echo "完成,但有 $fail_count 个标签失败,请检查 gh 认证与仓库权限。" + exit 1 +fi +echo "Labels 导入完成(共 $(jq length "$LABELS_FILE") 个)。"