统一Rust格式化工具链与构建门禁
固定Rust 1.96.0与rustfmt格式配置 接入本地lint、Codex提交前检查和Jenkins构建门禁 提交server-rs全workspace一次性格式化基线 同步开发运维文档与production-ops防回退检查
This commit is contained in:
@@ -14,20 +14,40 @@ if (hookInput && !isGitCommitCommand(extractShellCommand(hookInput))) {
|
||||
}
|
||||
|
||||
const validationSteps = [
|
||||
{
|
||||
label: 'Rust format check',
|
||||
command: npmCommand,
|
||||
args:
|
||||
process.platform === 'win32'
|
||||
? ['/d', '/s', '/c', 'npm run check:rustfmt']
|
||||
: ['run', 'check:rustfmt'],
|
||||
},
|
||||
{
|
||||
label: 'TypeScript typecheck',
|
||||
command: npmCommand,
|
||||
args: process.platform === 'win32' ? ['/d', '/s', '/c', 'npm run typecheck'] : ['run', 'typecheck'],
|
||||
args:
|
||||
process.platform === 'win32'
|
||||
? ['/d', '/s', '/c', 'npm run typecheck']
|
||||
: ['run', 'typecheck'],
|
||||
},
|
||||
{
|
||||
label: 'Admin web typecheck',
|
||||
command: npmCommand,
|
||||
args: process.platform === 'win32' ? ['/d', '/s', '/c', 'npm run admin-web:typecheck'] : ['run', 'admin-web:typecheck'],
|
||||
args:
|
||||
process.platform === 'win32'
|
||||
? ['/d', '/s', '/c', 'npm run admin-web:typecheck']
|
||||
: ['run', 'admin-web:typecheck'],
|
||||
},
|
||||
{
|
||||
label: 'Rust api-server compile check',
|
||||
command: 'cargo',
|
||||
args: ['check', '-p', 'api-server', '--manifest-path', 'server-rs/Cargo.toml'],
|
||||
args: [
|
||||
'check',
|
||||
'-p',
|
||||
'api-server',
|
||||
'--manifest-path',
|
||||
'server-rs/Cargo.toml',
|
||||
],
|
||||
},
|
||||
];
|
||||
|
||||
@@ -66,7 +86,9 @@ function runStep(step) {
|
||||
}
|
||||
|
||||
if (result.error) {
|
||||
console.error(`[codex-hook] ${step.label} 启动失败:${result.error.message}`);
|
||||
console.error(
|
||||
`[codex-hook] ${step.label} 启动失败:${result.error.message}`,
|
||||
);
|
||||
return { ok: false, status: 1 };
|
||||
}
|
||||
|
||||
@@ -104,12 +126,15 @@ function extractShellCommand(input) {
|
||||
input?.command,
|
||||
];
|
||||
|
||||
const command = candidates.find(value => typeof value === 'string' && value.trim().length > 0);
|
||||
const command = candidates.find(
|
||||
(value) => typeof value === 'string' && value.trim().length > 0,
|
||||
);
|
||||
if (command) {
|
||||
return command;
|
||||
}
|
||||
|
||||
const shellCommand = input?.tool_input?.cmd ?? input?.toolInput?.cmd ?? input?.arguments?.cmd;
|
||||
const shellCommand =
|
||||
input?.tool_input?.cmd ?? input?.toolInput?.cmd ?? input?.arguments?.cmd;
|
||||
if (Array.isArray(shellCommand)) {
|
||||
return shellCommand.join(' ');
|
||||
}
|
||||
|
||||
@@ -173,6 +173,19 @@ RAG 主要供 Agent 检索项目上下文,开发者仍按 `AGENTS.md`、`docs/
|
||||
|
||||
## 常用检查命令
|
||||
|
||||
Rust 格式检查:
|
||||
|
||||
```bash
|
||||
npm run check:rustfmt
|
||||
```
|
||||
|
||||
仓库通过根目录 `rust-toolchain.toml` 固定 Rust `1.96.0` 和 `rustfmt` 组件,
|
||||
并通过 `rustfmt.toml` 固定 Edition 2024 格式口径。需要修复格式时运行:
|
||||
|
||||
```bash
|
||||
cargo fmt --all --manifest-path server-rs/Cargo.toml
|
||||
```
|
||||
|
||||
- 后端通用用户行为埋点统一通过 `record_tracking_event_and_return` procedure、`SpacetimeRuntimeClient::record_tracking_event(...)` 与 api-server `tracking` 中间件写入 `tracking_event` / `tracking_daily_stat`;后台、RPG、大鱼吃小鱼、Visual Novel、Story、Combat 默认排除;作品级游玩埋点统一使用 `work_play_start`,详细事件清单见 `docs/technical/BACKEND_TRACKING_EVENT_COVERAGE_2026-05-09.md`。
|
||||
|
||||
编码检查:
|
||||
|
||||
@@ -233,7 +233,7 @@ npm run codegraph:index
|
||||
|
||||
Codex 项目级 hook 已放在 `.codex/config.toml` 与 `.codex/hooks/`:
|
||||
|
||||
- `PreToolUse` hook 会在 Codex 准备执行 `git commit` 前运行 `node .codex/hooks/pre-submit-compile-check.mjs`,依次执行 `npm run typecheck`、`npm run admin-web:typecheck`、`cargo check -p api-server --manifest-path server-rs/Cargo.toml`,发现编译错误会阻止本次提交。
|
||||
- `PreToolUse` hook 会在 Codex 准备执行 `git commit` 前运行 `node .codex/hooks/pre-submit-compile-check.mjs`,依次执行 `npm run check:rustfmt`、`npm run typecheck`、`npm run admin-web:typecheck`、`cargo check -p api-server --manifest-path server-rs/Cargo.toml`,发现格式或编译错误会阻止本次提交。
|
||||
- `PostToolUse` hook 会在 Codex 工具修改文件后运行 `node .codex/hooks/post-edit-codegraph-sync.mjs`,执行 `npm run codegraph:sync` 刷新本地语义索引。
|
||||
- 如果某个 Codex 客户端版本尚未自动加载项目级 hook,可先手动运行 `node .codex/hooks/pre-submit-compile-check.mjs` 与 `node .codex/hooks/post-edit-codegraph-sync.mjs`;个人模型、token、MCP server 仍放在个人 `~/.codex/config.toml`,不要提交。
|
||||
|
||||
@@ -243,6 +243,7 @@ Codex 项目级 hook 已放在 `.codex/config.toml` 与 `.codex/hooks/`:
|
||||
|
||||
后端代码修改后,按变更范围选择:
|
||||
|
||||
- `npm run check:rustfmt`
|
||||
- `cargo test -p <crate> --manifest-path server-rs/Cargo.toml`
|
||||
- `cargo test -p platform-image --manifest-path server-rs/Cargo.toml`
|
||||
- `cargo check -p api-server --manifest-path server-rs/Cargo.toml`
|
||||
@@ -265,6 +266,12 @@ npm run spacetime:generate
|
||||
npm run check:spacetime-schema
|
||||
```
|
||||
|
||||
仓库根目录的 `rust-toolchain.toml` 固定 Rust `1.96.0` 并要求 `rustfmt` 组件,
|
||||
`rustfmt.toml` 固定 Edition 2024 的格式化口径。Rust 源码统一使用
|
||||
`cargo fmt --all --manifest-path server-rs/Cargo.toml` 格式化,并用
|
||||
`npm run check:rustfmt` 做只读校验;Codex 提交前门禁、API 生产构建和
|
||||
SpacetimeDB module 生产构建都会执行同一检查,避免不同开发机或构建节点反复产生格式差异。
|
||||
|
||||
## 前端改动验收
|
||||
|
||||
前端修改后,根据范围选择:
|
||||
|
||||
@@ -125,6 +125,7 @@ pipeline {
|
||||
echo "[api-build] 未找到 sccache,改用 rustc 直接构建。"
|
||||
unset RUSTC_WRAPPER
|
||||
fi
|
||||
npm run check:rustfmt
|
||||
pingora_args=()
|
||||
if [[ "${INCLUDE_PINGORA_GATEWAY:-false}" == "true" ]]; then
|
||||
pingora_args+=(--include-pingora-gateway)
|
||||
|
||||
@@ -129,6 +129,7 @@ pipeline {
|
||||
echo "[stdb-build] 未找到可用 sccache,改用 rustc 直接构建。"
|
||||
unset RUSTC_WRAPPER
|
||||
fi
|
||||
npm run check:rustfmt
|
||||
SOURCE_BRANCH="${SOURCE_BRANCH}" SOURCE_COMMIT="${SOURCE_COMMIT}" \
|
||||
npm run build:production-release -- --component spacetime-module --name "${EFFECTIVE_BUILD_VERSION}"
|
||||
'
|
||||
|
||||
+2
-1
@@ -31,6 +31,7 @@
|
||||
"preview": "node scripts/vite-cli.mjs preview",
|
||||
"clean": "node -e \"require('fs').rmSync('dist', { recursive: true, force: true })\"",
|
||||
"check:encoding": "node scripts/check-encoding.mjs",
|
||||
"check:rustfmt": "cargo fmt --all --manifest-path server-rs/Cargo.toml -- --check",
|
||||
"check:spacetime-schema": "node scripts/check-spacetime-schema-guard.mjs",
|
||||
"check:production-ops": "node scripts/check-production-ops-guardrails.mjs",
|
||||
"check:maintenance-page": "node scripts/check-maintenance-page.mjs",
|
||||
@@ -84,7 +85,7 @@
|
||||
"lint:guardrails": "npm run lint:eslint",
|
||||
"typecheck": "tsc -p tsconfig.typecheck-guardrails.json --noEmit",
|
||||
"typecheck:guardrails": "npm run typecheck",
|
||||
"lint": "npm run check:encoding && npm run check:spacetime-schema && npm run check:production-ops && npm run check:maintenance-page && npm run lint:eslint && npm run typecheck",
|
||||
"lint": "npm run check:encoding && npm run check:rustfmt && npm run check:spacetime-schema && npm run check:production-ops && npm run check:maintenance-page && npm run lint:eslint && npm run typecheck",
|
||||
"lint:fix": "eslint . --ext .ts,.tsx,.js,.mjs,.cjs --fix && prettier --write .",
|
||||
"format": "prettier --write .",
|
||||
"format:check": "prettier --check .",
|
||||
|
||||
@@ -0,0 +1,4 @@
|
||||
[toolchain]
|
||||
channel = "1.96.0"
|
||||
profile = "minimal"
|
||||
components = ["rustfmt"]
|
||||
@@ -0,0 +1,4 @@
|
||||
edition = "2024"
|
||||
max_width = 100
|
||||
newline_style = "Unix"
|
||||
use_small_heuristics = "Default"
|
||||
@@ -3,6 +3,21 @@
|
||||
import { readFileSync } from 'node:fs';
|
||||
|
||||
const checks = [
|
||||
{
|
||||
file: 'package.json',
|
||||
includes: '"check:rustfmt": "cargo fmt --all --manifest-path server-rs/Cargo.toml -- --check"',
|
||||
reason: '仓库必须保留统一、只读的 Rust workspace 格式检查入口。',
|
||||
},
|
||||
{
|
||||
file: 'jenkins/Jenkinsfile.production-api-build',
|
||||
includes: 'npm run check:rustfmt',
|
||||
reason: 'API 生产构建必须在编译前执行 Rust 格式检查。',
|
||||
},
|
||||
{
|
||||
file: 'jenkins/Jenkinsfile.production-stdb-module-build',
|
||||
includes: 'npm run check:rustfmt',
|
||||
reason: 'Stdb module 生产构建必须在编译前执行 Rust 格式检查。',
|
||||
},
|
||||
{
|
||||
file: 'server-rs/crates/spacetime-module/src/migration.rs',
|
||||
includes:
|
||||
|
||||
@@ -118,22 +118,36 @@ mod tests {
|
||||
error.message()
|
||||
);
|
||||
let details = mapped.details().expect("details present");
|
||||
assert_eq!(details.get("transport").and_then(|v| v.as_bool()), Some(false));
|
||||
assert_eq!(details.get("timeout").and_then(|v| v.as_bool()), Some(false));
|
||||
assert_eq!(
|
||||
details.get("transport").and_then(|v| v.as_bool()),
|
||||
Some(false)
|
||||
);
|
||||
assert_eq!(
|
||||
details.get("timeout").and_then(|v| v.as_bool()),
|
||||
Some(false)
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn upstream_transport_failure_maps_to_retryable_transport() {
|
||||
let error =
|
||||
MattingError::upstream_transport_error("通用抠图请求失败:dns error".to_string(), false);
|
||||
let error = MattingError::upstream_transport_error(
|
||||
"通用抠图请求失败:dns error".to_string(),
|
||||
false,
|
||||
);
|
||||
let mapped = aliyun_matting_failure_to_app_error(&error, 12);
|
||||
|
||||
assert!(crate::external_api_audit::matting_failure_external_call_attempted(&mapped));
|
||||
let details = mapped.details().expect("details present");
|
||||
// 无 HTTP 状态的传输层失败标记为可重试 transport 故障,且不带 upstreamStatus。
|
||||
assert_eq!(details.get("transport").and_then(|v| v.as_bool()), Some(true));
|
||||
assert_eq!(details.get("timeout").and_then(|v| v.as_bool()), Some(false));
|
||||
assert_eq!(
|
||||
details.get("transport").and_then(|v| v.as_bool()),
|
||||
Some(true)
|
||||
);
|
||||
assert_eq!(
|
||||
details.get("timeout").and_then(|v| v.as_bool()),
|
||||
Some(false)
|
||||
);
|
||||
assert!(details.get("upstreamStatus").is_some_and(|v| v.is_null()));
|
||||
}
|
||||
|
||||
@@ -146,7 +160,10 @@ mod tests {
|
||||
assert_eq!(mapped.status_code(), StatusCode::GATEWAY_TIMEOUT);
|
||||
let details = mapped.details().expect("details present");
|
||||
assert_eq!(details.get("timeout").and_then(|v| v.as_bool()), Some(true));
|
||||
assert_eq!(details.get("transport").and_then(|v| v.as_bool()), Some(true));
|
||||
assert_eq!(
|
||||
details.get("transport").and_then(|v| v.as_bool()),
|
||||
Some(true)
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
@@ -159,7 +176,13 @@ mod tests {
|
||||
|
||||
assert!(crate::external_api_audit::matting_failure_external_call_attempted(&mapped));
|
||||
let details = mapped.details().expect("details present");
|
||||
assert_eq!(details.get("transport").and_then(|v| v.as_bool()), Some(false));
|
||||
assert_eq!(details.get("upstreamStatus").and_then(|v| v.as_u64()), Some(429));
|
||||
assert_eq!(
|
||||
details.get("transport").and_then(|v| v.as_bool()),
|
||||
Some(false)
|
||||
);
|
||||
assert_eq!(
|
||||
details.get("upstreamStatus").and_then(|v| v.as_u64()),
|
||||
Some(429)
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
use axum::{
|
||||
use axum::{
|
||||
Json,
|
||||
extract::{Extension, State},
|
||||
http::StatusCode,
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
pub(crate) use crate::prompt::character_animation::{
|
||||
pub(crate) use crate::prompt::character_animation::{
|
||||
build_character_animation_prompt, build_fallback_moderation_safe_animation_prompt,
|
||||
};
|
||||
pub(crate) use crate::prompt::character_visual::{
|
||||
|
||||
@@ -1 +1 @@
|
||||
pub(crate) use crate::prompt::agent_chat::*;
|
||||
pub(crate) use crate::prompt::agent_chat::*;
|
||||
|
||||
@@ -109,7 +109,6 @@ pub(crate) fn default_editor_screen_background_color() -> EditorScreenBackground
|
||||
EDITOR_SCREEN_BACKGROUND_COLORS[0]
|
||||
}
|
||||
|
||||
|
||||
pub(crate) fn parse_editor_screen_background_color(
|
||||
value: Option<&str>,
|
||||
) -> Result<EditorScreenBackgroundColor, AppError> {
|
||||
|
||||
@@ -177,16 +177,15 @@ pub(crate) async fn resolve_editor_screen_background_color(
|
||||
let mut last_error: Option<String> = None;
|
||||
for attempt in 1..=EDITOR_SCREEN_BACKGROUND_DECISION_MAX_ATTEMPTS {
|
||||
let user_message = match source_image_data_url {
|
||||
Some(image_url) => {
|
||||
LlmMessage::user(user_prompt.as_str()).with_image_url(image_url)
|
||||
}
|
||||
Some(image_url) => LlmMessage::user(user_prompt.as_str()).with_image_url(image_url),
|
||||
None => LlmMessage::user(user_prompt.as_str()),
|
||||
};
|
||||
// 预算要够推理模型(如 gpt-5-mini)先花几百 token 推理、再吐 JSON 答案;
|
||||
// 实测 low 档推理约 320~384 token,取 1024 留足余量。降级客户端遇 stop 提前结束,不会多花。
|
||||
let mut request = LlmTextRequest::new(vec![LlmMessage::system(system_prompt), user_message])
|
||||
.with_max_tokens(1024)
|
||||
.with_request_timeout_ms(EDITOR_SCREEN_BACKGROUND_DECISION_TIMEOUT_MS);
|
||||
let mut request =
|
||||
LlmTextRequest::new(vec![LlmMessage::system(system_prompt), user_message])
|
||||
.with_max_tokens(1024)
|
||||
.with_request_timeout_ms(EDITOR_SCREEN_BACKGROUND_DECISION_TIMEOUT_MS);
|
||||
if let Some(decision_model) = decision_model {
|
||||
// gpt-5-mini 是推理模型(有图视觉档 / 无图文本档均适用):走 Responses 协议并压到 low
|
||||
// 推理档,否则默认档会把预算全烧在推理上、返回空答案。
|
||||
@@ -296,8 +295,7 @@ async fn record_editor_screen_background_decision_llm_error(
|
||||
prompt_chars: usize,
|
||||
reference_image_count: usize,
|
||||
) {
|
||||
let (failure_stage, status_code, timeout, retryable, error_source, raw_excerpt) = match error
|
||||
{
|
||||
let (failure_stage, status_code, timeout, retryable, error_source, raw_excerpt) = match error {
|
||||
LlmError::InvalidConfig(_) | LlmError::InvalidRequest(_) => return,
|
||||
LlmError::Timeout { .. } => ("request_timeout", None, true, true, None, None),
|
||||
LlmError::Connectivity { message, .. } => (
|
||||
@@ -329,14 +327,9 @@ async fn record_editor_screen_background_decision_llm_error(
|
||||
),
|
||||
LlmError::EmptyResponse => ("missing_response", Some(200), false, false, None, None),
|
||||
LlmError::StreamUnavailable => ("response_body", Some(200), false, true, None, None),
|
||||
LlmError::Transport(message) => (
|
||||
"transport",
|
||||
None,
|
||||
false,
|
||||
true,
|
||||
Some(message.as_str()),
|
||||
None,
|
||||
),
|
||||
LlmError::Transport(message) => {
|
||||
("transport", None, false, true, Some(message.as_str()), None)
|
||||
}
|
||||
};
|
||||
record_editor_screen_background_decision_failure(
|
||||
audit,
|
||||
@@ -754,8 +747,7 @@ mod tests {
|
||||
request_id: Some("request-1".to_string()),
|
||||
},
|
||||
);
|
||||
let tracking =
|
||||
crate::external_api_audit::build_external_api_failure_tracking_draft(&audit);
|
||||
let tracking = crate::external_api_audit::build_external_api_failure_tracking_draft(&audit);
|
||||
|
||||
assert_eq!(audit.provider, "vector-engine");
|
||||
assert_eq!(audit.endpoint, "https://vector.example/v1/responses");
|
||||
@@ -809,8 +801,7 @@ mod tests {
|
||||
1,
|
||||
&ExternalApiAuditContext::default(),
|
||||
);
|
||||
let tracking =
|
||||
crate::external_api_audit::build_external_api_failure_tracking_draft(&audit);
|
||||
let tracking = crate::external_api_audit::build_external_api_failure_tracking_draft(&audit);
|
||||
|
||||
assert_eq!(audit.failure_stage, "request_timeout");
|
||||
assert_eq!(audit.status_code, None);
|
||||
@@ -897,7 +888,8 @@ mod tests {
|
||||
let (k, v) = trimmed.split_once('=').unwrap();
|
||||
let v = v.trim().trim_matches('"').trim_matches('\'');
|
||||
// 先出现的文件优先(.env.local > .env.secrets.local > .env),与服务端 dotenv 顺序一致。
|
||||
map.entry(k.trim().to_string()).or_insert_with(|| v.to_string());
|
||||
map.entry(k.trim().to_string())
|
||||
.or_insert_with(|| v.to_string());
|
||||
}
|
||||
}
|
||||
std::env::var(key).ok().or_else(|| map.get(key).cloned())
|
||||
@@ -934,7 +926,10 @@ mod tests {
|
||||
let image = RgbaImage::from_pixel(64, 64, Rgba([120, 180, 120, 255]));
|
||||
let mut bytes = Vec::new();
|
||||
image::DynamicImage::ImageRgba8(image)
|
||||
.write_to(&mut std::io::Cursor::new(&mut bytes), image::ImageFormat::Png)
|
||||
.write_to(
|
||||
&mut std::io::Cursor::new(&mut bytes),
|
||||
image::ImageFormat::Png,
|
||||
)
|
||||
.expect("test image should encode");
|
||||
format!(
|
||||
"data:image/png;base64,{}",
|
||||
@@ -966,7 +961,11 @@ mod tests {
|
||||
|
||||
eprintln!(
|
||||
"[live 无图] mode={:?} hex={} label={} attempts={} fallback={}",
|
||||
decision.mode, decision.color.hex, decision.color.label, decision.attempts, decision.fallback
|
||||
decision.mode,
|
||||
decision.color.hex,
|
||||
decision.color.label,
|
||||
decision.attempts,
|
||||
decision.fallback
|
||||
);
|
||||
assert_eq!(decision.mode, EditorScreenBackgroundDecisionMode::Auto);
|
||||
assert!(
|
||||
@@ -1000,7 +999,11 @@ mod tests {
|
||||
|
||||
eprintln!(
|
||||
"[live 有图] mode={:?} hex={} label={} attempts={} fallback={}",
|
||||
decision.mode, decision.color.hex, decision.color.label, decision.attempts, decision.fallback
|
||||
decision.mode,
|
||||
decision.color.hex,
|
||||
decision.color.label,
|
||||
decision.attempts,
|
||||
decision.fallback
|
||||
);
|
||||
assert_eq!(decision.mode, EditorScreenBackgroundDecisionMode::Auto);
|
||||
assert!(
|
||||
|
||||
@@ -203,11 +203,12 @@ impl ForegroundHistogram {
|
||||
.filter(|bin| bin.mass >= mass_floor)
|
||||
.filter_map(|bin| {
|
||||
let lab = bin.mean();
|
||||
(lab[0] > SKIN_MIN_LIGHTNESS && lab[1] > SKIN_MIN_A && lab[2] > SKIN_MIN_B)
|
||||
.then(|| SkinReference {
|
||||
(lab[0] > SKIN_MIN_LIGHTNESS && lab[1] > SKIN_MIN_A && lab[2] > SKIN_MIN_B).then(
|
||||
|| SkinReference {
|
||||
lab,
|
||||
rgb: bin.mean_rgb(),
|
||||
})
|
||||
},
|
||||
)
|
||||
})
|
||||
.max_by(|left, right| {
|
||||
left.lab[0]
|
||||
@@ -481,12 +482,18 @@ mod tests {
|
||||
let report = report_for(&transparent_image_with_center_block([250, 224, 200]), true);
|
||||
|
||||
assert!(
|
||||
report.excluded.iter().any(|(color, _)| color.hex == "#FFD6C2"),
|
||||
report
|
||||
.excluded
|
||||
.iter()
|
||||
.any(|(color, _)| color.hex == "#FFD6C2"),
|
||||
"肤色前景应剔除暖浅桃色,excluded: {}",
|
||||
report.excluded_summary()
|
||||
);
|
||||
assert!(
|
||||
report.excluded.iter().any(|(color, _)| color.hex == "#FFF2A8"),
|
||||
report
|
||||
.excluded
|
||||
.iter()
|
||||
.any(|(color, _)| color.hex == "#FFF2A8"),
|
||||
"肤色前景应剔除淡黄(Rule 2/3 关键新覆盖),excluded: {}",
|
||||
report.excluded_summary()
|
||||
);
|
||||
@@ -520,7 +527,10 @@ mod tests {
|
||||
|
||||
let enabled = report_for(&image, true);
|
||||
assert!(
|
||||
enabled.excluded.iter().any(|(color, _)| color.hex == "#FFF2A8"),
|
||||
enabled
|
||||
.excluded
|
||||
.iter()
|
||||
.any(|(color, _)| color.hex == "#FFF2A8"),
|
||||
"开启皮肤否决时淡黄应被剔除,excluded: {}",
|
||||
enabled.excluded_summary()
|
||||
);
|
||||
@@ -542,7 +552,10 @@ mod tests {
|
||||
let report = report_for(&transparent_image_with_center_block([127, 179, 255]), false);
|
||||
|
||||
assert!(
|
||||
report.excluded.iter().any(|(color, _)| color.hex == "#7FB3FF"),
|
||||
report
|
||||
.excluded
|
||||
.iter()
|
||||
.any(|(color, _)| color.hex == "#7FB3FF"),
|
||||
"蓝色前景应剔除中度天蓝,excluded: {}",
|
||||
report.excluded_summary()
|
||||
);
|
||||
|
||||
@@ -172,24 +172,14 @@ fn map_external_generation_job_status(
|
||||
100,
|
||||
None,
|
||||
),
|
||||
"running" => (
|
||||
ExternalGenerationJobStatus::Running,
|
||||
"正在生成。",
|
||||
35,
|
||||
None,
|
||||
),
|
||||
"running" => (ExternalGenerationJobStatus::Running, "正在生成。", 35, None),
|
||||
"failed" => (
|
||||
ExternalGenerationJobStatus::Failed,
|
||||
"生成失败。",
|
||||
0,
|
||||
job.last_error_message.clone(),
|
||||
),
|
||||
_ => (
|
||||
ExternalGenerationJobStatus::Queued,
|
||||
"排队中。",
|
||||
8,
|
||||
None,
|
||||
),
|
||||
_ => (ExternalGenerationJobStatus::Queued, "排队中。", 8, None),
|
||||
};
|
||||
|
||||
ExternalGenerationJobStatusRecord {
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
use axum::{
|
||||
use axum::{
|
||||
Json,
|
||||
extract::{Extension, State},
|
||||
};
|
||||
|
||||
@@ -4,8 +4,8 @@ mod admin;
|
||||
mod admin_accounts;
|
||||
mod admin_recharge;
|
||||
mod ai_generation_drafts;
|
||||
mod aliyun_matting;
|
||||
mod ai_tasks;
|
||||
mod aliyun_matting;
|
||||
mod api_response;
|
||||
mod app;
|
||||
mod asset_billing;
|
||||
@@ -670,8 +670,12 @@ mod tests {
|
||||
|
||||
#[test]
|
||||
fn profile_recharge_expiration_listener_is_limited_to_http_roles() {
|
||||
assert!(should_start_profile_recharge_expiration_listener(ProcessRole::Api));
|
||||
assert!(should_start_profile_recharge_expiration_listener(ProcessRole::All));
|
||||
assert!(should_start_profile_recharge_expiration_listener(
|
||||
ProcessRole::Api
|
||||
));
|
||||
assert!(should_start_profile_recharge_expiration_listener(
|
||||
ProcessRole::All
|
||||
));
|
||||
assert!(!should_start_profile_recharge_expiration_listener(
|
||||
ProcessRole::ExternalGenerationWorker
|
||||
));
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
use axum::{
|
||||
use axum::{
|
||||
Router, middleware,
|
||||
routing::{get, post},
|
||||
};
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
use axum::http::{HeaderValue, StatusCode};
|
||||
use axum::http::{HeaderValue, StatusCode};
|
||||
use platform_auth::{AuthPlatformErrorKind, WechatProviderError};
|
||||
use platform_llm::{LlmError, LlmErrorKind};
|
||||
use platform_oss::{OssError, OssErrorKind};
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
use std::{
|
||||
use std::{
|
||||
collections::BTreeMap,
|
||||
time::{Instant, SystemTime, UNIX_EPOCH},
|
||||
};
|
||||
|
||||
@@ -626,14 +626,12 @@ impl AppState {
|
||||
let models = editor_generation_pricing_to_records(&next)?;
|
||||
let record = self
|
||||
.spacetime_client
|
||||
.upsert_editor_generation_pricing_config(
|
||||
editor_generation_pricing_upsert_input(
|
||||
&self.config,
|
||||
admin_user_id,
|
||||
models,
|
||||
crate::editor_project::current_utc_micros(),
|
||||
),
|
||||
)
|
||||
.upsert_editor_generation_pricing_config(editor_generation_pricing_upsert_input(
|
||||
&self.config,
|
||||
admin_user_id,
|
||||
models,
|
||||
crate::editor_project::current_utc_micros(),
|
||||
))
|
||||
.await
|
||||
.map_err(|error| EditorGenerationPricingError::Persistence(error.to_string()))?;
|
||||
let pricing = editor_generation_pricing_from_record(record)?;
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
use axum::{
|
||||
use axum::{
|
||||
Json,
|
||||
extract::{Extension, Query, State},
|
||||
http::{HeaderMap, StatusCode},
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
use platform_auth::{
|
||||
use platform_auth::{
|
||||
DEFAULT_WECHAT_ACCESS_TOKEN_ENDPOINT, DEFAULT_WECHAT_AUTHORIZE_ENDPOINT,
|
||||
DEFAULT_WECHAT_JS_CODE_SESSION_ENDPOINT, DEFAULT_WECHAT_PHONE_NUMBER_ENDPOINT,
|
||||
DEFAULT_WECHAT_STABLE_ACCESS_TOKEN_ENDPOINT, DEFAULT_WECHAT_USER_INFO_ENDPOINT,
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user