feat: add Chinese UI, Responses models, and mirrored Windows setup
CI / Python 3.12 / ubuntu-latest (push) Canceled after 0s
CI / Python 3.12 / windows-latest (push) Canceled after 0s
CI / Frontend tests and production build (push) Canceled after 0s
CI / Build and smoke-test Python packages (push) Canceled after 0s

Localize the web console and persist custom model and capability settings.
Use OpenAI Responses exclusively and move runtime limits into environment configuration.
Bootstrap uv and the virtual environment on Windows, support optional China mirrors,
and package source distributions without environments, build output, or credentials.

Update regression tests and repository quality checks for the new behavior.
This commit is contained in:
chenmingxuan committed 2026-09-22 19:16:17 +08:00
1 parent 371aa6df56
commit 2ddec53954
183 files changed
+3427 -11857

No files matched your search

+7 -129
View File
@@ -80,7 +80,7 @@ def test_circuit_breaker_opens_and_half_opens():
@pytest.mark.asyncio
async def test_timeout_chunk_is_bisected_and_successful_leaves_survive():
with patch("artemis.agents.video_analyzer.video_analyzer.settings.GOOGLE_API_KEY", None):
with patch("artemis.agents.video_analyzer.video_analyzer.settings.OPENAI_API_KEY", None):
analyzer = VideoAnalyzer(_context())
async def analyze(start: float, end: float, query: str) -> str:
@@ -98,7 +98,7 @@ async def test_timeout_chunk_is_bisected_and_successful_leaves_survive():
@pytest.mark.asyncio
async def test_universal_primary_failure_uses_configured_fallback():
with patch("artemis.agents.video_analyzer.video_analyzer.settings.GOOGLE_API_KEY", None):
with patch("artemis.agents.video_analyzer.video_analyzer.settings.OPENAI_API_KEY", None):
analyzer = VideoAnalyzer(_context())
primary = MagicMock()
@@ -126,57 +126,8 @@ async def test_universal_primary_failure_uses_configured_fallback():
assert get_model.call_args_list[1].kwargs["use_fallback"] is True
@pytest.mark.asyncio
async def test_native_chunk_exhaustion_commits_universal_fallback(tmp_path):
with patch("artemis.agents.video_analyzer.video_analyzer.settings.GOOGLE_API_KEY", None):
analyzer = VideoAnalyzer(_context())
analyzer.use_native_gemini = True
raw_video = tmp_path / "segment.mp4"
raw_video.write_bytes(b"video")
extracted = SimpleNamespace(
success=True,
video_path=raw_video,
actual_start_relative_time=0.0,
duration_seconds=5.0,
warning=None,
)
controller = SimpleNamespace(extract_segment_metadata=AsyncMock(return_value=extracted))
fallback_text = "[from 0.0s to 5.0s] Summary: fallback recovered Analysis: grounded"
with (
patch(
"artemis.agents.video_analyzer.video_analyzer.get_controller",
return_value=controller,
),
patch(
"artemis.agents.video_analyzer.video_analyzer.compress_video_for_api",
new=AsyncMock(return_value=raw_video),
),
patch.object(
analyzer,
"upload_and_poll_file",
new=AsyncMock(side_effect=RuntimeError("503 provider unavailable")),
) as upload,
patch.object(
analyzer,
"_exec_single_chunk_universal",
new=AsyncMock(return_value=fallback_text),
) as fallback,
patch(
"artemis.agents.video_analyzer.video_analyzer.asyncio.sleep",
new=AsyncMock(),
),
):
result = await analyzer._exec_single_chunk(0.0, 5.0, "verify transition")
assert result == fallback_text
assert upload.await_count == 2
fallback.assert_awaited_once()
assert analyzer.blackboard.missing_intervals(0.0, 5.0, "verify transition") == []
def test_action_timestamps_drive_dense_sampling():
with patch("artemis.agents.video_analyzer.video_analyzer.settings.GOOGLE_API_KEY", None):
with patch("artemis.agents.video_analyzer.video_analyzer.settings.OPENAI_API_KEY", None):
analyzer = VideoAnalyzer(_context())
step = SimpleNamespace(timestamp=104.0, action_taken={"name": "tap"})
storage = SimpleNamespace(get_steps=lambda _session: [step])
@@ -304,7 +255,7 @@ class _AgenticRejected(Exception):
@pytest.mark.asyncio
async def test_pending_chunk_sized_for_agentic_ceiling_is_not_run_statically(tmp_path):
"""A chunk planned at 600s whose run degraded before it started is handed back."""
with patch("artemis.agents.video_analyzer.video_analyzer.settings.GOOGLE_API_KEY", None):
with patch("artemis.agents.video_analyzer.video_analyzer.settings.OPENAI_API_KEY", None):
analyzer = VideoAnalyzer(_context())
analyzer.use_native_gemini = True
# A sibling chunk already flipped the run to static (60s ceiling).
@@ -318,7 +269,7 @@ async def test_pending_chunk_sized_for_agentic_ceiling_is_not_run_statically(tmp
return_value=controller,
),
patch(
"artemis.agents.video_analyzer.chunk_native._run_native_chunk_conversation",
"artemis.agents.video_analyzer.chunk_runner._prepare_chunk_media",
new=AsyncMock(),
) as static_path,
patch.object(analyzer, "_exec_single_chunk_universal", new=AsyncMock()) as universal,
@@ -335,83 +286,10 @@ async def test_pending_chunk_sized_for_agentic_ceiling_is_not_run_statically(tmp
assert analyzer.blackboard.missing_intervals(0.0, 150.0, "verify toggle") == [(0.0, 150.0)]
@pytest.mark.asyncio
async def test_sibling_chunk_degraded_mid_run_is_replanned_into_static_pieces(tmp_path):
"""Two 600s chunks: the first is rejected while the second is still in media prep."""
with patch("artemis.agents.video_analyzer.video_analyzer.settings.GOOGLE_API_KEY", None):
analyzer = VideoAnalyzer(_context())
analyzer.use_native_gemini = True
analyzer.video_processing = "agentic"
analyzer.chunk_size_seconds = analyzer.agentic_chunk_size_seconds
assert analyzer.chunk_size_seconds == 600.0
raw_video = tmp_path / "segment.mp4"
raw_video.write_bytes(b"video")
first_rejected = asyncio.Event()
async def extract(start: float, end: float):
if start >= 600.0 and analyzer.video_processing == "agentic":
# The second 600s chunk is mid-prep when the first one degrades.
await first_rejected.wait()
return SimpleNamespace(
success=True,
video_path=raw_video,
actual_start_relative_time=start,
duration_seconds=end - start,
warning=None,
)
controller = SimpleNamespace(extract_segment_metadata=AsyncMock(side_effect=extract))
agentic_calls: list[tuple[float, float]] = []
async def agentic(analyzer_, media, cs, ce, *rest):
agentic_calls.append((cs, ce))
first_rejected.set()
raise _AgenticRejected("400 INVALID_ARGUMENT: processing 'agentic' is not supported")
async def static_answer(analyzer_, media, cs, ce, *rest):
return f"[from {cs:.1f}s to {ce:.1f}s] Summary: static Analysis: ok"
with (
patch(
"artemis.agents.video_analyzer.video_analyzer.get_controller",
return_value=controller,
),
patch(
"artemis.agents.video_analyzer.video_analyzer.compress_video_for_api",
new=AsyncMock(return_value=raw_video),
),
patch(
"artemis.agents.video_analyzer.chunk_native.run_agentic_chunk_conversation",
new=AsyncMock(side_effect=agentic),
),
patch(
"artemis.agents.video_analyzer.chunk_native._run_native_chunk_conversation",
new=AsyncMock(side_effect=static_answer),
) as static_path,
patch.object(analyzer, "_exec_single_chunk_universal", new=AsyncMock()) as universal,
patch("artemis.agents.video_analyzer.video_analyzer._record_llm_event"),
):
result = await analyzer.exec_spawn_sub_agent(0.0, 1200.0, "verify toggle")
# Only the first chunk ever reached the agentic API; the second never ran
# its 600s clip statically and neither went to the universal fallback.
assert agentic_calls == [(0.0, 600.0)]
universal.assert_not_awaited()
segments = sorted((call.args[2], call.args[3]) for call in static_path.await_args_list)
assert len(segments) == 20
assert segments[0] == (0.0, 60.0)
assert segments[-1] == (1140.0, 1200.0)
assert all(ce - cs <= 60.0 for cs, ce in segments)
assert "PARTIAL" not in result
assert result.count("Summary: static") == 20
assert analyzer.video_processing == "static"
@pytest.mark.asyncio
async def test_short_degraded_chunk_is_retried_statically_once():
"""A 5s chunk (below min_chunk*2) is re-run as-is instead of failing."""
with patch("artemis.agents.video_analyzer.video_analyzer.settings.GOOGLE_API_KEY", None):
with patch("artemis.agents.video_analyzer.video_analyzer.settings.OPENAI_API_KEY", None):
analyzer = VideoAnalyzer(_context())
calls: list[tuple[float, float]] = []
@@ -436,7 +314,7 @@ async def test_short_degraded_chunk_is_retried_statically_once():
@pytest.mark.asyncio
async def test_static_retry_of_degraded_chunk_is_bounded():
"""A second degrade on the same interval is terminal: no loop, no bisect."""
with patch("artemis.agents.video_analyzer.video_analyzer.settings.GOOGLE_API_KEY", None):
with patch("artemis.agents.video_analyzer.video_analyzer.settings.OPENAI_API_KEY", None):
analyzer = VideoAnalyzer(_context())
calls: list[tuple[float, float]] = []