foxclaw 0.1.2 → 0.1.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/index.js +1 -8
- package/package.json +1 -1
- package/python/.env.example +8 -0
- package/python/.github/workflows/ci.yml +47 -2
- package/python/CHANGELOG.md +57 -0
- package/python/CHANGELOG_CN.md +57 -0
- package/python/README.md +11 -2
- package/python/README_CN.md +32 -20
- package/python/bench/bm25_audit.py +1 -14
- package/python/bench/bm25_regression.py +0 -1
- package/python/bench/bm25_stress.py +0 -4
- package/python/bench/cd_audit.py +0 -23
- package/python/bench/chaos.py +6 -8
- package/python/bench/e2e_real_models.py +0 -1
- package/python/bench/llm_retry_test.py +2 -9
- package/python/bench/load_test.py +5 -8
- package/python/bench/vector_ivf_upgrade.py +1 -7
- package/python/docs/RELEASE.md +4 -3
- package/python/docs/RUN_CN.md +2 -2
- package/python/evals/governance/cases.py +0 -37
- package/python/evals/governance/run_evals.py +5 -37
- package/python/examples/governance_middleware_demo.py +1 -6
- package/python/examples/langgraph_governance_demo.py +7 -11
- package/python/foxclaw/__init__.py +34 -152
- package/python/foxclaw/agents/base.py +65 -136
- package/python/foxclaw/agents/impl/_helpers.py +2 -10
- package/python/foxclaw/agents/impl/finance.py +4 -21
- package/python/foxclaw/agents/impl/office.py +7 -24
- package/python/foxclaw/agents/impl/supervisor.py +12 -79
- package/python/foxclaw/agents/mixins/_context.py +0 -23
- package/python/foxclaw/agents/mixins/augment.py +10 -16
- package/python/foxclaw/agents/mixins/llm.py +7 -6
- package/python/foxclaw/agents/mixins/memory.py +7 -1
- package/python/foxclaw/agents/mixins/preference.py +58 -8
- package/python/foxclaw/agents/mixins/prompt.py +0 -6
- package/python/foxclaw/agents/mixins/skillbox.py +6 -6
- package/python/foxclaw/agents/mixins/tool_exec.py +16 -42
- package/python/foxclaw/api/__init__.py +18 -77
- package/python/foxclaw/api/_state.py +43 -0
- package/python/foxclaw/api/{app.py → bootstrap.py} +5 -32
- package/python/foxclaw/api/middleware.py +5 -28
- package/python/foxclaw/api/routes/ahs.py +60 -73
- package/python/foxclaw/api/routes/attachments.py +5 -6
- package/python/foxclaw/api/routes/browser.py +7 -11
- package/python/foxclaw/api/routes/domain.py +18 -26
- package/python/foxclaw/api/routes/knowledge.py +3 -21
- package/python/foxclaw/api/routes/llm_admin.py +21 -33
- package/python/foxclaw/api/routes/memory.py +28 -12
- package/python/foxclaw/api/routes/orchestration.py +3 -5
- package/python/foxclaw/api/routes/recovery.py +4 -3
- package/python/foxclaw/api/routes/review.py +2 -11
- package/python/foxclaw/api/routes/search.py +3 -7
- package/python/foxclaw/api/routes/system.py +33 -49
- package/python/foxclaw/api/routes/task.py +31 -49
- package/python/foxclaw/api/routes/ws.py +2 -6
- package/python/foxclaw/api/spine.py +1 -4
- package/python/foxclaw/browser/action/action_executor.py +1 -5
- package/python/foxclaw/browser/action/action_types.py +0 -1
- package/python/foxclaw/browser/action/url_policy.py +1 -5
- package/python/foxclaw/browser/adapter/web_adapter.py +1 -1
- package/python/foxclaw/browser/driver/playwright_driver.py +20 -16
- package/python/foxclaw/browser/perception/dom_extractor.py +0 -1
- package/python/foxclaw/browser/perception/element_indexer.py +1 -2
- package/python/foxclaw/browser/service.py +7 -11
- package/python/foxclaw/capability/embedding_service.py +4 -6
- package/python/foxclaw/channels/__init__.py +9 -11
- package/python/foxclaw/channels/discord.py +4 -13
- package/python/foxclaw/channels/dispatcher.py +2 -27
- package/python/foxclaw/channels/feishu.py +10 -20
- package/python/foxclaw/channels/openclaw_bridge.py +4 -9
- package/python/foxclaw/channels/signing.py +9 -9
- package/python/foxclaw/channels/telegram.py +3 -4
- package/python/foxclaw/channels/weixin.py +7 -17
- package/python/foxclaw/cli.py +42 -13
- package/python/foxclaw/cognitive/reasoner.py +9 -10
- package/python/foxclaw/cognitive/state.py +18 -24
- package/python/foxclaw/core/audit_chain.py +1 -3
- package/python/foxclaw/core/audit_sink.py +3 -3
- package/python/foxclaw/core/compaction.py +8 -26
- package/python/foxclaw/core/config.py +2 -2
- package/python/foxclaw/core/confirm_nonce.py +6 -12
- package/python/foxclaw/core/envfile.py +2 -2
- package/python/foxclaw/core/failure_policy.py +17 -26
- package/python/foxclaw/core/gateway.py +6 -10
- package/python/foxclaw/core/governance_audit.py +3 -7
- package/python/foxclaw/core/idempotency.py +15 -22
- package/python/foxclaw/core/injection_guard.py +10 -29
- package/python/foxclaw/core/memory_adapter.py +30 -57
- package/python/foxclaw/core/metrics.py +4 -5
- package/python/foxclaw/core/model_router.py +36 -148
- package/python/foxclaw/core/paths.py +1 -1
- package/python/foxclaw/core/recovery.py +21 -34
- package/python/foxclaw/core/scheduler.py +23 -26
- package/python/foxclaw/core/secrets.py +0 -3
- package/python/foxclaw/core/sessions.py +4 -6
- package/python/foxclaw/core/settings.py +16 -32
- package/python/foxclaw/core/shared_write.py +11 -23
- package/python/foxclaw/core/steering_queue.py +12 -11
- package/python/foxclaw/core/system_bus.py +1 -1
- package/python/foxclaw/core/thought_chain.py +30 -32
- package/python/foxclaw/core/token_meter.py +9 -13
- package/python/foxclaw/core/tools.py +4 -12
- package/python/foxclaw/core/tracing.py +25 -40
- package/python/foxclaw/core/truth_pipeline.py +5 -9
- package/python/foxclaw/core/update_check.py +149 -0
- package/python/foxclaw/core/vec_recall_adapter.py +11 -37
- package/python/foxclaw/core/verify.py +6 -14
- package/python/foxclaw/core/web_search.py +4 -28
- package/python/foxclaw/finance/evidence.py +14 -18
- package/python/foxclaw/finance/fx.py +1 -3
- package/python/foxclaw/finance/graph.py +9 -18
- package/python/foxclaw/finance/models.py +20 -24
- package/python/foxclaw/finance/nlparse.py +12 -72
- package/python/foxclaw/finance/policy.py +8 -36
- package/python/foxclaw/finance/reasoner.py +3 -11
- package/python/foxclaw/finance/reasoning.py +13 -34
- package/python/foxclaw/finance/reconcile.py +5 -10
- package/python/foxclaw/finance/runtime.py +19 -46
- package/python/foxclaw/governance/middleware.py +13 -21
- package/python/foxclaw/harness/_base.py +2 -8
- package/python/foxclaw/harness/_policy.py +19 -38
- package/python/foxclaw/harness/drift.py +1 -13
- package/python/foxclaw/harness/event_bus.py +2 -5
- package/python/foxclaw/harness/harness.py +9 -97
- package/python/foxclaw/harness/loop_detection.py +11 -18
- package/python/foxclaw/itinerary/amap_client.py +2 -5
- package/python/foxclaw/itinerary/models.py +7 -7
- package/python/foxclaw/itinerary/reasoner.py +1 -6
- package/python/foxclaw/itinerary/reasoning.py +15 -21
- package/python/foxclaw/itinerary/runtime.py +8 -12
- package/python/foxclaw/kernel/capabilities.py +5 -6
- package/python/foxclaw/kernel/policy_gate.py +4 -10
- package/python/foxclaw/kernel/process_manager.py +17 -36
- package/python/foxclaw/kernel/scheduler.py +3 -9
- package/python/foxclaw/kernel/sentinel.py +13 -23
- package/python/foxclaw/kernel/sentinel_client.py +12 -15
- package/python/foxclaw/kernel/spine.py +9 -9
- package/python/foxclaw/knowledge/__init__.py +4 -20
- package/python/foxclaw/knowledge/truth_crystallizer.py +5 -8
- package/python/foxclaw/learning/knowledge_graph.py +4 -9
- package/python/foxclaw/learning/models.py +13 -13
- package/python/foxclaw/learning/reasoner.py +1 -4
- package/python/foxclaw/learning/runtime.py +19 -31
- package/python/foxclaw/learning/spaced_repetition.py +2 -7
- package/python/foxclaw/mcp_server.py +2 -3
- package/python/foxclaw/orchestration/__init__.py +16 -33
- package/python/foxclaw/pipeline/cognitive_pipeline.py +45 -97
- package/python/foxclaw/pipeline/stage_meta.py +21 -0
- package/python/foxclaw/pipeline/supervisor_stages.py +13 -42
- package/python/foxclaw/planning/llm_planner.py +3 -3
- package/python/foxclaw/planning/planner.py +6 -11
- package/python/foxclaw/search/agent_port.py +7 -11
- package/python/foxclaw/search/evidence.py +0 -4
- package/python/foxclaw/search/internal_search.py +3 -5
- package/python/foxclaw/search/models.py +11 -13
- package/python/foxclaw/search/planner.py +3 -3
- package/python/foxclaw/search/retriever.py +1 -1
- package/python/foxclaw/search/runtime.py +1 -8
- package/python/foxclaw/skills/__init__.py +46 -100
- package/python/foxclaw/skills/archive.py +111 -0
- package/python/foxclaw/skills/fetch.py +226 -0
- package/python/foxclaw/skills/hooks_spec.py +21 -25
- package/python/foxclaw/skills/install_cmd.py +216 -0
- package/python/foxclaw/skills/lifecycle.py +16 -42
- package/python/foxclaw/skills/markdown_skill.py +10 -9
- package/python/foxclaw/skills/mcp_client.py +5 -11
- package/python/foxclaw/skills/mcp_store.py +0 -3
- package/python/foxclaw/skills/permission.py +13 -48
- package/python/foxclaw/skills/pipeline.py +21 -53
- package/python/foxclaw/skills/policy.py +4 -10
- package/python/foxclaw/skills/review.py +3 -3
- package/python/frontend/deck/auth.js +0 -7
- package/python/frontend/deck/chat_model_picker.js +0 -54
- package/python/frontend/deck/domain.js +0 -20
- package/python/frontend/deck/main.js +0 -144
- package/python/frontend/deck/markdown.js +0 -4
- package/python/frontend/deck/settings.js +1 -18
- package/python/frontend/deck/state.js +0 -1
- package/python/frontend/deck/util.js +0 -2
- package/python/frontend/deck/visuals.js +0 -22
- package/python/frontend/planet/planet.js +0 -201
- package/python/frontend/v3/assets/index-CQSBdxk4.js +34 -0
- package/python/frontend/v3/assets/index-D5nIsjms.css +1 -0
- package/python/frontend/v3/index.html +3 -3
- package/python/frontend/widget_runtime.js +0 -43
- package/python/frontend/widgets.js +0 -57
- package/python/frontend_src/index.html +1 -1
- package/python/frontend_src/package-lock.json +2 -2
- package/python/frontend_src/package.json +1 -1
- package/python/frontend_src/src/App.vue +3 -7
- package/python/frontend_src/src/api.js +0 -7
- package/python/frontend_src/src/components/ChatDock.vue +27 -39
- package/python/frontend_src/src/components/DocViewer.vue +0 -9
- package/python/frontend_src/src/components/Inspector.vue +0 -6
- package/python/frontend_src/src/components/LeftRail.vue +60 -39
- package/python/frontend_src/src/components/MenuPopover.vue +9 -11
- package/python/frontend_src/src/components/MessageStream.vue +18 -30
- package/python/frontend_src/src/components/ModelPicker.vue +0 -5
- package/python/frontend_src/src/components/QuickIngest.vue +0 -6
- package/python/frontend_src/src/components/Stage.vue +71 -25
- package/python/frontend_src/src/components/ToolMenu.vue +0 -3
- package/python/frontend_src/src/components/TopBar.vue +38 -10
- package/python/frontend_src/src/components/panels/ActivityPanel.vue +0 -2
- package/python/frontend_src/src/components/panels/AgentsPanel.vue +2 -5
- package/python/frontend_src/src/components/panels/KnowledgePanel.vue +0 -16
- package/python/frontend_src/src/components/panels/MemoryPanel.vue +0 -1
- package/python/frontend_src/src/components/panels/PanelBox.vue +6 -7
- package/python/frontend_src/src/components/panels/ReviewPanel.vue +0 -16
- package/python/frontend_src/src/components/panels/SettingsPanel.vue +142 -38
- package/python/frontend_src/src/components/panels/TasksPanel.vue +0 -1
- package/python/frontend_src/src/components/panels/ToolsPanel.vue +125 -19
- package/python/frontend_src/src/lib/fmt.js +0 -4
- package/python/frontend_src/src/lib/i18n.js +53 -0
- package/python/frontend_src/src/lib/locales/en.js +173 -0
- package/python/frontend_src/src/lib/locales/zh.js +174 -0
- package/python/frontend_src/src/lib/md.js +0 -14
- package/python/frontend_src/src/main.js +2 -0
- package/python/frontend_src/src/stores/app.js +17 -32
- package/python/frontend_src/src/stores/chat.js +0 -34
- package/python/frontend_src/src/stores/model.js +0 -8
- package/python/frontend_src/src/stores/panels.js +0 -41
- package/python/frontend_src/src/stores/profile.js +31 -0
- package/python/frontend_src/src/stores/system.js +25 -0
- package/python/frontend_src/vite.config.js +0 -7
- package/python/pyproject.toml +6 -3
- package/python/requirements.txt +311 -0
- package/python/tests/conftest.py +0 -22
- package/python/tests/run_all.py +3 -66
- package/python/tests/test_agent_lifecycle.py +4 -38
- package/python/tests/test_ahs_p2.py +4 -25
- package/python/tests/test_anthropic_tools.py +1 -15
- package/python/tests/test_api.py +19 -9
- package/python/tests/test_api_auth.py +1 -5
- package/python/tests/test_attachments.py +9 -25
- package/python/tests/test_audit_chain.py +2 -7
- package/python/tests/test_audit_sink.py +0 -6
- package/python/tests/test_browser.py +2 -6
- package/python/tests/test_browser_request_guard.py +0 -4
- package/python/tests/test_capabilities.py +0 -6
- package/python/tests/test_channel_signing.py +2 -9
- package/python/tests/test_channels.py +1 -11
- package/python/tests/test_config_layering.py +0 -4
- package/python/tests/test_confirm_nonce.py +1 -12
- package/python/tests/test_context_compaction_wiring.py +1 -7
- package/python/tests/test_dispatch.py +0 -6
- package/python/tests/test_domain_evals.py +11 -80
- package/python/tests/test_domain_import.py +0 -6
- package/python/tests/test_domain_reasoners.py +0 -14
- package/python/tests/test_e2e.py +0 -17
- package/python/tests/test_emb_cache.py +7 -17
- package/python/tests/test_embedding_singleton.py +1 -3
- package/python/tests/test_evals_smoke.py +0 -24
- package/python/tests/test_event_spine.py +10 -18
- package/python/tests/test_evidence_semantic.py +1 -14
- package/python/tests/test_evidence_sse.py +3 -6
- package/python/tests/test_file_ingest.py +0 -11
- package/python/tests/test_finance.py +0 -9
- package/python/tests/test_finance_ledger.py +5 -12
- package/python/tests/test_finance_persist.py +8 -12
- package/python/tests/test_gateway.py +3 -22
- package/python/tests/test_governance.py +5 -52
- package/python/tests/test_governance_audit.py +4 -39
- package/python/tests/test_governance_middleware.py +1 -7
- package/python/tests/test_hkc.py +6 -24
- package/python/tests/test_hkc_index_build_manager.py +0 -33
- package/python/tests/test_hkc_manifest_reuse.py +4 -27
- package/python/tests/test_hkc_search_event_sync.py +3 -54
- package/python/tests/test_import_path_guard.py +0 -6
- package/python/tests/test_injection_guard.py +18 -36
- package/python/tests/test_integration.py +5 -29
- package/python/tests/test_itinerary.py +0 -3
- package/python/tests/test_kee_semantic.py +0 -5
- package/python/tests/test_kernel.py +1 -11
- package/python/tests/test_learning.py +3 -12
- package/python/tests/test_llm_admin.py +4 -19
- package/python/tests/test_llm_planner.py +1 -23
- package/python/tests/test_markdown_skills.py +3 -14
- package/python/tests/test_markdown_skills_openclaw.py +4 -9
- package/python/tests/test_mcp_connectors.py +3 -15
- package/python/tests/test_mcp_guardian_discovery.py +2 -6
- package/python/tests/test_mcp_no_memory.py +3 -3
- package/python/tests/test_mcp_policy_gate.py +0 -1
- package/python/tests/test_mcp_server.py +6 -9
- package/python/tests/test_memory_recall.py +1 -4
- package/python/tests/test_metrics.py +0 -8
- package/python/tests/test_multi_chain.py +1 -13
- package/python/tests/test_multimodal.py +5 -18
- package/python/tests/test_negotiation.py +3 -13
- package/python/tests/test_orchestration.py +1 -11
- package/python/tests/test_orchestration_decoupled.py +0 -6
- package/python/tests/test_p3_p5_hardening.py +3 -17
- package/python/tests/test_parallel_dispatch.py +4 -12
- package/python/tests/test_pipeline.py +17 -58
- package/python/tests/test_plan_visible.py +4 -4
- package/python/tests/test_planner.py +0 -7
- package/python/tests/test_policy_gate.py +2 -3
- package/python/tests/test_profile.py +67 -0
- package/python/tests/test_readme_claims.py +163 -0
- package/python/tests/test_reasoner.py +2 -17
- package/python/tests/test_release_check.py +2 -13
- package/python/tests/test_resume_checkpoint.py +7 -15
- package/python/tests/test_routing.py +2 -12
- package/python/tests/test_scheduler.py +7 -10
- package/python/tests/test_search.py +1 -11
- package/python/tests/test_secrets.py +4 -10
- package/python/tests/test_semantic.py +1 -10
- package/python/tests/test_sessions.py +3 -4
- package/python/tests/test_settings.py +2 -18
- package/python/tests/test_shadow_learning.py +3 -43
- package/python/tests/test_shared_write.py +0 -7
- package/python/tests/test_skill_taint_gate.py +1 -10
- package/python/tests/test_skills.py +2 -18
- package/python/tests/test_skills_fetch.py +282 -0
- package/python/tests/test_stats_and_events.py +2 -5
- package/python/tests/test_streaming.py +2 -7
- package/python/tests/test_supervisor_pipeline.py +4 -5
- package/python/tests/test_supervisor_triage.py +4 -10
- package/python/tests/test_taint.py +2 -7
- package/python/tests/test_task_stream.py +1 -12
- package/python/tests/test_thinking_sse.py +0 -1
- package/python/tests/test_tool_center.py +1 -17
- package/python/tests/test_tool_parallel.py +2 -2
- package/python/tests/test_tool_params.py +1 -5
- package/python/tests/test_tool_use.py +0 -13
- package/python/tests/test_toplevel_concurrency.py +3 -4
- package/python/tests/test_tracing.py +7 -41
- package/python/tests/test_truth_crystallizer.py +4 -6
- package/python/tests/test_truth_flow.py +2 -7
- package/python/tests/test_update_check.py +95 -0
- package/python/tests/test_verify_evidence.py +8 -21
- package/python/tests/test_version_sync.py +35 -6
- package/python/tests/test_web_trigger.py +0 -17
- package/python/tools/check_faiss_type.py +0 -1
- package/python/tools/check_log.ps1 +0 -8
- package/python/tools/mypy_baseline.txt +12 -4
- package/python/tools/mypy_ratchet.py +52 -6
- package/python/tools/pack.py +0 -5
- package/python/tools/release_check.py +7 -7
- package/python/tools/restart_foxclaw.ps1 +5 -8
- package/python/tools/shared_write_audit.py +1 -15
- package/python/tools/size_check.py +1 -1
- package/python/tools/strip_comments.py +228 -0
- package/python/frontend/v3/assets/index-BD7VPbMV.js +0 -33
- package/python/frontend/v3/assets/index-CAvnYWGH.css +0 -1
|
@@ -1,6 +1,3 @@
|
|
|
1
|
-
// ════════════════════════════════════════════════════════
|
|
2
|
-
// HKC UI — 数据层:演示数据 + API 客户端(与 hkc_sdk 对齐)
|
|
3
|
-
// ════════════════════════════════════════════════════════
|
|
4
1
|
|
|
5
2
|
const KU_COLORS = {
|
|
6
3
|
Entity: '#002FA7', // 克莱因蓝
|
|
@@ -15,7 +12,6 @@ const KU_LABEL = {
|
|
|
15
12
|
Claim:'观点', Evidence:'证据', Ability:'能力'
|
|
16
13
|
};
|
|
17
14
|
|
|
18
|
-
// ── 演示数据:一个投资领域的小知识图谱 ──
|
|
19
15
|
const DEMO = {
|
|
20
16
|
kus: {
|
|
21
17
|
ENT_01:{ku_id:'ENT_01',ku_type:'Entity',name:'Charlie Munger',summary:'伯克希尔·哈撒韦副董事长,价值投资与多元思维模型的代表人物。',domain:'Investment',confidence:1.0,status:'active',tags:['投资','心理学'],extra:{entity_type:'Person',aliases:['查理·芒格']}},
|
|
@@ -66,7 +62,6 @@ const DEMO = {
|
|
|
66
62
|
],
|
|
67
63
|
};
|
|
68
64
|
|
|
69
|
-
// ── API 客户端:连真实 hkc-api,失败回退演示 ──
|
|
70
65
|
class HKCClient{
|
|
71
66
|
constructor(){ this.base=null; this.demo=true; this.apiKey=''; }
|
|
72
67
|
async connect(url){
|
|
@@ -77,7 +72,6 @@ class HKCClient{
|
|
|
77
72
|
}catch(e){ this.demo=true; this.base=null; return false; }
|
|
78
73
|
}
|
|
79
74
|
useDemo(){ this.demo=true; this.base=null; }
|
|
80
|
-
// 后端若设了 HKC_API_KEY,前端在此带上 X-API-Key;未配置则为空、不影响开放部署。
|
|
81
75
|
_h(extra){ const h=Object.assign({}, extra||{}); if(this.apiKey) h['X-API-Key']=this.apiKey; return h; }
|
|
82
76
|
async _get(p){ const r=await fetch(this.base+p,{headers:this._h()}); if(!r.ok) throw new Error(r.status); return r.json(); }
|
|
83
77
|
async _post(p,b){ const r=await fetch(this.base+p,{method:'POST',headers:this._h({'Content-Type':'application/json'}),body:JSON.stringify(b)});
|
|
@@ -87,9 +81,6 @@ class HKCClient{
|
|
|
87
81
|
}
|
|
88
82
|
const client=new HKCClient();
|
|
89
83
|
|
|
90
|
-
// ════════════════════════════════════════════════════════
|
|
91
|
-
// 状态 + 工具
|
|
92
|
-
// ════════════════════════════════════════════════════════
|
|
93
84
|
const state={ kus:{}, relations:[], abilities:[], conflicts:[], selected:null, mode:'hybrid', conflictPick:{}, synth:{}, pathSel:{} };
|
|
94
85
|
const $=s=>document.querySelector(s);
|
|
95
86
|
const $$=s=>document.querySelectorAll(s);
|
|
@@ -102,7 +93,6 @@ function toast(msg,kind=''){
|
|
|
102
93
|
setTimeout(()=>{t.style.opacity='0';t.style.transition='opacity .3s';setTimeout(()=>t.remove(),300)},2600);
|
|
103
94
|
}
|
|
104
95
|
|
|
105
|
-
// ── 载入数据(演示或真实)──
|
|
106
96
|
async function loadData(){
|
|
107
97
|
if(client.demo){
|
|
108
98
|
state.kus=JSON.parse(JSON.stringify(DEMO.kus));
|
|
@@ -110,11 +100,8 @@ async function loadData(){
|
|
|
110
100
|
state.abilities=DEMO.abilities.map(a=>({...a}));
|
|
111
101
|
state.conflicts=DEMO.conflicts.map(c=>({...c}));
|
|
112
102
|
}else{
|
|
113
|
-
// 真实模式:一次拉取全量知识图谱(所有 KU + 关系),不再写死领域
|
|
114
103
|
try{
|
|
115
104
|
const g=await client._get('/knowledge/graph?limit=5000'); // 拉到后端硬上限(默认仅 2000)
|
|
116
|
-
// 真实库为空 → 本次用演示数据渲染(星球不空白);**不翻 client.demo**,连接保留、摄入仍走真实后端,
|
|
117
|
-
// 摄入知识后刷新即自动显示真实星球。修「嵌入 Foxclaw 空库时星球一片空白」。
|
|
118
105
|
if(!(g.kus && g.kus.length)){
|
|
119
106
|
toast('知识库为空,先展示演示星球;摄入知识后自动显示真实数据','');
|
|
120
107
|
state.kus=JSON.parse(JSON.stringify(DEMO.kus));
|
|
@@ -136,19 +123,14 @@ async function loadData(){
|
|
|
136
123
|
domain:(rep.domains||[]).join(' / '),
|
|
137
124
|
coverage:rep.coverage,can_compile:rep.can_compile,missing:rep.missing_skills});
|
|
138
125
|
}
|
|
139
|
-
// 冲突
|
|
140
126
|
const cf=await client._get('/conflicts?status=open').catch(()=>({conflicts:[]}));
|
|
141
127
|
state.conflicts=cf.conflicts||[];
|
|
142
|
-
// 数据可见性安全网:拉 /stats 拿真实 data_dir + 全库 KU 总数,
|
|
143
|
-
// 显示到连接胶囊(hover)+ toast,空库时明显告警——防止"连错目录以为数据丢了"。
|
|
144
128
|
updateDataInfo().catch(()=>{});
|
|
145
129
|
}catch(e){ toast('数据加载出错,回退演示模式','err'); client.useDemo(); return loadData(); }
|
|
146
130
|
}
|
|
147
131
|
renderAll();
|
|
148
132
|
}
|
|
149
133
|
|
|
150
|
-
// 拉 /stats,把「后端实际数据目录 + 全库知识总数」暴露给用户。
|
|
151
|
-
// 关键:total_kus 是全库真实数,可能 > 星球显示数(星球有 limit 上限)。
|
|
152
134
|
async function updateDataInfo(){
|
|
153
135
|
const pill=$('#conn-pill');
|
|
154
136
|
const st=await client._get('/stats');
|
|
@@ -180,19 +162,11 @@ function renderPanels(){
|
|
|
180
162
|
$('#conflict-count').textContent=oc;
|
|
181
163
|
}
|
|
182
164
|
|
|
183
|
-
// ════════════════════════════════════════════════════════
|
|
184
|
-
// G6 知识图谱(本地 vendor/g6.min.js,零网络)
|
|
185
|
-
// ════════════════════════════════════════════════════════
|
|
186
|
-
// ════════════════════════════════════════════════════════
|
|
187
|
-
// 知识星球 3D 引擎(Three.js,本地 vendor,零网络)
|
|
188
|
-
// 节点 = 3D 球体浮于空间,语义引力聚拢,缓慢有机漂浮
|
|
189
|
-
// ════════════════════════════════════════════════════════
|
|
190
165
|
let THREE = null; // 动态加载的 Three.js 命名空间
|
|
191
166
|
let planet = null; // 当前星球实例
|
|
192
167
|
|
|
193
168
|
function disputedOf(k){ return k.extra && k.extra.claim_status==='disputed'; }
|
|
194
169
|
|
|
195
|
-
// 节点半径(3D 世界单位)
|
|
196
170
|
function nodeRadius(k){
|
|
197
171
|
if(k.ku_type==='Ability') return 7;
|
|
198
172
|
if(k.ku_type==='Entity'||k.ku_type==='Concept') return 6;
|
|
@@ -200,20 +174,10 @@ function nodeRadius(k){
|
|
|
200
174
|
return 5;
|
|
201
175
|
}
|
|
202
176
|
|
|
203
|
-
// ── 星球布局策略(伸缩的统一接入点)────────────────────────────
|
|
204
|
-
// 所有"节点/标签太多放不下怎么办"的逻辑都集中在此,后续扩展只改这个对象,
|
|
205
|
-
// 不动其它渲染代码:
|
|
206
|
-
// 已实现 · 单层 Fibonacci 球面 + 圆点随节点总数自适应缩小(dotScale)。
|
|
207
|
-
// 预留接入点 ↓
|
|
208
|
-
// · shells>1 → 分层多壳星球(外壳放不下→往里再套一层球);position() 已按
|
|
209
|
-
// shell 分配、内层半径递减,只需让 compute() 返回 shells>1 即启用。
|
|
210
|
-
// · labelScale → 书名标签随书数自适应缩小(_makeLabel 已乘该系数)。
|
|
211
|
-
// · SHELL_CAPACITY→ 单层舒适容量阈值,决定何时缩点 / 何时该分层。
|
|
212
177
|
const PLANET_LAYOUT = {
|
|
213
178
|
SHELL_CAPACITY: 800, // 单层舒适容量;超过开始缩点,(未来)超过可分层
|
|
214
179
|
BASE_R_MIN: 160, BASE_R_MAX: 320,
|
|
215
180
|
|
|
216
|
-
// 按节点总数计算布局参数(集中调参)
|
|
217
181
|
compute(n){
|
|
218
182
|
const radius = Math.max(this.BASE_R_MIN, Math.min(this.BASE_R_MAX, 90 + n*11));
|
|
219
183
|
const dotScale = Math.max(0.45, Math.min(1, Math.sqrt(this.SHELL_CAPACITY / Math.max(1, n))));
|
|
@@ -222,8 +186,6 @@ const PLANET_LAYOUT = {
|
|
|
222
186
|
return { radius, dotScale, shells, labelScale };
|
|
223
187
|
},
|
|
224
188
|
|
|
225
|
-
// 第 i 个节点(共 n 个)的球面坐标。已支持多壳:shells>1 时按层分配、内层半径递减;
|
|
226
|
-
// shells=1 时退化为单层 Fibonacci,布局与旧版完全一致。
|
|
227
189
|
position(i, n, params, T){
|
|
228
190
|
const golden = Math.PI * (3 - Math.sqrt(5));
|
|
229
191
|
const shells = params.shells || 1;
|
|
@@ -239,10 +201,6 @@ const PLANET_LAYOUT = {
|
|
|
239
201
|
},
|
|
240
202
|
};
|
|
241
203
|
|
|
242
|
-
// 关系标签
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
// ── 知识星球类 ──
|
|
246
204
|
class KnowledgePlanet {
|
|
247
205
|
constructor(container, THREE){
|
|
248
206
|
this.T = THREE;
|
|
@@ -263,11 +221,9 @@ class KnowledgePlanet {
|
|
|
263
221
|
|
|
264
222
|
this.scene = new T.Scene();
|
|
265
223
|
|
|
266
|
-
// 相机
|
|
267
224
|
this.camera = new T.PerspectiveCamera(55, w/h, 1, 4000);
|
|
268
225
|
this.camera.position.set(0, 0, 720);
|
|
269
226
|
|
|
270
|
-
// 渲染器
|
|
271
227
|
this.renderer = new T.WebGLRenderer({ antialias:true, alpha:true });
|
|
272
228
|
this.renderer.setSize(w, h);
|
|
273
229
|
this.renderer.setPixelRatio(Math.min(window.devicePixelRatio||1, 2));
|
|
@@ -276,20 +232,16 @@ class KnowledgePlanet {
|
|
|
276
232
|
this.canvas = this.renderer.domElement;
|
|
277
233
|
this.canvas.style.display = 'block';
|
|
278
234
|
|
|
279
|
-
// 灯光:环境光 + 两个点光(冷暖对冲,呼应品牌绿/蓝)
|
|
280
235
|
this.scene.add(new T.AmbientLight(0xffffff, 0.75));
|
|
281
236
|
const l1 = new T.PointLight(0x9fe8d2, 0.9, 0); l1.position.set(200, 180, 300); this.scene.add(l1);
|
|
282
237
|
const l2 = new T.PointLight(0x7d97ff, 0.7, 0); l2.position.set(-220, -120, 160); this.scene.add(l2);
|
|
283
238
|
|
|
284
|
-
// 旋转容器:所有节点挂在这个 group 上,整体缓缓自转
|
|
285
239
|
this.world = new T.Group();
|
|
286
240
|
this.scene.add(this.world);
|
|
287
241
|
|
|
288
|
-
// 轨道控制(自写:拖拽转视角 + 滚轮缩放)
|
|
289
242
|
this._initControls();
|
|
290
243
|
}
|
|
291
244
|
|
|
292
|
-
// 轻量轨道控制
|
|
293
245
|
_initControls(){
|
|
294
246
|
this.rot = { x: 0, y: 0 }; // 目标旋转
|
|
295
247
|
this.rotCur = { x: 0, y: 0 }; // 当前(缓动)
|
|
@@ -303,9 +255,7 @@ class KnowledgePlanet {
|
|
|
303
255
|
|
|
304
256
|
this._onDown = (e)=>{ dragging = true; this.dragging = true; lastX = e.clientX; lastY = e.clientY; el.style.cursor='grabbing';
|
|
305
257
|
this._downX = e.clientX; this._downY = e.clientY; // 记录起点,用于区分"拖动"与"点击"
|
|
306
|
-
// 接管当前视觉朝向再停自转,避免从摆动倾斜切到手动时跳变
|
|
307
258
|
this.rot.x = this.rotCur.x; this.rot.y = this.rotCur.y; this.autoSpin = false;
|
|
308
|
-
// 关键:清掉 flyTarget,否则聚焦态每帧把旋转拉回目标节点朝向 → 拖动被牵扯、松手即反弹
|
|
309
259
|
this._flyTarget = null; };
|
|
310
260
|
this._onMove = (e)=>{
|
|
311
261
|
if(!dragging) return;
|
|
@@ -327,18 +277,12 @@ class KnowledgePlanet {
|
|
|
327
277
|
el.addEventListener('wheel', this._onWheel, { passive:false });
|
|
328
278
|
}
|
|
329
279
|
|
|
330
|
-
// 构建节点与边
|
|
331
280
|
build(kus, relations){
|
|
332
281
|
const T = this.T;
|
|
333
282
|
const ids = Object.keys(kus);
|
|
334
283
|
const n = ids.length;
|
|
335
284
|
if(!n) return;
|
|
336
285
|
|
|
337
|
-
// 稳定打散:后端按类型分组返回 KU,直接按序号布 Fibonacci 会把同类型节点
|
|
338
|
-
// (尤其只有几个的"书"/Evidence)全挤到同一极点、标签叠成一坨。按 id 哈希
|
|
339
|
-
// 稳定排序,让各类型均匀铺满球面(布局跨刷新稳定,不随机跳)。
|
|
340
|
-
// 必须用有雪崩效应的哈希(FNV-1a+混合):id 是 EVD_0000000X 这类共享前缀、
|
|
341
|
-
// 仅末位不同的序列,简单滚动哈希会给相近值 → 同前缀仍聚堆,起不到打散作用。
|
|
342
286
|
const _h = s => {
|
|
343
287
|
let h = 2166136261 >>> 0;
|
|
344
288
|
for(let k=0;k<s.length;k++){ h ^= s.charCodeAt(k); h = Math.imul(h, 16777619); }
|
|
@@ -347,7 +291,6 @@ class KnowledgePlanet {
|
|
|
347
291
|
};
|
|
348
292
|
ids.sort((a,b)=> _h(a) - _h(b));
|
|
349
293
|
|
|
350
|
-
// 布局参数(半径/圆点缩放/层数/标签缩放)统一由 PLANET_LAYOUT 计算,便于扩展
|
|
351
294
|
const params = PLANET_LAYOUT.compute(n);
|
|
352
295
|
this._layoutParams = params; // 供 _makeLabel 读取 labelScale 等
|
|
353
296
|
|
|
@@ -356,7 +299,6 @@ class KnowledgePlanet {
|
|
|
356
299
|
const radius = nodeRadius(ku) * params.dotScale; // 圆点随节点总数自适应缩小
|
|
357
300
|
const pos = PLANET_LAYOUT.position(i, n, params, T);
|
|
358
301
|
|
|
359
|
-
// 球体
|
|
360
302
|
const color = new T.Color(KU_COLORS[ku.ku_type] || '#888');
|
|
361
303
|
const geo = new T.SphereGeometry(radius, 24, 24);
|
|
362
304
|
const mat = new T.MeshStandardMaterial({
|
|
@@ -368,12 +310,10 @@ class KnowledgePlanet {
|
|
|
368
310
|
mesh.userData.id = id;
|
|
369
311
|
this.world.add(mesh);
|
|
370
312
|
|
|
371
|
-
// 外发光(稍大的半透明球)
|
|
372
313
|
const glowMat = new T.MeshBasicMaterial({ color, transparent:true, opacity:0.12, side:T.BackSide });
|
|
373
314
|
const glow = new T.Mesh(new T.SphereGeometry(radius*1.6, 20, 20), glowMat);
|
|
374
315
|
mesh.add(glow);
|
|
375
316
|
|
|
376
|
-
// 冲突节点:暗红描边光环
|
|
377
317
|
if(disputedOf(ku)){
|
|
378
318
|
const ring = new T.Mesh(
|
|
379
319
|
new T.SphereGeometry(radius*1.35, 20, 20),
|
|
@@ -391,23 +331,17 @@ class KnowledgePlanet {
|
|
|
391
331
|
phase: Math.random()*Math.PI*2, // 漂浮相位
|
|
392
332
|
});
|
|
393
333
|
|
|
394
|
-
// 文字标签(Sprite,始终面向相机)
|
|
395
334
|
const label = this._makeLabel(ku.name);
|
|
396
335
|
label.position.set(0, -(radius+9), 0);
|
|
397
336
|
mesh.add(label);
|
|
398
337
|
const _nd = this.nodes[this.nodes.length-1];
|
|
399
338
|
_nd.label = label;
|
|
400
|
-
// 分级标签(按重要度钻取):
|
|
401
|
-
// 一级 = 书/来源(Evidence),默认在球面常显书名;
|
|
402
|
-
// 二级/三级 = 点击后按连接数(degree)展开该节点 top-N 重要邻居标签。
|
|
403
|
-
// 这样几十上百本默认只见几十个书名,清爽且可层层下钻。
|
|
404
339
|
_nd.isBook = (ku.ku_type === 'Evidence');
|
|
405
340
|
_nd.deg = 0; // 连接数,建边后回填(作重要度)
|
|
406
341
|
_nd._revealLabel = false;
|
|
407
342
|
label.visible = _nd.isBook;
|
|
408
343
|
});
|
|
409
344
|
|
|
410
|
-
// 边(连线)
|
|
411
345
|
const nodeById = {};
|
|
412
346
|
this.nodes.forEach(nd=> nodeById[nd.id] = nd);
|
|
413
347
|
(relations||[]).forEach(r=>{
|
|
@@ -425,13 +359,11 @@ class KnowledgePlanet {
|
|
|
425
359
|
this.edges.push({ from:a, to:b, line, contradicts });
|
|
426
360
|
});
|
|
427
361
|
|
|
428
|
-
// 连接数(degree)=重要度,用于钻取时挑 top-N 邻居标签
|
|
429
362
|
this.edges.forEach(e=>{ e.from.deg++; e.to.deg++; });
|
|
430
363
|
|
|
431
364
|
this._buildPicking();
|
|
432
365
|
}
|
|
433
366
|
|
|
434
|
-
// 射线拾取(hover / click)
|
|
435
367
|
_buildPicking(){
|
|
436
368
|
const T = this.T;
|
|
437
369
|
this.raycaster = new T.Raycaster();
|
|
@@ -444,8 +376,6 @@ class KnowledgePlanet {
|
|
|
444
376
|
this.pointer.y = -((e.clientY - rect.top)/rect.height)*2 + 1;
|
|
445
377
|
};
|
|
446
378
|
this._onClick = (e)=>{
|
|
447
|
-
// 拖动松手也会带出一次 click;若指针移动过阈值,判定为拖动而非点击 → 忽略,
|
|
448
|
-
// 否则空白处会 clearSelection 重开自转、有节点处会 selectKU 飞入,都表现为"反弹"。
|
|
449
379
|
const moved = Math.hypot(e.clientX - (this._downX||0), e.clientY - (this._downY||0));
|
|
450
380
|
if(moved > 6) return;
|
|
451
381
|
const hit = this._pick();
|
|
@@ -467,7 +397,6 @@ class KnowledgePlanet {
|
|
|
467
397
|
return null;
|
|
468
398
|
}
|
|
469
399
|
|
|
470
|
-
// 动画循环
|
|
471
400
|
start(){
|
|
472
401
|
const T = this.T;
|
|
473
402
|
const tick = ()=>{
|
|
@@ -483,28 +412,20 @@ class KnowledgePlanet {
|
|
|
483
412
|
const T = this.T;
|
|
484
413
|
const t = performance.now() * 0.001;
|
|
485
414
|
|
|
486
|
-
// 自动自转(绕 Y 轴)——静默时缓慢转动(约 100s/圈)
|
|
487
415
|
if(this.autoSpin) this.rot.y += 0.0011;
|
|
488
416
|
|
|
489
|
-
// 旋转缓动;自转时让倾斜角缓慢大幅度上下摆动(±~77°),使两极也周期性
|
|
490
|
-
// 转到正面,避免单轴自转把两极节点永久"沉"在顶/底而无法点击
|
|
491
|
-
// (即用户要的"无固定方向转动、无重力沉淀")。
|
|
492
417
|
const tiltX = this.autoSpin ? Math.sin(t*0.045)*1.35 : this.rot.x;
|
|
493
|
-
// 拖动时近乎 1:1 跟手,消除"橡皮筋/反馈力牵扯"感;静默时才平滑缓动。
|
|
494
418
|
const rotEase = this.dragging ? 0.85 : 0.08;
|
|
495
419
|
this.rotCur.x += (tiltX - this.rotCur.x) * rotEase;
|
|
496
420
|
this.rotCur.y += (this.rot.y - this.rotCur.y) * rotEase;
|
|
497
421
|
this.world.rotation.x = this.rotCur.x;
|
|
498
422
|
this.world.rotation.y = this.rotCur.y;
|
|
499
423
|
|
|
500
|
-
// 相机距离缓动
|
|
501
424
|
this.camDist += (this.camDistTarget - this.camDist) * 0.1;
|
|
502
425
|
this.camera.position.z = this.camDist;
|
|
503
426
|
|
|
504
|
-
// 飞入:把选中节点缓缓转到正面中心
|
|
505
427
|
if(this._flyTarget){
|
|
506
428
|
const nd = this._flyTarget;
|
|
507
|
-
// 期望旋转:让节点world坐标朝向 +Z(正对相机)
|
|
508
429
|
const p = nd.pos;
|
|
509
430
|
const targetRotY = -Math.atan2(p.x, p.z);
|
|
510
431
|
const targetRotX = Math.atan2(p.y, Math.sqrt(p.x*p.x+p.z*p.z));
|
|
@@ -512,12 +433,8 @@ class KnowledgePlanet {
|
|
|
512
433
|
this.rot.x = targetRotX; // 不钳制:点击两极节点也能完整转到正面中心
|
|
513
434
|
}
|
|
514
435
|
|
|
515
|
-
// ── 球面分布 + 聚焦汇聚 ──
|
|
516
|
-
// 正常态:节点固定在球面 + 轻微呼吸,整体自转
|
|
517
|
-
// 聚焦态:点击某节点,相关节点汇聚到它周围并发光,无关节点淡出退远
|
|
518
436
|
const nodes = this.nodes;
|
|
519
437
|
nodes.forEach(nd=>{
|
|
520
|
-
// 目标位置:有 focusPos(聚焦汇聚)用它,否则回球面 basePos
|
|
521
438
|
let tx, ty, tz;
|
|
522
439
|
if(nd.focusPos){
|
|
523
440
|
tx = nd.focusPos.x; ty = nd.focusPos.y; tz = nd.focusPos.z;
|
|
@@ -526,7 +443,6 @@ class KnowledgePlanet {
|
|
|
526
443
|
const by = Math.cos(t*0.4 + nd.phase)*1.0;
|
|
527
444
|
tx = nd.basePos.x + bx; ty = nd.basePos.y + by; tz = nd.basePos.z;
|
|
528
445
|
}
|
|
529
|
-
// 平滑移动到目标(汇聚/回归动画)
|
|
530
446
|
const cur = nd.mesh.position;
|
|
531
447
|
const ease = nd.focusPos ? 0.10 : 0.06;
|
|
532
448
|
cur.x += (tx - cur.x)*ease;
|
|
@@ -534,33 +450,23 @@ class KnowledgePlanet {
|
|
|
534
450
|
cur.z += (tz - cur.z)*ease;
|
|
535
451
|
nd.pos.copy(cur);
|
|
536
452
|
|
|
537
|
-
// 发光过渡:聚焦的相关节点发光增强,淡出的减弱
|
|
538
453
|
const targetEmissive = nd.glowOn ? 1.0 : 0.35;
|
|
539
454
|
nd._emi = (nd._emi===undefined?0.35:nd._emi) + (targetEmissive - (nd._emi||0.35))*0.12;
|
|
540
455
|
if(nd.mesh.material && nd.mesh.material.emissive && nd.baseColor){
|
|
541
456
|
nd.mesh.material.emissive.copy(nd.baseColor).multiplyScalar(nd._emi);
|
|
542
457
|
}
|
|
543
|
-
// 淡出:无关节点透明度降低
|
|
544
458
|
const targetOpacity = (nd.dim ? 0.18 : 1.0);
|
|
545
459
|
nd._op = (nd._op===undefined?1:nd._op) + (targetOpacity - (nd._op||1))*0.12;
|
|
546
460
|
nd.mesh.material.opacity = nd._op;
|
|
547
461
|
nd.mesh.material.transparent = nd._op < 0.99;
|
|
548
|
-
// 标签分级可见性:
|
|
549
|
-
// - 聚焦态(选中了某节点):只显示发光簇(glowOn)+悬停的标签,淡出的隐藏
|
|
550
|
-
// - 默认态:只显示常显类型(Entity/Concept)+悬停的标签
|
|
551
462
|
if(nd.label){
|
|
552
463
|
const forced = (nd===this.hovered || nd.id===this.selected);
|
|
553
|
-
// 分级:钻取态只显示当前选中节点点亮的 top-N 邻居(二级/三级);
|
|
554
|
-
// 默认态只显示一级书名(Evidence);悬停/选中强制显示。
|
|
555
|
-
// 分级后可见标签本就稀疏(默认≈书数、钻取≈7),不做背面剔除,
|
|
556
|
-
// 否则仅有的几个书名会在球背面时被剔空,出现"无标签"空窗。
|
|
557
464
|
const show = forced || (this.selected ? nd._revealLabel : nd.isBook);
|
|
558
465
|
nd.label.visible = show;
|
|
559
466
|
if(show) nd.label.material.opacity = nd._op;
|
|
560
467
|
}
|
|
561
468
|
});
|
|
562
469
|
|
|
563
|
-
// 更新边的端点(只更新可见的边,省性能)
|
|
564
470
|
this.edges.forEach(e=>{
|
|
565
471
|
if(!e.line.visible) return;
|
|
566
472
|
const arr = e.line.geometry.attributes.position.array;
|
|
@@ -569,7 +475,6 @@ class KnowledgePlanet {
|
|
|
569
475
|
e.line.geometry.attributes.position.needsUpdate = true;
|
|
570
476
|
});
|
|
571
477
|
|
|
572
|
-
// hover 检测 + 放大(用 scale,不改几何,不抖)
|
|
573
478
|
const hit = this._pick();
|
|
574
479
|
if(hit !== this.hovered){
|
|
575
480
|
if(this.hovered) this._setHover(this.hovered, false);
|
|
@@ -577,7 +482,6 @@ class KnowledgePlanet {
|
|
|
577
482
|
this.hovered = hit;
|
|
578
483
|
this.canvas.style.cursor = hit ? 'pointer' : 'grab';
|
|
579
484
|
}
|
|
580
|
-
// 平滑 scale
|
|
581
485
|
this.nodes.forEach(nd=>{
|
|
582
486
|
const target = (nd===this.hovered || nd.id===this.selected) ? 1.5 : 1.0;
|
|
583
487
|
const s = nd.mesh.scale.x + (target - nd.mesh.scale.x)*0.18;
|
|
@@ -587,7 +491,6 @@ class KnowledgePlanet {
|
|
|
587
491
|
|
|
588
492
|
_setHover(nd, on){ /* 状态切换占位,scale 在 _update 平滑处理 */ }
|
|
589
493
|
|
|
590
|
-
// 生成文字标签 Sprite
|
|
591
494
|
_makeLabel(text){
|
|
592
495
|
const T = this.T;
|
|
593
496
|
const label = (text||'').length>12 ? text.slice(0,11)+'…' : (text||'');
|
|
@@ -597,10 +500,8 @@ class KnowledgePlanet {
|
|
|
597
500
|
ctx.font = `600 ${fs}px -apple-system,"PingFang SC","Microsoft YaHei",sans-serif`;
|
|
598
501
|
const w = Math.ceil(ctx.measureText(label).width) + 24;
|
|
599
502
|
cv.width = w; cv.height = fs+24;
|
|
600
|
-
// 重设 font(canvas resize 后失效)
|
|
601
503
|
ctx.font = `600 ${fs}px -apple-system,"PingFang SC","Microsoft YaHei",sans-serif`;
|
|
602
504
|
ctx.textAlign='center'; ctx.textBaseline='middle';
|
|
603
|
-
// 半透明深色描边底,深空背景上清晰
|
|
604
505
|
ctx.fillStyle='rgba(255,255,255,.92)';
|
|
605
506
|
ctx.shadowColor='rgba(0,0,0,.85)'; ctx.shadowBlur=8;
|
|
606
507
|
ctx.fillText(label, cv.width/2, cv.height/2);
|
|
@@ -613,12 +514,10 @@ class KnowledgePlanet {
|
|
|
613
514
|
return sp;
|
|
614
515
|
}
|
|
615
516
|
|
|
616
|
-
// 相机平滑飞到某节点
|
|
617
517
|
flyTo(id){
|
|
618
518
|
const nd = this.nodes.find(n=>n.id===id);
|
|
619
519
|
if(!nd) return;
|
|
620
520
|
this.autoSpin = false;
|
|
621
|
-
// 把该节点转到正面:根据其当前世界位置反推目标旋转
|
|
622
521
|
this._flyTarget = nd;
|
|
623
522
|
this.camDistTarget = 380;
|
|
624
523
|
}
|
|
@@ -629,7 +528,6 @@ class KnowledgePlanet {
|
|
|
629
528
|
else this.clearFocus();
|
|
630
529
|
}
|
|
631
530
|
|
|
632
|
-
// 聚焦汇聚:点击节点 → 相关节点发光 + 汇聚到它周围,无关节点淡出
|
|
633
531
|
focusOn(id){
|
|
634
532
|
const T = this.T;
|
|
635
533
|
const center = this.nodes.find(n=>n.id===id);
|
|
@@ -638,7 +536,6 @@ class KnowledgePlanet {
|
|
|
638
536
|
this._flyTarget = center;
|
|
639
537
|
this.camDistTarget = 420;
|
|
640
538
|
|
|
641
|
-
// 找出所有与中心节点直接相连的节点;同时只显示该节点的连线(默认全隐藏)
|
|
642
539
|
const relatedIds = new Set([id]);
|
|
643
540
|
this.edges.forEach(e=>{
|
|
644
541
|
const on = (e.from.id===id || e.to.id===id);
|
|
@@ -647,22 +544,17 @@ class KnowledgePlanet {
|
|
|
647
544
|
});
|
|
648
545
|
const related = this.nodes.filter(n=>relatedIds.has(n.id) && n.id!==id);
|
|
649
546
|
|
|
650
|
-
// 分级标签:本次钻取只点亮 top-N 最重要(连接数最高)的邻居标签。
|
|
651
|
-
// 点书 → 看全书最核心的几个;点其中一个 → 再看它的核心邻居(逐级下钻)。
|
|
652
547
|
this.nodes.forEach(n=> n._revealLabel=false);
|
|
653
548
|
related.slice().sort((a,b)=> (b.deg||0)-(a.deg||0)).slice(0, 6)
|
|
654
549
|
.forEach(n=> n._revealLabel=true);
|
|
655
550
|
|
|
656
|
-
// 中心节点:移到球心附近、发光、不淡出
|
|
657
551
|
center.focusPos = new T.Vector3(0, 0, 0);
|
|
658
552
|
center.glowOn = true; center.dim = false;
|
|
659
553
|
|
|
660
|
-
// 相关节点:围绕中心排成一圈(球面环),发光
|
|
661
554
|
const R = 95; // 汇聚半径
|
|
662
555
|
const n = related.length;
|
|
663
556
|
related.forEach((nd, i)=>{
|
|
664
557
|
const ang = (i / Math.max(1,n)) * Math.PI*2;
|
|
665
|
-
// 在一个倾斜的环上分布,有立体感
|
|
666
558
|
const tilt = 0.5;
|
|
667
559
|
nd.focusPos = new T.Vector3(
|
|
668
560
|
Math.cos(ang)*R,
|
|
@@ -672,7 +564,6 @@ class KnowledgePlanet {
|
|
|
672
564
|
nd.glowOn = true; nd.dim = false;
|
|
673
565
|
});
|
|
674
566
|
|
|
675
|
-
// 无关节点:淡出、退远、不发光
|
|
676
567
|
this.nodes.forEach(nd=>{
|
|
677
568
|
if(!relatedIds.has(nd.id)){
|
|
678
569
|
nd.focusPos = nd.basePos.clone().multiplyScalar(2.4); // 退远
|
|
@@ -681,7 +572,6 @@ class KnowledgePlanet {
|
|
|
681
572
|
});
|
|
682
573
|
}
|
|
683
574
|
|
|
684
|
-
// 清除聚焦:所有节点平滑回归球面
|
|
685
575
|
clearFocus(){
|
|
686
576
|
this._flyTarget = null;
|
|
687
577
|
this.camDistTarget = 720;
|
|
@@ -695,7 +585,6 @@ class KnowledgePlanet {
|
|
|
695
585
|
this.edges.forEach(e=>{ e.line.visible = false; }); // 回到默认:不画边
|
|
696
586
|
}
|
|
697
587
|
|
|
698
|
-
// 搜索高亮:命中节点发光+显标签,其余淡出(原地,不汇聚);清空传空数组。
|
|
699
588
|
highlight(ids){
|
|
700
589
|
const set = new Set(ids || []);
|
|
701
590
|
if(!set.size){ this.setSelected(null); return; } // 无命中→恢复默认视图
|
|
@@ -727,7 +616,6 @@ class KnowledgePlanet {
|
|
|
727
616
|
this.disposed = true;
|
|
728
617
|
if(this.raf) cancelAnimationFrame(this.raf);
|
|
729
618
|
|
|
730
|
-
// 移除所有事件监听器
|
|
731
619
|
try{
|
|
732
620
|
window.removeEventListener('pointermove', this._onMove);
|
|
733
621
|
window.removeEventListener('pointerup', this._onUp);
|
|
@@ -739,7 +627,6 @@ class KnowledgePlanet {
|
|
|
739
627
|
}
|
|
740
628
|
}catch(e){}
|
|
741
629
|
|
|
742
|
-
// 递归释放场景里所有 geometry / material / texture
|
|
743
630
|
try{
|
|
744
631
|
this.scene.traverse(obj=>{
|
|
745
632
|
if(obj.geometry) obj.geometry.dispose();
|
|
@@ -753,25 +640,21 @@ class KnowledgePlanet {
|
|
|
753
640
|
});
|
|
754
641
|
}catch(e){}
|
|
755
642
|
|
|
756
|
-
// 强制释放 WebGL context(renderer.dispose 不会释放 context,必须 forceContextLoss)
|
|
757
643
|
try{
|
|
758
644
|
this.renderer.dispose();
|
|
759
645
|
this.renderer.forceContextLoss();
|
|
760
646
|
if(this.canvas && this.canvas.parentNode) this.canvas.parentNode.removeChild(this.canvas);
|
|
761
647
|
}catch(e){}
|
|
762
648
|
|
|
763
|
-
// 断开引用,助 GC
|
|
764
649
|
this.nodes = []; this.edges = []; this._meshes = null;
|
|
765
650
|
this.scene = null; this.renderer = null; this.camera = null; this.world = null;
|
|
766
651
|
}
|
|
767
652
|
}
|
|
768
653
|
|
|
769
|
-
// ── 等待 Three.js(由 head 的 module 预加载到 window.THREE)──
|
|
770
654
|
async function ensureThree(){
|
|
771
655
|
if(THREE) return THREE;
|
|
772
656
|
if(window.THREE){ THREE = window.THREE; return THREE; }
|
|
773
657
|
if(window.__threeLoadError) return null;
|
|
774
|
-
// 等待 three-ready 事件(最多 5 秒)
|
|
775
658
|
return await new Promise(resolve=>{
|
|
776
659
|
let done = false;
|
|
777
660
|
const onReady = ()=>{ if(done) return; done=true; THREE = window.THREE; resolve(THREE); };
|
|
@@ -788,9 +671,7 @@ async function renderGraph(){
|
|
|
788
671
|
const container = document.getElementById('graph-canvas');
|
|
789
672
|
if(!container) return;
|
|
790
673
|
|
|
791
|
-
// 清旧实例
|
|
792
674
|
if(planet){ try{ planet.dispose(); }catch(e){} planet = null; }
|
|
793
|
-
// 移除旧 canvas(保留 hint/legend 等覆盖层)
|
|
794
675
|
Array.from(container.querySelectorAll('canvas')).forEach(c=>c.remove());
|
|
795
676
|
|
|
796
677
|
const T = await ensureThree();
|
|
@@ -814,10 +695,6 @@ async function renderGraph(){
|
|
|
814
695
|
}
|
|
815
696
|
}
|
|
816
697
|
|
|
817
|
-
// startRotation/stopRotation:上传/解析/摄入期间停掉星球渲染循环 ——
|
|
818
|
-
// 每帧 _update() + renderer.render() 会争 GPU 和主线程,拖慢大文件上传。
|
|
819
|
-
// 真正省 CPU 的是停 RAF(只关 autoSpin 不够,缓动还在跑)。
|
|
820
|
-
// planet.raf 在 start() 里被赋值,dispose() 里被 cancel —— 这里只 cancel。
|
|
821
698
|
function startRotation(){
|
|
822
699
|
if(!planet) return;
|
|
823
700
|
if(planet.raf) return; // 已在跑(避免 start() 双重 RAF 嵌套)
|
|
@@ -831,7 +708,6 @@ function stopRotation(){
|
|
|
831
708
|
}
|
|
832
709
|
}
|
|
833
710
|
|
|
834
|
-
|
|
835
711
|
function relLabel(t){
|
|
836
712
|
const m={CREATED_BY:'提出',SUPPORTS:'支持',CONTRADICTS:'冲突',MENTIONS:'提及',
|
|
837
713
|
BELONGS_TO:'属于',DERIVED_FROM:'衍生',IMPLEMENTS:'实现',USES:'运用',SUPERSEDES:'取代'};
|
|
@@ -846,9 +722,6 @@ function renderLegend(){
|
|
|
846
722
|
<span class="lg-dot" style="background:none;border:2px dashed var(--alert);width:8px;height:8px"></span>观点冲突</div>`;
|
|
847
723
|
}
|
|
848
724
|
|
|
849
|
-
// ════════════════════════════════════════════════════════
|
|
850
|
-
// Wiki 详情面板
|
|
851
|
-
// ════════════════════════════════════════════════════════
|
|
852
725
|
function selectKU(id){
|
|
853
726
|
if(!state.kus[id]) return;
|
|
854
727
|
state.selected=id;
|
|
@@ -858,7 +731,6 @@ function selectKU(id){
|
|
|
858
731
|
loadSynthesis(id); // 若该节点已有缓存综述,自动显示(GET,不调 LLM)
|
|
859
732
|
}
|
|
860
733
|
|
|
861
|
-
// 读缓存综述(只 GET,不触发 LLM/不花钱)。有缓存就填进 state.synth 并重渲染。
|
|
862
734
|
async function loadSynthesis(id){
|
|
863
735
|
if(client.demo) return;
|
|
864
736
|
const cur=state.synth[id];
|
|
@@ -872,7 +744,6 @@ async function loadSynthesis(id){
|
|
|
872
744
|
}catch(e){ /* 无缓存/出错都静默,维持"生成"按钮 */ }
|
|
873
745
|
}
|
|
874
746
|
|
|
875
|
-
// 点「生成综述」→ 调 LLM 生成(或返回缓存)。派生只读视图,后端不写知识库。
|
|
876
747
|
async function genSynthesis(id, force){
|
|
877
748
|
if(client.demo){ toast('演示模式无法生成综述,请先连接后端','err'); return; }
|
|
878
749
|
state.synth[id]='loading';
|
|
@@ -888,7 +759,6 @@ async function genSynthesis(id, force){
|
|
|
888
759
|
}
|
|
889
760
|
window.genSynthesis=genSynthesis;
|
|
890
761
|
|
|
891
|
-
// 路径查询(高级检索):把当前节点设为起/终点,查两点最短路径并在星球高亮。
|
|
892
762
|
function setPathNode(id, which){
|
|
893
763
|
state.pathSel=state.pathSel||{};
|
|
894
764
|
state.pathSel[which]=id; state.pathSel.result='';
|
|
@@ -910,8 +780,6 @@ async function findPath(){
|
|
|
910
780
|
}
|
|
911
781
|
window.findPath=findPath;
|
|
912
782
|
|
|
913
|
-
// 按需补全:星球全量接口为瘦身省略了 source_text,点开节点时单独拉 /ku/{id} 拿完整原文,
|
|
914
|
-
// 合并回 state.kus 并重渲染(仍选中才刷新,避免快速点击的竞态覆盖)。
|
|
915
783
|
async function hydrateKU(id){
|
|
916
784
|
const ku=state.kus[id];
|
|
917
785
|
if(!ku || client.demo) return; // 演示数据自带原文,无需拉
|
|
@@ -928,19 +796,15 @@ function clearSelection(){
|
|
|
928
796
|
if(planet){ try{ planet.setSelected(null); }catch(e){} }
|
|
929
797
|
$('#wiki-body').innerHTML=emptyWiki();
|
|
930
798
|
}
|
|
931
|
-
// 暴露给 3D 引擎(planet 点击节点时回调)
|
|
932
799
|
window.selectKU = selectKU;
|
|
933
800
|
window.clearSelection = clearSelection;
|
|
934
801
|
|
|
935
|
-
// 可折叠区块开关(供 side-label 的 onclick 调用)
|
|
936
802
|
function toggleCollapse(id){ const el=document.getElementById(id); if(el) el.classList.toggle('collapsed'); }
|
|
937
803
|
window.toggleCollapse = toggleCollapse;
|
|
938
804
|
function emptyWiki(){
|
|
939
805
|
return `<div class="wiki-empty"><svg width="44" height="44" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.5"><path d="M4 19.5A2.5 2.5 0 0 1 6.5 17H20M6.5 2H20v20H6.5A2.5 2.5 0 0 1 4 19.5v-15A2.5 2.5 0 0 1 6.5 2z"/></svg><p>点击图谱中的节点<br>查看知识单元详情</p></div>`;
|
|
940
806
|
}
|
|
941
807
|
|
|
942
|
-
// 综合页(synthesis page):把节点在图里的邻域组织成可读的 wiki 词条,
|
|
943
|
-
// 而不只是几个原子字段。图仍是唯一真相源,这里只是它的一个只读投影(同 3D 星球)。
|
|
944
808
|
function renderWiki(ku){
|
|
945
809
|
if(!ku){ $('#wiki-body').innerHTML=emptyWiki(); return; }
|
|
946
810
|
const c=KU_COLORS[ku.ku_type];
|
|
@@ -951,7 +815,6 @@ function renderWiki(ku){
|
|
|
951
815
|
html+=`<div class="wiki-title">${esc(ku.name)}</div>`;
|
|
952
816
|
if(ku.domain) html+=`<div class="wiki-domain">${esc(ku.domain)}</div>`;
|
|
953
817
|
|
|
954
|
-
// ── 概述 ──
|
|
955
818
|
if(ku.ku_type==='Claim'){
|
|
956
819
|
if(ex.statement) html+=`<div class="wiki-summary">${esc(ex.statement)}</div>`;
|
|
957
820
|
const cs=ex.claim_status||'pending';
|
|
@@ -966,7 +829,6 @@ function renderWiki(ku){
|
|
|
966
829
|
if(ku.summary) html+=`<div class="wiki-summary">${esc(ku.summary)}</div>`;
|
|
967
830
|
}
|
|
968
831
|
|
|
969
|
-
// ── AI 综述(第2档:LLM 把节点邻域组织成连贯词条;派生只读视图,不写回知识库)──
|
|
970
832
|
if(!client.demo){
|
|
971
833
|
const sy=state.synth[ku.ku_id];
|
|
972
834
|
html+=`<div class="wiki-section"><h4>AI 综述</h4>`;
|
|
@@ -984,7 +846,6 @@ function renderWiki(ku){
|
|
|
984
846
|
html+=`</div>`;
|
|
985
847
|
}
|
|
986
848
|
|
|
987
|
-
// ── 路径查询(高级检索:两节点间最短路径,结果在星球高亮)──
|
|
988
849
|
if(!client.demo){
|
|
989
850
|
const ps=state.pathSel||{};
|
|
990
851
|
const nm=id=>id?esc(state.kus[id]&&state.kus[id].name||id):'(未设)';
|
|
@@ -999,16 +860,13 @@ function renderWiki(ku){
|
|
|
999
860
|
html+=`</div>`;
|
|
1000
861
|
}
|
|
1001
862
|
|
|
1002
|
-
// ── 原文段落 ──
|
|
1003
863
|
if(ex.source_text && ex.source_text.trim()){
|
|
1004
864
|
html+=`<div class="wiki-section"><h4>原文段落</h4><div class="wiki-source">${esc(ex.source_text)}</div></div>`;
|
|
1005
865
|
}
|
|
1006
|
-
// Entity 别名
|
|
1007
866
|
if(ku.ku_type==='Entity'&&ex.aliases&&ex.aliases.length){
|
|
1008
867
|
html+=`<div class="wiki-section"><h4>别名</h4><div class="tag-row">${ex.aliases.map(a=>`<span class="tag">${esc(a)}</span>`).join('')}</div></div>`;
|
|
1009
868
|
}
|
|
1010
869
|
|
|
1011
|
-
// ── 邻域按类型分组(去重),组织成词条各段 ──
|
|
1012
870
|
const nb={Fact:[],Claim:[],Concept:[],Entity:[],Evidence:[]};
|
|
1013
871
|
const seen=new Set([ku.ku_id]);
|
|
1014
872
|
getRelations(ku.ku_id).forEach(r=>{
|
|
@@ -1017,8 +875,6 @@ function renderWiki(ku){
|
|
|
1017
875
|
seen.add(o.ku_id);
|
|
1018
876
|
if(nb[o.ku_type]) nb[o.ku_type].push(o);
|
|
1019
877
|
});
|
|
1020
|
-
// 图里事实/观点通常只连到 Evidence(书),不直接连实体/概念。为让词条真正丰满,
|
|
1021
|
-
// 再按"陈述里提到本节点名字/别名"补充相关事实与观点(纯前端聚合,不改图)。
|
|
1022
878
|
if(ku.ku_type!=='Evidence'){
|
|
1023
879
|
const names=[ku.name, ...((ex.aliases)||[])].filter(n=>n && n.length>=2);
|
|
1024
880
|
if(names.length){
|
|
@@ -1035,7 +891,6 @@ function renderWiki(ku){
|
|
|
1035
891
|
if(ku.ku_type==='Evidence'){
|
|
1036
892
|
const isColl=(ku.tags||[]).includes('_collection');
|
|
1037
893
|
if(isColl){
|
|
1038
|
-
// 一级:合集(文件夹)→ 列出所含文件/章节(PART_OF 子节点),点击逐级下钻
|
|
1039
894
|
const files=(state.relations||[]).filter(r=>r.type==='PART_OF'&&r.to===ku.ku_id).map(r=>state.kus[r.from]).filter(Boolean);
|
|
1040
895
|
html+=`<div class="wiki-section"><h4>合集 · 来源</h4><div class="evid-item"><div class="evid-src">📁 ${esc(ku.name)}</div></div></div>`;
|
|
1041
896
|
if(files.length){
|
|
@@ -1044,7 +899,6 @@ function renderWiki(ku){
|
|
|
1044
899
|
html+=`</div></div>`;
|
|
1045
900
|
}
|
|
1046
901
|
} else {
|
|
1047
|
-
// 二级:文件(章节)或独立来源 → 来源 + 所属合集 + 本文件关键词
|
|
1048
902
|
html+=`<div class="wiki-section"><h4>来源</h4><div class="evid-item">
|
|
1049
903
|
<div class="evid-src">${esc(ex.source||ku.name)}</div>
|
|
1050
904
|
<div class="evid-meta">${[ex.author,ex.year,ex.page?'p.'+ex.page:''].filter(Boolean).join(' · ')}</div></div></div>`;
|
|
@@ -1064,7 +918,6 @@ function renderWiki(ku){
|
|
|
1064
918
|
}
|
|
1065
919
|
}
|
|
1066
920
|
} else {
|
|
1067
|
-
// 实体/概念/事实/观点:综合成词条。相关事实/观点放进固定高度框,内部滚动,省纵向空间。
|
|
1068
921
|
if(nb.Fact.length){
|
|
1069
922
|
let inner='';
|
|
1070
923
|
nb.Fact.slice(0,50).forEach(k=>{ inner+=`<div class="wiki-item fact" onclick="selectKU('${k.ku_id}')">${esc(stmt(k))}</div>`; });
|
|
@@ -1078,7 +931,6 @@ function renderWiki(ku){
|
|
|
1078
931
|
});
|
|
1079
932
|
html+=`<div class="wiki-section"><h4>相关观点 ${nb.Claim.length}</h4><div class="wiki-scroll">${inner}</div></div>`;
|
|
1080
933
|
}
|
|
1081
|
-
// 关联概念/实体:小星球式散点(省空间,悬停看名、点击进入)
|
|
1082
934
|
const linked=[...nb.Concept,...nb.Entity];
|
|
1083
935
|
if(linked.length){
|
|
1084
936
|
html+=`<div class="wiki-section"><h4>关联 ${linked.length}</h4>${kwCluster(linked)}</div>`;
|
|
@@ -1090,7 +942,6 @@ function renderWiki(ku){
|
|
|
1090
942
|
}
|
|
1091
943
|
}
|
|
1092
944
|
|
|
1093
|
-
// 标签
|
|
1094
945
|
if(ku.tags&&ku.tags.length){
|
|
1095
946
|
html+=`<div class="wiki-section"><h4>标签</h4><div class="tag-row">${ku.tags.map(t=>`<span class="tag">${esc(t)}</span>`).join('')}</div></div>`;
|
|
1096
947
|
}
|
|
@@ -1099,8 +950,6 @@ function renderWiki(ku){
|
|
|
1099
950
|
$('#wiki-body').innerHTML=html;
|
|
1100
951
|
}
|
|
1101
952
|
|
|
1102
|
-
// 关联关键词的小星球式散点:fibonacci 圆盘分布、按类型着色、悬停显名(<title>)、点击进入。
|
|
1103
|
-
// 比一长串 chip 省纵向空间,且呼应星球主题。
|
|
1104
953
|
function kwCluster(list, capN){
|
|
1105
954
|
const N=Math.min(list.length, capN||80);
|
|
1106
955
|
const cx=130, cy=90, R=82, ga=Math.PI*(3-Math.sqrt(5));
|
|
@@ -1126,9 +975,6 @@ function getRelations(id){
|
|
|
1126
975
|
return out;
|
|
1127
976
|
}
|
|
1128
977
|
|
|
1129
|
-
// ════════════════════════════════════════════════════════
|
|
1130
|
-
// 能力卡片
|
|
1131
|
-
// ════════════════════════════════════════════════════════
|
|
1132
978
|
function renderAbilities(){
|
|
1133
979
|
const body=$('#ability-body');
|
|
1134
980
|
body.innerHTML=state.abilities.map(a=>{
|
|
@@ -1182,9 +1028,6 @@ async function compileAbility(key,btn){
|
|
|
1182
1028
|
}
|
|
1183
1029
|
}
|
|
1184
1030
|
|
|
1185
|
-
// ════════════════════════════════════════════════════════
|
|
1186
|
-
// 冲突裁决
|
|
1187
|
-
// ════════════════════════════════════════════════════════
|
|
1188
1031
|
function renderConflicts(){
|
|
1189
1032
|
const open=state.conflicts.filter(c=>c.status==='open');
|
|
1190
1033
|
const body=$('#conflict-body');
|
|
@@ -1227,17 +1070,14 @@ async function resolveConflict(cid){
|
|
|
1227
1070
|
await new Promise(r=>setTimeout(r,500));
|
|
1228
1071
|
const c=state.conflicts.find(x=>x.conflict_id===cid);
|
|
1229
1072
|
if(c) c.status='resolved';
|
|
1230
|
-
// 落败方标 superseded
|
|
1231
1073
|
const loser=c.claim_a_id===winner?c.claim_b_id:c.claim_a_id;
|
|
1232
1074
|
if(state.kus[loser]) state.kus[loser].extra.claim_status='superseded';
|
|
1233
1075
|
if(state.kus[winner]) state.kus[winner].extra.claim_status='active';
|
|
1234
|
-
// 移除这对 Claim 间的 CONTRADICTS 关系
|
|
1235
1076
|
state.relations=state.relations.filter(r=>
|
|
1236
1077
|
!(r.type==='CONTRADICTS' &&
|
|
1237
1078
|
((r.from===loser&&r.to===winner)||(r.from===winner&&r.to===loser))));
|
|
1238
1079
|
delete state.conflictPick[cid];
|
|
1239
1080
|
toast('冲突已裁决,落败观点标记为 superseded','ok');
|
|
1240
|
-
// 裁决是低频操作,destroy 重建最稳(增量更新与 G6 hover-activate 有内部时序冲突)
|
|
1241
1081
|
renderGraph();
|
|
1242
1082
|
renderPanels();
|
|
1243
1083
|
return;
|
|
@@ -1250,14 +1090,10 @@ async function resolveConflict(cid){
|
|
|
1250
1090
|
}catch(e){ toast('裁决失败','err'); }
|
|
1251
1091
|
}
|
|
1252
1092
|
|
|
1253
|
-
// ════════════════════════════════════════════════════════
|
|
1254
|
-
// 搜索
|
|
1255
|
-
// ════════════════════════════════════════════════════════
|
|
1256
1093
|
let searchTimer;
|
|
1257
1094
|
async function doSearch(q){
|
|
1258
1095
|
if(!q.trim()){ if(window.planet) window.planet.setSelected(null); return; } // 清空搜索→恢复默认视图(不重建星球)
|
|
1259
1096
|
if(client.demo){
|
|
1260
|
-
// 演示:名字/摘要匹配,高亮命中节点
|
|
1261
1097
|
const hits=Object.values(state.kus).filter(k=>
|
|
1262
1098
|
(k.name+k.summary+((k.extra&&k.extra.statement)||'')).toLowerCase().includes(q.toLowerCase()));
|
|
1263
1099
|
highlightNodes(hits.map(h=>h.ku_id));
|
|
@@ -1272,13 +1108,9 @@ async function doSearch(q){
|
|
|
1272
1108
|
}catch(e){ toast('搜索失败','err'); }
|
|
1273
1109
|
}
|
|
1274
1110
|
function highlightNodes(ids){
|
|
1275
|
-
// 在 Three.js 星球上高亮命中节点(旧 G6 实现已废弃)
|
|
1276
1111
|
if(window.planet) window.planet.highlight(ids || []);
|
|
1277
1112
|
}
|
|
1278
1113
|
|
|
1279
|
-
// ════════════════════════════════════════════════════════
|
|
1280
|
-
// 摄入(文本 + 文件/文件夹批量)
|
|
1281
|
-
// ════════════════════════════════════════════════════════
|
|
1282
1114
|
let pickedFiles = []; // 待摄入文件列表
|
|
1283
1115
|
|
|
1284
1116
|
function fmtSize(b){
|
|
@@ -1312,7 +1144,6 @@ function renderFileList(){
|
|
|
1312
1144
|
});
|
|
1313
1145
|
}
|
|
1314
1146
|
|
|
1315
|
-
// 异步摄入(方案A):文件上传后立即转后台,前端轮询 job 真进度,期间可继续操作。
|
|
1316
1147
|
let ingJobs = []; // {jobId,name,status,processed,total,ku,error}
|
|
1317
1148
|
let _ingPoll = null;
|
|
1318
1149
|
|
|
@@ -1346,7 +1177,6 @@ async function pollIngestJobs(){
|
|
|
1346
1177
|
if((s.ku_count||0) > 0){
|
|
1347
1178
|
toast(`『${j.name}』摄入完成:${s.ku_count} 个知识单元`,'ok');
|
|
1348
1179
|
}else{
|
|
1349
|
-
// 上传/解析成功但抽取 0 知识:最常见是 LLM API Key 无效/未配置(如 401),或内容太短
|
|
1350
1180
|
toast(`『${j.name}』已上传但未抽取到知识(0 个)。多半是 LLM API Key 无效/未配置——请到「连接设置」重填正确 key 后重传`,'err');
|
|
1351
1181
|
}
|
|
1352
1182
|
}
|
|
@@ -1359,10 +1189,6 @@ async function pollIngestJobs(){
|
|
|
1359
1189
|
if(!ingJobs.length){ clearInterval(_ingPoll); _ingPoll=null; renderIngestStatus(); }
|
|
1360
1190
|
}
|
|
1361
1191
|
|
|
1362
|
-
// 「清空知识库」已从前端移除(危险的破坏性操作不该是任何人可点的按钮)。
|
|
1363
|
-
// 需要清库改走后端 API:POST /knowledge/reset(设了 HKC_API_KEY 时需带 X-API-Key),
|
|
1364
|
-
// 例:curl -X POST http://127.0.0.1:8000/knowledge/reset -H "X-API-Key: <key>"
|
|
1365
|
-
|
|
1366
1192
|
async function submitIngest(){
|
|
1367
1193
|
const text=$('#ingest-text').value.trim();
|
|
1368
1194
|
const url=$('#ingest-url').value.trim();
|
|
@@ -1378,13 +1204,11 @@ async function submitIngest(){
|
|
|
1378
1204
|
if(client.demo){
|
|
1379
1205
|
btn.disabled=false; btn.textContent='开始摄入';
|
|
1380
1206
|
toast('未连接后端,无法摄入。请点左下「连接设置」连接 hkc-api 后再试','err');
|
|
1381
|
-
// 自动打开连接设置,引导用户
|
|
1382
1207
|
closeModal('#ingest-modal');
|
|
1383
1208
|
openModal('#settings-modal');
|
|
1384
1209
|
return;
|
|
1385
1210
|
}
|
|
1386
1211
|
|
|
1387
|
-
// 1) 文本(若有)——通常较短,走同步端点,拿到结果即刷新
|
|
1388
1212
|
if(text){
|
|
1389
1213
|
btn.disabled=true; btn.innerHTML='<span class="spinner"></span> 摄入文本';
|
|
1390
1214
|
try{
|
|
@@ -1397,7 +1221,6 @@ async function submitIngest(){
|
|
|
1397
1221
|
btn.disabled=false; btn.textContent='开始摄入';
|
|
1398
1222
|
}
|
|
1399
1223
|
|
|
1400
|
-
// 1.5) 网页 URL——同步抓取正文 + 抽取(单页通常较短)
|
|
1401
1224
|
if(url){
|
|
1402
1225
|
btn.disabled=true; btn.innerHTML='<span class="spinner"></span> 抓取网页';
|
|
1403
1226
|
try{
|
|
@@ -1413,7 +1236,6 @@ async function submitIngest(){
|
|
|
1413
1236
|
btn.disabled=false; btn.textContent='开始摄入';
|
|
1414
1237
|
}
|
|
1415
1238
|
|
|
1416
|
-
// 2) 文件——异步提交,秒返回 job_id,转后台;不阻塞,可继续添加/上传下一个
|
|
1417
1239
|
if(hasFiles){
|
|
1418
1240
|
btn.disabled=true; btn.innerHTML='<span class="spinner"></span> 提交中';
|
|
1419
1241
|
let submitted=0;
|
|
@@ -1442,21 +1264,15 @@ async function submitIngest(){
|
|
|
1442
1264
|
}
|
|
1443
1265
|
}
|
|
1444
1266
|
|
|
1445
|
-
// ════════════════════════════════════════════════════════
|
|
1446
|
-
// 模态 + 连接
|
|
1447
|
-
// ════════════════════════════════════════════════════════
|
|
1448
|
-
// 打开摄入模态 → 停星球渲染(避免大文件上传被 GPU/主线程争用拖慢)
|
|
1449
1267
|
function openModal(s){
|
|
1450
1268
|
$(s).classList.add('show');
|
|
1451
1269
|
if(s === '#ingest-modal') stopRotation();
|
|
1452
1270
|
}
|
|
1453
|
-
// 关闭摄入模态 → 恢复星球渲染(idempotent:stopRotation 未调过也无害)
|
|
1454
1271
|
function closeModal(s){
|
|
1455
1272
|
$(s).classList.remove('show');
|
|
1456
1273
|
if(s === '#ingest-modal') startRotation();
|
|
1457
1274
|
}
|
|
1458
1275
|
|
|
1459
|
-
// ── 书籍管理(按来源删除)──
|
|
1460
1276
|
async function openBooks(){
|
|
1461
1277
|
if(client.demo){ toast('未连接后端,无法管理书籍','err'); openModal('#settings-modal'); return; }
|
|
1462
1278
|
openModal('#books-modal');
|
|
@@ -1502,7 +1318,6 @@ function setConn(){
|
|
|
1502
1318
|
else{ p.className='conn-pill live'; t.textContent='已连接'; }
|
|
1503
1319
|
}
|
|
1504
1320
|
|
|
1505
|
-
// 连接设置持久化(localStorage):刷新后回填 + 自动重连,避免每次重填 key。
|
|
1506
1321
|
const CONN_KEY='hkc_conn_v1';
|
|
1507
1322
|
function _connSnapshot(){
|
|
1508
1323
|
return {
|
|
@@ -1534,7 +1349,6 @@ async function tryConnect(){
|
|
|
1534
1349
|
const btn=$('#btn-connect'); btn.disabled=true; btn.innerHTML='<span class="spinner"></span> 连接中';
|
|
1535
1350
|
const ok=await client.connect(url);
|
|
1536
1351
|
if(ok){
|
|
1537
|
-
// 连接成功后,把 LLM 配置发给后端(若填了 key)
|
|
1538
1352
|
const provider=$('#llm-provider').value;
|
|
1539
1353
|
const key=$('#llm-key').value.trim();
|
|
1540
1354
|
const model=$('#llm-model').value.trim();
|
|
@@ -1562,7 +1376,6 @@ async function tryConnect(){
|
|
|
1562
1376
|
}
|
|
1563
1377
|
}
|
|
1564
1378
|
|
|
1565
|
-
// provider 切换:联动 model 占位符 + base_url 显隐
|
|
1566
1379
|
function _syncLLMFields(){
|
|
1567
1380
|
const p=$('#llm-provider').value;
|
|
1568
1381
|
const modelInput=$('#llm-model');
|
|
@@ -1570,9 +1383,7 @@ function _syncLLMFields(){
|
|
|
1570
1383
|
const baseInput=$('#llm-baseurl');
|
|
1571
1384
|
const defaults={ deepseek:'deepseek-chat', anthropic:'claude-sonnet-4-6', openai:'gpt-4o-mini', 'openai-compatible':'(自定义模型名)' };
|
|
1572
1385
|
modelInput.placeholder=defaults[p]||'';
|
|
1573
|
-
// 只有自定义/本地需要填 base_url
|
|
1574
1386
|
baseField.style.display=(p==='openai-compatible')?'':'none';
|
|
1575
|
-
// 本地模型默认填 Ollama 地址(后端要求 openai-compatible 必填 base_url,留空会 400);已有值不覆盖
|
|
1576
1387
|
if(p==='openai-compatible' && !baseInput.value.trim()){
|
|
1577
1388
|
baseInput.value='http://localhost:11434/v1';
|
|
1578
1389
|
}
|
|
@@ -1580,9 +1391,6 @@ function _syncLLMFields(){
|
|
|
1580
1391
|
|
|
1581
1392
|
function esc(s){ return String(s==null?'':s).replace(/[&<>"]/g,m=>({'&':'&','<':'<','>':'>','"':'"'}[m])); }
|
|
1582
1393
|
|
|
1583
|
-
// ════════════════════════════════════════════════════════
|
|
1584
|
-
// 事件绑定 + 启动
|
|
1585
|
-
// ════════════════════════════════════════════════════════
|
|
1586
1394
|
$('#modes').addEventListener('click',e=>{
|
|
1587
1395
|
const b=e.target.closest('.mode-btn'); if(!b)return;
|
|
1588
1396
|
$$('.mode-btn').forEach(x=>x.classList.remove('on'));
|
|
@@ -1597,7 +1405,6 @@ $('#search-input').addEventListener('keydown',e=>{ if(e.key==='Enter') doSearch(
|
|
|
1597
1405
|
$('#btn-ingest').onclick=()=>openModal('#ingest-modal');
|
|
1598
1406
|
$('#btn-books').onclick=openBooks;
|
|
1599
1407
|
$('#btn-settings').onclick=()=>openModal('#settings-modal');
|
|
1600
|
-
// 文件/文件夹选择
|
|
1601
1408
|
$('#btn-pick-files').onclick=()=>$('#ingest-files').click();
|
|
1602
1409
|
$('#btn-pick-folder').onclick=()=>$('#ingest-folder').click();
|
|
1603
1410
|
$('#ingest-files').addEventListener('change',e=>{ addFiles(e.target.files); e.target.value=''; });
|
|
@@ -1609,11 +1416,8 @@ _syncLLMFields(); // 初始化字段状态
|
|
|
1609
1416
|
$('#btn-use-demo').onclick=()=>{ client.useDemo(); setConn(); closeModal('#settings-modal'); loadData(); toast('已切换到演示数据'); };
|
|
1610
1417
|
$('#btn-fit').onclick=()=>window._fitGraph&&window._fitGraph();
|
|
1611
1418
|
$$('[data-close]').forEach(b=>b.onclick=()=>{ closeModal('#ingest-modal'); closeModal('#settings-modal'); closeModal('#books-modal'); });
|
|
1612
|
-
// 点背景关模态:走 closeModal(里面会触发 ingest 的 startRotation,见 openModal 注释)
|
|
1613
1419
|
$$('.modal-bg').forEach(m=>m.addEventListener('click',e=>{ if(e.target===m) closeModal('#'+m.id); }));
|
|
1614
|
-
// G6 的 autoResize:true 已处理窗口缩放,无需手动重建
|
|
1615
1420
|
|
|
1616
|
-
// 启动:有保存的连接设置则回填并静默自动重连,否则按演示模式启动
|
|
1617
1421
|
async function startup(){
|
|
1618
1422
|
const saved=loadConnSettings();
|
|
1619
1423
|
if(saved && saved.url){
|
|
@@ -1621,15 +1425,10 @@ async function startup(){
|
|
|
1621
1425
|
client.apiKey=saved.apiKey||''; // 鉴权 key 只能取保存的 apiKey,绝不混用模型的 key
|
|
1622
1426
|
const ok=await client.connect(saved.url);
|
|
1623
1427
|
if(ok){
|
|
1624
|
-
// 不再启动时自动 POST 保存的 LLM 配置:那会把 localStorage 里可能过期的配置
|
|
1625
|
-
// 覆盖到后端、压掉 .env 的权威配置。模型配置只在用户点「连接」时显式应用。
|
|
1626
1428
|
setConn(); await loadData(); return;
|
|
1627
1429
|
}
|
|
1628
|
-
// 自动重连失败(后端没起/地址变了)→ 回退演示,字段已回填,用户可手动重连
|
|
1629
1430
|
toast('已保存的后端未连上,回退演示模式(设置已回填,可在「连接设置」重连)','err');
|
|
1630
1431
|
}
|
|
1631
|
-
// 嵌入 Foxclaw(同源部署):无保存设置或保存地址连不上时,优先自动连**当前站点**(相对 /knowledge/*),
|
|
1632
|
-
// 连通即用真实知识;连不上(如独立打开本文件)再回退演示。
|
|
1633
1432
|
if(await client.connect('')){ setConn(); await loadData(); return; }
|
|
1634
1433
|
setConn();
|
|
1635
1434
|
loadData();
|