Deploy v4.5.0 platform reliability
Browse files- FIX_REPORT_v4.5.0.md +18 -0
- README.md +1 -1
- VERSION +1 -1
- routes/datasets.py +8 -0
- services/context_compressor.py +39 -3
- services/ocean_batch.py +10 -39
- services/region_registry.py +86 -0
- static/js/app.js +50 -18
- tests/test_chat_lifecycle_progress_v393.py +1 -1
- tests/test_download_progress_v391.py +1 -1
- tests/test_download_resilience_v422.py +1 -1
- tests/test_history_sidebar_v373.py +1 -1
- tests/test_large_download_jobs_v400.py +1 -1
- tests/test_merged_health_v433.py +1 -1
- tests/test_multitask_observability_v442.py +1 -1
- tests/test_ocean_batch_downloads_v420.py +1 -1
- tests/test_ocean_batch_routing_v421.py +2 -2
- tests/test_ocean_batch_v410.py +1 -1
- tests/test_p0_finish_v380.py +1 -1
- tests/test_p0_phase2_v371.py +1 -1
- tests/test_p0_phase3_v372.py +1 -1
- tests/test_platform_reliability_v450.py +65 -0
- tests/test_quality_center_v362.py +1 -1
- tests/test_quality_trust_v361.py +1 -1
- tests/test_release_stability.py +1 -1
- tests/test_runtime_hotfix_v376.py +1 -1
- tests/test_sidebar_collapse_v374.py +1 -1
- tests/test_sidebar_partial_collapse_v375.py +1 -1
- tests/test_smart_progress_avatar_sync_v441.py +1 -1
- tests/test_sse_heartbeat_v401.py +1 -1
- tests/test_ui_avatar_multitask_v440.py +1 -1
- tests/test_unified_download_manager_v392.py +1 -1
- tests/test_unified_query_v390.py +1 -1
- tests/test_v381_navigation_ocean_intent.py +1 -1
- ui_server.py +3 -2
FIX_REPORT_v4.5.0.md
ADDED
|
@@ -0,0 +1,18 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# v4.5.0 平台可靠性整合修复
|
| 2 |
+
|
| 3 |
+
本版针对《鱿鱼数据平台功能测试排查报告》的四项已定位问题,以及 v4.4.2
|
| 4 |
+
多任务实测中的“多任务路由优先级/上下文串扰/并行过程不可验证”问题进行整合修复。
|
| 5 |
+
|
| 6 |
+
## 已处理
|
| 7 |
+
1. 数据中心按实时 Hugging Face 仓库成员关系展示来源,Squid 不再只看 category=squid;
|
| 8 |
+
实时文件数直接采用 squid_file_count/tuna_file_count,避免来源匹配重复计数。
|
| 9 |
+
2. Ocean 单文件导出不再让浏览器直连上游/ngrok 地址,统一进入 `/api/download/jobs`
|
| 10 |
+
管理下载链路;批量 Ocean 下载继续沿用既有代理、Range、重试和链接刷新。
|
| 11 |
+
3. 连续追问扩展到深度、水深、米层、图层、层位和变量;结构化上下文新增 layer。
|
| 12 |
+
4. 新增 `services/region_registry.py`,命名海域通过平台固定映射解析;
|
| 13 |
+
显式经纬度优先,结构化上下文会回显实际 bbox 和映射来源。
|
| 14 |
+
5. 多任务解析支持“任务1:...”格式;显式多任务在前端先拆分,再让每个独立子会话
|
| 15 |
+
自行进入 Ocean/Fisheries/普通 Agent 路由。
|
| 16 |
+
6. 每个子任务线程独立、history 为空;原始多任务请求只作为共享指代解析材料,
|
| 17 |
+
并通过 `[TARGET_CHILD_TASK]` 明确限制目标,降低兄弟任务上下文串扰。
|
| 18 |
+
7. 多任务面板增加实时并发数、单任务耗时、墙钟耗时、串行估算和加速比。
|
README.md
CHANGED
|
@@ -10,7 +10,7 @@ pinned: false
|
|
| 10 |
|
| 11 |
# Global Marine Foundation Data Agent
|
| 12 |
|
| 13 |
-
Current UI release: **v4.
|
| 14 |
|
| 15 |
|
| 16 |
## v3.4.0 稳定性重构(第一阶段)
|
|
|
|
| 10 |
|
| 11 |
# Global Marine Foundation Data Agent
|
| 12 |
|
| 13 |
+
Current UI release: **v4.5.0**.
|
| 14 |
|
| 15 |
|
| 16 |
## v3.4.0 稳定性重构(第一阶段)
|
VERSION
CHANGED
|
@@ -1 +1 @@
|
|
| 1 |
-
4.
|
|
|
|
| 1 |
+
4.5.0
|
routes/datasets.py
CHANGED
|
@@ -55,6 +55,14 @@ async def sidebar_datasets(request: Request, refresh: bool = False):
|
|
| 55 |
"size_bytes": sum(item["size_bytes"] for item in matched),
|
| 56 |
"examples": [item["path"] for item in matched[:4]],
|
| 57 |
"matched_aliases": matched_aliases,
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 58 |
"metadata": {
|
| 59 |
"repository": "、".join(sorted({
|
| 60 |
item.get("repository", "") for item in matched
|
|
|
|
| 55 |
"size_bytes": sum(item["size_bytes"] for item in matched),
|
| 56 |
"examples": [item["path"] for item in matched[:4]],
|
| 57 |
"matched_aliases": matched_aliases,
|
| 58 |
+
"repository_domains": sorted({
|
| 59 |
+
str(item.get("repository_domain") or "")
|
| 60 |
+
for item in matched if item.get("repository_domain")
|
| 61 |
+
}),
|
| 62 |
+
"file_count_by_repository": {
|
| 63 |
+
domain: sum(1 for item in matched if item.get("repository_domain")==domain)
|
| 64 |
+
for domain in ("tuna","squid")
|
| 65 |
+
},
|
| 66 |
"metadata": {
|
| 67 |
"repository": "、".join(sorted({
|
| 68 |
item.get("repository", "") for item in matched
|
services/context_compressor.py
CHANGED
|
@@ -19,6 +19,8 @@ import re
|
|
| 19 |
from datetime import date
|
| 20 |
from typing import Any
|
| 21 |
|
|
|
|
|
|
|
| 22 |
from sidebar_catalog import (
|
| 23 |
HF_SOURCE_ALIASES,
|
| 24 |
OCEAN_CATALOG,
|
|
@@ -62,6 +64,7 @@ EXTRA_VARIABLE_ALIASES = {
|
|
| 62 |
"海温": "sst",
|
| 63 |
"海面高度": "zos",
|
| 64 |
"海面高度异常": "zos",
|
|
|
|
| 65 |
"叶绿素": "chl",
|
| 66 |
"溶解氧": "o2",
|
| 67 |
"混合层深度": "mlotst",
|
|
@@ -354,6 +357,9 @@ def _extract_region_text(text: str) -> str:
|
|
| 354 |
|
| 355 |
def extract_depth(text: str) -> str:
|
| 356 |
q = str(text or "")
|
|
|
|
|
|
|
|
|
|
| 357 |
match = DEPTH_RE.search(q)
|
| 358 |
if match:
|
| 359 |
return match.group(1).strip()
|
|
@@ -369,6 +375,16 @@ def extract_depth(text: str) -> str:
|
|
| 369 |
return ""
|
| 370 |
|
| 371 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 372 |
def extract_request_spec(text: Any) -> dict[str, Any]:
|
| 373 |
"""Extract the stable data-request fields from one user message."""
|
| 374 |
source_text = _clean_text(text)
|
|
@@ -419,7 +435,9 @@ def extract_request_spec(text: Any) -> dict[str, Any]:
|
|
| 419 |
|
| 420 |
variables = _dedupe(ocean_variables + fishery_variables)
|
| 421 |
region = _extract_region_text(source_text)
|
|
|
|
| 422 |
depth = extract_depth(source_text)
|
|
|
|
| 423 |
date_text = _extract_date_text(source_text)
|
| 424 |
output_format = extract_format(source_text)
|
| 425 |
|
|
@@ -428,8 +446,15 @@ def extract_request_spec(text: Any) -> dict[str, Any]:
|
|
| 428 |
"dataset": dataset_parts,
|
| 429 |
"dataset_origin": dataset_origin,
|
| 430 |
"variables": variables,
|
| 431 |
-
"region":
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 432 |
"depth": depth,
|
|
|
|
| 433 |
"date": date_text,
|
| 434 |
"format": output_format,
|
| 435 |
"raw_request": source_text[:2000],
|
|
@@ -496,8 +521,13 @@ def merge_request_spec(
|
|
| 496 |
spec["variables"] = new_vars
|
| 497 |
if parsed.get("region"):
|
| 498 |
spec["region"] = _merge_region(spec.get("region", ""), parsed["region"])
|
|
|
|
|
|
|
|
|
|
| 499 |
if parsed.get("depth"):
|
| 500 |
spec["depth"] = parsed["depth"]
|
|
|
|
|
|
|
| 501 |
if parsed.get("date"):
|
| 502 |
spec["date"] = parsed["date"]
|
| 503 |
if parsed.get("format"):
|
|
@@ -531,8 +561,14 @@ def render_request_spec(
|
|
| 531 |
lines.append(f"变量:{'、'.join(spec['variables'])}")
|
| 532 |
if spec.get("region"):
|
| 533 |
lines.append(f"区域:{spec['region']}")
|
|
|
|
|
|
|
|
|
|
|
|
|
| 534 |
if spec.get("depth"):
|
| 535 |
lines.append(f"深度:{spec['depth']}")
|
|
|
|
|
|
|
| 536 |
if spec.get("date"):
|
| 537 |
lines.append(f"日期/时间:{spec['date']}")
|
| 538 |
if spec.get("format"):
|
|
@@ -543,7 +579,7 @@ def render_request_spec(
|
|
| 543 |
spec.get(key)
|
| 544 |
for key in (
|
| 545 |
"domain", "dataset", "variables", "region",
|
| 546 |
-
"depth", "date", "format",
|
| 547 |
)
|
| 548 |
)
|
| 549 |
if include_raw and spec.get("raw_request") and not has_structured:
|
|
@@ -633,7 +669,7 @@ def build_request_spec_from_messages(
|
|
| 633 |
parsed.get(key)
|
| 634 |
for key in (
|
| 635 |
"domain", "dataset", "variables", "region",
|
| 636 |
-
"depth", "date", "format",
|
| 637 |
)
|
| 638 |
)
|
| 639 |
if has_data:
|
|
|
|
| 19 |
from datetime import date
|
| 20 |
from typing import Any
|
| 21 |
|
| 22 |
+
from services.region_registry import resolve_region, format_bbox
|
| 23 |
+
|
| 24 |
from sidebar_catalog import (
|
| 25 |
HF_SOURCE_ALIASES,
|
| 26 |
OCEAN_CATALOG,
|
|
|
|
| 64 |
"海温": "sst",
|
| 65 |
"海面高度": "zos",
|
| 66 |
"海面高度异常": "zos",
|
| 67 |
+
"ssh": "zos",
|
| 68 |
"叶绿素": "chl",
|
| 69 |
"溶解氧": "o2",
|
| 70 |
"混合层深度": "mlotst",
|
|
|
|
| 357 |
|
| 358 |
def extract_depth(text: str) -> str:
|
| 359 |
q = str(text or "")
|
| 360 |
+
layer_depth=re.search(r"([0-9]+(?:\.[0-9]+)?\s*(?:米|m|dbar|db))\s*层",q,re.IGNORECASE)
|
| 361 |
+
if layer_depth:
|
| 362 |
+
return layer_depth.group(1).strip()
|
| 363 |
match = DEPTH_RE.search(q)
|
| 364 |
if match:
|
| 365 |
return match.group(1).strip()
|
|
|
|
| 375 |
return ""
|
| 376 |
|
| 377 |
|
| 378 |
+
def extract_layer(text: str) -> str:
|
| 379 |
+
q=str(text or "")
|
| 380 |
+
m=re.search(r"(?:第\s*)?(\d{1,3})\s*(?:层|layer)",q,re.IGNORECASE)
|
| 381 |
+
if m:
|
| 382 |
+
return m.group(1)
|
| 383 |
+
if re.search(r"(?:图层|层位|layer)",q,re.IGNORECASE):
|
| 384 |
+
return "unspecified"
|
| 385 |
+
return ""
|
| 386 |
+
|
| 387 |
+
|
| 388 |
def extract_request_spec(text: Any) -> dict[str, Any]:
|
| 389 |
"""Extract the stable data-request fields from one user message."""
|
| 390 |
source_text = _clean_text(text)
|
|
|
|
| 435 |
|
| 436 |
variables = _dedupe(ocean_variables + fishery_variables)
|
| 437 |
region = _extract_region_text(source_text)
|
| 438 |
+
region_info = resolve_region(source_text) or (resolve_region(region) if region else None)
|
| 439 |
depth = extract_depth(source_text)
|
| 440 |
+
layer = extract_layer(source_text)
|
| 441 |
date_text = _extract_date_text(source_text)
|
| 442 |
output_format = extract_format(source_text)
|
| 443 |
|
|
|
|
| 446 |
"dataset": dataset_parts,
|
| 447 |
"dataset_origin": dataset_origin,
|
| 448 |
"variables": variables,
|
| 449 |
+
"region": (
|
| 450 |
+
region
|
| 451 |
+
if (region_info or {}).get("source")=="explicit_coordinates" and region
|
| 452 |
+
else ((region_info or {}).get("name") or region)
|
| 453 |
+
),
|
| 454 |
+
"region_bbox": (region_info or {}).get("bbox") or {},
|
| 455 |
+
"region_source": (region_info or {}).get("source") or "",
|
| 456 |
"depth": depth,
|
| 457 |
+
"layer": layer,
|
| 458 |
"date": date_text,
|
| 459 |
"format": output_format,
|
| 460 |
"raw_request": source_text[:2000],
|
|
|
|
| 521 |
spec["variables"] = new_vars
|
| 522 |
if parsed.get("region"):
|
| 523 |
spec["region"] = _merge_region(spec.get("region", ""), parsed["region"])
|
| 524 |
+
if parsed.get("region_bbox"):
|
| 525 |
+
spec["region_bbox"] = dict(parsed["region_bbox"])
|
| 526 |
+
spec["region_source"] = parsed.get("region_source") or ""
|
| 527 |
if parsed.get("depth"):
|
| 528 |
spec["depth"] = parsed["depth"]
|
| 529 |
+
if parsed.get("layer"):
|
| 530 |
+
spec["layer"] = parsed["layer"]
|
| 531 |
if parsed.get("date"):
|
| 532 |
spec["date"] = parsed["date"]
|
| 533 |
if parsed.get("format"):
|
|
|
|
| 561 |
lines.append(f"变量:{'、'.join(spec['variables'])}")
|
| 562 |
if spec.get("region"):
|
| 563 |
lines.append(f"区域:{spec['region']}")
|
| 564 |
+
if spec.get("region_bbox") and spec.get("region_source")!="explicit_coordinates":
|
| 565 |
+
bbox_text=format_bbox(spec.get("region_bbox"))
|
| 566 |
+
if bbox_text:
|
| 567 |
+
lines.append(f"平台本次固定经纬度范围:{bbox_text}({spec.get('region_source') or 'registry'})")
|
| 568 |
if spec.get("depth"):
|
| 569 |
lines.append(f"深度:{spec['depth']}")
|
| 570 |
+
if spec.get("layer"):
|
| 571 |
+
lines.append(f"图层:{spec['layer']}")
|
| 572 |
if spec.get("date"):
|
| 573 |
lines.append(f"日期/时间:{spec['date']}")
|
| 574 |
if spec.get("format"):
|
|
|
|
| 579 |
spec.get(key)
|
| 580 |
for key in (
|
| 581 |
"domain", "dataset", "variables", "region",
|
| 582 |
+
"depth", "layer", "date", "format",
|
| 583 |
)
|
| 584 |
)
|
| 585 |
if include_raw and spec.get("raw_request") and not has_structured:
|
|
|
|
| 669 |
parsed.get(key)
|
| 670 |
for key in (
|
| 671 |
"domain", "dataset", "variables", "region",
|
| 672 |
+
"depth", "layer", "date", "format",
|
| 673 |
)
|
| 674 |
)
|
| 675 |
if has_data:
|
services/ocean_batch.py
CHANGED
|
@@ -12,6 +12,8 @@ from pathlib import Path
|
|
| 12 |
from typing import Any
|
| 13 |
from urllib.parse import urlparse
|
| 14 |
|
|
|
|
|
|
|
| 15 |
import httpx
|
| 16 |
|
| 17 |
VARIABLE_MAP={
|
|
@@ -25,21 +27,6 @@ VARIABLE_MAP={
|
|
| 25 |
"vo":{"source":"cmems_physics","variable":"vo","label":"VO"},
|
| 26 |
}
|
| 27 |
|
| 28 |
-
# Named-region defaults used only when the user does not provide explicit
|
| 29 |
-
# coordinates. Keep these conservative and visible in the task metadata.
|
| 30 |
-
NAMED_OCEAN_REGIONS=(
|
| 31 |
-
{
|
| 32 |
-
"name":"西北太平洋",
|
| 33 |
-
"aliases":("西北太平洋","northwest pacific","north-west pacific","nw pacific"),
|
| 34 |
-
"bbox":{"lon_min":120.0,"lon_max":180.0,"lat_min":0.0,"lat_max":60.0},
|
| 35 |
-
},
|
| 36 |
-
{
|
| 37 |
-
"name":"西北太平洋中纬度",
|
| 38 |
-
"aliases":("西北太平洋中纬度","northwest pacific mid-latitude"),
|
| 39 |
-
"bbox":{"lon_min":120.0,"lon_max":160.0,"lat_min":20.0,"lat_max":50.0},
|
| 40 |
-
},
|
| 41 |
-
)
|
| 42 |
-
|
| 43 |
def _daterange(start:date,end:date):
|
| 44 |
cur=start
|
| 45 |
while cur<=end:
|
|
@@ -62,30 +49,14 @@ def _parse_date_range(prompt:str):
|
|
| 62 |
return None
|
| 63 |
|
| 64 |
def _parse_bbox(prompt:str):
|
| 65 |
-
|
| 66 |
-
|
| 67 |
-
|
| 68 |
-
|
| 69 |
-
|
| 70 |
-
|
| 71 |
-
|
| 72 |
-
|
| 73 |
-
ys=sorted((float(mlat.group(1)),float(mlat.group(2))))
|
| 74 |
-
return {
|
| 75 |
-
"bbox":{"lon_min":xs[0],"lon_max":xs[1],"lat_min":ys[0],"lat_max":ys[1]},
|
| 76 |
-
"region_name":"自定义经纬度范围",
|
| 77 |
-
"region_source":"explicit_coordinates",
|
| 78 |
-
}
|
| 79 |
-
|
| 80 |
-
low=raw.lower()
|
| 81 |
-
for item in NAMED_OCEAN_REGIONS:
|
| 82 |
-
if any(alias.lower() in low for alias in item["aliases"]):
|
| 83 |
-
return {
|
| 84 |
-
"bbox":dict(item["bbox"]),
|
| 85 |
-
"region_name":item["name"],
|
| 86 |
-
"region_source":"named_region_default",
|
| 87 |
-
}
|
| 88 |
-
return None
|
| 89 |
|
| 90 |
def _parse_variables(prompt:str):
|
| 91 |
low=str(prompt or "").lower()
|
|
|
|
| 12 |
from typing import Any
|
| 13 |
from urllib.parse import urlparse
|
| 14 |
|
| 15 |
+
from services.region_registry import resolve_region
|
| 16 |
+
|
| 17 |
import httpx
|
| 18 |
|
| 19 |
VARIABLE_MAP={
|
|
|
|
| 27 |
"vo":{"source":"cmems_physics","variable":"vo","label":"VO"},
|
| 28 |
}
|
| 29 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 30 |
def _daterange(start:date,end:date):
|
| 31 |
cur=start
|
| 32 |
while cur<=end:
|
|
|
|
| 49 |
return None
|
| 50 |
|
| 51 |
def _parse_bbox(prompt:str):
|
| 52 |
+
resolved=resolve_region(prompt)
|
| 53 |
+
if not resolved:
|
| 54 |
+
return None
|
| 55 |
+
return {
|
| 56 |
+
"bbox":dict(resolved["bbox"]),
|
| 57 |
+
"region_name":resolved["name"],
|
| 58 |
+
"region_source":resolved["source"],
|
| 59 |
+
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 60 |
|
| 61 |
def _parse_variables(prompt:str):
|
| 62 |
low=str(prompt or "").lower()
|
services/region_registry.py
ADDED
|
@@ -0,0 +1,86 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
from __future__ import annotations
|
| 2 |
+
|
| 3 |
+
import re
|
| 4 |
+
from typing import Any
|
| 5 |
+
|
| 6 |
+
# Platform-standard named regions.
|
| 7 |
+
# These are deterministic UI/query defaults, not claims that a fishery
|
| 8 |
+
# convention boundary is a simple rectangle. Explicit user coordinates always
|
| 9 |
+
# override these defaults. Each mapping is surfaced to the user for review.
|
| 10 |
+
REGIONS = (
|
| 11 |
+
{
|
| 12 |
+
"name": "西北太平洋",
|
| 13 |
+
"aliases": ("西北太平洋", "northwest pacific", "north-west pacific", "nw pacific"),
|
| 14 |
+
"bbox": {"lon_min": 120.0, "lon_max": 180.0, "lat_min": 0.0, "lat_max": 60.0},
|
| 15 |
+
"scope": "platform_standard",
|
| 16 |
+
},
|
| 17 |
+
{
|
| 18 |
+
"name": "西太平洋",
|
| 19 |
+
"aliases": ("西太平洋", "western pacific", "west pacific"),
|
| 20 |
+
"bbox": {"lon_min": 120.0, "lon_max": 180.0, "lat_min": -30.0, "lat_max": 30.0},
|
| 21 |
+
"scope": "platform_standard",
|
| 22 |
+
},
|
| 23 |
+
{
|
| 24 |
+
"name": "西北太平洋中纬度",
|
| 25 |
+
"aliases": ("西北太平洋中纬度", "northwest pacific mid-latitude"),
|
| 26 |
+
"bbox": {"lon_min": 120.0, "lon_max": 160.0, "lat_min": 20.0, "lat_max": 50.0},
|
| 27 |
+
"scope": "platform_standard",
|
| 28 |
+
},
|
| 29 |
+
{
|
| 30 |
+
"name": "南海",
|
| 31 |
+
"aliases": ("南海", "south china sea"),
|
| 32 |
+
"bbox": {"lon_min": 105.0, "lon_max": 121.0, "lat_min": 3.0, "lat_max": 25.0},
|
| 33 |
+
"scope": "platform_standard",
|
| 34 |
+
},
|
| 35 |
+
{
|
| 36 |
+
"name": "东海",
|
| 37 |
+
"aliases": ("东海", "east china sea"),
|
| 38 |
+
"bbox": {"lon_min": 117.0, "lon_max": 131.0, "lat_min": 23.0, "lat_max": 34.0},
|
| 39 |
+
"scope": "platform_standard",
|
| 40 |
+
},
|
| 41 |
+
)
|
| 42 |
+
|
| 43 |
+
def _explicit_bbox(text: str) -> dict[str, Any] | None:
|
| 44 |
+
raw = str(text or "")
|
| 45 |
+
q = raw.upper().replace("°", "").replace("–", "-").replace("—", "-").replace("至", "-")
|
| 46 |
+
mlon = re.search(r"(-?\d+(?:\.\d+)?)\s*E\s*-\s*(-?\d+(?:\.\d+)?)\s*E", q)
|
| 47 |
+
mlat = re.search(r"(-?\d+(?:\.\d+)?)\s*N\s*-\s*(-?\d+(?:\.\d+)?)\s*N", q)
|
| 48 |
+
if not (mlon and mlat):
|
| 49 |
+
return None
|
| 50 |
+
xs = sorted((float(mlon.group(1)), float(mlon.group(2))))
|
| 51 |
+
ys = sorted((float(mlat.group(1)), float(mlat.group(2))))
|
| 52 |
+
return {
|
| 53 |
+
"name": "自定义经纬度范围",
|
| 54 |
+
"bbox": {"lon_min": xs[0], "lon_max": xs[1], "lat_min": ys[0], "lat_max": ys[1]},
|
| 55 |
+
"source": "explicit_coordinates",
|
| 56 |
+
}
|
| 57 |
+
|
| 58 |
+
def resolve_region(text: str) -> dict[str, Any] | None:
|
| 59 |
+
explicit = _explicit_bbox(text)
|
| 60 |
+
if explicit:
|
| 61 |
+
return explicit
|
| 62 |
+
low = str(text or "").lower()
|
| 63 |
+
# Longest alias first prevents “西北太平洋” from being swallowed by a
|
| 64 |
+
# shorter future alias such as “太平洋”.
|
| 65 |
+
matches = []
|
| 66 |
+
for item in REGIONS:
|
| 67 |
+
for alias in item["aliases"]:
|
| 68 |
+
if alias.lower() in low:
|
| 69 |
+
matches.append((len(alias), item))
|
| 70 |
+
if not matches:
|
| 71 |
+
return None
|
| 72 |
+
item = sorted(matches, key=lambda x: -x[0])[0][1]
|
| 73 |
+
return {
|
| 74 |
+
"name": item["name"],
|
| 75 |
+
"bbox": dict(item["bbox"]),
|
| 76 |
+
"source": item.get("scope") or "platform_standard",
|
| 77 |
+
}
|
| 78 |
+
|
| 79 |
+
def format_bbox(bbox: dict[str, Any] | None) -> str:
|
| 80 |
+
bbox = bbox or {}
|
| 81 |
+
if not all(k in bbox for k in ("lon_min","lon_max","lat_min","lat_max")):
|
| 82 |
+
return ""
|
| 83 |
+
return (
|
| 84 |
+
f"{bbox['lon_min']}°E–{bbox['lon_max']}°E, "
|
| 85 |
+
f"{bbox['lat_min']}°N–{bbox['lat_max']}°N"
|
| 86 |
+
)
|
static/js/app.js
CHANGED
|
@@ -755,8 +755,17 @@ const DOMAIN_PROMPTS={
|
|
| 755 |
let DOMAIN_CACHE=null;
|
| 756 |
function domainSources(data,domain){
|
| 757 |
if(domain==="ocean")return (data.ocean?.sources||[]).map(x=>({...x,group:"Ocean"}));
|
| 758 |
-
const
|
| 759 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 760 |
}
|
| 761 |
async function refreshHomeDomains(force=false){
|
| 762 |
try{
|
|
@@ -781,12 +790,14 @@ async function openDomainPanel(domain){
|
|
| 781 |
overlay.classList.remove("hidden");panel.innerHTML='<div class="loading">正在读取实时数据目录…</div>';
|
| 782 |
try{
|
| 783 |
const data=DOMAIN_CACHE||await refreshHomeDomains(false),sources=domainSources(data,domain),available=sources.filter(x=>datasetAvailable(x.status));
|
| 784 |
-
const files=domain==="ocean"?"—":
|
| 785 |
-
const size=domain==="ocean"?"实时接口":formatBytes(
|
|
|
|
|
|
|
| 786 |
const repoKey=domain==="tuna"?data.fisheries?.repositories?.tuna:(domain==="squid"?data.fisheries?.repositories?.squid:"");
|
| 787 |
const repoError=repoKey?(data.fisheries?.repository_errors?.[repoKey]||""):"";
|
| 788 |
const sourceHtml=(repoError?('<div class="detail-note"><b>仓库读取失败:</b>'+esc(repoError)+'</div>'):"")+ (sources.slice(0,12).map(x=>'<div class="domain-source"><b>'+esc(x.name)+(x.name_zh?' · '+esc(x.name_zh):'')+'</b><small>'+esc(datasetStatusLabel(x.status))+(x.file_count!==undefined?' · '+esc(x.file_count)+' 个文件':'')+(x.variables?.length?' · '+esc(x.variables.length)+' 个变量':'')+'</small></div>').join("")||'<div class="empty-view">当前没有可显示的数据源。</div>');
|
| 789 |
-
$("domainPanel").innerHTML='<div class="domain-head"><div><h2>'+names[domain][0]+'</h2><p>'+names[domain][1]+' · 实时
|
| 790 |
$("domainPanel").querySelector(".domain-close").onclick=closeDomainPanel;
|
| 791 |
$("domainBrowse").onclick=()=>{closeDomainPanel();openDatasetsForDomain(domain)};
|
| 792 |
$("domainQuery").onclick=()=>{closeDomainPanel();goChat(DOMAIN_PROMPTS[domain])};
|
|
@@ -1523,8 +1534,11 @@ function attachOceanSingleExportCard(A,prompt,answer){
|
|
| 1523 |
'<div class="ocean-batch-counts"><span>✓ 完成 <b>1</b></span><span>↻ 执行中 <b>0</b></span><span>… 等待 <b>0</b></span><span>! 失败 <b>0</b></span></div>'+
|
| 1524 |
'<div class="ocean-batch-meta">'+esc(label)+' · '+esc(tag)+' · '+esc(name)+'</div>'+
|
| 1525 |
'<div class="ocean-batch-message">'+esc(request||"文件已生成,可开始下载。")+'</div>'+
|
| 1526 |
-
'<div class="ocean-batch-actions"><
|
|
|
|
| 1527 |
A.q.parentNode.insertBefore(card,A.m);
|
|
|
|
|
|
|
| 1528 |
return true;
|
| 1529 |
}
|
| 1530 |
|
|
@@ -1533,18 +1547,23 @@ function parseParallelTasks(prompt){
|
|
| 1533 |
const text=String(prompt||"").trim();
|
| 1534 |
if(!SETTINGS.multiTaskAuto)return null;
|
| 1535 |
if(/先.{0,18}(再|然后|之后)|基于(前|上)一个|完成.*后再/i.test(text))return null;
|
| 1536 |
-
if(!/(同时|分别|并行|多个任务|以下任务|这几项)/i.test(text))return null;
|
| 1537 |
-
const lines=text.split(/\r?\n/);
|
| 1538 |
const tasks=[];
|
|
|
|
| 1539 |
for(const line of lines){
|
| 1540 |
-
|
| 1541 |
-
|
|
|
|
| 1542 |
}
|
| 1543 |
if(tasks.length<2){
|
| 1544 |
-
const
|
| 1545 |
-
|
| 1546 |
-
|
|
|
|
|
|
|
|
|
|
| 1547 |
}
|
|
|
|
| 1548 |
return tasks.slice(0,6);
|
| 1549 |
}
|
| 1550 |
function newMultitaskRequestId(){
|
|
@@ -1557,13 +1576,13 @@ function createParallelTaskBoard(tasks,requestId){
|
|
| 1557 |
const box=document.createElement("div");box.className="parallel-board";
|
| 1558 |
box.dataset.requestId=requestId;
|
| 1559 |
box.innerHTML='<div class="parallel-head"><div><b>多任务并行执行</b><small>'+tasks.length+' 个独立子任务 · 独立会话并行 · '+esc(requestId)+'</small></div><strong>0 / '+tasks.length+'</strong></div>'+
|
| 1560 |
-
'<div class="parallel-overview"><span>执行策略 <b>parallel / child-session</b></span><span>总耗时 <b data-parallel-wall>0.0s</b></span><span>串行估算 <b data-parallel-serial>—</b></span></div>'+
|
| 1561 |
'<div class="parallel-grid">'+tasks.map((t,i)=>'<div class="parallel-task" data-parallel-index="'+i+'"><div class="parallel-task-title"><span>'+(i+1)+'</span><b>'+esc(t)+'</b><em data-task-elapsed>0.0s</em></div><div class="parallel-task-state">等待启动</div><div class="parallel-task-result"></div></div>').join("")+'</div>';
|
| 1562 |
body.appendChild(box);r.appendChild(body);$("msgs").appendChild(r);
|
| 1563 |
$("chat").scrollTop=$("chat").scrollHeight;
|
| 1564 |
-
return{root:r,box,body,requestId,started:performance.now()};
|
| 1565 |
}
|
| 1566 |
-
async function runParallelChild(prompt,index,board){
|
| 1567 |
const card=board.box.querySelector('[data-parallel-index="'+index+'"]');
|
| 1568 |
const state=card.querySelector(".parallel-task-state"),resultEl=card.querySelector(".parallel-task-result");
|
| 1569 |
const elapsedEl=card.querySelector("[data-task-elapsed]");
|
|
@@ -1571,6 +1590,9 @@ async function runParallelChild(prompt,index,board){
|
|
| 1571 |
const started=performance.now();
|
| 1572 |
let timer=setInterval(()=>{elapsedEl.textContent=((performance.now()-started)/1000).toFixed(1)+"s"},250);
|
| 1573 |
state.textContent="正在创建子会话…";card.classList.add("running");
|
|
|
|
|
|
|
|
|
|
| 1574 |
try{
|
| 1575 |
const tx=await j("/api/threads",{method:"POST",body:JSON.stringify({user_id:USER_ID})});
|
| 1576 |
state.textContent="Agent 正在处理…";
|
|
@@ -1578,7 +1600,14 @@ async function runParallelChild(prompt,index,board){
|
|
| 1578 |
headers["X-Multitask-Request-ID"]=board.requestId;
|
| 1579 |
headers["X-Multitask-Task-ID"]=taskId;
|
| 1580 |
headers["X-Multitask-Mode"]="parallel-child-session";
|
| 1581 |
-
const
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1582 |
if(!r.ok)throw Error(await r.text());
|
| 1583 |
const rd=r.body.getReader(),dec=new TextDecoder();let buf="",answer="",failed="";
|
| 1584 |
while(true){
|
|
@@ -1605,6 +1634,9 @@ async function runParallelChild(prompt,index,board){
|
|
| 1605 |
}finally{
|
| 1606 |
clearInterval(timer);timer=null;
|
| 1607 |
elapsedEl.textContent=((performance.now()-started)/1000).toFixed(1)+"s";
|
|
|
|
|
|
|
|
|
|
| 1608 |
}
|
| 1609 |
}
|
| 1610 |
async function sendParallelTasks(originalPrompt,tasks){
|
|
@@ -1617,7 +1649,7 @@ async function sendParallelTasks(originalPrompt,tasks){
|
|
| 1617 |
const serialEl=board.box.querySelector("[data-parallel-serial]");
|
| 1618 |
const wallTimer=setInterval(()=>{wallEl.textContent=((performance.now()-board.started)/1000).toFixed(1)+"s"},250);
|
| 1619 |
let finished=0;
|
| 1620 |
-
const promises=tasks.map((task,i)=>runParallelChild(task,i,board).then(x=>{finished++;counter.textContent=finished+" / "+tasks.length;return x}));
|
| 1621 |
const results=await Promise.all(promises);
|
| 1622 |
clearInterval(wallTimer);
|
| 1623 |
const wall=(performance.now()-board.started)/1000;
|
|
|
|
| 755 |
let DOMAIN_CACHE=null;
|
| 756 |
function domainSources(data,domain){
|
| 757 |
if(domain==="ocean")return (data.ocean?.sources||[]).map(x=>({...x,group:"Ocean"}));
|
| 758 |
+
const sources=data.fisheries?.sources||[];
|
| 759 |
+
// Dataset-center membership follows the actual live repository inventory,
|
| 760 |
+
// not only the semantic category. This keeps FAO / Sea Around Us / RAM /
|
| 761 |
+
// GFW / VIIRS visible when those sources physically exist in squid_dataset.
|
| 762 |
+
return sources.filter(x=>{
|
| 763 |
+
const domains=Array.isArray(x.repository_domains)?x.repository_domains:[];
|
| 764 |
+
if(domains.includes(domain))return true;
|
| 765 |
+
if(domain==="tuna"&&x.category==="tuna")return true;
|
| 766 |
+
if(domain==="squid"&&x.category==="squid")return true;
|
| 767 |
+
return false;
|
| 768 |
+
});
|
| 769 |
}
|
| 770 |
async function refreshHomeDomains(force=false){
|
| 771 |
try{
|
|
|
|
| 790 |
overlay.classList.remove("hidden");panel.innerHTML='<div class="loading">正在读取实时数据目录…</div>';
|
| 791 |
try{
|
| 792 |
const data=DOMAIN_CACHE||await refreshHomeDomains(false),sources=domainSources(data,domain),available=sources.filter(x=>datasetAvailable(x.status));
|
| 793 |
+
const files=domain==="ocean"?"—":Number(domain==="tuna"?data.fisheries?.tuna_file_count:data.fisheries?.squid_file_count)||0;
|
| 794 |
+
const size=domain==="ocean"?"实时接口":formatBytes(
|
| 795 |
+
sources.reduce((a,x)=>a+Number((x.file_count_by_repository||{})[domain]?x.size_bytes||0:0),0)
|
| 796 |
+
);
|
| 797 |
const repoKey=domain==="tuna"?data.fisheries?.repositories?.tuna:(domain==="squid"?data.fisheries?.repositories?.squid:"");
|
| 798 |
const repoError=repoKey?(data.fisheries?.repository_errors?.[repoKey]||""):"";
|
| 799 |
const sourceHtml=(repoError?('<div class="detail-note"><b>仓库读取失败:</b>'+esc(repoError)+'</div>'):"")+ (sources.slice(0,12).map(x=>'<div class="domain-source"><b>'+esc(x.name)+(x.name_zh?' · '+esc(x.name_zh):'')+'</b><small>'+esc(datasetStatusLabel(x.status))+(x.file_count!==undefined?' · '+esc(x.file_count)+' 个文件':'')+(x.variables?.length?' · '+esc(x.variables.length)+' 个变量':'')+'</small></div>').join("")||'<div class="empty-view">当前没有可显示的数据源。</div>');
|
| 800 |
+
$("domainPanel").innerHTML='<div class="domain-head"><div><h2>'+names[domain][0]+'</h2><p>'+names[domain][1]+' · 实时仓库文件树口径;综合统计/观测来源只要实际入库也会显示</p></div><button class="domain-close" aria-label="关闭">×</button></div><div class="domain-summary"><div class="domain-stat"><strong>'+sources.length+'</strong><span>数据来源</span></div><div class="domain-stat"><strong>'+available.length+'</strong><span>当前已接入</span></div><div class="domain-stat"><strong>'+esc(domain==="ocean"?size:String(files)+" 文件")+'</strong><span>'+(domain==="ocean"?'数据服务':'实时文件')+'</span></div></div><div class="domain-sources">'+sourceHtml+'</div><div class="domain-actions"><button id="domainBrowse" class="view-btn primary">打开完整数据集目录</button><button id="domainQuery" class="view-btn">在对话中查询</button><button id="domainRefresh" class="view-btn">↻ 刷新实时数据</button></div>';
|
| 801 |
$("domainPanel").querySelector(".domain-close").onclick=closeDomainPanel;
|
| 802 |
$("domainBrowse").onclick=()=>{closeDomainPanel();openDatasetsForDomain(domain)};
|
| 803 |
$("domainQuery").onclick=()=>{closeDomainPanel();goChat(DOMAIN_PROMPTS[domain])};
|
|
|
|
| 1534 |
'<div class="ocean-batch-counts"><span>✓ 完成 <b>1</b></span><span>↻ 执行中 <b>0</b></span><span>… 等待 <b>0</b></span><span>! 失败 <b>0</b></span></div>'+
|
| 1535 |
'<div class="ocean-batch-meta">'+esc(label)+' · '+esc(tag)+' · '+esc(name)+'</div>'+
|
| 1536 |
'<div class="ocean-batch-message">'+esc(request||"文件已生成,可开始下载。")+'</div>'+
|
| 1537 |
+
'<div class="ocean-batch-actions"><button class="view-btn primary" data-ocean-managed-download>↓ 通过平台下载</button></div>'+
|
| 1538 |
+
'<div class="detail-note">下载会统一经过平台代理任务,浏览器不直接访问学校/ngrok 临时地址;支持进度、鉴权与 Range 能力检测。</div>';
|
| 1539 |
A.q.parentNode.insertBefore(card,A.m);
|
| 1540 |
+
const btn=card.querySelector("[data-ocean-managed-download]");
|
| 1541 |
+
if(btn)btn.onclick=()=>downloadWithProgress(urls[0],name,{title:"正在下载 "+name});
|
| 1542 |
return true;
|
| 1543 |
}
|
| 1544 |
|
|
|
|
| 1547 |
const text=String(prompt||"").trim();
|
| 1548 |
if(!SETTINGS.multiTaskAuto)return null;
|
| 1549 |
if(/先.{0,18}(再|然后|之后)|基于(前|上)一个|完成.*后再/i.test(text))return null;
|
| 1550 |
+
if(!/(同时|分别|并行|多个任务|以下\s*\d*\s*个?.*任务|这几项)/i.test(text))return null;
|
|
|
|
| 1551 |
const tasks=[];
|
| 1552 |
+
const lines=text.split(/\r?\n/);
|
| 1553 |
for(const line of lines){
|
| 1554 |
+
// Supports “任务1:...”, “任务 1: ...”, “1. ...”, “1、...”.
|
| 1555 |
+
const m=line.match(/^\s*(?:任务\s*)?(\d+)\s*(?:[\.、\)::)]|\-\s*)\s*(.+?)\s*$/);
|
| 1556 |
+
if(m&&m[2].length>=3)tasks.push(m[2]);
|
| 1557 |
}
|
| 1558 |
if(tasks.length<2){
|
| 1559 |
+
const rx=/(?:^|[\n;;])\s*(?:任务\s*)?\d+\s*(?:[\.、\)::)])\s*([\s\S]*?)(?=(?:\n|[;;])\s*(?:任务\s*)?\d+\s*(?:[\.、\)::)])|$)/g;
|
| 1560 |
+
let match;
|
| 1561 |
+
while((match=rx.exec(text))!==null){
|
| 1562 |
+
const task=String(match[1]||"").trim();
|
| 1563 |
+
if(task.length>=3)tasks.push(task);
|
| 1564 |
+
}
|
| 1565 |
}
|
| 1566 |
+
if(tasks.length<2)return null;
|
| 1567 |
return tasks.slice(0,6);
|
| 1568 |
}
|
| 1569 |
function newMultitaskRequestId(){
|
|
|
|
| 1576 |
const box=document.createElement("div");box.className="parallel-board";
|
| 1577 |
box.dataset.requestId=requestId;
|
| 1578 |
box.innerHTML='<div class="parallel-head"><div><b>多任务并行执行</b><small>'+tasks.length+' 个独立子任务 · 独立会话并行 · '+esc(requestId)+'</small></div><strong>0 / '+tasks.length+'</strong></div>'+
|
| 1579 |
+
'<div class="parallel-overview"><span>执行策略 <b>parallel / child-session</b></span><span>当前并发 <b data-parallel-active>0 / '+tasks.length+'</b></span><span>总耗时 <b data-parallel-wall>0.0s</b></span><span>串行估算 <b data-parallel-serial>—</b></span></div>'+
|
| 1580 |
'<div class="parallel-grid">'+tasks.map((t,i)=>'<div class="parallel-task" data-parallel-index="'+i+'"><div class="parallel-task-title"><span>'+(i+1)+'</span><b>'+esc(t)+'</b><em data-task-elapsed>0.0s</em></div><div class="parallel-task-state">等待启动</div><div class="parallel-task-result"></div></div>').join("")+'</div>';
|
| 1581 |
body.appendChild(box);r.appendChild(body);$("msgs").appendChild(r);
|
| 1582 |
$("chat").scrollTop=$("chat").scrollHeight;
|
| 1583 |
+
return{root:r,box,body,requestId,started:performance.now(),active:0,total:tasks.length};
|
| 1584 |
}
|
| 1585 |
+
async function runParallelChild(prompt,index,board,originalPrompt){
|
| 1586 |
const card=board.box.querySelector('[data-parallel-index="'+index+'"]');
|
| 1587 |
const state=card.querySelector(".parallel-task-state"),resultEl=card.querySelector(".parallel-task-result");
|
| 1588 |
const elapsedEl=card.querySelector("[data-task-elapsed]");
|
|
|
|
| 1590 |
const started=performance.now();
|
| 1591 |
let timer=setInterval(()=>{elapsedEl.textContent=((performance.now()-started)/1000).toFixed(1)+"s"},250);
|
| 1592 |
state.textContent="正在创建子会话…";card.classList.add("running");
|
| 1593 |
+
board.active=(board.active||0)+1;
|
| 1594 |
+
const activeEl=board.box.querySelector("[data-parallel-active]");
|
| 1595 |
+
if(activeEl)activeEl.textContent=board.active+" / "+board.total;
|
| 1596 |
try{
|
| 1597 |
const tx=await j("/api/threads",{method:"POST",body:JSON.stringify({user_id:USER_ID})});
|
| 1598 |
state.textContent="Agent 正在处理…";
|
|
|
|
| 1600 |
headers["X-Multitask-Request-ID"]=board.requestId;
|
| 1601 |
headers["X-Multitask-Task-ID"]=taskId;
|
| 1602 |
headers["X-Multitask-Mode"]="parallel-child-session";
|
| 1603 |
+
const childPrompt=
|
| 1604 |
+
"[MULTITASK_CHILD]\n"+
|
| 1605 |
+
"你只执行下面指定的一个子任务。兄弟任务的结果不能作为本任务上下文,也不要汇总其他任务。\n"+
|
| 1606 |
+
"原始多任务请求仅用于解析‘同一区域/同一月份/上述条件’等共享指代;若目标子任务已经给出明确参数,以目标子任务为准。\n"+
|
| 1607 |
+
"[ORIGINAL_MULTI_REQUEST]\n"+originalPrompt+"\n[/ORIGINAL_MULTI_REQUEST]\n"+
|
| 1608 |
+
"[TARGET_CHILD_TASK]\n"+prompt+"\n[/TARGET_CHILD_TASK]\n"+
|
| 1609 |
+
"[/MULTITASK_CHILD]";
|
| 1610 |
+
const r=await fetch("/api/chat",{method:"POST",headers,body:JSON.stringify({thread_id:tx.thread_id,prompt:promptWithDisplayPreferences(childPrompt),user_id:USER_ID,upload_ids:[],history:[]})});
|
| 1611 |
if(!r.ok)throw Error(await r.text());
|
| 1612 |
const rd=r.body.getReader(),dec=new TextDecoder();let buf="",answer="",failed="";
|
| 1613 |
while(true){
|
|
|
|
| 1634 |
}finally{
|
| 1635 |
clearInterval(timer);timer=null;
|
| 1636 |
elapsedEl.textContent=((performance.now()-started)/1000).toFixed(1)+"s";
|
| 1637 |
+
board.active=Math.max(0,(board.active||1)-1);
|
| 1638 |
+
const activeEl=board.box.querySelector("[data-parallel-active]");
|
| 1639 |
+
if(activeEl)activeEl.textContent=board.active+" / "+board.total;
|
| 1640 |
}
|
| 1641 |
}
|
| 1642 |
async function sendParallelTasks(originalPrompt,tasks){
|
|
|
|
| 1649 |
const serialEl=board.box.querySelector("[data-parallel-serial]");
|
| 1650 |
const wallTimer=setInterval(()=>{wallEl.textContent=((performance.now()-board.started)/1000).toFixed(1)+"s"},250);
|
| 1651 |
let finished=0;
|
| 1652 |
+
const promises=tasks.map((task,i)=>runParallelChild(task,i,board,originalPrompt).then(x=>{finished++;counter.textContent=finished+" / "+tasks.length;return x}));
|
| 1653 |
const results=await Promise.all(promises);
|
| 1654 |
clearInterval(wallTimer);
|
| 1655 |
const wall=(performance.now()-board.started)/1000;
|
tests/test_chat_lifecycle_progress_v393.py
CHANGED
|
@@ -3,7 +3,7 @@ import pathlib
|
|
| 3 |
ROOT=pathlib.Path(__file__).resolve().parents[1]
|
| 4 |
|
| 5 |
def test_version_393():
|
| 6 |
-
assert (ROOT/"VERSION").read_text(encoding="utf-8").strip()=="4.
|
| 7 |
|
| 8 |
def test_frontend_lifecycle_progress():
|
| 9 |
js=(ROOT/"static"/"js"/"app.js").read_text(encoding="utf-8")
|
|
|
|
| 3 |
ROOT=pathlib.Path(__file__).resolve().parents[1]
|
| 4 |
|
| 5 |
def test_version_393():
|
| 6 |
+
assert (ROOT/"VERSION").read_text(encoding="utf-8").strip()=="4.5.0"
|
| 7 |
|
| 8 |
def test_frontend_lifecycle_progress():
|
| 9 |
js=(ROOT/"static"/"js"/"app.js").read_text(encoding="utf-8")
|
tests/test_download_progress_v391.py
CHANGED
|
@@ -3,7 +3,7 @@ import pathlib
|
|
| 3 |
ROOT=pathlib.Path(__file__).resolve().parents[1]
|
| 4 |
|
| 5 |
def test_version_391():
|
| 6 |
-
assert (ROOT/"VERSION").read_text(encoding="utf-8").strip()=="4.
|
| 7 |
|
| 8 |
def test_p1_label_removed():
|
| 9 |
js=(ROOT/"static"/"js"/"app.js").read_text(encoding="utf-8")
|
|
|
|
| 3 |
ROOT=pathlib.Path(__file__).resolve().parents[1]
|
| 4 |
|
| 5 |
def test_version_391():
|
| 6 |
+
assert (ROOT/"VERSION").read_text(encoding="utf-8").strip()=="4.5.0"
|
| 7 |
|
| 8 |
def test_p1_label_removed():
|
| 9 |
js=(ROOT/"static"/"js"/"app.js").read_text(encoding="utf-8")
|
tests/test_download_resilience_v422.py
CHANGED
|
@@ -3,7 +3,7 @@ import pathlib
|
|
| 3 |
ROOT=pathlib.Path(__file__).resolve().parents[1]
|
| 4 |
|
| 5 |
def test_version():
|
| 6 |
-
assert (ROOT/"VERSION").read_text(encoding="utf-8").strip()=="4.
|
| 7 |
|
| 8 |
def test_refresh_route_registered():
|
| 9 |
t=(ROOT/"routes"/"ocean_batch.py").read_text(encoding="utf-8")
|
|
|
|
| 3 |
ROOT=pathlib.Path(__file__).resolve().parents[1]
|
| 4 |
|
| 5 |
def test_version():
|
| 6 |
+
assert (ROOT/"VERSION").read_text(encoding="utf-8").strip()=="4.5.0"
|
| 7 |
|
| 8 |
def test_refresh_route_registered():
|
| 9 |
t=(ROOT/"routes"/"ocean_batch.py").read_text(encoding="utf-8")
|
tests/test_history_sidebar_v373.py
CHANGED
|
@@ -3,7 +3,7 @@ import pathlib
|
|
| 3 |
ROOT=pathlib.Path(__file__).resolve().parents[1]
|
| 4 |
|
| 5 |
def test_version_373():
|
| 6 |
-
assert (ROOT/"VERSION").read_text(encoding="utf-8").strip()=="4.
|
| 7 |
|
| 8 |
def test_project_history_backend():
|
| 9 |
route=(ROOT/"routes"/"project_package.py").read_text(encoding="utf-8")
|
|
|
|
| 3 |
ROOT=pathlib.Path(__file__).resolve().parents[1]
|
| 4 |
|
| 5 |
def test_version_373():
|
| 6 |
+
assert (ROOT/"VERSION").read_text(encoding="utf-8").strip()=="4.5.0"
|
| 7 |
|
| 8 |
def test_project_history_backend():
|
| 9 |
route=(ROOT/"routes"/"project_package.py").read_text(encoding="utf-8")
|
tests/test_large_download_jobs_v400.py
CHANGED
|
@@ -3,7 +3,7 @@ import pathlib
|
|
| 3 |
ROOT=pathlib.Path(__file__).resolve().parents[1]
|
| 4 |
|
| 5 |
def test_version_400():
|
| 6 |
-
assert (ROOT/"VERSION").read_text(encoding="utf-8").strip()=="4.
|
| 7 |
|
| 8 |
def test_download_job_backend():
|
| 9 |
route=(ROOT/"routes"/"downloads.py").read_text(encoding="utf-8")
|
|
|
|
| 3 |
ROOT=pathlib.Path(__file__).resolve().parents[1]
|
| 4 |
|
| 5 |
def test_version_400():
|
| 6 |
+
assert (ROOT/"VERSION").read_text(encoding="utf-8").strip()=="4.5.0"
|
| 7 |
|
| 8 |
def test_download_job_backend():
|
| 9 |
route=(ROOT/"routes"/"downloads.py").read_text(encoding="utf-8")
|
tests/test_merged_health_v433.py
CHANGED
|
@@ -3,7 +3,7 @@ from services.ocean_batch import parse_ocean_batch_request
|
|
| 3 |
ROOT=Path(__file__).resolve().parents[1]
|
| 4 |
|
| 5 |
def test_merged_version():
|
| 6 |
-
assert (ROOT/"VERSION").read_text(encoding="utf-8").strip()=="4.
|
| 7 |
|
| 8 |
def test_ocean_month_batch_survives_merge():
|
| 9 |
p=parse_ocean_batch_request("查询 2002 年 8 月西北太平洋的 SST 数据,并导出为 CSV 文件供我下载")
|
|
|
|
| 3 |
ROOT=Path(__file__).resolve().parents[1]
|
| 4 |
|
| 5 |
def test_merged_version():
|
| 6 |
+
assert (ROOT/"VERSION").read_text(encoding="utf-8").strip()=="4.5.0"
|
| 7 |
|
| 8 |
def test_ocean_month_batch_survives_merge():
|
| 9 |
p=parse_ocean_batch_request("查询 2002 年 8 月西北太平洋的 SST 数据,并导出为 CSV 文件供我下载")
|
tests/test_multitask_observability_v442.py
CHANGED
|
@@ -2,7 +2,7 @@ from pathlib import Path
|
|
| 2 |
ROOT=Path(__file__).resolve().parents[1]
|
| 3 |
|
| 4 |
def test_version():
|
| 5 |
-
assert (ROOT/"VERSION").read_text(encoding="utf-8").strip()=="4.
|
| 6 |
|
| 7 |
def test_frontend_sends_multitask_trace_headers():
|
| 8 |
js=(ROOT/"static"/"js"/"app.js").read_text(encoding="utf-8")
|
|
|
|
| 2 |
ROOT=Path(__file__).resolve().parents[1]
|
| 3 |
|
| 4 |
def test_version():
|
| 5 |
+
assert (ROOT/"VERSION").read_text(encoding="utf-8").strip()=="4.5.0"
|
| 6 |
|
| 7 |
def test_frontend_sends_multitask_trace_headers():
|
| 8 |
js=(ROOT/"static"/"js"/"app.js").read_text(encoding="utf-8")
|
tests/test_ocean_batch_downloads_v420.py
CHANGED
|
@@ -3,7 +3,7 @@ import pathlib
|
|
| 3 |
ROOT=pathlib.Path(__file__).resolve().parents[1]
|
| 4 |
|
| 5 |
def test_version_420():
|
| 6 |
-
assert (ROOT/"VERSION").read_text(encoding="utf-8").strip()=="4.
|
| 7 |
|
| 8 |
def test_manifest_has_filename_and_sizes():
|
| 9 |
svc=(ROOT/"services"/"ocean_batch.py").read_text(encoding="utf-8")
|
|
|
|
| 3 |
ROOT=pathlib.Path(__file__).resolve().parents[1]
|
| 4 |
|
| 5 |
def test_version_420():
|
| 6 |
+
assert (ROOT/"VERSION").read_text(encoding="utf-8").strip()=="4.5.0"
|
| 7 |
|
| 8 |
def test_manifest_has_filename_and_sizes():
|
| 9 |
svc=(ROOT/"services"/"ocean_batch.py").read_text(encoding="utf-8")
|
tests/test_ocean_batch_routing_v421.py
CHANGED
|
@@ -4,7 +4,7 @@ from services.ocean_batch import parse_ocean_batch_request
|
|
| 4 |
ROOT=pathlib.Path(__file__).resolve().parents[1]
|
| 5 |
|
| 6 |
def test_version_421():
|
| 7 |
-
assert (ROOT/"VERSION").read_text(encoding="utf-8").strip()=="4.
|
| 8 |
|
| 9 |
def test_exact_user_month_query_routes_to_batch():
|
| 10 |
p=parse_ocean_batch_request("查询 2002 年 8 月西北太平洋的 SST 数据,并导出为 CSV 文件供我下载")
|
|
@@ -16,7 +16,7 @@ def test_exact_user_month_query_routes_to_batch():
|
|
| 16 |
assert p["variables"]==["sst"]
|
| 17 |
assert p["format"]=="csv"
|
| 18 |
assert p["region_name"]=="西北太平洋"
|
| 19 |
-
assert p["region_source"]=="
|
| 20 |
assert p["bbox"]=={"lon_min":120.0,"lon_max":180.0,"lat_min":0.0,"lat_max":60.0}
|
| 21 |
|
| 22 |
def test_explicit_coordinates_override_named_region():
|
|
|
|
| 4 |
ROOT=pathlib.Path(__file__).resolve().parents[1]
|
| 5 |
|
| 6 |
def test_version_421():
|
| 7 |
+
assert (ROOT/"VERSION").read_text(encoding="utf-8").strip()=="4.5.0"
|
| 8 |
|
| 9 |
def test_exact_user_month_query_routes_to_batch():
|
| 10 |
p=parse_ocean_batch_request("查询 2002 年 8 月西北太平洋的 SST 数据,并导出为 CSV 文件供我下载")
|
|
|
|
| 16 |
assert p["variables"]==["sst"]
|
| 17 |
assert p["format"]=="csv"
|
| 18 |
assert p["region_name"]=="西北太平洋"
|
| 19 |
+
assert p["region_source"]=="platform_standard"
|
| 20 |
assert p["bbox"]=={"lon_min":120.0,"lon_max":180.0,"lat_min":0.0,"lat_max":60.0}
|
| 21 |
|
| 22 |
def test_explicit_coordinates_override_named_region():
|
tests/test_ocean_batch_v410.py
CHANGED
|
@@ -4,7 +4,7 @@ from services.ocean_batch import parse_ocean_batch_request
|
|
| 4 |
ROOT=pathlib.Path(__file__).resolve().parents[1]
|
| 5 |
|
| 6 |
def test_version_410():
|
| 7 |
-
assert (ROOT/"VERSION").read_text(encoding="utf-8").strip()=="4.
|
| 8 |
|
| 9 |
def test_month_three_variable_batch_plan():
|
| 10 |
p=parse_ocean_batch_request("查询并导出 2002 年 8 月西北太平洋 120°E–140°E、20°N–40°N 的 SST、SSH、CHL 数据,并打包下载")
|
|
|
|
| 4 |
ROOT=pathlib.Path(__file__).resolve().parents[1]
|
| 5 |
|
| 6 |
def test_version_410():
|
| 7 |
+
assert (ROOT/"VERSION").read_text(encoding="utf-8").strip()=="4.5.0"
|
| 8 |
|
| 9 |
def test_month_three_variable_batch_plan():
|
| 10 |
p=parse_ocean_batch_request("查询并导出 2002 年 8 月西北太平洋 120°E–140°E、20°N–40°N 的 SST、SSH、CHL 数据,并打包下载")
|
tests/test_p0_finish_v380.py
CHANGED
|
@@ -3,7 +3,7 @@ import pathlib, re, subprocess, shutil
|
|
| 3 |
ROOT=pathlib.Path(__file__).resolve().parents[1]
|
| 4 |
|
| 5 |
def test_version_380():
|
| 6 |
-
assert (ROOT/"VERSION").read_text(encoding="utf-8").strip()=="4.
|
| 7 |
|
| 8 |
def test_chat_runtime_extracted():
|
| 9 |
ui=(ROOT/"ui_server.py").read_text(encoding="utf-8")
|
|
|
|
| 3 |
ROOT=pathlib.Path(__file__).resolve().parents[1]
|
| 4 |
|
| 5 |
def test_version_380():
|
| 6 |
+
assert (ROOT/"VERSION").read_text(encoding="utf-8").strip()=="4.5.0"
|
| 7 |
|
| 8 |
def test_chat_runtime_extracted():
|
| 9 |
ui=(ROOT/"ui_server.py").read_text(encoding="utf-8")
|
tests/test_p0_phase2_v371.py
CHANGED
|
@@ -3,7 +3,7 @@ import pathlib
|
|
| 3 |
ROOT=pathlib.Path(__file__).resolve().parents[1]
|
| 4 |
|
| 5 |
def test_version():
|
| 6 |
-
assert (ROOT/"VERSION").read_text(encoding="utf-8").strip()=="4.
|
| 7 |
|
| 8 |
def test_frontend_feature_modules_exist():
|
| 9 |
html=(ROOT/"app.html").read_text(encoding="utf-8")
|
|
|
|
| 3 |
ROOT=pathlib.Path(__file__).resolve().parents[1]
|
| 4 |
|
| 5 |
def test_version():
|
| 6 |
+
assert (ROOT/"VERSION").read_text(encoding="utf-8").strip()=="4.5.0"
|
| 7 |
|
| 8 |
def test_frontend_feature_modules_exist():
|
| 9 |
html=(ROOT/"app.html").read_text(encoding="utf-8")
|
tests/test_p0_phase3_v372.py
CHANGED
|
@@ -3,7 +3,7 @@ import pathlib
|
|
| 3 |
ROOT=pathlib.Path(__file__).resolve().parents[1]
|
| 4 |
|
| 5 |
def test_version_372():
|
| 6 |
-
assert (ROOT/"VERSION").read_text(encoding="utf-8").strip()=="4.
|
| 7 |
|
| 8 |
def test_route_modules_exist():
|
| 9 |
for name in ("datasets.py","user_state.py","project_package.py","data_health.py"):
|
|
|
|
| 3 |
ROOT=pathlib.Path(__file__).resolve().parents[1]
|
| 4 |
|
| 5 |
def test_version_372():
|
| 6 |
+
assert (ROOT/"VERSION").read_text(encoding="utf-8").strip()=="4.5.0"
|
| 7 |
|
| 8 |
def test_route_modules_exist():
|
| 9 |
for name in ("datasets.py","user_state.py","project_package.py","data_health.py"):
|
tests/test_platform_reliability_v450.py
ADDED
|
@@ -0,0 +1,65 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
from pathlib import Path
|
| 2 |
+
from services.context_compressor import extract_request_spec, merge_request_spec, render_request_spec
|
| 3 |
+
from services.region_registry import resolve_region
|
| 4 |
+
from services.ocean_batch import parse_ocean_batch_request
|
| 5 |
+
|
| 6 |
+
ROOT=Path(__file__).resolve().parents[1]
|
| 7 |
+
|
| 8 |
+
def test_version():
|
| 9 |
+
assert (ROOT/"VERSION").read_text(encoding="utf-8").strip()=="4.5.0"
|
| 10 |
+
|
| 11 |
+
def test_squid_center_uses_repository_membership():
|
| 12 |
+
js=(ROOT/"static"/"js"/"app.js").read_text(encoding="utf-8")
|
| 13 |
+
api=(ROOT/"routes"/"datasets.py").read_text(encoding="utf-8")
|
| 14 |
+
assert "repository_domains" in api
|
| 15 |
+
assert "file_count_by_repository" in api
|
| 16 |
+
assert "domains.includes(domain)" in js
|
| 17 |
+
assert "squid_file_count" in js
|
| 18 |
+
assert "实时仓库文件树口径" in js
|
| 19 |
+
|
| 20 |
+
def test_single_ocean_export_forces_managed_proxy():
|
| 21 |
+
js=(ROOT/"static"/"js"/"app.js").read_text(encoding="utf-8")
|
| 22 |
+
assert "data-ocean-managed-download" in js
|
| 23 |
+
assert "downloadWithProgress(urls[0],name" in js
|
| 24 |
+
assert "浏览器不直接访问学校/ngrok" in js
|
| 25 |
+
|
| 26 |
+
def test_followup_depth_layer_context():
|
| 27 |
+
base=merge_request_spec(None,"查询2002年8月西北太平洋 SST,导出 CSV",revision=False)
|
| 28 |
+
updated=merge_request_spec(base,"其中200米层的",revision=True)
|
| 29 |
+
assert updated.get("depth")
|
| 30 |
+
rendered=render_request_spec(updated)
|
| 31 |
+
assert "200" in rendered
|
| 32 |
+
assert "SST" in rendered or "sst" in rendered.lower()
|
| 33 |
+
assert "2002" in rendered
|
| 34 |
+
assert "西北太平洋" in rendered
|
| 35 |
+
|
| 36 |
+
def test_layer_is_structured():
|
| 37 |
+
p=extract_request_spec("还是这个区域,第5层,换成SSH")
|
| 38 |
+
assert p["layer"]=="5"
|
| 39 |
+
assert "zos" in p["variables"]
|
| 40 |
+
|
| 41 |
+
def test_region_is_deterministic():
|
| 42 |
+
a=resolve_region("查询西太平洋 SST")
|
| 43 |
+
b=resolve_region("再查询一次西太平洋 SSH")
|
| 44 |
+
assert a==b
|
| 45 |
+
assert a["bbox"]=={"lon_min":120.0,"lon_max":180.0,"lat_min":-30.0,"lat_max":30.0}
|
| 46 |
+
|
| 47 |
+
def test_explicit_bbox_overrides_registry():
|
| 48 |
+
x=resolve_region("西太平洋 130°E-140°E 20°N-30°N")
|
| 49 |
+
assert x["source"]=="explicit_coordinates"
|
| 50 |
+
assert x["bbox"]["lon_min"]==130.0
|
| 51 |
+
|
| 52 |
+
def test_batch_uses_registry():
|
| 53 |
+
p=parse_ocean_batch_request("查询2002年8月西北太平洋 SST 并导出 CSV")
|
| 54 |
+
assert p is not None
|
| 55 |
+
assert p["region_name"]=="西北太平洋"
|
| 56 |
+
assert p["bbox"]["lat_max"]==60.0
|
| 57 |
+
|
| 58 |
+
def test_multitask_parser_accepts_task_colon_and_isolates_context():
|
| 59 |
+
js=(ROOT/"static"/"js"/"app.js").read_text(encoding="utf-8")
|
| 60 |
+
assert "任务\\s*)?(\\d+)" in js
|
| 61 |
+
assert "[MULTITASK_CHILD]" in js
|
| 62 |
+
assert "[TARGET_CHILD_TASK]" in js
|
| 63 |
+
assert "history:[]" in js
|
| 64 |
+
assert "data-parallel-active" in js
|
| 65 |
+
assert "runParallelChild(task,i,board,originalPrompt)" in js
|
tests/test_quality_center_v362.py
CHANGED
|
@@ -3,7 +3,7 @@ import pathlib
|
|
| 3 |
ROOT = pathlib.Path(__file__).resolve().parents[1]
|
| 4 |
|
| 5 |
def test_version_371():
|
| 6 |
-
assert (ROOT / "VERSION").read_text(encoding="utf-8").strip() == "4.
|
| 7 |
|
| 8 |
def test_native_health_backend():
|
| 9 |
text = (ROOT / "routes" / "data_health.py").read_text(encoding="utf-8")
|
|
|
|
| 3 |
ROOT = pathlib.Path(__file__).resolve().parents[1]
|
| 4 |
|
| 5 |
def test_version_371():
|
| 6 |
+
assert (ROOT / "VERSION").read_text(encoding="utf-8").strip() == "4.5.0"
|
| 7 |
|
| 8 |
def test_native_health_backend():
|
| 9 |
text = (ROOT / "routes" / "data_health.py").read_text(encoding="utf-8")
|
tests/test_quality_trust_v361.py
CHANGED
|
@@ -2,7 +2,7 @@ from pathlib import Path
|
|
| 2 |
ROOT=Path(__file__).resolve().parents[1]
|
| 3 |
|
| 4 |
def test_version_current():
|
| 5 |
-
assert (ROOT/"VERSION").read_text(encoding="utf-8").strip()=="4.
|
| 6 |
|
| 7 |
def test_quality_confidence_is_now_native_api_metric():
|
| 8 |
text=(ROOT/"routes"/"data_health.py").read_text(encoding="utf-8")
|
|
|
|
| 2 |
ROOT=Path(__file__).resolve().parents[1]
|
| 3 |
|
| 4 |
def test_version_current():
|
| 5 |
+
assert (ROOT/"VERSION").read_text(encoding="utf-8").strip()=="4.5.0"
|
| 6 |
|
| 7 |
def test_quality_confidence_is_now_native_api_metric():
|
| 8 |
text=(ROOT/"routes"/"data_health.py").read_text(encoding="utf-8")
|
tests/test_release_stability.py
CHANGED
|
@@ -7,7 +7,7 @@ def test_version_is_single_source():
|
|
| 7 |
version = (ROOT / "VERSION").read_text(encoding="utf-8").strip()
|
| 8 |
ui = (ROOT / "ui_server.py").read_text(encoding="utf-8")
|
| 9 |
html = (ROOT / "app.html").read_text(encoding="utf-8")
|
| 10 |
-
assert version == "4.
|
| 11 |
assert 'with_name("VERSION")' in ui
|
| 12 |
assert "__APP_VERSION__" in html
|
| 13 |
|
|
|
|
| 7 |
version = (ROOT / "VERSION").read_text(encoding="utf-8").strip()
|
| 8 |
ui = (ROOT / "ui_server.py").read_text(encoding="utf-8")
|
| 9 |
html = (ROOT / "app.html").read_text(encoding="utf-8")
|
| 10 |
+
assert version == "4.5.0"
|
| 11 |
assert 'with_name("VERSION")' in ui
|
| 12 |
assert "__APP_VERSION__" in html
|
| 13 |
|
tests/test_runtime_hotfix_v376.py
CHANGED
|
@@ -3,7 +3,7 @@ import pathlib
|
|
| 3 |
ROOT=pathlib.Path(__file__).resolve().parents[1]
|
| 4 |
|
| 5 |
def test_version_376():
|
| 6 |
-
assert (ROOT/"VERSION").read_text(encoding="utf-8").strip()=="4.
|
| 7 |
|
| 8 |
def test_no_stray_async_before_sidebar_toggle():
|
| 9 |
js=(ROOT/"static"/"js"/"app.js").read_text(encoding="utf-8")
|
|
|
|
| 3 |
ROOT=pathlib.Path(__file__).resolve().parents[1]
|
| 4 |
|
| 5 |
def test_version_376():
|
| 6 |
+
assert (ROOT/"VERSION").read_text(encoding="utf-8").strip()=="4.5.0"
|
| 7 |
|
| 8 |
def test_no_stray_async_before_sidebar_toggle():
|
| 9 |
js=(ROOT/"static"/"js"/"app.js").read_text(encoding="utf-8")
|
tests/test_sidebar_collapse_v374.py
CHANGED
|
@@ -3,7 +3,7 @@ import pathlib
|
|
| 3 |
ROOT=pathlib.Path(__file__).resolve().parents[1]
|
| 4 |
|
| 5 |
def test_version_374():
|
| 6 |
-
assert (ROOT/"VERSION").read_text(encoding="utf-8").strip()=="4.
|
| 7 |
|
| 8 |
def test_sidebar_toggle_markup():
|
| 9 |
html=(ROOT/"app.html").read_text(encoding="utf-8")
|
|
|
|
| 3 |
ROOT=pathlib.Path(__file__).resolve().parents[1]
|
| 4 |
|
| 5 |
def test_version_374():
|
| 6 |
+
assert (ROOT/"VERSION").read_text(encoding="utf-8").strip()=="4.5.0"
|
| 7 |
|
| 8 |
def test_sidebar_toggle_markup():
|
| 9 |
html=(ROOT/"app.html").read_text(encoding="utf-8")
|
tests/test_sidebar_partial_collapse_v375.py
CHANGED
|
@@ -3,7 +3,7 @@ import pathlib
|
|
| 3 |
ROOT=pathlib.Path(__file__).resolve().parents[1]
|
| 4 |
|
| 5 |
def test_version_375():
|
| 6 |
-
assert (ROOT/"VERSION").read_text(encoding="utf-8").strip()=="4.
|
| 7 |
|
| 8 |
def test_sidebar_order_and_groups():
|
| 9 |
html=(ROOT/"app.html").read_text(encoding="utf-8")
|
|
|
|
| 3 |
ROOT=pathlib.Path(__file__).resolve().parents[1]
|
| 4 |
|
| 5 |
def test_version_375():
|
| 6 |
+
assert (ROOT/"VERSION").read_text(encoding="utf-8").strip()=="4.5.0"
|
| 7 |
|
| 8 |
def test_sidebar_order_and_groups():
|
| 9 |
html=(ROOT/"app.html").read_text(encoding="utf-8")
|
tests/test_smart_progress_avatar_sync_v441.py
CHANGED
|
@@ -2,7 +2,7 @@ from pathlib import Path
|
|
| 2 |
ROOT=Path(__file__).resolve().parents[1]
|
| 3 |
|
| 4 |
def test_version():
|
| 5 |
-
assert (ROOT/"VERSION").read_text(encoding="utf-8").strip()=="4.
|
| 6 |
|
| 7 |
def test_simple_chat_hides_progress_until_needed():
|
| 8 |
js=(ROOT/"static"/"js"/"app.js").read_text(encoding="utf-8")
|
|
|
|
| 2 |
ROOT=Path(__file__).resolve().parents[1]
|
| 3 |
|
| 4 |
def test_version():
|
| 5 |
+
assert (ROOT/"VERSION").read_text(encoding="utf-8").strip()=="4.5.0"
|
| 6 |
|
| 7 |
def test_simple_chat_hides_progress_until_needed():
|
| 8 |
js=(ROOT/"static"/"js"/"app.js").read_text(encoding="utf-8")
|
tests/test_sse_heartbeat_v401.py
CHANGED
|
@@ -3,7 +3,7 @@ import pathlib
|
|
| 3 |
ROOT=pathlib.Path(__file__).resolve().parents[1]
|
| 4 |
|
| 5 |
def test_version_401():
|
| 6 |
-
assert (ROOT/"VERSION").read_text(encoding="utf-8").strip()=="4.
|
| 7 |
|
| 8 |
def test_chat_has_independent_heartbeat_wrapper():
|
| 9 |
route=(ROOT/"routes"/"chat.py").read_text(encoding="utf-8")
|
|
|
|
| 3 |
ROOT=pathlib.Path(__file__).resolve().parents[1]
|
| 4 |
|
| 5 |
def test_version_401():
|
| 6 |
+
assert (ROOT/"VERSION").read_text(encoding="utf-8").strip()=="4.5.0"
|
| 7 |
|
| 8 |
def test_chat_has_independent_heartbeat_wrapper():
|
| 9 |
route=(ROOT/"routes"/"chat.py").read_text(encoding="utf-8")
|
tests/test_ui_avatar_multitask_v440.py
CHANGED
|
@@ -2,7 +2,7 @@ from pathlib import Path
|
|
| 2 |
ROOT=Path(__file__).resolve().parents[1]
|
| 3 |
|
| 4 |
def test_version():
|
| 5 |
-
assert (ROOT/"VERSION").read_text(encoding="utf-8").strip()=="4.
|
| 6 |
|
| 7 |
def test_assistant_avatar_removed_user_avatar_customizable():
|
| 8 |
js=(ROOT/"static"/"js"/"app.js").read_text(encoding="utf-8")
|
|
|
|
| 2 |
ROOT=Path(__file__).resolve().parents[1]
|
| 3 |
|
| 4 |
def test_version():
|
| 5 |
+
assert (ROOT/"VERSION").read_text(encoding="utf-8").strip()=="4.5.0"
|
| 6 |
|
| 7 |
def test_assistant_avatar_removed_user_avatar_customizable():
|
| 8 |
js=(ROOT/"static"/"js"/"app.js").read_text(encoding="utf-8")
|
tests/test_unified_download_manager_v392.py
CHANGED
|
@@ -3,7 +3,7 @@ import pathlib
|
|
| 3 |
ROOT=pathlib.Path(__file__).resolve().parents[1]
|
| 4 |
|
| 5 |
def test_version_392():
|
| 6 |
-
assert (ROOT/"VERSION").read_text(encoding="utf-8").strip()=="4.
|
| 7 |
|
| 8 |
def test_download_proxy_route_registered():
|
| 9 |
route=(ROOT/"routes"/"downloads.py").read_text(encoding="utf-8")
|
|
|
|
| 3 |
ROOT=pathlib.Path(__file__).resolve().parents[1]
|
| 4 |
|
| 5 |
def test_version_392():
|
| 6 |
+
assert (ROOT/"VERSION").read_text(encoding="utf-8").strip()=="4.5.0"
|
| 7 |
|
| 8 |
def test_download_proxy_route_registered():
|
| 9 |
route=(ROOT/"routes"/"downloads.py").read_text(encoding="utf-8")
|
tests/test_unified_query_v390.py
CHANGED
|
@@ -4,7 +4,7 @@ from services.unified_query import plan_unified_query
|
|
| 4 |
ROOT=pathlib.Path(__file__).resolve().parents[1]
|
| 5 |
|
| 6 |
def test_version_390():
|
| 7 |
-
assert (ROOT/"VERSION").read_text(encoding="utf-8").strip()=="4.
|
| 8 |
|
| 9 |
def test_joint_squid_environment_plan():
|
| 10 |
p=plan_unified_query("查询 2002 年 8 月西北太平洋柔鱼记录,并匹配 SST、SSH、CHL")
|
|
|
|
| 4 |
ROOT=pathlib.Path(__file__).resolve().parents[1]
|
| 5 |
|
| 6 |
def test_version_390():
|
| 7 |
+
assert (ROOT/"VERSION").read_text(encoding="utf-8").strip()=="4.5.0"
|
| 8 |
|
| 9 |
def test_joint_squid_environment_plan():
|
| 10 |
p=plan_unified_query("查询 2002 年 8 月西北太平洋柔鱼记录,并匹配 SST、SSH、CHL")
|
tests/test_v381_navigation_ocean_intent.py
CHANGED
|
@@ -3,7 +3,7 @@ import pathlib, ast
|
|
| 3 |
ROOT=pathlib.Path(__file__).resolve().parents[1]
|
| 4 |
|
| 5 |
def test_version_381():
|
| 6 |
-
assert (ROOT/"VERSION").read_text(encoding="utf-8").strip()=="4.
|
| 7 |
|
| 8 |
def test_recent_conversation_switches_to_chat():
|
| 9 |
js=(ROOT/"static"/"js"/"app.js").read_text(encoding="utf-8")
|
|
|
|
| 3 |
ROOT=pathlib.Path(__file__).resolve().parents[1]
|
| 4 |
|
| 5 |
def test_version_381():
|
| 6 |
+
assert (ROOT/"VERSION").read_text(encoding="utf-8").strip()=="4.5.0"
|
| 7 |
|
| 8 |
def test_recent_conversation_switches_to_chat():
|
| 9 |
js=(ROOT/"static"/"js"/"app.js").read_text(encoding="utf-8")
|
ui_server.py
CHANGED
|
@@ -1765,7 +1765,8 @@ def _looks_like_data_parameter(prompt: str) -> bool:
|
|
| 1765 |
r"(?:19|20)\d{2}年|\d{1,2}月|\d{1,2}日|"
|
| 1766 |
r"(?:19|20)\d{2}-\d{2}-\d{2}|"
|
| 1767 |
r"[0-9.]+(?:°|[ew])|[0-9.]+[ewns]|"
|
| 1768 |
-
r"经度|纬度|海域|区域|范围|变量|数据源|输出格式|日期|时间|年份|月份"
|
|
|
|
| 1769 |
q,
|
| 1770 |
):
|
| 1771 |
return True
|
|
@@ -1776,7 +1777,7 @@ def _looks_like_data_parameter(prompt: str) -> bool:
|
|
| 1776 |
"ssrd","strd","tp","thetao","mlotst","zos","spco2","era5","cmems",
|
| 1777 |
"oisst","occci","netcdf","geotiff","tiff","csv","xlsx","json",
|
| 1778 |
"柔鱼","鱿鱼","squid","金枪鱼","tuna","捕捞","努力量","cpue",
|
| 1779 |
-
"biomass","下载","导出","数据",
|
| 1780 |
)
|
| 1781 |
)
|
| 1782 |
|
|
|
|
| 1765 |
r"(?:19|20)\d{2}年|\d{1,2}月|\d{1,2}日|"
|
| 1766 |
r"(?:19|20)\d{2}-\d{2}-\d{2}|"
|
| 1767 |
r"[0-9.]+(?:°|[ew])|[0-9.]+[ewns]|"
|
| 1768 |
+
r"经度|纬度|海域|区域|范围|变量|数据源|输出格式|日期|时间|年份|月份|"
|
| 1769 |
+
r"深度|水深|层位|图层|第\d+层|\d+(?:\.\d+)?米层|surface|layer",
|
| 1770 |
q,
|
| 1771 |
):
|
| 1772 |
return True
|
|
|
|
| 1777 |
"ssrd","strd","tp","thetao","mlotst","zos","spco2","era5","cmems",
|
| 1778 |
"oisst","occci","netcdf","geotiff","tiff","csv","xlsx","json",
|
| 1779 |
"柔鱼","鱿鱼","squid","金枪鱼","tuna","捕捞","努力量","cpue",
|
| 1780 |
+
"biomass","下载","导出","数据","深度","水深","米层","图层","层位","surface","layer",
|
| 1781 |
)
|
| 1782 |
)
|
| 1783 |
|