文件最后提交记录最后更新时间
21 天前
21 天前
1 个月前
1 个月前
1 个月前
1 个月前
1 个月前
21 天前
README

aesthetics

Standalone UI aesthetics evaluation proxy (Rust). The proxy does not compute scores — evaluation lives in real evaluators behind one HTTP API:

  • arktsx.aesthetics cascade (ArkTS, primary): L0 tree geometry, L1 pixel heuristics, L2 local neural prior, L3 vision-LLM judge, US/EU/CN/JP cultural profiles — reached headlessly via demo/eval.ets (subprocess) or in-app via the grasp aesthetics_assess/aesthetics_poll tool
  • SPbU analyzer (external service): OCR contrast/readability/typography
    • guideline CNN metrics the cascade does not compute; every 0..1 analyzer score maps onto the 12-axis vector (see analyzer_axes: contrast/typography/ consistency/hierarchy + the _JSON geometry family → balance/density/ alignment, touch_target_size_JSON → affordance). With the parity-converted L2 ensemble vendored, the in-framework ResNet50 signal (hierarchy axis, neuralPrior) reproduces the analyzer's binarized guideline metric exactly (E2E max|Δ| = 0.0 on the tag-score; see parity/ in the aestetic workspace).

The proxy adds what evaluators should not care about: evidence assembly (A2UI rendering through the a2ui-server adapter), request routing with ordered fallback, the legacy dashboard projection, repair suggestions, telemetry, async jobs and HTML reports.

Layout

crates/aesthetic/              library crate — embed the proxy directly
  src/proxy/                   EvaluatorBackend trait + router + backends
    cascade_subprocess.rs      headless eval.ets runner (arkts run / bundle run.sh)
    cascade_grasp.rs           in-app cascade via the grasp channel
    analyzer.rs                SPbU analyzer client + Judgement mapping
    types.rs                   EvalRequest, canonical Judgement (12 axes)
  src/providers/               evidence: a2ui-server WS/STF render adapter,
                               OHOS device render adapter (grasp)
  src/legacy.rs                Judgement → legacy AestheticsReport (dashboards)
  src/engine.rs                AestheticProxy (evidence assembly + routing)
  src/report.rs                HTML report
crates/aesthetic-server/       axum HTTP service

Configuration (env)

var meaning
EST_CASCADE_DIR arktsx/aesthetics package dir → enables the cascade subprocess backend; also the cwd for the runners (needs a compiled demo target in a checkout)
EST_ARKTS_BIN arkts binary (default arkts); unused when a bundle is configured
EST_EVAL_BUNDLE arkts bundle dir for demo/eval.ets → the proxy spawns its run.sh instead of arkts run (packaged deployments)
EST_CONVERGENCE_BUNDLE arkts bundle dir for demo/convergence.ets
EST_ANALYZER_URL SPbU analyzer base URL → enables the analyzer backend
EST_GRASP_DEVICE + EST_GRASP_SSE in-app cascade via grasp (default sse http://localhost:4001/sse)
EST_RENDER_WS_URL a2ui-server WS for A2UI evidence rendering
EST_BIND listen address (default 0.0.0.0:8090)

Cascade layer/judge wiring inside the subprocess follows the package's own env keys (AESTHETICS_L2_{PARAM,BIN}, AESTHETICS_VISION_* / API keys) — L2/L3 skip cleanly when unconfigured.

L2 guideline ensemble (SPbU vendor models, parity-converted ncnn): set AESTHETICS_L2_DIR to a directory holding {Apple,Google,Huawei,Microsoft, Samsung}.{param,bin} (see arktsx/aesthetics/models/l2-ensemble/). This takes precedence over the single-pair AESTHETICS_L2_{PARAM,BIN} path and feeds neuralPrior = mean vendor probability, with per-vendor scores in the layers.l2 detail. The optional YOLOv8 UI detector loads separately (AESTHETICS_YOLO_{PARAM,BIN}) and is consumed by the affordance axis tooling. Both run CPU/fp32/single-thread (setFp16(0), 1 thread) — the parity-proven deterministic configuration.

HTTP API

endpoint purpose
GET /healthz liveness + configured backends
GET /v1/backends backend catalog (capabilities)
POST /v1/score JSON: {screenId?, a2ui?, dump?, imageB64?, profile?, intent?, backend?, renderIfMissing?} → 202 {job_id}
POST /v1/score/image multipart image field → 202 {job_id}
POST /v1/aesthetics/report evaluate → legacy dashboard JSON (projection)
GET /v1/jobs/{id} job status/result — canonical Judgement + legacy projection + suggestions + telemetry
GET /v1/reports/{id} HTML report of a finished job

The canonical result is the cascade's JudgementReport JSON 1:1 (camelCase: screenId, composite, axes{12}, layers{l0..l3}, violations, judgeFindings, regionalComposites, gates...). The legacy projection adds stats.extendedAxes so no cascade axis is lost.

Library usage

let proxy = AestheticProxy::builder()
    .with_backend(Arc::new(CascadeSubprocessBackend::new("arktsx/aesthetics")))
    .with_backend(Arc::new(AnalyzerBackend { client: AnalyzerClient::new("http://192.168.100.13:8000") }))
    .with_renderer(Arc::new(A2uiServerAdapter::new("ws://localhost:3099/ws/client")))
    .build();

let report = proxy.score(ScoreRequest {
    image: Some(png_bytes),          // or a2ui + render_if_missing: true
    profile: Some(Profile::Eu),
    intent: "a flight card".into(),
    ..Default::default()
}).await?;
// report.judgement (canonical) / report.legacy (dashboards) / report.suggestions

Tests

cargo test          # hermetic: judgement parsing, router selection/fallback,
                    # legacy translation, HTTP contract on ephemeral ports

The evaluator implementations themselves live upstream: arktsx/aesthetics (cascade + its own test suite) and the SPbU analyzer service. Proxy tests use mock backends; live runs are opt-in via env.

Extending

Add an evaluator by implementing EvaluatorBackend:

#[async_trait]
impl EvaluatorBackend for MyEvaluator {
    fn name(&self) -> String { "mine".into() }
    fn capabilities(&self) -> Capabilities { Capabilities { image: true, ..Default::default() } }
    async fn evaluate(&self, req: &EvalRequest) -> Result<Judgement, EngineError> { /* ... */ }
}

Register it on the proxy/router; selection is capability-based with ordered fallback. Output must be a Judgement — parse your evaluator's report or map it (see proxy/analyzer.rs for a mapping example).