New router intelligence modules (26 files): alert_ingest/store, audit_store, architecture_pressure, backlog_generator/store, cost_analyzer, data_governance, dependency_scanner, drift_analyzer, incident_* (5 files), llm_enrichment, platform_priority_digest, provider_budget, release_check_runner, risk_* (6 files), signature_state_store, sofiia_auto_router, tool_governance New services: - sofiia-console: Dockerfile, adapters/, monitor/nodes/ops/voice modules, launchd, react static - memory-service: integration_endpoints, integrations, voice_endpoints, static UI - aurora-service: full app suite (analysis, job_store, orchestrator, reporting, schemas, subagents) - sofiia-supervisor: new supervisor service - aistalk-bridge-lite: Telegram bridge lite - calendar-service: CalDAV calendar service with reminders - mlx-stt-service / mlx-tts-service: Apple Silicon speech services - binance-bot-monitor: market monitor service - node-worker: STT/TTS memory providers New tools (9): agent_email, browser_tool, contract_tool, observability_tool, oncall_tool, pr_reviewer_tool, repo_tool, safe_code_executor, secure_vault New crews: agromatrix_crew (10 modules: depth_classifier, doc_facts, doc_focus, farm_state, light_reply, llm_factory, memory_manager, proactivity, reflection_engine, session_context, style_adapter, telemetry) Tests: 85+ test files for all new modules Made-with: Cursor
226 lines
7.0 KiB
TypeScript
226 lines
7.0 KiB
TypeScript
import React, { useMemo } from "react";
|
|
|
|
export type AuroraResolution = "original" | "1080p" | "4k" | "8k" | "custom";
|
|
export type AuroraFormat = "mp4_h264" | "mp4_h265" | "avi_lossless" | "frames_png";
|
|
export type AuroraRoi = "full_frame" | "auto_faces" | "auto_plates" | "manual";
|
|
|
|
export interface AuroraCropBox {
|
|
x: number;
|
|
y: number;
|
|
width: number;
|
|
height: number;
|
|
}
|
|
|
|
export interface ExportSettingsValue {
|
|
resolution: AuroraResolution;
|
|
format: AuroraFormat;
|
|
roi: AuroraRoi;
|
|
customWidth?: number;
|
|
customHeight?: number;
|
|
crop?: AuroraCropBox | null;
|
|
}
|
|
|
|
interface ExportSettingsProps {
|
|
value: ExportSettingsValue;
|
|
onChange: (next: ExportSettingsValue) => void;
|
|
disabled?: boolean;
|
|
}
|
|
|
|
function toInt(v: string, fallback = 0): number {
|
|
const n = Number.parseInt(v, 10);
|
|
return Number.isFinite(n) ? Math.max(0, n) : fallback;
|
|
}
|
|
|
|
export const ExportSettings: React.FC<ExportSettingsProps> = ({
|
|
value,
|
|
onChange,
|
|
disabled = false,
|
|
}) => {
|
|
const crop = value.crop ?? { x: 0, y: 0, width: 0, height: 0 };
|
|
const showCustomResolution = value.resolution === "custom";
|
|
const showManualCrop = value.roi === "manual";
|
|
const summary = useMemo(() => {
|
|
const res =
|
|
value.resolution === "custom"
|
|
? `${value.customWidth || 0}x${value.customHeight || 0}`
|
|
: value.resolution;
|
|
return `${res} • ${value.format} • ${value.roi}`;
|
|
}, [value]);
|
|
|
|
return (
|
|
<section
|
|
style={{
|
|
border: "1px solid #2f2f35",
|
|
borderRadius: 10,
|
|
padding: 12,
|
|
background: "#111217",
|
|
color: "#e5e7eb",
|
|
}}
|
|
aria-label="Aurora export settings"
|
|
>
|
|
<div style={{ fontSize: 13, fontWeight: 700, marginBottom: 10 }}>Export Settings</div>
|
|
|
|
<label style={{ display: "block", marginBottom: 8 }}>
|
|
<span style={{ fontSize: 12, color: "#9ca3af", display: "block", marginBottom: 4 }}>
|
|
Resolution
|
|
</span>
|
|
<select
|
|
disabled={disabled}
|
|
value={value.resolution}
|
|
onChange={(e) => onChange({ ...value, resolution: e.target.value as AuroraResolution })}
|
|
style={{ width: "100%" }}
|
|
>
|
|
<option value="original">Original</option>
|
|
<option value="1080p">1080p</option>
|
|
<option value="4k">4K</option>
|
|
<option value="8k">8K</option>
|
|
<option value="custom">Custom</option>
|
|
</select>
|
|
</label>
|
|
|
|
{showCustomResolution && (
|
|
<div style={{ display: "grid", gridTemplateColumns: "1fr 1fr", gap: 8, marginBottom: 8 }}>
|
|
<label>
|
|
<span style={{ fontSize: 12, color: "#9ca3af", display: "block", marginBottom: 4 }}>
|
|
Width
|
|
</span>
|
|
<input
|
|
type="number"
|
|
min={1}
|
|
disabled={disabled}
|
|
value={value.customWidth || 0}
|
|
onChange={(e) => onChange({ ...value, customWidth: toInt(e.target.value, 0) })}
|
|
/>
|
|
</label>
|
|
<label>
|
|
<span style={{ fontSize: 12, color: "#9ca3af", display: "block", marginBottom: 4 }}>
|
|
Height
|
|
</span>
|
|
<input
|
|
type="number"
|
|
min={1}
|
|
disabled={disabled}
|
|
value={value.customHeight || 0}
|
|
onChange={(e) => onChange({ ...value, customHeight: toInt(e.target.value, 0) })}
|
|
/>
|
|
</label>
|
|
</div>
|
|
)}
|
|
|
|
<label style={{ display: "block", marginBottom: 8 }}>
|
|
<span style={{ fontSize: 12, color: "#9ca3af", display: "block", marginBottom: 4 }}>
|
|
Format
|
|
</span>
|
|
<select
|
|
disabled={disabled}
|
|
value={value.format}
|
|
onChange={(e) => onChange({ ...value, format: e.target.value as AuroraFormat })}
|
|
style={{ width: "100%" }}
|
|
>
|
|
<option value="mp4_h264">MP4 (H.264)</option>
|
|
<option value="mp4_h265">MP4 (H.265)</option>
|
|
<option value="avi_lossless">AVI (lossless)</option>
|
|
<option value="frames_png">Frames (PNG)</option>
|
|
</select>
|
|
</label>
|
|
|
|
<label style={{ display: "block", marginBottom: 8 }}>
|
|
<span style={{ fontSize: 12, color: "#9ca3af", display: "block", marginBottom: 4 }}>
|
|
ROI
|
|
</span>
|
|
<select
|
|
disabled={disabled}
|
|
value={value.roi}
|
|
onChange={(e) => onChange({ ...value, roi: e.target.value as AuroraRoi })}
|
|
style={{ width: "100%" }}
|
|
>
|
|
<option value="full_frame">Full frame</option>
|
|
<option value="auto_faces">Auto faces</option>
|
|
<option value="auto_plates">Auto license plates</option>
|
|
<option value="manual">Manual crop box</option>
|
|
</select>
|
|
</label>
|
|
|
|
{showManualCrop && (
|
|
<div style={{ display: "grid", gridTemplateColumns: "1fr 1fr", gap: 8, marginBottom: 8 }}>
|
|
<label>
|
|
<span style={{ fontSize: 12, color: "#9ca3af", display: "block", marginBottom: 4 }}>
|
|
X
|
|
</span>
|
|
<input
|
|
type="number"
|
|
min={0}
|
|
disabled={disabled}
|
|
value={crop.x}
|
|
onChange={(e) =>
|
|
onChange({
|
|
...value,
|
|
crop: { ...crop, x: toInt(e.target.value, 0) },
|
|
})
|
|
}
|
|
/>
|
|
</label>
|
|
<label>
|
|
<span style={{ fontSize: 12, color: "#9ca3af", display: "block", marginBottom: 4 }}>
|
|
Y
|
|
</span>
|
|
<input
|
|
type="number"
|
|
min={0}
|
|
disabled={disabled}
|
|
value={crop.y}
|
|
onChange={(e) =>
|
|
onChange({
|
|
...value,
|
|
crop: { ...crop, y: toInt(e.target.value, 0) },
|
|
})
|
|
}
|
|
/>
|
|
</label>
|
|
<label>
|
|
<span style={{ fontSize: 12, color: "#9ca3af", display: "block", marginBottom: 4 }}>
|
|
Width
|
|
</span>
|
|
<input
|
|
type="number"
|
|
min={0}
|
|
disabled={disabled}
|
|
value={crop.width}
|
|
onChange={(e) =>
|
|
onChange({
|
|
...value,
|
|
crop: { ...crop, width: toInt(e.target.value, 0) },
|
|
})
|
|
}
|
|
/>
|
|
</label>
|
|
<label>
|
|
<span style={{ fontSize: 12, color: "#9ca3af", display: "block", marginBottom: 4 }}>
|
|
Height
|
|
</span>
|
|
<input
|
|
type="number"
|
|
min={0}
|
|
disabled={disabled}
|
|
value={crop.height}
|
|
onChange={(e) =>
|
|
onChange({
|
|
...value,
|
|
crop: { ...crop, height: toInt(e.target.value, 0) },
|
|
})
|
|
}
|
|
/>
|
|
</label>
|
|
</div>
|
|
)}
|
|
|
|
<div style={{ marginTop: 6, fontSize: 12, color: "#9ca3af" }}>
|
|
Selected: <span style={{ color: "#e5e7eb" }}>{summary}</span>
|
|
</div>
|
|
</section>
|
|
);
|
|
};
|
|
|
|
export default ExportSettings;
|
|
|