foxclaw 0.1.2 → 0.1.4
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/.github/workflows/release.yml +5 -0
- package/python/CHANGELOG.md +100 -0
- package/python/CHANGELOG_CN.md +100 -0
- package/python/README.md +17 -2
- package/python/README_CN.md +38 -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/CONTENT_GUARD.md +189 -0
- package/python/docs/CONTENT_GUARD_CN.md +189 -0
- package/python/docs/RELEASE.md +78 -37
- 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 +74 -152
- package/python/foxclaw/agents/base.py +82 -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 +23 -3
- package/python/foxclaw/agents/mixins/preference.py +58 -8
- package/python/foxclaw/agents/mixins/prompt.py +59 -12
- package/python/foxclaw/agents/mixins/skillbox.py +6 -6
- package/python/foxclaw/agents/mixins/tool_exec.py +16 -42
- package/python/foxclaw/agents/rules/finance/RULES.md +131 -0
- package/python/foxclaw/api/__init__.py +18 -77
- package/python/foxclaw/api/_state.py +43 -0
- package/python/foxclaw/api/bootstrap.py +114 -0
- 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 +50 -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/behavior/__init__.py +20 -0
- package/python/foxclaw/behavior/behavior.yaml +62 -0
- package/python/foxclaw/behavior/spec.py +287 -0
- 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/content_guard.py +341 -0
- 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 +53 -60
- package/python/foxclaw/core/metrics.py +4 -5
- package/python/foxclaw/core/model_router.py +74 -150
- 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/sensitive_write.py +283 -0
- 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 +18 -201
- package/python/frontend/v3/assets/index-JKfHlb-a.css +1 -0
- package/python/frontend/v3/assets/index-tN90dpUH.js +34 -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 +42 -3
- 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 +16 -44
- 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 +17 -3
- package/python/requirements.txt +311 -0
- package/python/tests/conftest.py +0 -22
- package/python/tests/run_all.py +4 -66
- package/python/tests/test_agent_lifecycle.py +4 -38
- package/python/tests/test_agent_rules.py +81 -0
- 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_behavior_spec.py +198 -0
- 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_conflict_dismiss.py +143 -0
- package/python/tests/test_content_guard.py +177 -0
- 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_sensitive_write.py +181 -0
- 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/build_content_lexicon.py +218 -0
- package/python/tools/bundle_web.py +98 -0
- 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/recheck_conflicts.py +156 -0
- package/python/tools/release_check.py +24 -15
- 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/vendor/hkc/hkc_api/routes/ability.py +31 -0
- package/python/vendor/hkc/hkc_api/schemas.py +11 -0
- package/python/vendor/hkc/hkc_kee/kee.py +124 -3
- package/python/vendor/hkc/hkc_sdk/base.py +22 -0
- package/python/vendor/hkc/hkc_sdk/direct_client.py +10 -0
- package/python/vendor/hkc/hkc_sdk/http_client.py +19 -0
- package/python/foxclaw/api/app.py +0 -123
- package/python/frontend/v3/assets/index-BD7VPbMV.js +0 -33
- package/python/frontend/v3/assets/index-CAvnYWGH.css +0 -1
|
@@ -2,6 +2,7 @@
|
|
|
2
2
|
import { nextTick, ref, watch } from 'vue'
|
|
3
3
|
import { chat, retry } from '../stores/chat.js'
|
|
4
4
|
import { mdToHTML } from '../lib/md.js'
|
|
5
|
+
import { t } from '../lib/i18n.js'
|
|
5
6
|
|
|
6
7
|
const box = ref(null)
|
|
7
8
|
let stick = true
|
|
@@ -12,7 +13,6 @@ function onScroll() {
|
|
|
12
13
|
stick = el.scrollHeight - el.scrollTop - el.clientHeight < 80
|
|
13
14
|
}
|
|
14
15
|
|
|
15
|
-
/* 依赖「长度和」而不是整个数组:流式时只有最后一个对象在变,深度 watch 会打爆 */
|
|
16
16
|
watch(
|
|
17
17
|
() =>
|
|
18
18
|
chat.messages
|
|
@@ -40,12 +40,16 @@ function stepText(s) {
|
|
|
40
40
|
return s.desc || s.task || s.label || JSON.stringify(s)
|
|
41
41
|
}
|
|
42
42
|
|
|
43
|
-
const
|
|
43
|
+
const SRC_KEY = {
|
|
44
|
+
memory: 'msg.srcMemory',
|
|
45
|
+
knowledge: 'msg.srcKnowledge',
|
|
46
|
+
web: 'msg.srcWeb',
|
|
47
|
+
agent: 'msg.srcAgent',
|
|
48
|
+
}
|
|
44
49
|
function srcLabel(s) {
|
|
45
|
-
return
|
|
50
|
+
return (s && SRC_KEY[s] ? t(SRC_KEY[s]) : s) || t('msg.srcEvidence')
|
|
46
51
|
}
|
|
47
52
|
|
|
48
|
-
/* ── 复制 / 重试 ── */
|
|
49
53
|
const copiedKey = ref(-1)
|
|
50
54
|
let copyTimer = 0
|
|
51
55
|
|
|
@@ -54,7 +58,6 @@ async function copy(m, i) {
|
|
|
54
58
|
try {
|
|
55
59
|
await navigator.clipboard.writeText(text)
|
|
56
60
|
} catch {
|
|
57
|
-
/* 局域网纯 http 不是安全上下文,clipboard 会拒绝 → 退回 textarea 选中法 */
|
|
58
61
|
const ta = document.createElement('textarea')
|
|
59
62
|
ta.value = text
|
|
60
63
|
ta.style.position = 'fixed'
|
|
@@ -64,7 +67,6 @@ async function copy(m, i) {
|
|
|
64
67
|
try {
|
|
65
68
|
document.execCommand('copy')
|
|
66
69
|
} catch {
|
|
67
|
-
/* 再失败就只好让用户手动选 */
|
|
68
70
|
}
|
|
69
71
|
ta.remove()
|
|
70
72
|
}
|
|
@@ -73,7 +75,6 @@ async function copy(m, i) {
|
|
|
73
75
|
copyTimer = setTimeout(() => (copiedKey.value = -1), 1600)
|
|
74
76
|
}
|
|
75
77
|
|
|
76
|
-
/* 只有最后一条能重试:往前还有更新的对话时,重发会把后面的记录截掉 */
|
|
77
78
|
function canRetry(i) {
|
|
78
79
|
return i === chat.messages.length - 1
|
|
79
80
|
}
|
|
@@ -83,7 +84,6 @@ function canRetry(i) {
|
|
|
83
84
|
<div ref="box" class="ms scroll" @scroll="onScroll">
|
|
84
85
|
<div class="ms-in">
|
|
85
86
|
<article v-for="(m, i) in chat.messages" :key="i" class="msg" :class="m.role">
|
|
86
|
-
<!-- 用户 -->
|
|
87
87
|
<div v-if="m.role === 'user'" class="u-body">
|
|
88
88
|
<div v-if="m.attachments && m.attachments.length" class="u-atts">
|
|
89
89
|
<span v-for="(a, j) in m.attachments" :key="j" class="u-att">{{ a.name }}</span>
|
|
@@ -92,24 +92,23 @@ function canRetry(i) {
|
|
|
92
92
|
<div class="u-time">{{ fmtTime(m.ts) }}</div>
|
|
93
93
|
</div>
|
|
94
94
|
|
|
95
|
-
<!-- 助手 -->
|
|
96
95
|
<div v-else class="a-body">
|
|
97
96
|
<div class="a-head">
|
|
98
97
|
<span class="avatar" :class="{ live: m.streaming }" />
|
|
99
|
-
<span class="who"
|
|
98
|
+
<span class="who">{{ t('msg.supervisor') }}</span>
|
|
100
99
|
<span v-if="m.stage" class="stage">◌ {{ m.stage }}</span>
|
|
101
|
-
<span v-if="m.streaming" class="live-tag"
|
|
100
|
+
<span v-if="m.streaming" class="live-tag">{{ t('msg.generating') }}</span>
|
|
102
101
|
<span class="a-time">{{ fmtTime(m.ts) }}</span>
|
|
103
102
|
</div>
|
|
104
103
|
|
|
105
104
|
<details v-if="m.thinking" class="fold think">
|
|
106
|
-
<summary
|
|
105
|
+
<summary>{{ t('msg.reasoning') }} <em>{{ t('msg.chars', { n: m.thinking.length }) }}</em></summary>
|
|
107
106
|
<pre class="fold-body">{{ m.thinking }}</pre>
|
|
108
107
|
</details>
|
|
109
108
|
|
|
110
109
|
<details v-if="m.plan && (m.plan.steps || []).length" class="fold plan" open>
|
|
111
110
|
<summary>
|
|
112
|
-
|
|
111
|
+
{{ t('msg.plan') }} <em>{{ t('msg.steps', { n: m.plan.steps.length }) }} · {{ m.plan.flow || 'single' }}</em>
|
|
113
112
|
</summary>
|
|
114
113
|
<ol class="fold-body">
|
|
115
114
|
<li v-for="(s, k) in m.plan.steps" :key="k">{{ stepText(s) }}</li>
|
|
@@ -119,8 +118,8 @@ function canRetry(i) {
|
|
|
119
118
|
<div v-if="m.search" class="sres">
|
|
120
119
|
<div class="sres-h">
|
|
121
120
|
<span class="sroute">{{ m.search.route }}</span>
|
|
122
|
-
<span
|
|
123
|
-
<span>{{ m.search.iterations }}
|
|
121
|
+
<span>{{ t('msg.confidence', { n: Math.round((m.search.confidence || 0) * 100) }) }}</span>
|
|
122
|
+
<span>{{ t('msg.iterations', { n: m.search.iterations }) }}</span>
|
|
124
123
|
</div>
|
|
125
124
|
<div v-for="(n, k) in m.search.nodes" :key="k" class="srow">
|
|
126
125
|
<span class="ssrc" :class="n.source">{{ srcLabel(n.source) }}</span>
|
|
@@ -141,11 +140,11 @@ function canRetry(i) {
|
|
|
141
140
|
<div v-if="m.error" class="err">✕ {{ m.error }}</div>
|
|
142
141
|
|
|
143
142
|
<details v-if="m.evidence && m.evidence.length" class="fold ev">
|
|
144
|
-
<summary
|
|
143
|
+
<summary>{{ t('msg.evidence') }} <em>{{ t('msg.items', { n: m.evidence.length }) }}</em></summary>
|
|
145
144
|
<div class="fold-body">
|
|
146
145
|
<div v-for="(e, k) in m.evidence" :key="k" class="ev-row">
|
|
147
146
|
<span class="ev-dot" :class="e.status || 'unverified'" />
|
|
148
|
-
<b>{{ e.tool || e.kind || '
|
|
147
|
+
<b>{{ e.tool || e.kind || t('msg.tool') }}</b>
|
|
149
148
|
<span class="ev-st">{{ e.status || e.reason || '' }}</span>
|
|
150
149
|
<template v-for="(x, y) in e.evidence || []" :key="y">
|
|
151
150
|
<a v-if="isUrl(x)" class="ev-link" :href="x.ref" target="_blank" rel="noreferrer">
|
|
@@ -159,9 +158,9 @@ function canRetry(i) {
|
|
|
159
158
|
|
|
160
159
|
<div v-if="!m.streaming && (m.content || m.error)" class="a-acts">
|
|
161
160
|
<button v-if="m.content" class="mini" @click="copy(m, i)">
|
|
162
|
-
{{ copiedKey === i ? '
|
|
161
|
+
{{ copiedKey === i ? t('msg.copied') : t('msg.copy') }}
|
|
163
162
|
</button>
|
|
164
|
-
<button v-if="canRetry(i)" class="mini" @click="retry(m)"
|
|
163
|
+
<button v-if="canRetry(i)" class="mini" @click="retry(m)">{{ t('msg.retry') }}</button>
|
|
165
164
|
</div>
|
|
166
165
|
</div>
|
|
167
166
|
</article>
|
|
@@ -177,7 +176,6 @@ function canRetry(i) {
|
|
|
177
176
|
display: flex; flex-direction: column; gap: 20px;
|
|
178
177
|
}
|
|
179
178
|
|
|
180
|
-
/* ── 用户 ── */
|
|
181
179
|
.msg.user { display: flex; justify-content: flex-end; }
|
|
182
180
|
.u-body { max-width: 78%; display: flex; flex-direction: column; align-items: flex-end; gap: 5px; }
|
|
183
181
|
.u-atts { display: flex; gap: 5px; flex-wrap: wrap; justify-content: flex-end; }
|
|
@@ -195,7 +193,6 @@ function canRetry(i) {
|
|
|
195
193
|
}
|
|
196
194
|
.u-time { font-size: 11px; color: var(--faint); font-family: var(--mono); }
|
|
197
195
|
|
|
198
|
-
/* ── 助手 ── */
|
|
199
196
|
.a-body { display: flex; flex-direction: column; gap: 9px; }
|
|
200
197
|
.a-head { display: flex; align-items: center; gap: 7px; font-size: 11.5px; color: var(--dim); }
|
|
201
198
|
.avatar {
|
|
@@ -213,13 +210,9 @@ function canRetry(i) {
|
|
|
213
210
|
.a-head .live-tag { color: var(--green); font-size: 10.5px; font-weight: 600; }
|
|
214
211
|
.a-time { margin-left: auto; font-family: var(--mono); }
|
|
215
212
|
|
|
216
|
-
/* 正文由 lib/md.js 渲染成 HTML(见 v-html),所以子元素都是 :deep ——
|
|
217
|
-
块级元素自己带 margin,这里整体压紧,避免气泡被撑得很松。 */
|
|
218
213
|
.a-text {
|
|
219
214
|
font-size: 13.5px; line-height: 1.8; color: var(--ink);
|
|
220
215
|
word-break: break-word;
|
|
221
|
-
/* 渲染出来的 HTML 里没有裸换行(段落内用 <br>),pre-wrap 只负责保住连续空格 ——
|
|
222
|
-
/帮助 之类的对齐文本原来靠它,去掉就会被折叠成单个空格。 */
|
|
223
216
|
white-space: pre-wrap;
|
|
224
217
|
}
|
|
225
218
|
.a-text :deep(pre),
|
|
@@ -280,8 +273,6 @@ function canRetry(i) {
|
|
|
280
273
|
.a-text :deep(th) { color: var(--dim); font-size: 11.5px; }
|
|
281
274
|
.a-text :deep(a) { color: var(--green); border-bottom: 1px solid rgba(26, 157, 95, 0.3); }
|
|
282
275
|
|
|
283
|
-
/* 流式光标挂在最后一个块级元素的末尾 —— 原来是一个 <span>,现在内容是 HTML 了,
|
|
284
|
-
放在外面会掉到下一行开头。 */
|
|
285
276
|
.a-text.streaming > :last-child::after {
|
|
286
277
|
content: '';
|
|
287
278
|
display: inline-block; width: 6px; height: 13px; margin-left: 3px;
|
|
@@ -328,7 +319,6 @@ function canRetry(i) {
|
|
|
328
319
|
.ev-file { color: var(--dim); }
|
|
329
320
|
.ev-link:hover { text-decoration: underline; }
|
|
330
321
|
|
|
331
|
-
/* ── 认知搜索结果卡 ── */
|
|
332
322
|
.sres {
|
|
333
323
|
border-radius: var(--r-sm);
|
|
334
324
|
background: rgba(255, 255, 255, 0.75);
|
|
@@ -371,7 +361,6 @@ function canRetry(i) {
|
|
|
371
361
|
background: rgba(200, 140, 40, 0.09);
|
|
372
362
|
}
|
|
373
363
|
|
|
374
|
-
/* 复制 / 重试:平时不占位,鼠标到这条消息才浮出 */
|
|
375
364
|
.a-acts { display: flex; gap: 6px; opacity: 0; transition: opacity 0.13s; }
|
|
376
365
|
.msg:hover .a-acts,
|
|
377
366
|
.a-acts:focus-within { opacity: 1; }
|
|
@@ -387,7 +376,6 @@ function canRetry(i) {
|
|
|
387
376
|
border-color: rgba(26, 157, 95, 0.35);
|
|
388
377
|
}
|
|
389
378
|
|
|
390
|
-
/* 触屏没有 hover:常显,否则永远点不到 */
|
|
391
379
|
@media (hover: none) {
|
|
392
380
|
.a-acts { opacity: 1; }
|
|
393
381
|
}
|
|
@@ -46,7 +46,6 @@ function startAdd() {
|
|
|
46
46
|
form.value = { edit: false, name: '', base_url: '', api_key: '', model: '', note: '' }
|
|
47
47
|
}
|
|
48
48
|
|
|
49
|
-
/* 编辑:先 GET 详情预填(key 只回 has_key,输入框留空 = 保留原值,后端语义一致) */
|
|
50
49
|
async function startEdit(m) {
|
|
51
50
|
busy.value = true
|
|
52
51
|
try {
|
|
@@ -91,7 +90,6 @@ async function submit() {
|
|
|
91
90
|
}
|
|
92
91
|
}
|
|
93
92
|
|
|
94
|
-
/* 连通性测试:POST /llm/test,后端失败也返回 200 + {ok:false,error},原样显示原因 */
|
|
95
93
|
const tState = ref('idle') // idle | busy | ok | err
|
|
96
94
|
const tKey = ref('')
|
|
97
95
|
const tMsg = ref('')
|
|
@@ -295,12 +293,10 @@ function isCur(source, key) {
|
|
|
295
293
|
.tg.warn { color: var(--amber); background: rgba(200, 140, 40, 0.13); }
|
|
296
294
|
.it .x { flex-shrink: 0; font-size: 11px; color: var(--dim); padding: 2px 4px; }
|
|
297
295
|
.it .x:hover { color: var(--red); }
|
|
298
|
-
/* 测 / 编 是 button,去掉按钮底框,和 ✕ 同一个观感 */
|
|
299
296
|
button.x { background: none; border: 0; font-family: inherit; cursor: pointer; }
|
|
300
297
|
button.x:hover:not(:disabled) { color: var(--accent); }
|
|
301
298
|
button.x:disabled { opacity: 0.4; cursor: default; }
|
|
302
299
|
|
|
303
|
-
/* 连通性测试结果条:失败原因原样显示(常见是 key 错 / base_url 多一层 /v1) */
|
|
304
300
|
.tres {
|
|
305
301
|
margin-top: 5px; padding: 7px 9px; border-radius: var(--r-sm);
|
|
306
302
|
font-size: 11.5px; line-height: 1.55; color: var(--dim);
|
|
@@ -317,7 +313,6 @@ button.x:disabled { opacity: 0.4; cursor: default; }
|
|
|
317
313
|
}
|
|
318
314
|
.empty { padding: 10px 7px; font-size: 12px; color: var(--dim); }
|
|
319
315
|
|
|
320
|
-
/* 列表可能长到滚出视口,底部两个按钮必须常驻可点 */
|
|
321
316
|
.foot {
|
|
322
317
|
position: sticky; bottom: -6px;
|
|
323
318
|
display: flex; gap: 5px;
|
|
@@ -1,8 +1,4 @@
|
|
|
1
1
|
<script setup>
|
|
2
|
-
/* 右栏上部的紧凑录入口。
|
|
3
|
-
和知识库面板里那个是同一个 store 写操作(ingestKnowledge),
|
|
4
|
-
区别只是这里只留「写一句 + 收录」,标题可填可不填 —— 右栏宽度放不下
|
|
5
|
-
标题/领域两行输入,而随手记的时候本来也不需要它们。 */
|
|
6
2
|
import { computed, ref, onUnmounted } from 'vue'
|
|
7
3
|
import { panels, ingestKnowledge } from '../stores/panels.js'
|
|
8
4
|
|
|
@@ -30,7 +26,6 @@ async function go() {
|
|
|
30
26
|
await ingestKnowledge({ text, title: title.value })
|
|
31
27
|
title.value = ''
|
|
32
28
|
} catch {
|
|
33
|
-
/* 失败原因已经在 kp.ingestErr 里,这里不用再抛 */
|
|
34
29
|
}
|
|
35
30
|
clearLater()
|
|
36
31
|
}
|
|
@@ -61,7 +56,6 @@ onUnmounted(() => clearTimeout(timer))
|
|
|
61
56
|
</template>
|
|
62
57
|
|
|
63
58
|
<style scoped>
|
|
64
|
-
/* 挂在右栏 .body 里(body 已有 12px 内边距),这里只管垂直节奏 */
|
|
65
59
|
.qi {
|
|
66
60
|
padding: 0 0 10px;
|
|
67
61
|
margin-bottom: 14px;
|
|
@@ -11,13 +11,14 @@ import KnowledgePanel from './panels/KnowledgePanel.vue'
|
|
|
11
11
|
import ReviewPanel from './panels/ReviewPanel.vue'
|
|
12
12
|
import MemoryPanel from './panels/MemoryPanel.vue'
|
|
13
13
|
import SettingsPanel from './panels/SettingsPanel.vue'
|
|
14
|
-
import { chat, loadSession, send } from '../stores/chat.js'
|
|
14
|
+
import { chat, loadSession, say, send } from '../stores/chat.js'
|
|
15
15
|
import { state, moduleLabel, closeDoc, loadSessions, openSession } from '../stores/app.js'
|
|
16
|
+
import { profile, loadProfile, saveNickname } from '../stores/profile.js'
|
|
16
17
|
import { openKu } from '../stores/panels.js'
|
|
17
18
|
import { getJSON } from '../api.js'
|
|
18
19
|
import { clip } from '../lib/fmt.js'
|
|
20
|
+
import { t } from '../lib/i18n.js'
|
|
19
21
|
|
|
20
|
-
/* 菜单 8 项 → 真实面板。overview 是核心对话视图,不在这里。 */
|
|
21
22
|
const PANELS = {
|
|
22
23
|
activity: ActivityPanel,
|
|
23
24
|
tasks: TasksPanel,
|
|
@@ -31,29 +32,39 @@ const PANELS = {
|
|
|
31
32
|
}
|
|
32
33
|
const panel = computed(() => PANELS[state.module] || null)
|
|
33
34
|
|
|
34
|
-
/* 标签条只在打开文档时需要(文档 + 回到面板)。
|
|
35
|
-
没文档时面板头部已经写着模块名,再挂一条同名 tab 就是重复。 */
|
|
36
35
|
const hasTabs = computed(() => !!state.doc)
|
|
37
36
|
|
|
38
|
-
|
|
39
|
-
一块空白 + 一句「问我任何事」等于让用户自己想问题。给三条真实可用的起点:
|
|
40
|
-
示例提问(点了直接发)、继续上次会话、最近知识(点了开正文)。
|
|
41
|
-
数据各自 .catch:任何一条挂了都只是少一块引导,不该让空态变成报错页。 */
|
|
42
|
-
const EXAMPLES = [
|
|
43
|
-
'现在系统里有哪些技能可以用?',
|
|
44
|
-
'帮我把今天要做的事排个优先级',
|
|
45
|
-
'用一句话说清你能帮我做什么',
|
|
46
|
-
]
|
|
37
|
+
const EXAMPLES = computed(() => [t('hero.q1'), t('hero.q2'), t('hero.q3')])
|
|
47
38
|
|
|
48
39
|
const recent = ref([])
|
|
40
|
+
|
|
41
|
+
const nick = ref('')
|
|
42
|
+
const nickSkipped = ref(localStorage.getItem('sfx.nick.skip') === '1')
|
|
43
|
+
const askNick = computed(() => profile.loaded && !profile.nickname && !nickSkipped.value)
|
|
44
|
+
|
|
49
45
|
onMounted(async () => {
|
|
46
|
+
loadProfile().then(() => { nick.value = profile.nickname })
|
|
50
47
|
if (!state.sessions.length) loadSessions().catch(() => {})
|
|
51
48
|
const d = await getJSON('/knowledge/recent?limit=5').catch(() => null)
|
|
52
49
|
recent.value = d?.kus || []
|
|
53
50
|
})
|
|
54
51
|
|
|
55
|
-
|
|
56
|
-
|
|
52
|
+
async function saveNick() {
|
|
53
|
+
const n = nick.value.trim()
|
|
54
|
+
if (!n) return
|
|
55
|
+
try {
|
|
56
|
+
await saveNickname(n)
|
|
57
|
+
say(t('hero.saidNick', { name: n }))
|
|
58
|
+
} catch (e) {
|
|
59
|
+
say(t('hero.errSave', { msg: e.message }), 4000)
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
function skipNick() {
|
|
64
|
+
nickSkipped.value = true
|
|
65
|
+
localStorage.setItem('sfx.nick.skip', '1')
|
|
66
|
+
}
|
|
67
|
+
|
|
57
68
|
const lastSession = computed(() => {
|
|
58
69
|
const cur = chat.messages.length ? state.sessionId : ''
|
|
59
70
|
return state.sessions.find((s) => s.id !== cur && (s.message_count || 0) > 0) || null
|
|
@@ -80,7 +91,7 @@ function resume() {
|
|
|
80
91
|
</button>
|
|
81
92
|
<button v-if="state.doc" class="tab on doc" @click="closeDoc()">
|
|
82
93
|
<span class="t">{{ state.doc.title }}</span>
|
|
83
|
-
<span class="x" title="
|
|
94
|
+
<span class="x" :title="t('hero.close')">✕</span>
|
|
84
95
|
</button>
|
|
85
96
|
</div>
|
|
86
97
|
|
|
@@ -93,8 +104,21 @@ function resume() {
|
|
|
93
104
|
<MessageStream v-if="chat.messages.length" />
|
|
94
105
|
<div v-else class="core-empty">
|
|
95
106
|
<div class="hero">
|
|
96
|
-
<div class="hero-t"
|
|
97
|
-
<div class="core-cap"
|
|
107
|
+
<div class="hero-t">{{ t('hero.title') }}</div>
|
|
108
|
+
<div class="core-cap">{{ t('hero.cap') }}</div>
|
|
109
|
+
</div>
|
|
110
|
+
|
|
111
|
+
<div v-if="askNick" class="ask-nick">
|
|
112
|
+
<span class="an-q">{{ t('hero.askName') }}</span>
|
|
113
|
+
<input
|
|
114
|
+
v-model="nick"
|
|
115
|
+
class="an-in"
|
|
116
|
+
maxlength="24"
|
|
117
|
+
:placeholder="t('hero.namePh')"
|
|
118
|
+
@keydown.enter.prevent="saveNick"
|
|
119
|
+
/>
|
|
120
|
+
<button class="an-ok" :disabled="profile.saving || !nick.trim()" @click="saveNick">{{ t('hero.confirm') }}</button>
|
|
121
|
+
<button class="an-skip" :title="t('hero.skipTip')" @click="skipNick">{{ t('hero.skip') }}</button>
|
|
98
122
|
</div>
|
|
99
123
|
|
|
100
124
|
<div class="tries">
|
|
@@ -104,13 +128,13 @@ function resume() {
|
|
|
104
128
|
</div>
|
|
105
129
|
|
|
106
130
|
<button v-if="lastSession" class="resume" @click="resume">
|
|
107
|
-
<span class="rl"
|
|
108
|
-
<b>{{ clip(lastSession.title || '
|
|
109
|
-
<span class="rm">{{ lastSession.message_count }}
|
|
131
|
+
<span class="rl">{{ t('hero.resume') }}</span>
|
|
132
|
+
<b>{{ clip(lastSession.title || t('hero.untitled'), 28) }}</b>
|
|
133
|
+
<span class="rm">{{ t('hero.msgs', { n: lastSession.message_count }) }}</span>
|
|
110
134
|
</button>
|
|
111
135
|
|
|
112
136
|
<div v-if="recent.length" class="recent">
|
|
113
|
-
<div class="rh"
|
|
137
|
+
<div class="rh">{{ t('hero.recentKnowledge') }}</div>
|
|
114
138
|
<div class="rk-list">
|
|
115
139
|
<button v-for="k in recent" :key="k.ku_id" class="rk" :title="k.summary || k.name" @click="openKu(k.ku_id)">
|
|
116
140
|
{{ clip(k.name || k.ku_id, 30) }}
|
|
@@ -158,10 +182,8 @@ function resume() {
|
|
|
158
182
|
overflow: hidden;
|
|
159
183
|
display: flex;
|
|
160
184
|
}
|
|
161
|
-
/* 没有标签条时不留缺口,四角全圆 */
|
|
162
185
|
.stage-body.bare { margin-top: 8px; border-radius: var(--r-mid); }
|
|
163
186
|
|
|
164
|
-
/* ── 核心:空态居中,有消息时整块交给消息流 ── */
|
|
165
187
|
.core { flex: 1; min-height: 0; display: flex; flex-direction: column; }
|
|
166
188
|
.core-empty {
|
|
167
189
|
flex: 1; display: flex; flex-direction: column;
|
|
@@ -178,7 +200,31 @@ function resume() {
|
|
|
178
200
|
color: var(--ink); margin-bottom: 6px;
|
|
179
201
|
}
|
|
180
202
|
|
|
181
|
-
|
|
203
|
+
.ask-nick {
|
|
204
|
+
display: flex; align-items: center; gap: 8px;
|
|
205
|
+
width: 100%; max-width: 420px;
|
|
206
|
+
padding: 9px 11px; border-radius: var(--r-sm);
|
|
207
|
+
background: rgba(26, 157, 95, 0.06);
|
|
208
|
+
border: 1px solid rgba(26, 157, 95, 0.22);
|
|
209
|
+
}
|
|
210
|
+
.an-q { font-size: 12.5px; font-weight: 600; color: var(--ink); flex-shrink: 0; }
|
|
211
|
+
.an-in {
|
|
212
|
+
flex: 1; min-width: 56px;
|
|
213
|
+
padding: 5px 8px; border-radius: 7px;
|
|
214
|
+
font-size: 12.5px; color: var(--ink);
|
|
215
|
+
background: #fff; border: 1px solid var(--line);
|
|
216
|
+
}
|
|
217
|
+
.an-in:focus { outline: none; border-color: rgba(26, 157, 95, 0.5); }
|
|
218
|
+
.an-ok {
|
|
219
|
+
flex-shrink: 0;
|
|
220
|
+
padding: 5px 10px; border-radius: 7px;
|
|
221
|
+
font-size: 12px; font-weight: 600; color: #fff;
|
|
222
|
+
background: var(--green);
|
|
223
|
+
}
|
|
224
|
+
.an-ok:disabled { opacity: 0.45; cursor: default; }
|
|
225
|
+
.an-skip { flex-shrink: 0; padding: 4px 2px; font-size: 11.5px; color: var(--dim); }
|
|
226
|
+
.an-skip:hover { color: var(--ink); }
|
|
227
|
+
|
|
182
228
|
.tries { display: flex; flex-direction: column; gap: 6px; width: 100%; max-width: 420px; }
|
|
183
229
|
.try {
|
|
184
230
|
display: flex; align-items: center; gap: 8px; width: 100%;
|
|
@@ -21,8 +21,6 @@ const rows = computed(() => [
|
|
|
21
21
|
{ ic: '📁', b: '导入文件夹', s: '逐个摄入', act: () => { close(); inFolder.value?.click() } },
|
|
22
22
|
{ ic: '🖼', b: '附加图片', s: '发给视觉模型', act: () => { close(); inImg.value?.click() } },
|
|
23
23
|
{ ic: '📄', b: '附件文件', s: '随问题发给 AI', act: () => { close(); inAttach.value?.click() } },
|
|
24
|
-
/* 连接器只有一份实现,在技能面板里。这里不再内嵌一份只读副本(信任档位还是另一套词),
|
|
25
|
-
点进来直接去那个面板,要管 MCP 也只在一个地方管。 */
|
|
26
24
|
{ ic: '🔌', b: '技能 · 连接器', s: '到技能面板看全部', act: () => { close(); setModule('tools') } },
|
|
27
25
|
])
|
|
28
26
|
|
|
@@ -32,7 +30,6 @@ function toggle() {
|
|
|
32
30
|
function close() {
|
|
33
31
|
open.value = false
|
|
34
32
|
}
|
|
35
|
-
/* 斜杠命令 /摄入 通过 store 信号打到这里,避免把 input 引用抬到全局 */
|
|
36
33
|
watch(() => ui.ingestTick, () => inFile.value?.click())
|
|
37
34
|
|
|
38
35
|
function onDocDown(e) {
|
|
@@ -1,18 +1,31 @@
|
|
|
1
1
|
<script setup>
|
|
2
|
-
import { onMounted, ref } from 'vue'
|
|
2
|
+
import { computed, onMounted, ref } from 'vue'
|
|
3
3
|
import { getJSON } from '../api.js'
|
|
4
4
|
import { state, setModule } from '../stores/app.js'
|
|
5
|
+
import { system, loadSystemInfo } from '../stores/system.js'
|
|
6
|
+
import { t } from '../lib/i18n.js'
|
|
5
7
|
|
|
6
|
-
const uplink = ref('···')
|
|
8
|
+
const uplink = ref('···') // '···' | 'online' | 'offline'
|
|
7
9
|
const tone = ref('dim')
|
|
8
10
|
|
|
11
|
+
const ver = computed(() => system.version)
|
|
12
|
+
|
|
13
|
+
const upd = computed(() => (system.update?.update_available ? system.update : null))
|
|
14
|
+
|
|
15
|
+
const uplinkText = computed(() => {
|
|
16
|
+
if (uplink.value === 'online') return t('common.online')
|
|
17
|
+
if (uplink.value === 'offline') return t('common.offlineShort')
|
|
18
|
+
return '···'
|
|
19
|
+
})
|
|
20
|
+
|
|
9
21
|
onMounted(async () => {
|
|
22
|
+
loadSystemInfo()
|
|
10
23
|
try {
|
|
11
24
|
await getJSON('/status')
|
|
12
|
-
uplink.value = '
|
|
25
|
+
uplink.value = 'online'
|
|
13
26
|
tone.value = 'ok'
|
|
14
27
|
} catch {
|
|
15
|
-
uplink.value = '
|
|
28
|
+
uplink.value = 'offline'
|
|
16
29
|
tone.value = 'bad'
|
|
17
30
|
}
|
|
18
31
|
})
|
|
@@ -21,7 +34,6 @@ function openPlanet() {
|
|
|
21
34
|
window.open('/planet/', '_blank', 'noopener')
|
|
22
35
|
}
|
|
23
36
|
|
|
24
|
-
/* 这个 ⚙ 一直是死按钮(title 写着「P3 接入」)。设置面板补齐后接上去。 */
|
|
25
37
|
function openSettings() {
|
|
26
38
|
setModule(state.module === 'settings' ? 'overview' : 'settings')
|
|
27
39
|
}
|
|
@@ -32,18 +44,24 @@ function openSettings() {
|
|
|
32
44
|
<div class="brand">
|
|
33
45
|
<div class="logo">F</div>
|
|
34
46
|
<h1>Foxclaw</h1>
|
|
35
|
-
<span class="ver">
|
|
47
|
+
<span v-if="ver" class="ver">v{{ ver }}</span>
|
|
48
|
+
<button
|
|
49
|
+
v-if="upd"
|
|
50
|
+
class="upd"
|
|
51
|
+
:title="t('topbar.newVersion', { latest: upd.latest, current: upd.current })"
|
|
52
|
+
@click="openSettings"
|
|
53
|
+
>↑</button>
|
|
36
54
|
</div>
|
|
37
55
|
<div class="spacer" />
|
|
38
56
|
<div class="stat">
|
|
39
|
-
<span class="lbl"
|
|
40
|
-
<span class="val" :class="tone">{{
|
|
57
|
+
<span class="lbl">{{ t('topbar.systemStatus') }}</span>
|
|
58
|
+
<span class="val" :class="tone">{{ uplinkText }}</span>
|
|
41
59
|
</div>
|
|
42
|
-
<button class="tbtn" title="
|
|
60
|
+
<button class="tbtn" :title="t('topbar.planet')" @click="openPlanet">{{ t('topbar.planetBtn') }}</button>
|
|
43
61
|
<button
|
|
44
62
|
class="tbtn"
|
|
45
63
|
:class="{ on: state.module === 'settings' }"
|
|
46
|
-
title="
|
|
64
|
+
:title="t('topbar.settings')"
|
|
47
65
|
@click="openSettings"
|
|
48
66
|
>⚙</button>
|
|
49
67
|
</header>
|
|
@@ -73,6 +91,16 @@ function openSettings() {
|
|
|
73
91
|
padding: 2px 7px; border-radius: var(--r-pill);
|
|
74
92
|
background: rgba(255, 255, 255, 0.7); border: 1px solid var(--line);
|
|
75
93
|
}
|
|
94
|
+
.upd {
|
|
95
|
+
display: grid; place-items: center;
|
|
96
|
+
width: 18px; height: 18px; margin-left: -3px;
|
|
97
|
+
border-radius: 50%;
|
|
98
|
+
font-size: 11px; font-weight: 700; line-height: 1;
|
|
99
|
+
color: var(--amber);
|
|
100
|
+
background: rgba(214, 158, 46, 0.12);
|
|
101
|
+
border: 1px solid rgba(214, 158, 46, 0.35);
|
|
102
|
+
}
|
|
103
|
+
.upd:hover { background: rgba(214, 158, 46, 0.2); }
|
|
76
104
|
.spacer { flex: 1; }
|
|
77
105
|
.stat { display: flex; align-items: center; gap: 6px; font-size: 12.5px; color: var(--dim); }
|
|
78
106
|
.stat .val { font-weight: 700; color: var(--ink); }
|
|
@@ -66,11 +66,8 @@ function toggle(id) { open.value = open.value === id ? '' : id }
|
|
|
66
66
|
</div>
|
|
67
67
|
</div>
|
|
68
68
|
|
|
69
|
-
<div class="pn-note">
|
|
70
|
-
<
|
|
71
|
-
版本 <code>{{ p.info.version }}</code> · 数据目录 <code>{{ p.info.data_dir }}</code>
|
|
72
|
-
</template>
|
|
73
|
-
数据来自 <code>/api/status</code> 与 <code>/api/system/info</code>。
|
|
69
|
+
<div v-if="p.info" class="pn-note">
|
|
70
|
+
版本 <code>{{ p.info.version }}</code>
|
|
74
71
|
</div>
|
|
75
72
|
</PanelBox>
|
|
76
73
|
</template>
|
|
@@ -17,9 +17,6 @@ const idx = computed(() => p.index || {})
|
|
|
17
17
|
const kus = computed(() => Object.entries(p.stats?.ku_counts || {}))
|
|
18
18
|
const busy = computed(() => p.ingest === 'busy')
|
|
19
19
|
|
|
20
|
-
/* 用户开始写新内容时,把上一次的「已收录 N 条」收起来,否则会让人以为这版也提交过了。
|
|
21
|
-
只在**写入了非空内容**时清 —— 提交成功后代码会把 draft 置空,那不是用户输入,
|
|
22
|
-
不能让它把刚拿到的结果提示擦掉。 */
|
|
23
20
|
watch(draft, (v) => {
|
|
24
21
|
if (v && !busy.value) { p.ingest = 'idle'; p.ingestMsg = ''; p.ingestErr = '' }
|
|
25
22
|
})
|
|
@@ -31,12 +28,9 @@ async function submit() {
|
|
|
31
28
|
draft.value = ''
|
|
32
29
|
title.value = ''
|
|
33
30
|
} catch {
|
|
34
|
-
/* 错误已写进 store,模板里显示 */
|
|
35
31
|
}
|
|
36
32
|
}
|
|
37
33
|
|
|
38
|
-
/* ── 检索:POST /api/knowledge/search ──
|
|
39
|
-
后端一直有这条混合检索,界面却只给「最近录入」,想按关键词翻库只能去知识星球。 */
|
|
40
34
|
const q = ref('')
|
|
41
35
|
const mode = ref('hybrid')
|
|
42
36
|
const MODES = [
|
|
@@ -77,8 +71,6 @@ async function doSearch() {
|
|
|
77
71
|
}
|
|
78
72
|
}
|
|
79
73
|
|
|
80
|
-
/* ── 能力详情:GET /api/knowledge/ability/{key} ──
|
|
81
|
-
能力 chip 以前是死的,点了没反应 —— 而这条接口正是"这个能力到底够不够"的答案。 */
|
|
82
74
|
const abKey = ref('')
|
|
83
75
|
const abState = ref('idle') // idle | busy | ok | err
|
|
84
76
|
const abErr = ref('')
|
|
@@ -104,7 +96,6 @@ async function showAbility(k) {
|
|
|
104
96
|
}
|
|
105
97
|
}
|
|
106
98
|
|
|
107
|
-
/* coverage 在不同能力下是数字或对象,取几个常见键;取不到就显示 '—',不猜格式 */
|
|
108
99
|
function covText(c) {
|
|
109
100
|
if (c == null) return ''
|
|
110
101
|
if (typeof c === 'number') return `${Math.round(c * 100)}%`
|
|
@@ -124,7 +115,6 @@ function covText(c) {
|
|
|
124
115
|
:ready="ready"
|
|
125
116
|
@refresh="refreshPanel('knowledge')"
|
|
126
117
|
>
|
|
127
|
-
<!-- 录入:短知识在这里手录;整本书 / 大文档走知识星球 -->
|
|
128
118
|
<div class="kb-in">
|
|
129
119
|
<textarea
|
|
130
120
|
v-model="draft"
|
|
@@ -273,7 +263,6 @@ function covText(c) {
|
|
|
273
263
|
<div class="pn-note meta">HKC 未启用,无法编译能力。</div>
|
|
274
264
|
</template>
|
|
275
265
|
|
|
276
|
-
<!-- 覆盖不足不是错误,是"知识还不足以具备该能力"(证据驱动 gate) -->
|
|
277
266
|
<template v-else-if="ab.sufficient === false">
|
|
278
267
|
<div class="kb-ab-h">{{ abKey }}<span class="v">知识不足</span></div>
|
|
279
268
|
<div class="pn-note meta">{{ ab.reason || '覆盖不足,暂不授予该能力。' }}</div>
|
|
@@ -312,7 +301,6 @@ function covText(c) {
|
|
|
312
301
|
</template>
|
|
313
302
|
|
|
314
303
|
<style scoped>
|
|
315
|
-
/* ── 录入卡 ── */
|
|
316
304
|
.kb-in {
|
|
317
305
|
margin-bottom: 14px;
|
|
318
306
|
border-radius: var(--r-mid);
|
|
@@ -369,8 +357,6 @@ function covText(c) {
|
|
|
369
357
|
}
|
|
370
358
|
.go:hover:not(:disabled) { opacity: 0.9; }
|
|
371
359
|
.go:active:not(:disabled) { transform: scale(0.97); }
|
|
372
|
-
/* 禁用态原来用 --faint 压在 12% 墨底上,对比度不到 2:1、几乎读不出字。
|
|
373
|
-
改成实心浅底 + 深墨字:一眼看得出「现在点不了」,但字仍然清楚。 */
|
|
374
360
|
.go:disabled {
|
|
375
361
|
background: rgba(23, 32, 26, 0.07); color: var(--dim);
|
|
376
362
|
border: 1px solid var(--line-soft);
|
|
@@ -380,7 +366,6 @@ function covText(c) {
|
|
|
380
366
|
.sec-gap { margin-top: 14px; }
|
|
381
367
|
.pn-note.meta.bad { color: var(--red); }
|
|
382
368
|
|
|
383
|
-
/* ── 检索条 ── */
|
|
384
369
|
.kb-find {
|
|
385
370
|
display: flex; align-items: center; gap: 6px;
|
|
386
371
|
margin-bottom: 8px;
|
|
@@ -424,7 +409,6 @@ function covText(c) {
|
|
|
424
409
|
font-variant-numeric: tabular-nums;
|
|
425
410
|
}
|
|
426
411
|
|
|
427
|
-
/* ── 能力详情 ── */
|
|
428
412
|
.pn-chips.gap { margin-top: 8px; }
|
|
429
413
|
.kb-ab {
|
|
430
414
|
margin-top: 10px; padding: 10px 12px;
|