Compare commits
2 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
1dd2921674 | ||
|
|
d30f7d08c5 |
@@ -1,139 +0,0 @@
|
||||
---
|
||||
description: 审查当前工作区未提交代码中的垃圾代码,并在不影响逻辑的前提下自动清理
|
||||
argument-hint: 可选:指定要检查的文件或目录(默认检查所有未提交修改)
|
||||
allowed-tools: ["Read", "Edit", "Bash", "Grep", "Glob"]
|
||||
---
|
||||
|
||||
# /cleanup — 垃圾代码审查与清理
|
||||
|
||||
分析当前工作区(git diff)中的未提交代码,找出并修复常见垃圾代码,**不得改变任何运行逻辑**。
|
||||
|
||||
## 检查范围
|
||||
|
||||
若 `$ARGUMENTS` 非空,则只检查指定文件/目录;否则检查所有未提交修改(`git diff HEAD`)。
|
||||
|
||||
## 节省上下文规则
|
||||
|
||||
优先用确定性的 CLI 检查缩小范围,不要一上来把完整文件或大 diff 读入上下文:
|
||||
|
||||
```bash
|
||||
git diff --name-only HEAD
|
||||
git diff --unified=0 HEAD -- <path>
|
||||
git diff --check
|
||||
rg -n "TODO|FIXME|console\.log|debugger|print\(" <changed-paths>
|
||||
```
|
||||
|
||||
只有 focused diff 不足以安全判断或修改时,才读取完整文件。
|
||||
|
||||
## 审查清单
|
||||
|
||||
按优先级检查以下问题(只报告在本次 diff 中**新增或修改**的代码里存在的问题):
|
||||
|
||||
### 1. 重复逻辑 (Duplicate Logic)
|
||||
- 完全相同或高度相似的代码块在多处出现
|
||||
- 同一函数/方法被多个地方各自实现,已有公共版本未被复用
|
||||
- 相同的 DOM 查询、正则、模板字符串在同一文件重复
|
||||
|
||||
### 2. Magic Numbers / Magic Strings
|
||||
- 裸数字直接参与计算(如偏移量、时间、尺寸、阈值),没有命名常量
|
||||
- 硬编码字符串(如 id 名、状态值、URL 片段)散落在逻辑中
|
||||
- 例外:`0`, `1`, `-1`, `100`, `""` 等语义明确的惯用值不算
|
||||
|
||||
### 3. 命名问题
|
||||
- 含义不明的缩写变量(如 `or_`, `tmp2`, `x2`)
|
||||
- 命名与实际用途不符
|
||||
- 同一概念在不同地方用不同名字表达
|
||||
|
||||
### 4. 死代码 / 无效代码
|
||||
- 注释掉的旧代码块(3行以上)
|
||||
- 声明后从未使用的变量/参数/导入
|
||||
- 永远不会执行的条件分支
|
||||
|
||||
### 5. 代码风格问题
|
||||
- 尾部空白字符(trailing whitespace)
|
||||
- 同一文件内风格不一致(如混用单双引号、缩进不统一)
|
||||
- 空行使用不一致(连续多个空行等)
|
||||
|
||||
### 6. 其他常见问题
|
||||
- 私有辅助函数应被 export 但没有,导致调用方重复实现
|
||||
- 类型/接口重复定义
|
||||
- 过于冗长的条件表达式可以简化(不改逻辑)
|
||||
|
||||
## 执行步骤
|
||||
|
||||
### Step 1 — 获取待检查文件列表
|
||||
|
||||
```bash
|
||||
# 无参数时:获取所有未提交修改
|
||||
git diff HEAD --name-only
|
||||
|
||||
# 有参数时:用 $ARGUMENTS 过滤
|
||||
```
|
||||
|
||||
### Step 2 — 逐文件阅读并分析
|
||||
|
||||
先从 focused diff 开始:
|
||||
|
||||
```bash
|
||||
git diff --unified=0 HEAD -- <file>
|
||||
```
|
||||
|
||||
用 `rg`、`git diff --check`、编译器或 linter 输出确认确定性问题。只有需要上下文时才用 Read 读取完整文件。对照审查清单,记录每个问题:文件名、行号、问题类型、建议修复方式。
|
||||
|
||||
### Step 3 — 报告问题清单
|
||||
|
||||
在修改前,先以列表形式输出所有发现的问题:
|
||||
|
||||
```
|
||||
发现 N 个问题:
|
||||
|
||||
[文件] js/foo.js
|
||||
· L34, L78: 重复逻辑 — 两处都实现了相同的 DOM 查询,可提取到 getPanel()
|
||||
· L91: Magic number — 硬编码 14 作为偏移量,应命名为 TOOLTIP_OFFSET
|
||||
|
||||
[文件] js/bar.js
|
||||
· L12: 命名问题 — 变量 `or_` 语义不明,应命名为 outerR/outerG/outerB
|
||||
...
|
||||
```
|
||||
|
||||
如果没有发现问题,直接输出"未发现垃圾代码,当前代码质量良好。"并停止。
|
||||
|
||||
### Step 4 — 执行修复
|
||||
|
||||
对每个问题,使用 Edit 工具进行**最小化修改**:
|
||||
|
||||
- **重复逻辑**:提取为共享常量/函数,更新所有调用点
|
||||
- **Magic number**:在文件顶部或逻辑附近声明 `const NAME = value`,替换所有引用
|
||||
- **命名问题**:重命名变量,更新所有使用处
|
||||
- **死代码**:直接删除
|
||||
- **尾部空白/风格**:修正
|
||||
- **未 export 的函数**:添加 `export`,在调用方改为导入(不重复实现)
|
||||
|
||||
**修复原则:**
|
||||
- 只改在审查清单中发现的问题,不做额外优化
|
||||
- 每次 Edit 只修改确实有问题的行,保持 diff 最小
|
||||
- 改完后用 `grep` 验证旧的坏代码已消失
|
||||
- 优先做精确补丁;只有仓库已有对应格式化流程时,才运行格式化工具
|
||||
|
||||
### Step 5 — 输出总结
|
||||
|
||||
```
|
||||
清理完成:
|
||||
|
||||
修复了 N 个问题:
|
||||
✓ earth.js — 提取重复 vertexShader 为 ATMOS_VERTEX_SHADER 常量
|
||||
✓ main.js — 提取 TOOLTIP_CURSOR_OFFSET = 14(4处引用)
|
||||
✓ controls.js — export updateLayerButtonState,移除 main.js 中的重复实现
|
||||
...
|
||||
|
||||
未修改的问题(需人工确认):
|
||||
! foo.js L45 — 注释代码块较长,建议手动确认是否可删除
|
||||
```
|
||||
|
||||
## 约束
|
||||
|
||||
- **禁止**改变函数签名、接口定义、导出 API(除非问题正是私有函数应被 export)
|
||||
- **禁止**添加新功能、新抽象、新参数
|
||||
- **禁止**修改注释内容(只删除注释掉的死代码)
|
||||
- **禁止**修改测试文件逻辑
|
||||
- 如果一个 Magic number 的语义不完全确定,**跳过**,在总结中标记为"需人工确认"
|
||||
@@ -1,104 +0,0 @@
|
||||
---
|
||||
description: Create or update repository documentation from current code changes
|
||||
argument-hint: Optional: topic to document, or leave empty to infer from git diff
|
||||
allowed-tools: ["Read", "Edit", "Write", "Bash", "Glob", "Grep"]
|
||||
---
|
||||
|
||||
# /docs — Documentation Workflow
|
||||
|
||||
## Goal
|
||||
|
||||
Create or update documentation that explains why a change exists, how it behaves, and what maintainers need to know. Keep this command generic. Repository-specific coverage rules live in the repository and must be loaded separately.
|
||||
|
||||
## Repository Rules
|
||||
|
||||
Before deciding scope, check whether the repository has a documentation rules file:
|
||||
|
||||
```bash
|
||||
test -f docs/documentation-coverage-rules.md && sed -n '1,240p' docs/documentation-coverage-rules.md
|
||||
```
|
||||
|
||||
If it exists, apply it as the project-specific coverage checklist. If it does not exist, continue with the generic workflow below.
|
||||
|
||||
## Workflow
|
||||
|
||||
### Step 1 — Understand The Change
|
||||
|
||||
```bash
|
||||
git diff HEAD --stat
|
||||
git diff HEAD --name-only
|
||||
git log --oneline -10
|
||||
rg --files docs
|
||||
```
|
||||
|
||||
If `$ARGUMENTS` specifies a topic, focus on that topic. Otherwise infer the documentation topic from the changed files. Do not read the full repository diff by default; inspect focused files only:
|
||||
|
||||
```bash
|
||||
git diff HEAD -- <path>
|
||||
rg -n "class |def |function |export |router|@router|interface |type " <path>
|
||||
```
|
||||
|
||||
### Step 2 — Decide Scope
|
||||
|
||||
- Prefer updating an existing relevant document over creating a duplicate.
|
||||
- Use one document for one coherent topic.
|
||||
- Split documents only when the change crosses meaningful domains.
|
||||
- Keep filenames lowercase and hyphenated.
|
||||
- Apply the repository-specific rules file before writing.
|
||||
|
||||
#### Document Audience Routing (Planet)
|
||||
|
||||
In this repository, classify the action's performer before picking a target file:
|
||||
|
||||
- Browser/UI end user → `docs/technical/{zh,en}/manual.md` or `quickstart.md`.
|
||||
- Shell / Docker / log paths / `planet.sh` / SMTP fallbacks / port forwarding → `docs/technical/{zh,en}/ops-runbook.md` (or an existing `ops-*.md`).
|
||||
- Second-party developers → existing `*-context.md` / `backend-*.md` / `earth-*.md` files.
|
||||
|
||||
Never put shell commands, log paths, or Docker operations into `manual.md` / `quickstart.md`. Never put UI button labels or screenshots into `ops-*.md`. When the same action has both a UI and a CLI path, write each in its own home and cross-link them with one sentence.
|
||||
|
||||
For ambiguous or large documentation changes, briefly state the intended doc plan before editing. For clear small changes, proceed directly.
|
||||
|
||||
### Step 3 — Write
|
||||
|
||||
Explain:
|
||||
|
||||
- Background/problem: what was wrong or missing before.
|
||||
- Core design decisions and rationale.
|
||||
- Operational or user-facing impact.
|
||||
- Relevant code paths, only when useful for future maintainers.
|
||||
|
||||
Style:
|
||||
|
||||
- Follow the repository’s existing language and heading conventions.
|
||||
- Use fenced code blocks with language tags.
|
||||
- Prefer tables for comparisons or parameter lists.
|
||||
- Keep snippets concise and relevant.
|
||||
- For UI labels, chart labels, feature names, datasource names, and other terms that may become mixed Chinese/English copy, check `docs/technical/{zh,en}/naming-glossary.md` and use the documented display name. If a confusing term is missing, update the glossary in both languages as part of the docs change.
|
||||
|
||||
### Step 4 — Verify
|
||||
|
||||
- Read the completed docs once for clarity and stale statements.
|
||||
- Verify referenced paths exist with `test -e` or `rg --files`.
|
||||
- Run applicable checks from `docs/documentation-coverage-rules.md`.
|
||||
- Check Markdown links use readable user-facing titles unless repository rules allow otherwise.
|
||||
|
||||
### Step 5 — Report
|
||||
|
||||
Summarize changed docs and verification:
|
||||
|
||||
```md
|
||||
Updated:
|
||||
- path/to/doc.md — what changed
|
||||
|
||||
Verified:
|
||||
- checks that passed
|
||||
- checks that could not be run, if any
|
||||
```
|
||||
|
||||
## Hard Constraints
|
||||
|
||||
- Do not leave placeholder docs.
|
||||
- Do not duplicate bilingual files byte-for-byte.
|
||||
- Do not reference PR numbers, issue numbers, or the current conversation unless explicitly requested.
|
||||
- Do not write changelog-style lists without the reasoning and tradeoffs behind the change.
|
||||
- Keep docs maintainable and concise.
|
||||
@@ -1,93 +0,0 @@
|
||||
---
|
||||
description: 用 goal-driven 方法推动一个复杂任务持续执行,直到明确成功标准被满足
|
||||
argument-hint: 建议填写任务目标;若同时给出成功标准更好
|
||||
allowed-tools: ["Read", "Edit", "Bash", "Grep", "Glob"]
|
||||
---
|
||||
|
||||
# /goal-driven — 目标驱动执行模式
|
||||
|
||||
使用 `lidangzzz/goal-driven` 的核心思想来推进复杂任务:先固定目标与成功标准,再持续执行和反复验收,直到标准真正满足。
|
||||
|
||||
适用场景:
|
||||
|
||||
- 长周期实现任务
|
||||
- 高复杂度工程任务
|
||||
- 可被明确验收的研究、实现、迁移、验证类工作
|
||||
|
||||
不适用场景:
|
||||
|
||||
- 纯脑暴
|
||||
- 无法定义成功标准的模糊任务
|
||||
- 很小的一次性修改
|
||||
|
||||
## 输入要求
|
||||
|
||||
若 `$ARGUMENTS` 只包含目标,没有成功标准,先补全一版可执行的成功标准再开始。
|
||||
|
||||
启动时先输出:
|
||||
|
||||
```md
|
||||
Goal
|
||||
- ...
|
||||
|
||||
Criteria for success
|
||||
- ...
|
||||
|
||||
Plan
|
||||
1. ...
|
||||
2. ...
|
||||
3. ...
|
||||
|
||||
Verification
|
||||
- ...
|
||||
```
|
||||
|
||||
## 执行规则
|
||||
|
||||
1. 先把任务固化为两个核心块:
|
||||
- `Goal`
|
||||
- `Criteria for success`
|
||||
|
||||
2. 成功标准必须尽量客观,可验证,可落地。
|
||||
优先写成:
|
||||
- 需要交付什么
|
||||
- 需要通过哪些测试或验证
|
||||
- 如何判断结果真的完成
|
||||
|
||||
3. 进入持续执行循环:
|
||||
- 完成一个阶段
|
||||
- 检查当前结果是否满足成功标准
|
||||
- 若未满足,明确剩余差距并继续推进
|
||||
|
||||
4. 任何“完成了”“差不多了”“已实现”之类的结论,都必须经过验证,不能直接接受。
|
||||
|
||||
5. 如果验证失败:
|
||||
- 明确指出哪条成功标准没满足
|
||||
- 继续工作,不要把阶段性进展误判为完成
|
||||
|
||||
6. 只有在以下情况之一才能停止:
|
||||
- 成功标准已满足
|
||||
- 用户明确要求停止
|
||||
|
||||
## 执行风格
|
||||
|
||||
- 重证据,轻口头判断
|
||||
- 优先使用确定性工具证据:`rg`、`git diff --stat`、`git diff -- <path>`、测试、构建、lint、`curl`、数据库查询等能直接证明成功标准的方式
|
||||
- 不把大段命令输出粘进回复;保留在工具调用里,回复只总结关键证据
|
||||
- 重验收,轻自我感觉
|
||||
- 优先用测试、日志、产物、对比结果来证明完成
|
||||
- 对长期任务保持“未达标就继续”的节奏
|
||||
|
||||
## 简版模板
|
||||
|
||||
```md
|
||||
Goal: [[[[[在此填写最终目标]]]]]
|
||||
|
||||
Criteria for success: [[[[[在此填写成功标准]]]]]
|
||||
|
||||
循环执行:
|
||||
1. 推进任务
|
||||
2. 检查是否满足成功标准
|
||||
3. 若未满足,继续工作
|
||||
4. 直到满足标准或用户明确停止
|
||||
```
|
||||
@@ -1,160 +0,0 @@
|
||||
---
|
||||
description: 发版工作流:根据变更类型决定版本号,更新所有版本文件和 changelog,运行验证,commit 并 push
|
||||
argument-hint: 可选:feature | bugfix | 或直接描述本次发布内容
|
||||
allowed-tools: ["Read", "Edit", "Bash", "Glob", "Grep"]
|
||||
---
|
||||
|
||||
# /release — Planet 发版工作流
|
||||
|
||||
## 版本号规则
|
||||
|
||||
| 变更类型 | 版本跳动 | 适用场景 |
|
||||
|---------|---------|---------|
|
||||
| `feature` | `+0.1.0` | 纯新功能,无 bugfix |
|
||||
| `improvement` | `+0.0.1` | UI 调整、小功能增强、bugfix 混合,或以 UI/体验改进为主的迭代 |
|
||||
| `bugfix` | `+0.0.1` | 纯 bug 修复,无新功能 |
|
||||
| `docs` / `maintenance` / `refactor` | 默认不发版,除非用户明确要求 |
|
||||
|
||||
意图混合时以用户明确描述为准;bugfix + 小 feature 混合默认判定为 `improvement`(`+0.0.1`)。
|
||||
|
||||
## 必须同步更新的文件
|
||||
|
||||
使用 `git rev-parse --show-toplevel` 获取仓库根目录,以下路径均相对于根目录:
|
||||
|
||||
- `VERSION`
|
||||
- `frontend/package.json`(`"version"` 字段)
|
||||
- `pyproject.toml`(`version =` 字段)
|
||||
- `uv.lock`(**不要手动编辑**,通过 `uv lock` 重新生成)
|
||||
- `docs/CHANGELOG.md`
|
||||
- `docs/version-history.md`
|
||||
|
||||
## 节省上下文规则
|
||||
|
||||
发版判断应以确定性 CLI 证据为主,优先使用紧凑命令和定点读取:
|
||||
|
||||
```bash
|
||||
git status --short
|
||||
git diff --stat HEAD
|
||||
git diff --name-only HEAD
|
||||
rg -n "version|^## |^Released:|当前开发版本|current" VERSION frontend/package.json pyproject.toml docs/CHANGELOG.md docs/version-history.md
|
||||
```
|
||||
|
||||
除非需要判断某个代码变更是否属于本次发版,否则不要读取完整 diff。
|
||||
|
||||
## 执行步骤
|
||||
|
||||
### Step 1 — 环境检查
|
||||
|
||||
```bash
|
||||
git branch --show-current # 确认在 dev 分支
|
||||
git status --short # 检查是否有无关的未暂存修改
|
||||
cat VERSION # 读取当前版本
|
||||
```
|
||||
|
||||
若当前**不在 `dev` 分支**,停下来告知用户,不要继续。
|
||||
|
||||
若存在无关的未暂存修改,列出并询问用户是否一并提交,或先 stash。
|
||||
|
||||
### Step 2 — 确定发版类型与新版本号
|
||||
|
||||
- 若 `$ARGUMENTS` 提供了明确类型(`feature` / `bugfix`),直接使用
|
||||
- 否则根据 `git diff --stat HEAD`、`git diff --name-only HEAD`、必要的 focused diff 和 `git log` 推断
|
||||
- 计算新版本号(例:`0.26.2` → bugfix → `0.26.3`)
|
||||
- **先输出发版计划供用户确认**:
|
||||
|
||||
```
|
||||
发版计划:
|
||||
类型:bugfix
|
||||
版本:0.26.2 → 0.26.3
|
||||
分支:dev
|
||||
将更新:VERSION, frontend/package.json, pyproject.toml, uv.lock, CHANGELOG.md, version-history.md
|
||||
```
|
||||
|
||||
### Step 3 — 更新版本号文件
|
||||
|
||||
按顺序更新(每步用 Edit 工具,精确替换,不要重写整个文件):
|
||||
|
||||
1. `VERSION` — 直接替换全部内容为新版本号
|
||||
2. `frontend/package.json` — 替换 `"version": "x.x.x"` 行
|
||||
3. `pyproject.toml` — 替换 `version = "x.x.x"` 行
|
||||
4. 运行 `uv lock` 重新生成 `uv.lock`(在仓库根目录下执行)
|
||||
|
||||
### Step 4 — 更新 CHANGELOG.md
|
||||
|
||||
在文件顶部插入新条目,格式:
|
||||
|
||||
```markdown
|
||||
## [x.x.x] — YYYY-MM-DD
|
||||
|
||||
### ✨ Features / 🐛 Fixes / 🔧 Improvements
|
||||
- ...(只列高信号条目,最多 5 条)
|
||||
- ...
|
||||
|
||||
---
|
||||
```
|
||||
|
||||
日期使用 `date +%Y-%m-%d` 获取今天的日期。
|
||||
|
||||
### Step 5 — 更新 docs/version-history.md
|
||||
|
||||
- 更新文件头部的"当前开发版本"字段
|
||||
- 在时间线表格顶部插入新行:`| vx.x.x | YYYY-MM-DD | 一句话摘要 |`
|
||||
|
||||
### Step 6 — 验证
|
||||
|
||||
针对本次变更范围做最小验证:
|
||||
|
||||
- Python 文件有修改:先用 `git diff --name-only HEAD -- '*.py'` 列出,再运行 `python3 -m py_compile <changed_files>`
|
||||
- Frontend 文件有修改:先用 `git diff --name-only HEAD -- frontend` 判断范围,再运行项目标准检查(若无则跳过并说明)
|
||||
- 版本号一致性检查:用 grep 确认 VERSION、package.json、pyproject.toml 中的版本号完全一致
|
||||
|
||||
```bash
|
||||
cat VERSION
|
||||
rg -n "\"version\":|^version =|version = " frontend/package.json pyproject.toml uv.lock
|
||||
```
|
||||
|
||||
### Step 7 — 提交前预览
|
||||
|
||||
展示将要提交的文件列表:
|
||||
|
||||
```bash
|
||||
git diff --stat HEAD
|
||||
```
|
||||
|
||||
再次确认所有必须文件都在变更列表中,**不包含**非预期文件(如调试文件、.env 等)。
|
||||
|
||||
### Step 8 — Commit & Push(用户确认后)
|
||||
|
||||
```bash
|
||||
git add VERSION frontend/package.json pyproject.toml uv.lock docs/CHANGELOG.md docs/version-history.md
|
||||
# 若有代码变更也一并 stage
|
||||
git add <code_files>
|
||||
|
||||
git commit -m "release: bump version to x.x.x"
|
||||
git tag vx.x.x
|
||||
git push origin dev
|
||||
git push origin vx.x.x
|
||||
```
|
||||
|
||||
commit message 固定格式:`release: bump version to x.x.x`
|
||||
|
||||
### Step 9 — 完成确认
|
||||
|
||||
输出摘要:
|
||||
|
||||
```
|
||||
✓ 版本号已更新:0.26.2 → 0.26.3
|
||||
✓ CHANGELOG 已更新
|
||||
✓ version-history 已更新
|
||||
✓ uv.lock 已重新生成
|
||||
✓ 验证通过
|
||||
✓ commit: release: bump version to 0.26.3
|
||||
✓ tag: v0.26.3
|
||||
✓ 已 push 到 origin/dev
|
||||
```
|
||||
|
||||
## 注意事项
|
||||
|
||||
- `uv.lock` 只能通过 `uv lock` 生成,绝不手动编辑
|
||||
- 发版 commit 只包含版本文件 + 本次功能代码,不混入无关改动
|
||||
- 若环境中 `uv` 不可用,说明原因并跳过 lockfile 更新,提醒用户手动运行
|
||||
302
AGENTS.md
302
AGENTS.md
@@ -1,6 +1,6 @@
|
||||
# AGENTS.md
|
||||
|
||||
**AI Agent 角色设定。定义 AI 如何行为、沟通和工作。**
|
||||
**Planet agent harness. Defines behavior for coding agents working in this repository.**
|
||||
|
||||
---
|
||||
|
||||
@@ -10,26 +10,32 @@ This file is the single authoritative agent guide for the Planet repository.
|
||||
The older lowercase `agents.md` entry has been merged here so coding agents and
|
||||
harness tools use one source of truth.
|
||||
|
||||
### Read First
|
||||
### Source Of Truth
|
||||
|
||||
- `rules.md` is the mandatory repository rule source. Always load `core`,
|
||||
`security`, and `workflow`; load only task-relevant modules after that.
|
||||
- `AGENTS.md` defines the local agent operating mode and evidence gates.
|
||||
- `project_context.md` is background, not a rule source. Prefer newer
|
||||
implementation docs when it disagrees with current code.
|
||||
- `.codex/skills/` is the active specialized workflow layer for cleanup, docs,
|
||||
goal-driven work, and release.
|
||||
- Do not duplicate long workflow text across harness files. Durable constraints
|
||||
belong in `rules.md`; task procedures belong in skills or scripts.
|
||||
|
||||
Read these files before changing code:
|
||||
|
||||
1. `rules.md` - mandatory repository rules. Always load `core`, `security`, and
|
||||
`workflow`; load `docs`, `uiux`, `frontend`, `backend`, `earth`, `ai`, or
|
||||
`release` when the task touches those areas.
|
||||
2. `AGENTS.md` - this file, including role, communication, workflow, and
|
||||
harness compatibility guidance.
|
||||
3. `project_context.md` - static project background. Prefer newer
|
||||
implementation docs when this context disagrees with current code.
|
||||
4. `README.md` - current architecture, startup, and toolchain summary.
|
||||
5. `docs/HARNESS.md` - harness workflow, conflict policy, and validation tiers.
|
||||
6. `CODEMAP.md` - codebase entry points, ownership boundaries, and deeper docs.
|
||||
1. `rules.md`
|
||||
2. `AGENTS.md`
|
||||
3. `project_context.md`
|
||||
4. `README.md`
|
||||
5. `docs/HARNESS.md`
|
||||
6. `CODEMAP.md`
|
||||
|
||||
For documentation work, also read `docs/documentation-coverage-rules.md`.
|
||||
|
||||
### Start Safely
|
||||
|
||||
Before editing:
|
||||
Before broad edits:
|
||||
|
||||
```bash
|
||||
git status --short
|
||||
@@ -45,11 +51,6 @@ git diff --name-only HEAD
|
||||
git diff --unified=0 HEAD -- <path>
|
||||
```
|
||||
|
||||
When the user provides screenshots or images, inspect the actual image before
|
||||
making visual claims. If the referenced path is missing, search alternate
|
||||
attachment/worktree/local locations or ask for the file; never guess the image
|
||||
content from text, filenames, or memory.
|
||||
|
||||
Preserve user changes already present in the worktree.
|
||||
|
||||
### Validation
|
||||
@@ -66,8 +67,9 @@ Full local validation:
|
||||
scripts/harness/validate.sh
|
||||
```
|
||||
|
||||
`validate.sh` includes the quick check and the frontend Bun build. Docker image
|
||||
smoke builds are intentionally opt-in:
|
||||
`validate.sh` includes quick checks, frontend Bun build, and frontend smoke
|
||||
unless disabled by its documented environment flags. Docker image smoke builds
|
||||
are intentionally opt-in:
|
||||
|
||||
```bash
|
||||
PLANET_HARNESS_DOCKER_SMOKE=1 scripts/harness/validate.sh
|
||||
@@ -108,229 +110,71 @@ behavior and document the compatibility note in `docs/harness-audit.md` or
|
||||
|
||||
---
|
||||
|
||||
## Identity
|
||||
## Operating Mode
|
||||
|
||||
You are **opencode**, an AI coding assistant specialized in enterprise-level systems.
|
||||
|
||||
You are working on the **智能星球计划 (Intelligent Planet Plan)** - a situational awareness system for data-centric competition featuring:
|
||||
- Python FastAPI backend
|
||||
- React admin console, public Docs UI, and browser Web Earth shell
|
||||
- AI Provider model gateway
|
||||
- Multi-source data collection
|
||||
- Future physical display directions such as UE5 / Cesium remain optional
|
||||
roadmap work, not the active local development loop
|
||||
- Default to acting directly when the user gives a clear task.
|
||||
- Ask before acting only when the missing decision is risky, cannot be
|
||||
discovered from repository context, and no conservative assumption is safe.
|
||||
- Read relevant files before editing.
|
||||
- Prefer focused CLI evidence: `rg`, `git diff --stat`, `git diff --name-only`,
|
||||
focused file reads, tests, builds, linters, and harness scripts.
|
||||
- Keep changes scoped to the requested area. Do not mix cleanup, feature work,
|
||||
release work, and documentation unless the task requires it.
|
||||
|
||||
---
|
||||
|
||||
## Communication Style
|
||||
## Evidence Gates
|
||||
|
||||
### Tone
|
||||
- **Professional but concise**
|
||||
- Technical accuracy with clarity
|
||||
- No unnecessary verbosity
|
||||
- Use code comments sparingly (explain **why**, not **what**)
|
||||
|
||||
### When Responding
|
||||
1. **Answer directly** - 1-3 sentences for simple questions
|
||||
2. **Use code blocks** for all code snippets
|
||||
3. **Include file:line_number** references when discussing code
|
||||
4. **Never** start with "I am an AI assistant" or similar phrases
|
||||
5. **Never** add unnecessary preambles/postambles
|
||||
|
||||
### Examples
|
||||
|
||||
**Good:**
|
||||
```
|
||||
GPU clusters are stored in `backend/app/services/collectors/top500.py:45`.
|
||||
```
|
||||
|
||||
**Bad:**
|
||||
```
|
||||
Based on the information you provided, I can see that the GPU clusters are stored in the top500.py file at line 45. Let me explain more about this...
|
||||
```
|
||||
- Visual inputs are blocking evidence. If the user provides a screenshot, image,
|
||||
mock, browser capture, or visual reference, obtain evidence from the artifact
|
||||
before interpreting intent or editing code.
|
||||
- Path resolution is part of the task. If the path cannot be opened, first try
|
||||
reasonable local equivalents such as WSL/Windows path conversion,
|
||||
workspace-relative lookup, absolute paths, and attached-file locations.
|
||||
- Never guess from prompt text, filenames, previous context, logs, OCR, or
|
||||
memory when a visual artifact was provided but cannot be accessed.
|
||||
- OCR is acceptable evidence for text-only visual questions or non-multimodal
|
||||
environments; state that OCR was used as the fallback. Layout, color, spacing,
|
||||
pixel, and rendering issues need real visual inspection or a clear limitation
|
||||
note.
|
||||
- If a visual artifact still cannot be inspected, say so and pause that
|
||||
visual-dependent part of the work.
|
||||
- Claims of completion need evidence: a relevant test, build, lint, screenshot,
|
||||
diff, direct file check, or harness result.
|
||||
- For UI and rendering changes, verify the rendered result when local tooling
|
||||
allows it.
|
||||
|
||||
---
|
||||
|
||||
## Operational Mode
|
||||
## Communication
|
||||
|
||||
### Plan Mode (default for complex tasks)
|
||||
- Analyze requirements
|
||||
- Propose architecture
|
||||
- Confirm with user before execution
|
||||
- **DO NOT** write code until approved
|
||||
|
||||
### Build Mode (after user approval)
|
||||
- Execute the approved plan
|
||||
- Write code, run commands
|
||||
- Verify results
|
||||
- Report completion concisely
|
||||
|
||||
### Read-Only Mode
|
||||
- Analyze code
|
||||
- Explain functionality
|
||||
- Answer questions
|
||||
- **DO NOT** modify files
|
||||
- Match the user's language. Use Chinese for Chinese requests unless the user
|
||||
asks otherwise.
|
||||
- Keep updates short and specific: what is being inspected, edited, or verified.
|
||||
- Final responses should summarize changed files and verification, with blockers
|
||||
stated plainly.
|
||||
- Use file references with line numbers when explaining code or review findings.
|
||||
|
||||
---
|
||||
|
||||
## Decision Framework
|
||||
## Quality Bar
|
||||
|
||||
### When to Ask Before Acting
|
||||
- Unclear requirements
|
||||
- Multiple implementation approaches
|
||||
- Architecture changes
|
||||
- Dependency additions
|
||||
- Anything that could break existing functionality
|
||||
|
||||
### When to Act Directly
|
||||
- Clear, approved requirements
|
||||
- Routine tasks (linting, formatting, running tests)
|
||||
- Following established patterns
|
||||
- Fixing obvious bugs
|
||||
|
||||
### When to Refuse
|
||||
- Malicious code requests
|
||||
- Security violations (secrets, credentials)
|
||||
- Anything that violates `rules.md`
|
||||
- Prefer existing project patterns over new abstractions.
|
||||
- Remove stale branches, mocks, compatibility paths, and duplicated helpers once
|
||||
a stable path exists.
|
||||
- Centralize prompts, constants, defaults, and shared request/response handling.
|
||||
- Do not add secrets, generated runtime output, or local environment files.
|
||||
- Frontend commands use Bun only. Do not use `npm`, `pnpm`, or `yarn`.
|
||||
- Run the smallest relevant verification for the changed scope and report
|
||||
anything skipped.
|
||||
|
||||
---
|
||||
|
||||
## Working Principles
|
||||
## Prohibited
|
||||
|
||||
### 1. First Understand, Then Act
|
||||
- Read relevant files before editing
|
||||
- Understand existing patterns and conventions
|
||||
- Follow the code style in the codebase
|
||||
- Match the project's technology choices
|
||||
|
||||
### 2. Incremental Progress
|
||||
- Break large tasks into smaller PRs
|
||||
- Complete one feature before starting the next
|
||||
- Run tests after each significant change
|
||||
- Commit frequently with clear messages
|
||||
|
||||
### 3. Quality First
|
||||
- Write tests for new functionality
|
||||
- Run linters before committing
|
||||
- Fix warnings, don't ignore them
|
||||
- Document non-obvious decisions
|
||||
|
||||
### 4. Communication Clarity
|
||||
- Use precise technical language
|
||||
- Show relevant code, not explanations
|
||||
- Report errors with context
|
||||
- Confirm understanding of requirements
|
||||
|
||||
---
|
||||
|
||||
## Code Review Checklist
|
||||
|
||||
Before marking a task complete:
|
||||
|
||||
- [ ] Code follows `rules.md` style guidelines
|
||||
- [ ] Type hints are correct and complete
|
||||
- [ ] Error handling is proper (no silent failures)
|
||||
- [ ] Tests pass locally
|
||||
- [ ] Linting passes
|
||||
- [ ] No TODO comments left behind
|
||||
- [ ] Documentation updated if needed
|
||||
- [ ] Commit message is clear
|
||||
|
||||
---
|
||||
|
||||
## Common Workflows
|
||||
|
||||
### Feature Development
|
||||
```
|
||||
1. Understand requirements
|
||||
2. Check existing patterns in codebase
|
||||
3. Design solution (brief mental model)
|
||||
4. Write code following rules.md
|
||||
5. Write/run tests
|
||||
6. Lint and format
|
||||
7. Commit with clear message
|
||||
8. Report completion
|
||||
```
|
||||
|
||||
### Bug Fix
|
||||
```
|
||||
1. Reproduce the bug (write failing test)
|
||||
2. Locate the source
|
||||
3. Fix the issue
|
||||
4. Verify test passes
|
||||
5. Check for regressions
|
||||
6. Commit fix
|
||||
```
|
||||
|
||||
### Refactoring
|
||||
```
|
||||
1. Understand current behavior
|
||||
2. Design target state
|
||||
3. Make incremental changes
|
||||
4. Preserve tests
|
||||
5. Verify functionality
|
||||
6. Clean up dead code
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Special Considerations
|
||||
|
||||
### WebSocket Services
|
||||
- Implement heartbeat mechanism (30-second intervals)
|
||||
- Handle disconnection gracefully
|
||||
- Include camera position in control frames
|
||||
- Support both update and full sync modes
|
||||
|
||||
### Data Collectors
|
||||
- Inherit from BaseCollector
|
||||
- Implement fetch() and transform() methods
|
||||
- Support incremental updates
|
||||
- Handle API changes gracefully
|
||||
|
||||
### UE5 Integration
|
||||
- Communicate via WebSocket
|
||||
- Send data frames at configurable intervals (default 5 min)
|
||||
- Support auto-cruise and manual modes
|
||||
- Optimize for 4K@120Hz rendering
|
||||
|
||||
### Multi-User Security
|
||||
- JWT tokens with 15-minute expiration
|
||||
- Redis token blacklist for logout
|
||||
- Role-based access control (RBAC)
|
||||
- Audit logging for all actions
|
||||
|
||||
---
|
||||
|
||||
## Output Format
|
||||
|
||||
### When Writing Code
|
||||
```python
|
||||
# File: backend/app/services/collectors/top500.py
|
||||
from typing import List, Dict
|
||||
|
||||
class TOP500Collector:
|
||||
async def fetch(self) -> List[Dict]:
|
||||
...
|
||||
```
|
||||
|
||||
### When Explaining
|
||||
- Use concise paragraphs
|
||||
- Include code references
|
||||
- No conversational filler
|
||||
|
||||
### When Reporting Progress
|
||||
- What was done
|
||||
- What remains
|
||||
- Any blockers
|
||||
- Next action
|
||||
|
||||
---
|
||||
|
||||
## Remember
|
||||
|
||||
1. **Rules are hard constraints** - follow `rules.md` absolutely
|
||||
2. **Context provides understanding** - use `project_context.md` for background
|
||||
3. **Role defines behavior** - follow `AGENTS.md` for how to work
|
||||
4. **Quality over speed** - Enterprise systems require precision
|
||||
5. **Communicate clearly** - Precision in, precision out
|
||||
- Do not skip visual evidence handling when a visual artifact was provided.
|
||||
- Do not preserve obsolete harness files just because they already exist.
|
||||
- Do not invent behavior not present in code, docs, or verified external
|
||||
sources.
|
||||
- Do not rewrite unrelated files during cleanup.
|
||||
- Do not mark a task complete without checking concrete success criteria.
|
||||
|
||||
@@ -8,6 +8,39 @@ This project follows the repository versioning rule:
|
||||
- `improvement` -> `+0.0.1`(bugfix + 小功能混合)
|
||||
- `bugfix` -> `+0.0.1`
|
||||
|
||||
## [0.74.2] — 2026-07-01
|
||||
|
||||
Released: 2026-07-01
|
||||
|
||||
### Highlights
|
||||
- 收敛 agent harness 到 `rules.md`、`AGENTS.md`、`docs/HARNESS.md` 和 `.codex/skills/`,删除重复维护的旧 Claude command 入口。
|
||||
- 强化视觉证据规则:截图或视觉引用路径打不开时必须先处理 WSL/Windows 路径、相对路径和附件位置,而不是跳过后猜测。
|
||||
- 明确 OCR 可作为文本类视觉证据或非多模态环境 fallback,同时要求布局、颜色、像素和渲染类问题保留真实视觉验证或明确限制说明。
|
||||
|
||||
### Added / Fixed / Improved
|
||||
- `AGENTS.md` 替换旧 opencode/默认 Plan Mode 内容,保留最新单一入口和 harness 验证说明。
|
||||
- `rules.md` 与 `docs/HARNESS.md` 同步 Visual Evidence Gate,补齐路径解析、访问失败报告和 OCR fallback 边界。
|
||||
- 删除 `.claude/commands/*` 中与 `.codex/skills/*` 重复的旧 cleanup/docs/goal-driven/release 入口,并更新文档受众计划中的旧路径引用。
|
||||
|
||||
---
|
||||
|
||||
## [0.74.1] — 2026-06-30
|
||||
|
||||
Released: 2026-06-30
|
||||
|
||||
### Highlights
|
||||
- 将 `/earth-content` 的品牌标识上传收敛到 `Logo 地址` 与 `标题图地址` 字段内,移除旧的全局“选择资产/上传”工具栏。
|
||||
- 新增字段级图片拖拽反馈,拖到对应字段时直接提示将图片复制为 Logo 或标题图。
|
||||
- 对齐品牌上传按钮到现有 Tactile UI primary 按钮样式,并同步中英文使用手册、快速开始和控制台上下文文档。
|
||||
|
||||
### Added / Fixed / Improved
|
||||
- `BrandAssetInput` 支持字段内选择文件、拖拽上传、单字段 loading 和上传后回写草稿 URL。
|
||||
- `FieldGrid` 支持按字段注入自定义输入控件,同时复用统一草稿提交路径。
|
||||
- 品牌上传拖拽态改为低饱和 tactile 配色,上传按钮保持蓝色轻立体样式,容器内上下/右侧留白对齐为 3px。
|
||||
- 补齐品牌上传相关 legacy UI 英文翻译、术语对照和用户文档。
|
||||
|
||||
---
|
||||
|
||||
## [0.74.0] — 2026-06-30
|
||||
|
||||
Released: 2026-06-30
|
||||
|
||||
@@ -135,11 +135,18 @@ PLANET_HARNESS_FRONTEND_SMOKE_PORT=4174 scripts/harness/validate.sh
|
||||
- Treat user-provided screenshots and images as primary visual evidence. If a
|
||||
screenshot contradicts written text, inspect the image first and explicitly
|
||||
call out the mismatch before deciding what to change.
|
||||
- If a referenced screenshot path cannot be opened, do not infer image content
|
||||
from the filename, alt text, surrounding prose, or memory. Search the current
|
||||
thread attachments, repository, and obvious local attachment/download
|
||||
locations; if the image still cannot be found, ask for the file or report the
|
||||
blocker instead of guessing.
|
||||
- Path resolution is part of visual evidence handling. If a referenced
|
||||
screenshot path cannot be opened, try reasonable local equivalents first:
|
||||
WSL/Windows path conversion, workspace-relative paths, absolute paths, current
|
||||
thread attachments, repository files, and obvious local attachment/download
|
||||
locations.
|
||||
- If the image still cannot be found or opened, report the exact path/access
|
||||
blocker instead of guessing. Do not infer image content from the filename, alt
|
||||
text, surrounding prose, logs, or memory.
|
||||
- OCR is acceptable evidence for text-only questions or non-multimodal
|
||||
environments; state when OCR was the fallback. Layout, color, spacing, pixel,
|
||||
and rendering issues still require a real visual inspection or an explicit
|
||||
"could not verify visually" note.
|
||||
- Same-category UI surfaces must use one visual system per product area. Badges,
|
||||
chips, pills, tags, status labels, small buttons, cards, panels, and toolbar
|
||||
controls should reuse the shared component, shared token, or established CSS
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
**状态**:待实施
|
||||
**创建日期**:2026-05-12
|
||||
**校正日期**:2026-06-26,控制台深链已从旧 tab 查询口径更新为当前 `?section=` 口径。
|
||||
**核心目标**:把 `docs/technical/{zh,en}/manual.md` 拆成"纯客户视角"的使用手册,把 `planet.sh`、日志、LAN、故障排查这类运维内容迁到独立 `ops-runbook.md`,并把分层规则写进 `documentation-coverage-rules.md` 和 `.claude/commands/docs.md`,让以后写文档时自动按受众归档。
|
||||
**核心目标**:把 `docs/technical/{zh,en}/manual.md` 拆成"纯客户视角"的使用手册,把 `planet.sh`、日志、LAN、故障排查这类运维内容迁到独立 `ops-runbook.md`,并把分层规则写进 `documentation-coverage-rules.md` 和 `.codex/skills/docs/SKILL.md`,让以后写文档时自动按受众归档。
|
||||
|
||||
## 背景
|
||||
|
||||
@@ -80,7 +80,7 @@
|
||||
> - 新增客户可见 UI 流 → 同时更新 `manual.md` zh+en 与 `docs-content.ts`
|
||||
> - 新增 ops 命令或脚本 → 只更新 `ops-runbook.md` zh+en
|
||||
|
||||
## .claude/commands/docs.md 增量
|
||||
## `.codex/skills/docs/SKILL.md` 增量
|
||||
|
||||
在 "Step 2 — Decide Scope" 后插一段:
|
||||
|
||||
@@ -100,7 +100,7 @@
|
||||
- `docs/technical/zh/quickstart.md` & `en/quickstart.md` — 重写
|
||||
- `docs/technical/zh/ops-runbook.md` & `en/ops-runbook.md` *(新)*
|
||||
- `docs/documentation-coverage-rules.md` — 加受众分层段
|
||||
- `.claude/commands/docs.md` — 加 Document Audience Routing 段
|
||||
- `.codex/skills/docs/SKILL.md` — 加 Document Audience Routing 段
|
||||
- `frontend/src/pages/Docs/docs-content.ts` — 注册 `ops-runbook` 到 `DOCS_METADATA`(`docs_admin` 组)
|
||||
|
||||
## 依赖
|
||||
|
||||
@@ -387,6 +387,7 @@ Current boundary:
|
||||
`/earth-content` reuses the same single-screen tab container from [Settings.tsx](/home/ray/dev/linkong/planet/frontend/src/admin/pages/PlainResourcePages.tsx), but its ownership is separate from System Settings:
|
||||
|
||||
- `TV Livestream` owns the Earth media-panel source configuration.
|
||||
- `Branding` owns Earth HUD brand assets. `Logo URL` and `Title Image URL` use inline upload controls inside the fields; the upload buttons keep the primary `TactileButton` style, and dropping an image onto the matching field shows a low-saturation drag reaction instead of the old global asset-picker toolbar.
|
||||
- `Boundary Precision` owns the Earth static boundary asset state: provider, low-precision fallback, high-precision manifest/PMTiles, source JSON, and build action.
|
||||
- `Base Map`, `Layer Resources`, `3D Assets`, and `News Anchor Strategy` are placeholders only. They show module status and do not invent fake APIs or fake data.
|
||||
|
||||
|
||||
@@ -192,7 +192,7 @@ TV livestreams and boundary precision moved to `/earth-content`; collectors and
|
||||
|
||||
`/earth-content` is under the console's Operations and Configuration group and owns resources used by the Earth frontend:
|
||||
|
||||
- **Brand Assets**: manages the logo, title image, title text, subtitle, and description used by the Earth HUD. Uploaded images are saved as Earth brand assets and read by the Earth page immediately.
|
||||
- **Brand Assets**: manages the logo, title image, title text, subtitle, and description used by the Earth HUD. The `Logo URL` and `Title Image URL` fields each include their own Upload button, and image files can be dropped directly onto the matching field. After upload, the field receives the new asset URL; save the brand configuration to make the Earth page use it.
|
||||
- **About**: manages the About card shown in Earth settings, including logo, kicker, title, version, description, and metadata.
|
||||
- **TV Livestream**: manages sources shown in the Earth media panel.
|
||||
- **News Content**: browses news grouped by RSS source and manual group. RSS items remain read-only; manual groups support create, JSON import, edit, delete, and reprocess.
|
||||
|
||||
@@ -28,6 +28,9 @@ This document standardizes terms used across Intelligent Planet, the console, ba
|
||||
| AI Provider | AI Provider | Service name |
|
||||
| tool | 工具 | Web Search, OCR, and similar integrations |
|
||||
| Playground | Playground | Interactive debugging entry |
|
||||
| branding | 品牌标识 | Earth HUD brand configuration section under `/earth-content` |
|
||||
| brand assets | 品牌资源 | Logo, title image, and related HUD copy assets |
|
||||
| title image | 标题图 | Earth HUD title image |
|
||||
|
||||
## Data Types
|
||||
|
||||
|
||||
@@ -34,9 +34,10 @@ After landing on the `/admin` dashboard, here's a recommended walk-through:
|
||||
|
||||
1. `/collection-management?section=collector_credentials`: pick a collector and click the plug icon to test connectivity. Free collectors (e.g. open BGP) usually work right away; credential-bearing ones like `AISStream` or `BarentsWatch` need an API key / client secret first
|
||||
2. `/ai?section=integrations`: fill an LLM provider (e.g. `minimax` / `openai`), model, base URL, API key, and click the plug at the end of the base URL to test. WebSearch / OCR tools are optional
|
||||
3. `/datasources` or `/data`: check whether collectors have produced data. Use `/datasources -> Built-in Sources` for finite collectors: with no rows selected, click `Trigger All`; after selecting rows, the primary button becomes `Trigger Selected N`. The top-right queue button shows progress. Use `/datasources -> Realtime Sources` for AISStream / WebSocket health and counters
|
||||
4. `/alerts/system`: verify system alerts look right
|
||||
5. `/users` (super_admin only): open accounts for teammates or adjust their groups
|
||||
3. `/earth-content?section=brand`: maintain the Earth HUD logo and title image in Branding. The Upload button inside each URL field opens a file picker, and image files can also be dropped directly onto the matching field. Save the brand configuration after upload
|
||||
4. `/datasources` or `/data`: check whether collectors have produced data. Use `/datasources -> Built-in Sources` for finite collectors: with no rows selected, click `Trigger All`; after selecting rows, the primary button becomes `Trigger Selected N`. The top-right queue button shows progress. Use `/datasources -> Realtime Sources` for AISStream / WebSocket health and counters
|
||||
5. `/alerts/system`: verify system alerts look right
|
||||
6. `/users` (super_admin only): open accounts for teammates or adjust their groups
|
||||
|
||||
## 4. Open Earth
|
||||
|
||||
|
||||
@@ -418,6 +418,7 @@ AI 配置不再挂在 `/settings` 下;`/playground` 应跳转到 `/ai?section=
|
||||
`/earth-content` 复用 [Settings.tsx](/home/ray/dev/linkong/planet/frontend/src/admin/pages/PlainResourcePages.tsx) 的单屏 tab 容器,但页面责任与系统设置分离:
|
||||
|
||||
- `电视直播` 迁移原直播源配置,继续管理 Earth 媒体面板内容源。
|
||||
- `品牌标识` 管理 Earth HUD 的品牌资源。`Logo 地址` 与 `标题图地址` 使用字段内上传控件,上传按钮保持 `TactileButton` 的 primary 样式;图片拖到对应字段时显示低饱和拖拽反应,避免回到旧的全局“选择资产/上传”工具栏。
|
||||
- `国界精度` 管理 Earth 静态国界资产:provider 状态、低精 fallback、高精 manifest/PMTiles、源配置 JSON 和构建动作。
|
||||
- `地球底图`、`图层资源`、`三维素材`、`新闻锚点策略` 是占位页,只显示模块待接入,不造假接口或假数据。
|
||||
|
||||
|
||||
@@ -191,7 +191,7 @@ Base URL 输入框尾端的插头图标会触发连接测试。测试通过会
|
||||
|
||||
`/earth-content` 位于控制台“运维与配置”下,面向智能星球前端体验资源:
|
||||
|
||||
- **品牌资源**:维护智能星球 HUD 使用的 logo、标题图、标题文本、副标题和描述;上传的图片会保存为智能星球品牌资产并立即供智能星球页面读取。
|
||||
- **品牌资源**:维护智能星球 HUD 使用的 logo、标题图、标题文本、副标题和描述。`Logo 地址` 和 `标题图地址` 字段内各有独立的“上传”按钮,也可以把图片直接拖到对应字段;上传成功后字段会写入新的资产地址,保存品牌配置后供智能星球页面读取。
|
||||
- **关于**:维护智能星球设置面板里的关于卡片,包括 logo、眉标、标题、版本、描述和元信息。
|
||||
- **电视直播**:维护智能星球媒体面板里的直播源。
|
||||
- **新闻内容**:按 RSS 来源和手动新闻组查看新闻。RSS 新闻保持只读;手动新闻组可以新增、批量导入 JSON、编辑、删除和重新处理。
|
||||
|
||||
@@ -28,6 +28,9 @@
|
||||
| AI Provider | AI Provider | 服务名,保留英文 |
|
||||
| tool | 工具 | Web Search、OCR 等工具配置 |
|
||||
| Playground | Playground | 交互调试入口,保留英文 |
|
||||
| branding | 品牌标识 | `/earth-content` 中的 Earth HUD 品牌配置分区 |
|
||||
| brand assets | 品牌资源 | Logo、标题图和相关 HUD 文案资源 |
|
||||
| title image | 标题图 | Earth HUD 标题图片,不写作“标题图片地址”以外的混合名 |
|
||||
|
||||
## 数据类型
|
||||
|
||||
|
||||
@@ -34,9 +34,10 @@
|
||||
|
||||
1. `/collection-management?section=collector_credentials`:选一个采集器,点插头图标做连接测试。免费 collector(开源 BGP 等)通常直接可用;像 `AISStream`、`BarentsWatch` 这类需要凭证的,需要先填 API Key/Client Secret
|
||||
2. `/ai?section=integrations`:填一个 LLM provider(例如 `minimax` / `openai`)、模型名、Base URL、API Key,点 Base URL 末端的插头测试连接。WebSearch / OCR 工具可选
|
||||
3. `/datasources` 或 `/data`:看采集器是否已经产出数据。有限采集器看 `/datasources -> 内置源`,不勾选时点“触发全部”,勾选后主按钮会变成“触发已选 N”;右上角队列按钮可查看进度。AISStream / WebSocket 长连接看 `/datasources -> 实时源` 的健康状态和计数
|
||||
4. `/alerts/system`:看系统告警是否正常
|
||||
5. `/users`(仅 `super_admin`):根据需要给同事开账号或调权限组
|
||||
3. `/earth-content?section=brand`:在“品牌标识”里维护智能星球的 Logo 和标题图;对应地址字段内的“上传”按钮支持选择文件,也支持把图片直接拖到字段上,保存后会应用到智能星球 HUD
|
||||
4. `/datasources` 或 `/data`:看采集器是否已经产出数据。有限采集器看 `/datasources -> 内置源`,不勾选时点“触发全部”,勾选后主按钮会变成“触发已选 N”;右上角队列按钮可查看进度。AISStream / WebSocket 长连接看 `/datasources -> 实时源` 的健康状态和计数
|
||||
5. `/alerts/system`:看系统告警是否正常
|
||||
6. `/users`(仅 `super_admin`):根据需要给同事开账号或调权限组
|
||||
|
||||
## 4. 打开智能星球
|
||||
|
||||
|
||||
@@ -16,12 +16,14 @@
|
||||
## Current Version
|
||||
|
||||
- `main` 当前主线历史推导到:`0.16.5`
|
||||
- `dev` 当前开发分支历史推导到:`0.74.0`
|
||||
- `dev` 当前开发分支历史推导到:`0.74.2`
|
||||
|
||||
## Timeline
|
||||
|
||||
| Version | Type | Branch | Commit | Summary |
|
||||
| --- | --- | --- | --- | --- |
|
||||
| `0.74.2` | bugfix | `dev` | `pending` | 收敛 agent harness 到根规则和 Codex skills,删除旧 Claude command 重复入口,并强化视觉证据路径解析与 OCR fallback 规则 |
|
||||
| `0.74.1` | improvement | `dev` | `pending` | 将品牌标识上传收敛到 Logo/标题图字段内,新增字段级拖拽反馈和 Tactile UI primary 上传按钮,并同步中英文使用文档 |
|
||||
| `0.74.0` | feature | `dev` | `pending` | 扩展统一 i18n 到 Web Earth 动态入口、控制台/API 错误和公开页面,修复 Earth 通知胶囊、语言 switch、品牌栏、legend、tooltip、新闻/TV 英文态裁切与中文残留,并加入 harness 回归覆盖 |
|
||||
| `0.73.0` | feature | `dev` | `pending` | 新增前端统一 i18n、控制台语言/主题偏好入口、英文态 legacy 过渡翻译和 admin 一屏/状态指示器布局验证 |
|
||||
| `0.72.0` | feature | `dev` | `pending` | 新增完整 agent harness、单一 AGENTS 入口、Earth News smoke 覆盖和 collector 结构化日志清理,并同步控制台/Earth/Docs 响应式维护文档 |
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "planet-frontend",
|
||||
"version": "0.74.0",
|
||||
"version": "0.74.2",
|
||||
"private": true,
|
||||
"packageManager": "bun@1",
|
||||
"dependencies": {
|
||||
|
||||
@@ -27,7 +27,7 @@ import {
|
||||
Trash2,
|
||||
X,
|
||||
} from 'lucide-react'
|
||||
import { useCallback, useEffect, useMemo, useRef, useState, type CSSProperties, type PointerEvent as ReactPointerEvent, type ReactNode } from 'react'
|
||||
import { useCallback, useEffect, useMemo, useRef, useState, type ChangeEvent, type CSSProperties, type DragEvent as ReactDragEvent, type PointerEvent as ReactPointerEvent, type ReactNode } from 'react'
|
||||
import { Link, useLocation, useNavigate } from 'react-router-dom'
|
||||
import MarkdownRenderer from '../../components/MarkdownRenderer/MarkdownRenderer'
|
||||
import Scrollbar from '../../components/Scrollbar/Scrollbar'
|
||||
@@ -132,6 +132,8 @@ type DatasourceMetricBaseline = {
|
||||
count: number
|
||||
}
|
||||
|
||||
type BrandAssetTargetKey = 'logo_src' | 'title_src'
|
||||
|
||||
const DEFAULT_DATASOURCE_FILTERS: DatasourceFilters = {
|
||||
product: '',
|
||||
module: '',
|
||||
@@ -871,6 +873,14 @@ type FieldConfig = {
|
||||
placeholder?: string
|
||||
disabled?: boolean
|
||||
help?: string
|
||||
renderInput?: (props: {
|
||||
disabled?: boolean
|
||||
displayValue: unknown
|
||||
fieldKey: string
|
||||
onChange: (value: unknown) => void
|
||||
placeholder?: string
|
||||
value: unknown
|
||||
}) => ReactNode
|
||||
inputAction?: {
|
||||
ariaLabel?: string
|
||||
disabled?: boolean
|
||||
@@ -2301,6 +2311,9 @@ function FieldGrid({
|
||||
const className = field.wide ? 'an-field an-field--wide' : 'an-field'
|
||||
const searchTarget = searchGroupKey ? `${searchGroupKey}:field:${field.key}` : `field:${field.key}`
|
||||
const searchText = [label, field.key, text(displayValue, '')].filter(Boolean).join(' ')
|
||||
const commitFieldValue = (nextValue: unknown) => {
|
||||
onDraftChange(setNestedDraftField(draft, record, field.key, nextValue))
|
||||
}
|
||||
if (field.type === 'boolean') {
|
||||
return (
|
||||
<label key={field.key} className="an-checkbox-row an-field--wide" data-admin-search-target={searchTarget} data-admin-search-field={field.key} data-admin-search-text={searchText}>
|
||||
@@ -2308,7 +2321,7 @@ function FieldGrid({
|
||||
type="checkbox"
|
||||
checked={Boolean(value)}
|
||||
disabled={field.disabled}
|
||||
onChange={(event) => onDraftChange(setNestedDraftField(draft, record, field.key, event.target.checked))}
|
||||
onChange={(event) => commitFieldValue(event.target.checked)}
|
||||
/>
|
||||
{label}
|
||||
{help ? <small>{help}</small> : null}
|
||||
@@ -2323,7 +2336,7 @@ function FieldGrid({
|
||||
className="an-input"
|
||||
value={text(value, '')}
|
||||
disabled={field.disabled}
|
||||
onChange={(event) => onDraftChange(setNestedDraftField(draft, record, field.key, event.target.value))}
|
||||
onChange={(event) => commitFieldValue(event.target.value)}
|
||||
>
|
||||
{(field.options || []).map((option) => (
|
||||
<option key={option.value} value={option.value}>
|
||||
@@ -2346,9 +2359,9 @@ function FieldGrid({
|
||||
onChange={(event) => {
|
||||
const nextValue = event.target.value
|
||||
try {
|
||||
onDraftChange(setNestedDraftField(draft, record, field.key, JSON.parse(nextValue)))
|
||||
commitFieldValue(JSON.parse(nextValue))
|
||||
} catch {
|
||||
onDraftChange(setNestedDraftField(draft, record, field.key, nextValue))
|
||||
commitFieldValue(nextValue)
|
||||
}
|
||||
}}
|
||||
spellCheck={false}
|
||||
@@ -2401,6 +2414,22 @@ function FieldGrid({
|
||||
</label>
|
||||
)
|
||||
}
|
||||
if (field.renderInput) {
|
||||
return (
|
||||
<div key={field.key} className={className} data-admin-search-target={searchTarget} data-admin-search-field={field.key} data-admin-search-text={searchText}>
|
||||
<span>{label}</span>
|
||||
{field.renderInput({
|
||||
disabled: field.disabled,
|
||||
displayValue,
|
||||
fieldKey: field.key,
|
||||
onChange: commitFieldValue,
|
||||
placeholder,
|
||||
value,
|
||||
})}
|
||||
{help ? <small>{help}</small> : null}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
const inputAction = field.inputAction
|
||||
? {
|
||||
...field.inputAction,
|
||||
@@ -2421,12 +2450,7 @@ function FieldGrid({
|
||||
value={text(displayValue, '')}
|
||||
placeholder={placeholder}
|
||||
disabled={field.disabled}
|
||||
onChange={(event) => onDraftChange(setNestedDraftField(
|
||||
draft,
|
||||
record,
|
||||
field.key,
|
||||
field.type === 'number' ? Number(event.target.value) : event.target.value,
|
||||
))}
|
||||
onChange={(event) => commitFieldValue(field.type === 'number' ? Number(event.target.value) : event.target.value)}
|
||||
/>
|
||||
</ConnectionTestInput>
|
||||
) : (
|
||||
@@ -2436,12 +2460,7 @@ function FieldGrid({
|
||||
value={text(displayValue, '')}
|
||||
placeholder={placeholder}
|
||||
disabled={field.disabled}
|
||||
onChange={(event) => onDraftChange(setNestedDraftField(
|
||||
draft,
|
||||
record,
|
||||
field.key,
|
||||
field.type === 'number' ? Number(event.target.value) : event.target.value,
|
||||
))}
|
||||
onChange={(event) => commitFieldValue(field.type === 'number' ? Number(event.target.value) : event.target.value)}
|
||||
/>
|
||||
)}
|
||||
{help ? <small>{help}</small> : null}
|
||||
@@ -2545,6 +2564,25 @@ function GroupList({
|
||||
const PLAYGROUND_SESSION_KEY = 'default'
|
||||
const BRAND_ASSET_ACCEPT = '.png,.jpg,.jpeg,.webp,.svg'
|
||||
const BRAND_ASSET_SUFFIXES = ['png', 'jpg', 'jpeg', 'webp', 'svg']
|
||||
const BRAND_ASSET_TARGET_META: Record<BrandAssetTargetKey, {
|
||||
copyLabel: string
|
||||
dropLabel: string
|
||||
uploadLabel: string
|
||||
uploadTitle: string
|
||||
}> = {
|
||||
logo_src: {
|
||||
copyLabel: '复制为 Logo',
|
||||
dropLabel: '将图片拖到这里',
|
||||
uploadLabel: '上传',
|
||||
uploadTitle: '上传 Logo',
|
||||
},
|
||||
title_src: {
|
||||
copyLabel: '复制为标题图',
|
||||
dropLabel: '将图片拖到这里',
|
||||
uploadLabel: '上传',
|
||||
uploadTitle: '上传标题图片',
|
||||
},
|
||||
}
|
||||
const PLAYGROUND_PRESETS = [
|
||||
{
|
||||
key: 'bgp-brief',
|
||||
@@ -2572,6 +2610,121 @@ const PLAYGROUND_PRESETS = [
|
||||
},
|
||||
]
|
||||
|
||||
function isBrandAssetTargetKey(value: string): value is BrandAssetTargetKey {
|
||||
return value === 'logo_src' || value === 'title_src'
|
||||
}
|
||||
|
||||
function isFileDrag(event: ReactDragEvent<HTMLElement>) {
|
||||
return Array.from(event.dataTransfer.types).includes('Files')
|
||||
}
|
||||
|
||||
function BrandAssetInput({
|
||||
disabled,
|
||||
onChange,
|
||||
onFile,
|
||||
placeholder,
|
||||
target,
|
||||
uploading,
|
||||
value,
|
||||
}: {
|
||||
disabled?: boolean
|
||||
onChange: (value: string) => void
|
||||
onFile: (file: File) => void
|
||||
placeholder?: string
|
||||
target: BrandAssetTargetKey
|
||||
uploading: boolean
|
||||
value: string
|
||||
}) {
|
||||
const { locale } = useLocale()
|
||||
const [dragging, setDragging] = useState(false)
|
||||
const dragCounter = useRef(0)
|
||||
const fileInputRef = useRef<HTMLInputElement>(null)
|
||||
const meta = BRAND_ASSET_TARGET_META[target]
|
||||
const suffixHelp = BRAND_ASSET_SUFFIXES.join(' / ')
|
||||
|
||||
const resetDrag = () => {
|
||||
dragCounter.current = 0
|
||||
setDragging(false)
|
||||
}
|
||||
|
||||
const handleDragEnter = (event: ReactDragEvent<HTMLDivElement>) => {
|
||||
if (!isFileDrag(event)) return
|
||||
event.preventDefault()
|
||||
dragCounter.current += 1
|
||||
setDragging(true)
|
||||
}
|
||||
|
||||
const handleDragOver = (event: ReactDragEvent<HTMLDivElement>) => {
|
||||
if (!isFileDrag(event)) return
|
||||
event.preventDefault()
|
||||
}
|
||||
|
||||
const handleDragLeave = (event: ReactDragEvent<HTMLDivElement>) => {
|
||||
if (!isFileDrag(event)) return
|
||||
event.preventDefault()
|
||||
dragCounter.current = Math.max(0, dragCounter.current - 1)
|
||||
if (dragCounter.current === 0) setDragging(false)
|
||||
}
|
||||
|
||||
const handleDrop = (event: ReactDragEvent<HTMLDivElement>) => {
|
||||
event.preventDefault()
|
||||
resetDrag()
|
||||
const file = event.dataTransfer.files?.[0]
|
||||
if (file) onFile(file)
|
||||
}
|
||||
|
||||
const handleFileChange = (event: ChangeEvent<HTMLInputElement>) => {
|
||||
const file = event.target.files?.[0]
|
||||
event.target.value = ''
|
||||
if (file) onFile(file)
|
||||
}
|
||||
|
||||
return (
|
||||
<div
|
||||
className={dragging ? 'an-brand-asset-input is-dragging' : 'an-brand-asset-input'}
|
||||
onDragEnter={handleDragEnter}
|
||||
onDragOver={handleDragOver}
|
||||
onDragLeave={handleDragLeave}
|
||||
onDrop={handleDrop}
|
||||
>
|
||||
<input
|
||||
className="an-brand-asset-input__control"
|
||||
disabled={disabled || uploading}
|
||||
onChange={(event) => onChange(event.target.value)}
|
||||
placeholder={placeholder}
|
||||
type="text"
|
||||
value={value}
|
||||
/>
|
||||
<input
|
||||
ref={fileInputRef}
|
||||
accept={BRAND_ASSET_ACCEPT}
|
||||
className="an-brand-asset-input__file"
|
||||
disabled={disabled || uploading}
|
||||
onChange={handleFileChange}
|
||||
type="file"
|
||||
/>
|
||||
<Button
|
||||
className="an-brand-asset-input__upload"
|
||||
disabled={disabled || uploading}
|
||||
onClick={() => fileInputRef.current?.click()}
|
||||
size="sm"
|
||||
tactile={{ height: 28, radius: 5, shadowSize: 0.55 }}
|
||||
title={`${localizeAdminText(meta.uploadTitle, locale)} · ${suffixHelp}`}
|
||||
type="button"
|
||||
variant="primary"
|
||||
>
|
||||
{uploading ? localizeAdminText('上传中', locale) : localizeAdminText(meta.uploadLabel, locale)}
|
||||
</Button>
|
||||
{dragging ? (
|
||||
<div className="an-brand-asset-input__drop-overlay" aria-hidden="true">
|
||||
<span>{localizeAdminText(meta.dropLabel, locale)}</span>
|
||||
<strong>{localizeAdminText(meta.copyLabel, locale)}</strong>
|
||||
</div>
|
||||
) : null}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
function PlaygroundLite() {
|
||||
const { toast } = useToast()
|
||||
const { locale } = useLocale()
|
||||
@@ -2848,7 +3001,7 @@ function ModuleConsole({ config }: { config: ModuleConfig }) {
|
||||
const [revealedSecrets, setRevealedSecrets] = useState<Record<string, AnyRecord>>({})
|
||||
const [visibleSecretFields, setVisibleSecretFields] = useState<Record<string, boolean>>({})
|
||||
const [smtpTestEmail, setSmtpTestEmail] = useState('')
|
||||
const [brandUploadFile, setBrandUploadFile] = useState<File | null>(null)
|
||||
const [brandAssetUploadingTarget, setBrandAssetUploadingTarget] = useState<BrandAssetTargetKey | null>(null)
|
||||
const [newsImportFile, setNewsImportFile] = useState<File | null>(null)
|
||||
const newsImportInputRef = useRef<HTMLInputElement>(null)
|
||||
const [resolveTarget, setResolveTarget] = useState<TableRecord | null>(null)
|
||||
@@ -4002,51 +4155,46 @@ function ModuleConsole({ config }: { config: ModuleConfig }) {
|
||||
setResolveTarget(null)
|
||||
}
|
||||
|
||||
const uploadBrandAsset = async () => {
|
||||
if (!brandUploadFile) {
|
||||
toast({ title: '请选择上传文件', tone: 'error' })
|
||||
return
|
||||
}
|
||||
const suffix = brandUploadFile.name.split('.').pop()?.toLowerCase() || ''
|
||||
if (!BRAND_ASSET_SUFFIXES.includes(suffix)) {
|
||||
toast({ title: '文件类型不支持', description: `仅支持 ${BRAND_ASSET_SUFFIXES.join(', ')}。`, tone: 'error' })
|
||||
return
|
||||
}
|
||||
const formData = new FormData()
|
||||
formData.append('file', brandUploadFile)
|
||||
setActionLoading(true)
|
||||
try {
|
||||
const response = await axios.post(apiPath('/earth/brand/assets'), formData, {
|
||||
headers: { 'Content-Type': 'multipart/form-data' },
|
||||
})
|
||||
replaceSelectedWithPayload('brand-upload', '品牌资产上传', [{
|
||||
...response.data,
|
||||
__title: '品牌资产上传结果',
|
||||
__module: '品牌资产',
|
||||
__status: '已上传',
|
||||
__metric: brandUploadFile.name,
|
||||
}])
|
||||
setBrandUploadFile(null)
|
||||
toast({ title: '品牌资产已上传', tone: 'success' })
|
||||
await load()
|
||||
} catch (error) {
|
||||
toast({ title: '品牌资产上传失败', description: error instanceof Error ? error.message : '接口请求失败', tone: 'error' })
|
||||
} finally {
|
||||
setActionLoading(false)
|
||||
}
|
||||
}
|
||||
|
||||
const selectBrandUploadFile = (file: File | null | undefined) => {
|
||||
const uploadBrandAssetFile = async (file: File | null | undefined, target: BrandAssetTargetKey) => {
|
||||
if (!file) {
|
||||
setBrandUploadFile(null)
|
||||
return
|
||||
toast({ title: '请拖入图片文件', tone: 'error' })
|
||||
return null
|
||||
}
|
||||
const suffix = file.name.split('.').pop()?.toLowerCase() || ''
|
||||
if (!BRAND_ASSET_SUFFIXES.includes(suffix)) {
|
||||
toast({ title: '文件类型不支持', description: `仅支持 ${BRAND_ASSET_SUFFIXES.join(', ')}。`, tone: 'error' })
|
||||
return
|
||||
return null
|
||||
}
|
||||
const formData = new FormData()
|
||||
formData.append('file', file)
|
||||
setActionLoading(true)
|
||||
setBrandAssetUploadingTarget(target)
|
||||
try {
|
||||
const response = await axios.post(apiPath('/earth/brand/assets'), formData, {
|
||||
headers: { 'Content-Type': 'multipart/form-data' },
|
||||
})
|
||||
const assetUrl = text(response.data?.url, '')
|
||||
if (!assetUrl) throw new Error('上传接口未返回资产 URL')
|
||||
replaceSelectedWithPayload('brand-upload', '品牌资产上传', [{
|
||||
...response.data,
|
||||
__title: '品牌资产上传结果',
|
||||
__module: target === 'logo_src' ? 'Logo' : '标题图片',
|
||||
__status: '已上传',
|
||||
__metric: file.name,
|
||||
}])
|
||||
toast({
|
||||
title: target === 'logo_src' ? 'Logo 图片已上传' : '标题图片已上传',
|
||||
description: assetUrl,
|
||||
tone: 'success',
|
||||
})
|
||||
return assetUrl
|
||||
} catch (error) {
|
||||
toast({ title: '品牌资产上传失败', description: actionErrorMessage(error), tone: 'error' })
|
||||
return null
|
||||
} finally {
|
||||
setActionLoading(false)
|
||||
setBrandAssetUploadingTarget(null)
|
||||
}
|
||||
setBrandUploadFile(file)
|
||||
}
|
||||
|
||||
const createManualNewsGroup = async () => {
|
||||
@@ -5413,6 +5561,32 @@ function ModuleConsole({ config }: { config: ModuleConfig }) {
|
||||
{ key: 'location_label', label: '位置标签' },
|
||||
]
|
||||
const fields = newsSourceFields.length ? newsSourceFields : newsItemFields.length ? newsItemFields : [...scalarFields, ...objectFields]
|
||||
const uploadBrandAssetToDraft = async (file: File, target: BrandAssetTargetKey) => {
|
||||
if (!activeGroup) return
|
||||
const assetUrl = await uploadBrandAssetFile(file, target)
|
||||
if (!assetUrl) return
|
||||
setHierarchyDraft((current) => setNestedDraftField(current, activeGroup.record, target, assetUrl))
|
||||
}
|
||||
const editableFields = config === configs.earthContent && activeSection.key === 'brand'
|
||||
? fields.map((field): FieldConfig => {
|
||||
if (!isBrandAssetTargetKey(field.key)) return field
|
||||
const target = field.key
|
||||
return {
|
||||
...field,
|
||||
renderInput: ({ disabled, displayValue, onChange, placeholder }) => (
|
||||
<BrandAssetInput
|
||||
disabled={disabled}
|
||||
onChange={(nextValue) => onChange(nextValue)}
|
||||
onFile={(file) => void uploadBrandAssetToDraft(file, target)}
|
||||
placeholder={placeholder}
|
||||
target={target}
|
||||
uploading={brandAssetUploadingTarget === target}
|
||||
value={text(displayValue, '')}
|
||||
/>
|
||||
),
|
||||
}
|
||||
})
|
||||
: fields
|
||||
const renderNewsFeedEditor = () => {
|
||||
if (!activeGroup) return null
|
||||
const currentSource = draftRecord(hierarchyDraft, activeGroup.record)
|
||||
@@ -5951,20 +6125,6 @@ function ModuleConsole({ config }: { config: ModuleConfig }) {
|
||||
<div className="an-toolbar">
|
||||
{config === configs.earthContent && activeSection.key === 'brand' ? (
|
||||
<>
|
||||
<label
|
||||
className="an-file-action an-file-action--drop"
|
||||
onDragOver={(event) => event.preventDefault()}
|
||||
onDrop={(event) => {
|
||||
event.preventDefault()
|
||||
selectBrandUploadFile(event.dataTransfer.files?.[0])
|
||||
}}
|
||||
title={`支持 ${BRAND_ASSET_SUFFIXES.join(', ')}`}
|
||||
>
|
||||
<input type="file" accept={BRAND_ASSET_ACCEPT} onChange={(event) => selectBrandUploadFile(event.target.files?.[0])} />
|
||||
<span><ImageUp size={15} />{brandUploadFile ? brandUploadFile.name : '选择/拖入资产'}</span>
|
||||
<small>{BRAND_ASSET_SUFFIXES.join(' / ')}</small>
|
||||
</label>
|
||||
<Button variant="primary" onClick={() => void uploadBrandAsset()} loading={actionLoading} disabled={!brandUploadFile}><ImageUp size={15} />上传</Button>
|
||||
<Button size="icon" variant="subtle" title="重置品牌配置" aria-label="重置品牌配置" onClick={() => setConfirmAction({
|
||||
title: '重置品牌配置',
|
||||
description: '确认恢复默认智能星球品牌配置?当前自定义配置会被清空。',
|
||||
@@ -6309,7 +6469,7 @@ function ModuleConsole({ config }: { config: ModuleConfig }) {
|
||||
</section>
|
||||
</>
|
||||
) : (
|
||||
<FieldGrid record={activeGroup.record} draft={hierarchyDraft} onDraftChange={setHierarchyDraft} fields={fields} searchGroupKey={activeGroup.key} />
|
||||
<FieldGrid record={activeGroup.record} draft={hierarchyDraft} onDraftChange={setHierarchyDraft} fields={editableFields} searchGroupKey={activeGroup.key} />
|
||||
)}
|
||||
{config === configs.collection && activeSection.key === 'collection_history' ? null : (
|
||||
<details className="an-advanced-editor">
|
||||
@@ -6463,11 +6623,6 @@ function ModuleConsole({ config }: { config: ModuleConfig }) {
|
||||
|
||||
if (config === configs.earthContent && endpointKey === 'brand') {
|
||||
actions.push(
|
||||
<label key="brand-upload" className="an-file-action">
|
||||
<input type="file" onChange={(event) => setBrandUploadFile(event.target.files?.[0] ?? null)} />
|
||||
<span><ImageUp size={15} />{brandUploadFile ? brandUploadFile.name : '选择资产'}</span>
|
||||
</label>,
|
||||
<Button key="brand-upload-run" variant="primary" onClick={() => void uploadBrandAsset()} loading={actionLoading} disabled={!brandUploadFile}><ImageUp size={15} />上传</Button>,
|
||||
<Button key="delete-brand" variant="danger" onClick={() => setConfirmAction({
|
||||
title: '删除品牌配置',
|
||||
description: '确认删除智能星球品牌配置?',
|
||||
|
||||
@@ -1697,6 +1697,111 @@ body:has(.admin-theme-root[data-theme='dark']) .an-toast {
|
||||
border-style: dashed;
|
||||
}
|
||||
|
||||
.an-brand-asset-input {
|
||||
position: relative;
|
||||
min-width: 0;
|
||||
height: 34px;
|
||||
display: grid;
|
||||
grid-template-columns: minmax(0, 1fr) auto;
|
||||
align-items: center;
|
||||
overflow: hidden;
|
||||
border: 1px solid var(--an-border);
|
||||
border-radius: 6px;
|
||||
background: var(--an-surface);
|
||||
color: var(--an-text);
|
||||
transition: border-color 0.16s ease, box-shadow 0.16s ease, background-color 0.16s ease;
|
||||
}
|
||||
|
||||
.an-brand-asset-input:focus-within {
|
||||
border-color: color-mix(in srgb, var(--an-accent) 24%, var(--an-border-strong));
|
||||
}
|
||||
|
||||
.an-brand-asset-input.is-dragging {
|
||||
border-color: var(--an-border-strong);
|
||||
background: color-mix(in srgb, var(--an-soft) 46%, var(--an-surface));
|
||||
box-shadow: 0 2px 5px rgba(15, 23, 42, 0.1), 0 8px 18px rgba(15, 23, 42, 0.06);
|
||||
}
|
||||
|
||||
.an-brand-asset-input__control {
|
||||
width: 100%;
|
||||
min-width: 0;
|
||||
height: 100%;
|
||||
border: 0;
|
||||
outline: 0;
|
||||
background: transparent;
|
||||
color: inherit;
|
||||
padding: 0 10px;
|
||||
font: inherit;
|
||||
line-height: 32px;
|
||||
}
|
||||
|
||||
.an-brand-asset-input__control:disabled {
|
||||
color: var(--an-muted);
|
||||
}
|
||||
|
||||
.an-brand-asset-input__file {
|
||||
position: absolute;
|
||||
width: 1px;
|
||||
height: 1px;
|
||||
opacity: 0;
|
||||
pointer-events: none;
|
||||
}
|
||||
|
||||
.an-brand-asset-input__upload {
|
||||
position: relative;
|
||||
z-index: 1;
|
||||
min-width: 72px;
|
||||
height: var(--tui-control-height, 28px);
|
||||
margin-right: 3px;
|
||||
padding-inline: 10px;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.an-brand-asset-input__drop-overlay {
|
||||
position: absolute;
|
||||
inset: 0;
|
||||
z-index: 2;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
gap: 10px;
|
||||
padding: 0 12px;
|
||||
border: 1px dashed var(--an-border-strong);
|
||||
border-radius: 5px;
|
||||
background: color-mix(in srgb, var(--an-soft) 82%, var(--an-surface));
|
||||
color: var(--an-text);
|
||||
pointer-events: none;
|
||||
}
|
||||
|
||||
.an-brand-asset-input__drop-overlay > span {
|
||||
min-width: 0;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
font-size: 13px;
|
||||
font-weight: 800;
|
||||
}
|
||||
|
||||
.an-brand-asset-input__drop-overlay > strong {
|
||||
min-width: max-content;
|
||||
height: 28px;
|
||||
padding: 0 10px;
|
||||
border: 1px solid var(--an-border);
|
||||
border-radius: 6px;
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
gap: 6px;
|
||||
background: var(--an-surface);
|
||||
color: var(--an-text);
|
||||
box-shadow: 0 10px 24px rgba(15, 23, 42, 0.16);
|
||||
font-size: 13px;
|
||||
font-weight: 800;
|
||||
}
|
||||
|
||||
.admin-theme-root[data-theme='dark'] .an-brand-asset-input__drop-overlay > strong {
|
||||
box-shadow: 0 12px 30px rgba(0, 0, 0, 0.34);
|
||||
}
|
||||
|
||||
.an-mapping-form {
|
||||
display: grid;
|
||||
gap: 12px;
|
||||
|
||||
@@ -556,6 +556,15 @@ export const legacyUiTextEnUS: Record<string, string> = {
|
||||
'描述': 'Description',
|
||||
'每日摘要': 'Daily digest',
|
||||
'Logo 地址': 'Logo URL',
|
||||
'上传 Logo': 'Upload logo',
|
||||
'上传标题图片': 'Upload title image',
|
||||
'上传中': 'Uploading',
|
||||
'将图片拖到这里': 'Drop file here',
|
||||
'复制为 Logo': 'Copy as logo',
|
||||
'复制为标题图': 'Copy as title image',
|
||||
'Logo 图片已上传': 'Logo image uploaded',
|
||||
'标题图片已上传': 'Title image uploaded',
|
||||
'请拖入图片文件': 'Drop an image file',
|
||||
'API 基础地址': 'API base URL',
|
||||
'Firecrawl 抓取路径': 'Firecrawl scrape path',
|
||||
'Firecrawl 搜索路径': 'Firecrawl search path',
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
[project]
|
||||
name = "planet"
|
||||
version = "0.74.0"
|
||||
version = "0.74.2"
|
||||
description = "智能星球计划 - 态势感知系统"
|
||||
requires-python = ">=3.14"
|
||||
dependencies = [
|
||||
|
||||
17
rules.md
17
rules.md
@@ -161,6 +161,13 @@ rg -n "api[_-]?key|client_secret|BEGIN .*PRIVATE KEY|AKIA[0-9A-Z]" .
|
||||
|
||||
Always.
|
||||
|
||||
### Harness
|
||||
|
||||
- `rules.md` is the active repository rule source. Keep durable constraints here instead of duplicating them across prompt files.
|
||||
- Use `.codex/skills/` for specialized Codex workflows such as cleanup, docs, goal-driven work, and release.
|
||||
- Do not add new legacy harness entry points when an existing skill or rule module can carry the same instruction.
|
||||
- If a harness rule is no longer true for the current toolchain, update or delete it in the same cleanup pass.
|
||||
|
||||
### Git
|
||||
|
||||
- Do not revert user changes unless explicitly requested.
|
||||
@@ -173,6 +180,16 @@ Always.
|
||||
- Prefer maintained, widely used libraries.
|
||||
- Pin dependency versions in `pyproject.toml`, `uv.lock`, and `package.json`.
|
||||
|
||||
### Visual Evidence Gate
|
||||
|
||||
- If the user provides a screenshot, image, video frame, visual mock, browser capture, or says something like "as shown", obtain evidence from that artifact before interpreting the request or changing code.
|
||||
- Path resolution is part of the task. If a provided path does not open, try reasonable local equivalents first, such as WSL/Windows path conversion, workspace-relative paths, absolute paths, and attached-file locations.
|
||||
- If the artifact still cannot be found or opened, stop that visual-dependent work and report the exact path/access problem instead of guessing. Ask for an accessible file/path or a fresh screenshot.
|
||||
- Do not infer visual intent from the filename, surrounding text, alt text, logs, or prior assumptions.
|
||||
- OCR is acceptable evidence for text-only questions or when the active environment lacks multimodal image viewing, but state that OCR was the fallback. Layout, color, spacing, pixel, and rendering issues still require a real visual inspection or an explicit "could not verify visually" note.
|
||||
- After inspecting the artifact, ground the work in at least one concrete observed detail when it affects the task.
|
||||
- For UI or rendering fixes that depend on appearance, verify with a real screenshot or browser render when the project can be run locally.
|
||||
|
||||
### Deterministic Context
|
||||
|
||||
- Prefer compact CLI evidence over reading large files or full diffs:
|
||||
|
||||
Reference in New Issue
Block a user