dsh-biocore 0.2.0 → 0.3.0
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/README.md +11 -0
- package/lib/core-worker.js +268 -0
- package/lib/index.js +160 -70
- package/lib/modules/autonomy.js +125 -0
- package/lib/modules/cache-stats.js +48 -0
- package/lib/modules/core-turn.js +24 -6
- package/lib/modules/expression-inject.js +0 -10
- package/lib/modules/expression-refresh.js +0 -9
- package/lib/modules/memory-capture.js +2 -1
- package/lib/modules/memory-inject.js +50 -5
- package/lib/modules/memory-tools.js +7 -4
- package/lib/modules/python-env.js +46 -0
- package/lib/modules/safety-gate.js +0 -3
- package/lib/modules/series.js +54 -0
- package/lib/modules/switch.js +16 -0
- package/lib/py-bridge.js +81 -15
- package/package.json +1 -1
- package/python/biocore_memory/core/config.py +138 -127
- package/python/biocore_memory/engine.py +2653 -2476
- package/python/biocore_memory/recall/embedding.py +129 -0
- package/python/biocore_memory/recall/feedback.py +202 -0
- package/python/biocore_model/preference_drift.py +255 -0
- package/python/biocore_model/tests/__init__.py +1 -0
- package/python/biocore_model/tests/core_mode_ui_check.mjs +63 -0
- package/python/biocore_model/tests/core_status_check.mjs +29 -0
- package/python/biocore_model/tests/dom_safe_check.mjs +22 -0
- package/python/biocore_model/tests/leak_samples_run.py +18 -0
- package/python/biocore_model/tests/orb_ring_check.mjs +20 -0
- package/python/biocore_model/tests/panel_html_check.mjs +63 -0
- package/python/biocore_model/tests/persona_flow_check.mjs +17 -0
- package/python/biocore_model/tests/render_guard_check.mjs +19 -0
- package/python/biocore_model/tests/switch_groups_check.mjs +94 -0
- package/python/biocore_model/tests/tabs_state_check.mjs +24 -0
- package/python/biocore_model/tests/test_affective_labor.py +102 -0
- package/python/biocore_model/tests/test_appraisal.py +137 -0
- package/python/biocore_model/tests/test_appraisal_action.py +55 -0
- package/python/biocore_model/tests/test_arch_convergence.py +35 -0
- package/python/biocore_model/tests/test_attachment_behavior.py +95 -0
- package/python/biocore_model/tests/test_attachment_regulation.py +63 -0
- package/python/biocore_model/tests/test_attachment_state.py +29 -0
- package/python/biocore_model/tests/test_attribution.py +56 -0
- package/python/biocore_model/tests/test_attributor.py +108 -0
- package/python/biocore_model/tests/test_autonomic.py +69 -0
- package/python/biocore_model/tests/test_autonomy_accumulator.py +84 -0
- package/python/biocore_model/tests/test_autonomy_affect.py +62 -0
- package/python/biocore_model/tests/test_autonomy_blame.py +44 -0
- package/python/biocore_model/tests/test_autonomy_caps.py +29 -0
- package/python/biocore_model/tests/test_autonomy_check.py +40 -0
- package/python/biocore_model/tests/test_autonomy_clingy.py +41 -0
- package/python/biocore_model/tests/test_autonomy_cooldown_selfcontact.py +138 -0
- package/python/biocore_model/tests/test_autonomy_frustration.py +42 -0
- package/python/biocore_model/tests/test_autonomy_hourly_limit.py +46 -0
- package/python/biocore_model/tests/test_autonomy_loop.py +64 -0
- package/python/biocore_model/tests/test_autonomy_mode.py +37 -0
- package/python/biocore_model/tests/test_autonomy_model.py +60 -0
- package/python/biocore_model/tests/test_autonomy_model_v2.py +68 -0
- package/python/biocore_model/tests/test_autonomy_module_text.py +104 -0
- package/python/biocore_model/tests/test_autonomy_params.py +163 -0
- package/python/biocore_model/tests/test_autonomy_reason.py +34 -0
- package/python/biocore_model/tests/test_autonomy_self.py +153 -0
- package/python/biocore_model/tests/test_autonomy_send_gate.py +38 -0
- package/python/biocore_model/tests/test_autonomy_silence.py +37 -0
- package/python/biocore_model/tests/test_autonomy_status.py +35 -0
- package/python/biocore_model/tests/test_autonomy_test_trigger.py +24 -0
- package/python/biocore_model/tests/test_autonomy_tick.py +148 -0
- package/python/biocore_model/tests/test_autonomy_v4.py +64 -0
- package/python/biocore_model/tests/test_autonomy_v5.py +43 -0
- package/python/biocore_model/tests/test_autonomy_v7.py +48 -0
- package/python/biocore_model/tests/test_autonomy_v_theta.py +85 -0
- package/python/biocore_model/tests/test_batch1_wiring.py +231 -0
- package/python/biocore_model/tests/test_batch2_wiring.py +231 -0
- package/python/biocore_model/tests/test_batch3_wiring.py +223 -0
- package/python/biocore_model/tests/test_bio_tick.py +202 -0
- package/python/biocore_model/tests/test_boundary.py +183 -0
- package/python/biocore_model/tests/test_boundary_gate.py +295 -0
- package/python/biocore_model/tests/test_brain_split.py +99 -0
- package/python/biocore_model/tests/test_co_recall.py +350 -0
- package/python/biocore_model/tests/test_commitment_extractor.py +197 -0
- package/python/biocore_model/tests/test_compassion_fatigue.py +84 -0
- package/python/biocore_model/tests/test_connection_bids.py +69 -0
- package/python/biocore_model/tests/test_core_bridge.py +88 -0
- package/python/biocore_model/tests/test_core_bridge_episode.py +14 -0
- package/python/biocore_model/tests/test_core_bridge_forget.py +17 -0
- package/python/biocore_model/tests/test_core_bridge_identity.py +15 -0
- package/python/biocore_model/tests/test_core_bridge_media.py +17 -0
- package/python/biocore_model/tests/test_core_bridge_relation.py +17 -0
- package/python/biocore_model/tests/test_core_bridge_summary.py +17 -0
- package/python/biocore_model/tests/test_core_client.py +156 -0
- package/python/biocore_model/tests/test_core_commands.py +146 -0
- package/python/biocore_model/tests/test_core_mode.py +161 -0
- package/python/biocore_model/tests/test_core_state.py +240 -0
- package/python/biocore_model/tests/test_criticality.py +61 -0
- package/python/biocore_model/tests/test_cross_session_copy.py +67 -0
- package/python/biocore_model/tests/test_debug_values.py +169 -0
- package/python/biocore_model/tests/test_defense_mechanisms.py +177 -0
- package/python/biocore_model/tests/test_delta_budget.py +157 -0
- package/python/biocore_model/tests/test_diag_trace.py +201 -0
- package/python/biocore_model/tests/test_directives_compat.py +698 -0
- package/python/biocore_model/tests/test_dissonance.py +97 -0
- package/python/biocore_model/tests/test_drift_sign.py +87 -0
- package/python/biocore_model/tests/test_embedding_fallback.py +277 -0
- package/python/biocore_model/tests/test_embedding_local.py +279 -0
- package/python/biocore_model/tests/test_embedding_mode.py +175 -0
- package/python/biocore_model/tests/test_embedding_modes.py +342 -0
- package/python/biocore_model/tests/test_emotion_geometry.py +114 -0
- package/python/biocore_model/tests/test_emotion_probe.py +214 -0
- package/python/biocore_model/tests/test_emotion_probe_concurrency.py +128 -0
- package/python/biocore_model/tests/test_entity_graph.py +198 -0
- package/python/biocore_model/tests/test_entity_resolve.py +446 -0
- package/python/biocore_model/tests/test_envy.py +173 -0
- package/python/biocore_model/tests/test_exit_or_silence.py +209 -0
- package/python/biocore_model/tests/test_expr_guard.py +1 -0
- package/python/biocore_model/tests/test_expr_intent.py +66 -0
- package/python/biocore_model/tests/test_expr_state.py +66 -0
- package/python/biocore_model/tests/test_expr_template.py +71 -0
- package/python/biocore_model/tests/test_expr_tone.py +41 -0
- package/python/biocore_model/tests/test_extreme_scenarios.py +147 -0
- package/python/biocore_model/tests/test_feedback_flow.py +185 -0
- package/python/biocore_model/tests/test_feedback_guard.py +65 -0
- package/python/biocore_model/tests/test_feedback_llm.py +59 -0
- package/python/biocore_model/tests/test_feedback_llm_inferred.py +140 -0
- package/python/biocore_model/tests/test_feedback_memory.py +63 -0
- package/python/biocore_model/tests/test_feedback_user.py +61 -0
- package/python/biocore_model/tests/test_frustration_curve.py +66 -0
- package/python/biocore_model/tests/test_global_bio_state.py +116 -0
- package/python/biocore_model/tests/test_governance.py +181 -0
- package/python/biocore_model/tests/test_granularity.py +133 -0
- package/python/biocore_model/tests/test_graph_extract.py +224 -0
- package/python/biocore_model/tests/test_graph_recall.py +226 -0
- package/python/biocore_model/tests/test_graph_sqlite.py +298 -0
- package/python/biocore_model/tests/test_growth.py +301 -0
- package/python/biocore_model/tests/test_intent_render.py +60 -0
- package/python/biocore_model/tests/test_intero_regulation.py +224 -0
- package/python/biocore_model/tests/test_intero_style.py +146 -0
- package/python/biocore_model/tests/test_item3_numeric.py +105 -0
- package/python/biocore_model/tests/test_item6_numeric.py +116 -0
- package/python/biocore_model/tests/test_layered_memory.py +174 -0
- package/python/biocore_model/tests/test_leak_guard.py +75 -0
- package/python/biocore_model/tests/test_learning_probe.py +93 -0
- package/python/biocore_model/tests/test_life_loop.py +222 -0
- package/python/biocore_model/tests/test_life_loop_10s.py +131 -0
- package/python/biocore_model/tests/test_local_embedding_health.py +258 -0
- package/python/biocore_model/tests/test_memory_affect_encoding.py +79 -0
- package/python/biocore_model/tests/test_memory_backend.py +332 -0
- package/python/biocore_model/tests/test_memory_dedup.py +316 -0
- package/python/biocore_model/tests/test_memory_dual.py +219 -0
- package/python/biocore_model/tests/test_memory_dual_wiring.py +219 -0
- package/python/biocore_model/tests/test_memory_feedback.py +241 -0
- package/python/biocore_model/tests/test_memory_fixes.py +84 -0
- package/python/biocore_model/tests/test_memory_forgetting.py +58 -0
- package/python/biocore_model/tests/test_memory_inject.py +211 -0
- package/python/biocore_model/tests/test_memory_mood_recall.py +118 -0
- package/python/biocore_model/tests/test_memory_store.py +372 -0
- package/python/biocore_model/tests/test_metacog.py +103 -0
- package/python/biocore_model/tests/test_migrate.py +245 -0
- package/python/biocore_model/tests/test_migrate_store.py +194 -0
- package/python/biocore_model/tests/test_mind_wander.py +65 -0
- package/python/biocore_model/tests/test_mood.py +35 -0
- package/python/biocore_model/tests/test_narrative.py +114 -0
- package/python/biocore_model/tests/test_needs_esteem.py +51 -0
- package/python/biocore_model/tests/test_needs_events.py +205 -0
- package/python/biocore_model/tests/test_needs_sleep.py +54 -0
- package/python/biocore_model/tests/test_noise_bank.py +54 -0
- package/python/biocore_model/tests/test_numeric_schema.py +98 -0
- package/python/biocore_model/tests/test_output_protocol.py +65 -0
- package/python/biocore_model/tests/test_page_debug.py +147 -0
- package/python/biocore_model/tests/test_page_status_autonomy.py +215 -0
- package/python/biocore_model/tests/test_pipeline.py +345 -0
- package/python/biocore_model/tests/test_pipeline_api.py +192 -0
- package/python/biocore_model/tests/test_pipeline_mode.py +222 -0
- package/python/biocore_model/tests/test_plugin_config.py +85 -0
- package/python/biocore_model/tests/test_policy_learning.py +116 -0
- package/python/biocore_model/tests/test_possible_selves.py +88 -0
- package/python/biocore_model/tests/test_predictor.py +227 -0
- package/python/biocore_model/tests/test_preference_drift.py +142 -0
- package/python/biocore_model/tests/test_preset.py +142 -0
- package/python/biocore_model/tests/test_proactive_context.py +70 -0
- package/python/biocore_model/tests/test_proactive_history.py +90 -0
- package/python/biocore_model/tests/test_proactive_memory.py +141 -0
- package/python/biocore_model/tests/test_proactive_send_record.py +88 -0
- package/python/biocore_model/tests/test_probe_contract.py +71 -0
- package/python/biocore_model/tests/test_prospective.py +154 -0
- package/python/biocore_model/tests/test_provider_fallback.py +89 -0
- package/python/biocore_model/tests/test_reconstruction.py +111 -0
- package/python/biocore_model/tests/test_regulation_strategies.py +228 -0
- package/python/biocore_model/tests/test_relation_stage.py +114 -0
- package/python/biocore_model/tests/test_relationship_stage.py +59 -0
- package/python/biocore_model/tests/test_release_140.py +55 -0
- package/python/biocore_model/tests/test_repair_after_boundary.py +249 -0
- package/python/biocore_model/tests/test_reply_limit.py +86 -0
- package/python/biocore_model/tests/test_reply_limit_persist.py +48 -0
- package/python/biocore_model/tests/test_reply_split.py +52 -0
- package/python/biocore_model/tests/test_reply_split_astrbot.py +66 -0
- package/python/biocore_model/tests/test_reply_split_inject.py +53 -0
- package/python/biocore_model/tests/test_rerank.py +229 -0
- package/python/biocore_model/tests/test_rollback.py +175 -0
- package/python/biocore_model/tests/test_runtime.py +175 -0
- package/python/biocore_model/tests/test_runtime_active.py +205 -0
- package/python/biocore_model/tests/test_runtime_pipeline.py +1583 -0
- package/python/biocore_model/tests/test_runtime_proactive.py +231 -0
- package/python/biocore_model/tests/test_scheduler.py +79 -0
- package/python/biocore_model/tests/test_secrecy.py +105 -0
- package/python/biocore_model/tests/test_self_esteem.py +45 -0
- package/python/biocore_model/tests/test_self_intent.py +347 -0
- package/python/biocore_model/tests/test_self_interest.py +183 -0
- package/python/biocore_model/tests/test_self_layer_wiring.py +198 -0
- package/python/biocore_model/tests/test_self_verification.py +113 -0
- package/python/biocore_model/tests/test_situation.py +142 -0
- package/python/biocore_model/tests/test_sleep_circadian.py +41 -0
- package/python/biocore_model/tests/test_social_pain.py +49 -0
- package/python/biocore_model/tests/test_social_repair.py +67 -0
- package/python/biocore_model/tests/test_social_theory.py +78 -0
- package/python/biocore_model/tests/test_source_monitoring.py +204 -0
- package/python/biocore_model/tests/test_speak_like_group.py +36 -0
- package/python/biocore_model/tests/test_stages.py +226 -0
- package/python/biocore_model/tests/test_state_tag.py +86 -0
- package/python/biocore_model/tests/test_template_expr.py +65 -0
- package/python/biocore_model/tests/test_text_inference.py +70 -0
- package/python/biocore_model/tests/test_theta_params.py +115 -0
- package/python/biocore_model/tests/test_topic_control.py +200 -0
- package/python/biocore_model/tests/test_turn_memory.py +105 -0
- package/python/biocore_model/tests/test_turn_memory_panel.py +174 -0
- package/python/biocore_model/tests/test_ui_settings.py +84 -0
- package/python/biocore_model/tests/test_ui_settings_authority.py +179 -0
- package/python/biocore_model/tests/test_user_appraisal_async.py +155 -0
- package/python/biocore_model/tests/test_user_classify.py +122 -0
- package/python/biocore_model/tests/test_value_order.py +117 -0
- package/python/biocore_model/tests/test_voice_bank.py +105 -0
- package/python/biocore_model/tests/test_waiting_sense.py +216 -0
- package/python/biocore_model/tests/test_x_directives.py +101 -0
- package/python/biocore_model/tests/ui_compat_check.mjs +22 -0
- package/python/biocore_model/tests/view_mode_check.mjs +17 -0
- package/python/biocore_runtime/autonomy.py +208 -0
- package/python/biocore_runtime/core_state.py +302 -0
- package/python/biocore_runtime/governance.py +495 -0
- package/python/biocore_runtime/growth.py +528 -0
- package/python/biocore_runtime/pipeline.py +850 -23
- package/python/biocore_runtime/relationship_state.py +214 -0
- package/python/biocore_runtime/self_layer.py +103 -0
- package/python/biocore_runtime/service.py +162 -0
- package/python/biocore_runtime/standby.py +472 -0
- package/ui/panel-layout.js +83 -39
- package/ui/panel.css +885 -743
- package/ui/panel.html +356 -296
- package/ui/panel.js +739 -865
package/ui/panel.js
CHANGED
|
@@ -1,28 +1,98 @@
|
|
|
1
|
-
/** BioCore instrument panel
|
|
1
|
+
/** BioCore instrument panel runtime — vanilla JS, no dependencies.
|
|
2
2
|
*
|
|
3
|
-
*
|
|
4
|
-
*
|
|
5
|
-
*
|
|
3
|
+
* Data contract (unchanged): GET /api/biocore/state?session=<id> is the single
|
|
4
|
+
* polling source; /series /stats /trace /persona /settings /switch are additive
|
|
5
|
+
* reads plus the two write paths the panel is allowed to touch.
|
|
6
|
+
*
|
|
7
|
+
* Pages are classified once, hard:
|
|
8
|
+
* dashboard data only memory data only relation data only
|
|
9
|
+
* settings controls only tools diagnostics only
|
|
6
10
|
*/
|
|
7
11
|
(function () {
|
|
8
12
|
'use strict'
|
|
9
13
|
|
|
14
|
+
var QUERY = new URLSearchParams(window.location.search)
|
|
15
|
+
var SESSION = (QUERY.get('session') || '').trim()
|
|
16
|
+
var INITIAL_TAB = (QUERY.get('tab') || 'dashboard').trim()
|
|
17
|
+
var THEME = (QUERY.get('theme') || '').trim()
|
|
18
|
+
|
|
10
19
|
var POLL_MS = 5000
|
|
11
|
-
var
|
|
20
|
+
var STATS_MS = 30000
|
|
21
|
+
var PAGE_START = Date.now()
|
|
22
|
+
|
|
12
23
|
var SWITCHES = [
|
|
13
|
-
{ key: '
|
|
14
|
-
{ key: '
|
|
15
|
-
{ key: '
|
|
16
|
-
{ key: '
|
|
24
|
+
{ key: 'core_enabled', label: '核心', desc: '关掉后整条核心链不运行', group: '总开关' },
|
|
25
|
+
{ key: 'safety_enabled', label: '安全闸', desc: '可能拦截或改写回复', group: '安全' },
|
|
26
|
+
{ key: 'boundary_enabled', label: '底线', desc: '越界时先守边界', group: '安全' },
|
|
27
|
+
{ key: 'crisis_safety_enabled', label: '危机兜底', desc: '危机信号优先安全', group: '安全' },
|
|
28
|
+
{ key: 'proactive_send', label: '主动回复', desc: '允许私聊主动发消息', group: '主动' },
|
|
29
|
+
{ key: 'mood_recall', label: '情绪匹配召回', desc: '召回偏向当前心情', group: '学习' },
|
|
30
|
+
{ key: 'affect_encoding', label: '编码带情绪', desc: '写记忆时带情绪强度', group: '学习' },
|
|
31
|
+
{ key: 'memory_feedback', label: '回忆回灌情绪', desc: '召回的记忆小幅回灌心情', group: '学习' },
|
|
32
|
+
{ key: 'preference_drift', label: '主题偏好漂移', desc: '重复主题缓慢调整偏好', group: '学习' },
|
|
17
33
|
]
|
|
34
|
+
|
|
35
|
+
var EMPTY_COPY = {
|
|
36
|
+
no_state_yet: ['这个会话还没聊过', '发一条消息后就有状态'],
|
|
37
|
+
request_failed: ['连不上宿主', '确认 DSH 与插件已加载,面板每 5 秒自动重试'],
|
|
38
|
+
bad_state: ['状态文件坏了', '删掉该会话的状态文件后重试'],
|
|
39
|
+
}
|
|
40
|
+
var STAT_WORDS = {
|
|
41
|
+
tension: ['松弛', '适中', '略紧'],
|
|
42
|
+
warmth: ['偏冷', '适中', '偏暖'],
|
|
43
|
+
heaviness: ['轻', '适中', '偏沉'],
|
|
44
|
+
clarity: ['模糊', '适中', '清醒'],
|
|
45
|
+
}
|
|
46
|
+
var STAT_NOTES = {
|
|
47
|
+
tension: ['松下来了', '还压得住', '明显绷着'],
|
|
48
|
+
warmth: ['有点冷,想靠近', '温度刚好', '心里暖,愿意靠近'],
|
|
49
|
+
heaviness: ['轻一些,能提劲', '有点分量,还扛得住', '沉下去了,先放一放'],
|
|
50
|
+
clarity: ['有点糊,慢一点', '还算清楚,能接话', '想得清楚,状态在线'],
|
|
51
|
+
}
|
|
52
|
+
var KNOWN_SINCE_KEY = 'biocore-known-since-v1'
|
|
53
|
+
var MODEL_DRAFT_KEY = 'biocore-panel-model-draft-v1'
|
|
54
|
+
var TRACE_LIMIT = 5
|
|
55
|
+
var LONG_PRESS_MS = 400
|
|
56
|
+
var MOVE_CANCEL_PX = 8
|
|
57
|
+
|
|
18
58
|
var lastSuccess = 0
|
|
19
59
|
var lastData = null
|
|
20
60
|
var seriesPoints = null
|
|
21
|
-
var localPoints = []
|
|
22
61
|
var switchBusy = false
|
|
62
|
+
var personaText = ''
|
|
63
|
+
var personaDirty = false
|
|
64
|
+
var toastTimer = null
|
|
65
|
+
var bootDone = false
|
|
23
66
|
|
|
24
67
|
function $(id) { return document.getElementById(id) }
|
|
25
68
|
|
|
69
|
+
/** Theme: the host may pin <html data-theme>; ?theme= wins for previews,
|
|
70
|
+
* otherwise prefers-color-scheme decides inside CSS. */
|
|
71
|
+
function applyTheme() {
|
|
72
|
+
if (THEME === 'light' || THEME === 'dark') {
|
|
73
|
+
document.documentElement.setAttribute('data-theme', THEME)
|
|
74
|
+
}
|
|
75
|
+
var scheme = window.matchMedia ? window.matchMedia('(prefers-color-scheme: dark)') : null
|
|
76
|
+
if (!scheme || !scheme.addEventListener) return
|
|
77
|
+
scheme.addEventListener('change', function () {
|
|
78
|
+
if (THEME === 'light' || THEME === 'dark') return
|
|
79
|
+
renderChart(lastData)
|
|
80
|
+
})
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
function chartPalette() {
|
|
84
|
+
var styles = window.getComputedStyle(document.documentElement)
|
|
85
|
+
var read = function (name, fallback) {
|
|
86
|
+
var value = styles.getPropertyValue(name)
|
|
87
|
+
return value && value.trim() ? value.trim() : fallback
|
|
88
|
+
}
|
|
89
|
+
return {
|
|
90
|
+
accent: read('--accent', '#6d5dfc'),
|
|
91
|
+
ink: read('--ink', '#333333'),
|
|
92
|
+
grid: read('--shadow-dark', '#b8bcc2'),
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
|
|
26
96
|
function fmt(value, digits) {
|
|
27
97
|
var number = Number(value)
|
|
28
98
|
if (!Number.isFinite(number)) return '–'
|
|
@@ -34,6 +104,16 @@
|
|
|
34
104
|
if (el) el.textContent = value
|
|
35
105
|
}
|
|
36
106
|
|
|
107
|
+
function clamp01(value) {
|
|
108
|
+
var number = Number(value)
|
|
109
|
+
if (!Number.isFinite(number)) return 0
|
|
110
|
+
return Math.max(0, Math.min(1, number))
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
function pct(value) {
|
|
114
|
+
return Math.round(clamp01(value) * 100) + '%'
|
|
115
|
+
}
|
|
116
|
+
|
|
37
117
|
/** Presentation helper: pick a one-line label out of string/object payloads. */
|
|
38
118
|
function scalarLabel(value, fallback) {
|
|
39
119
|
if (typeof value === 'string') return value.trim() || fallback
|
|
@@ -63,15 +143,15 @@
|
|
|
63
143
|
|
|
64
144
|
function updateStatusClock() {
|
|
65
145
|
if (!lastSuccess) return
|
|
146
|
+
if (lastData && lastData.available === false) return
|
|
66
147
|
var seconds = Math.max(0, Math.round((Date.now() - lastSuccess) / 1000))
|
|
67
|
-
setStatus(true, '已连接
|
|
148
|
+
setStatus(true, '已连接 ' + seconds + ' 秒前')
|
|
68
149
|
}
|
|
69
150
|
|
|
70
151
|
function setBar(id, ratio) {
|
|
71
152
|
var el = $(id)
|
|
72
153
|
if (!el) return
|
|
73
|
-
|
|
74
|
-
el.style.width = Math.round(value * 100) + '%'
|
|
154
|
+
el.style.width = pct(ratio)
|
|
75
155
|
}
|
|
76
156
|
|
|
77
157
|
function formatDelta(previous, current) {
|
|
@@ -102,10 +182,13 @@
|
|
|
102
182
|
el.classList.toggle('down', delta.charAt(0) === '-')
|
|
103
183
|
}
|
|
104
184
|
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
185
|
+
function paintArrow(id, delta) {
|
|
186
|
+
var el = $(id)
|
|
187
|
+
if (!el) return
|
|
188
|
+
var direction = arrowFor(delta)
|
|
189
|
+
el.textContent = direction
|
|
190
|
+
el.classList.toggle('up', direction === '↑')
|
|
191
|
+
el.classList.toggle('down', direction === '↓')
|
|
109
192
|
}
|
|
110
193
|
|
|
111
194
|
function renderEmpty(kind) {
|
|
@@ -122,34 +205,8 @@
|
|
|
122
205
|
|
|
123
206
|
function shortSession() {
|
|
124
207
|
if (!SESSION) return '-'
|
|
125
|
-
return SESSION.length >
|
|
126
|
-
}
|
|
127
|
-
|
|
128
|
-
function isPlausibleMs(ts) {
|
|
129
|
-
return Number.isFinite(ts) && ts > 1000000000000
|
|
130
|
-
}
|
|
131
|
-
|
|
132
|
-
function timeLabel(ts) {
|
|
133
|
-
if (!isPlausibleMs(ts)) return '--:--'
|
|
134
|
-
var date = new Date(ts)
|
|
135
|
-
var hh = String(date.getHours()).padStart(2, '0')
|
|
136
|
-
var mm = String(date.getMinutes()).padStart(2, '0')
|
|
137
|
-
return hh + ':' + mm
|
|
138
|
-
}
|
|
139
|
-
|
|
140
|
-
var STAT_WORDS = {
|
|
141
|
-
tension: ['松弛', '适中', '略紧'],
|
|
142
|
-
warmth: ['偏冷', '适中', '偏暖'],
|
|
143
|
-
heaviness: ['轻', '适中', '偏沉'],
|
|
144
|
-
clarity: ['模糊', '适中', '清醒'],
|
|
208
|
+
return SESSION.length > 12 ? SESSION.slice(0, 10) + '…' : SESSION
|
|
145
209
|
}
|
|
146
|
-
var STAT_NOTES = {
|
|
147
|
-
tension: ['松下来了,不紧绷', '还压得住,但不太放松', '明显绷着,先缓一缓'],
|
|
148
|
-
warmth: ['有点冷,想要靠近', '温度刚好,慢慢热', '心里暖,愿意靠近'],
|
|
149
|
-
heaviness: ['轻一些,能提起劲', '有点分量,还扛得住', '沉下去了,先放一放'],
|
|
150
|
-
clarity: ['有点糊,慢一点', '还算清楚,能接话', '想得清楚,状态在线'],
|
|
151
|
-
}
|
|
152
|
-
var KNOWN_SINCE_KEY = 'biocore-known-since-v1'
|
|
153
210
|
|
|
154
211
|
function statLevel(value) {
|
|
155
212
|
var number = Number(value)
|
|
@@ -159,26 +216,12 @@
|
|
|
159
216
|
return 2
|
|
160
217
|
}
|
|
161
218
|
|
|
162
|
-
function
|
|
163
|
-
var
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
if (
|
|
167
|
-
|
|
168
|
-
arrow.textContent = direction
|
|
169
|
-
arrow.classList.toggle('up', direction === '↑')
|
|
170
|
-
arrow.classList.toggle('down', direction === '↓')
|
|
171
|
-
}
|
|
172
|
-
|
|
173
|
-
function setStatBar(channel, value) {
|
|
174
|
-
var ratio = Number.isFinite(Number(value)) ? Math.max(0, Math.min(1, Number(value))) : 0
|
|
175
|
-
var fill = $('bar-' + channel)
|
|
176
|
-
if (fill) fill.style.width = Math.round(ratio * 100) + '%'
|
|
177
|
-
var over = $('over-' + channel)
|
|
178
|
-
if (over) {
|
|
179
|
-
over.style.left = '65%'
|
|
180
|
-
over.style.width = ratio > 0.65 ? Math.round((ratio - 0.65) * 100) + '%' : '0%'
|
|
181
|
-
}
|
|
219
|
+
function moodWord(value) {
|
|
220
|
+
var number = Number(value)
|
|
221
|
+
if (!Number.isFinite(number)) return '—'
|
|
222
|
+
if (number > 0.25) return '偏高'
|
|
223
|
+
if (number < -0.25) return '偏低'
|
|
224
|
+
return '平稳'
|
|
182
225
|
}
|
|
183
226
|
|
|
184
227
|
function knownDays() {
|
|
@@ -201,16 +244,6 @@
|
|
|
201
244
|
return '焦虑型'
|
|
202
245
|
}
|
|
203
246
|
|
|
204
|
-
function closenessNote(value) {
|
|
205
|
-
var level = statLevel(value)
|
|
206
|
-
return ['还不太熟,慢慢来', '挺自然,不装', '很亲近,想粘着'][level]
|
|
207
|
-
}
|
|
208
|
-
|
|
209
|
-
function trustNote(value) {
|
|
210
|
-
var level = statLevel(value)
|
|
211
|
-
return ['还在观察你', '慢慢信你', '信你,能交底'][level]
|
|
212
|
-
}
|
|
213
|
-
|
|
214
247
|
function memoryKind(text) {
|
|
215
248
|
var value = String(text || '')
|
|
216
249
|
if (/(喜欢|爱|讨厌|偏好|习惯|最怕|想要|想吃)/.test(value)) return '偏好'
|
|
@@ -228,164 +261,192 @@
|
|
|
228
261
|
return Math.round(seconds / 86400) + ' 天前'
|
|
229
262
|
}
|
|
230
263
|
|
|
231
|
-
function
|
|
232
|
-
var
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
setText('num-tension', available ? fmt(intero.tension) : '–')
|
|
236
|
-
setText('num-warmth', available ? fmt(intero.warmth) : '–')
|
|
237
|
-
setText('num-heaviness', available ? fmt(intero.heaviness) : '–')
|
|
238
|
-
setText('num-clarity', available ? fmt(intero.clarity) : '–')
|
|
239
|
-
setBar('bar-v', available ? (Number(data.v) + 1) / 2 : 0)
|
|
240
|
-
|
|
241
|
-
var channels = ['tension', 'warmth', 'heaviness', 'clarity']
|
|
242
|
-
for (var channelIndex = 0; channelIndex < channels.length; channelIndex += 1) {
|
|
243
|
-
var channel = channels[channelIndex]
|
|
244
|
-
var rawValue = available ? intero[channel] : null
|
|
245
|
-
setText('status-' + channel, available ? STAT_WORDS[channel][statLevel(rawValue)] : '—')
|
|
246
|
-
setText('note-' + channel, available ? STAT_NOTES[channel][statLevel(rawValue)] : '—')
|
|
247
|
-
setStatBar(channel, rawValue)
|
|
248
|
-
paintStatDelta(channel, data, available)
|
|
249
|
-
}
|
|
250
|
-
|
|
251
|
-
// 情绪卡右侧「张力」复用同一份数据(只换 DOM 节点,不改数据)。
|
|
252
|
-
var emotionTension = available ? intero.tension : null
|
|
253
|
-
setText('num-emotion-tension', available ? fmt(emotionTension) : '–')
|
|
254
|
-
setText('note-emotion-tension', available ? STAT_NOTES.tension[statLevel(emotionTension)] : '—')
|
|
255
|
-
setBar('bar-emotion-tension', emotionTension)
|
|
256
|
-
paintDelta('delta-emotion-tension', available ? historyDelta(data, 'tension') : '—')
|
|
257
|
-
var emotionArrow = $('arrow-emotion-tension')
|
|
258
|
-
if (emotionArrow) {
|
|
259
|
-
var emotionDirection = arrowFor(available ? historyDelta(data, 'tension') : '—')
|
|
260
|
-
emotionArrow.textContent = emotionDirection
|
|
261
|
-
emotionArrow.classList.toggle('up', emotionDirection === '↑')
|
|
262
|
-
emotionArrow.classList.toggle('down', emotionDirection === '↓')
|
|
263
|
-
}
|
|
264
|
-
setText('moodAside', available ? emotionMoodWord(data.v) : '—')
|
|
265
|
-
setText('moodNote', available ? emotionMoodWord(data.v) : '—')
|
|
266
|
-
|
|
267
|
-
var tone = available ? scalarLabel(data.tone, '') : ''
|
|
268
|
-
setText('word-tone', tone || (available ? '正常' : '–'))
|
|
269
|
-
setText('word-relation', available ? (scalarLabel(data.relation, '') || '–') : '–')
|
|
270
|
-
setText('word-autonomy', available ? (scalarLabel(data.autonomy, '') || '–') : '–')
|
|
271
|
-
if (available) {
|
|
272
|
-
setText('motivation', data.motivation || '安静待着,等你开口')
|
|
273
|
-
}
|
|
274
|
-
var fields = ['v', 'tension', 'warmth', 'heaviness', 'clarity']
|
|
275
|
-
for (var index = 0; index < fields.length; index += 1) {
|
|
276
|
-
var delta = available ? historyDelta(data, fields[index]) : '—'
|
|
277
|
-
paintDelta('delta-' + fields[index], delta)
|
|
278
|
-
}
|
|
279
|
-
var arrow = $('arrow-v')
|
|
280
|
-
if (arrow) {
|
|
281
|
-
var direction = arrowFor(available ? historyDelta(data, 'v') : '—')
|
|
282
|
-
arrow.textContent = direction
|
|
283
|
-
arrow.classList.toggle('up', direction === '↑')
|
|
284
|
-
arrow.classList.toggle('down', direction === '↓')
|
|
285
|
-
}
|
|
264
|
+
function startOfToday() {
|
|
265
|
+
var dayStart = new Date()
|
|
266
|
+
dayStart.setHours(0, 0, 0, 0)
|
|
267
|
+
return dayStart.getTime()
|
|
286
268
|
}
|
|
287
269
|
|
|
288
|
-
|
|
289
|
-
var number = Number(value)
|
|
290
|
-
if (!Number.isFinite(number)) return '—'
|
|
291
|
-
if (number > 0.25) return '偏高'
|
|
292
|
-
if (number < -0.25) return '偏低'
|
|
293
|
-
return '平稳'
|
|
294
|
-
}
|
|
270
|
+
/* ------------------------------------------------------------ 仪表盘 --- */
|
|
295
271
|
|
|
296
272
|
function renderMetrics(data) {
|
|
297
|
-
var box = $('metrics')
|
|
298
|
-
if (!box) return
|
|
299
|
-
box.textContent = ''
|
|
300
273
|
var available = data && data.available !== false
|
|
301
|
-
var
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
var
|
|
307
|
-
|
|
274
|
+
var intero = (available && data.intero) || {}
|
|
275
|
+
|
|
276
|
+
setText('moodValue', available ? fmt(data.v) : '—')
|
|
277
|
+
setText('moodWord', available ? moodWord(data.v) : '')
|
|
278
|
+
setBar('moodBar', available ? (Number(data.v) + 1) / 2 : 0)
|
|
279
|
+
var moodDelta = available ? historyDelta(data, 'v') : '—'
|
|
280
|
+
paintDelta('moodDelta', moodDelta)
|
|
281
|
+
paintArrow('moodArrow', moodDelta)
|
|
282
|
+
|
|
283
|
+
setText('tensionValue', available ? fmt(intero.tension) : '—')
|
|
284
|
+
setText('tensionWord', available ? STAT_WORDS.tension[statLevel(intero.tension)] : '')
|
|
285
|
+
setBar('tensionBar', intero.tension)
|
|
286
|
+
var tensionDelta = available ? historyDelta(data, 'tension') : '—'
|
|
287
|
+
paintDelta('tensionDelta', tensionDelta)
|
|
288
|
+
paintArrow('tensionArrow', tensionDelta)
|
|
289
|
+
var over = $('tensionOver')
|
|
290
|
+
if (over) {
|
|
291
|
+
var ratio = clamp01(intero.tension)
|
|
292
|
+
over.style.width = ratio > 0.65 ? Math.round((ratio - 0.65) * 100) + '%' : '0%'
|
|
293
|
+
}
|
|
308
294
|
|
|
309
|
-
var
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
[
|
|
313
|
-
|
|
295
|
+
var channels = ['warmth', 'heaviness', 'clarity']
|
|
296
|
+
for (var i = 0; i < channels.length; i += 1) {
|
|
297
|
+
var channel = channels[i]
|
|
298
|
+
var raw = available ? intero[channel] : null
|
|
299
|
+
setText(channel + 'Value', available ? fmt(raw) : '—')
|
|
300
|
+
setText(channel + 'Status', available ? STAT_WORDS[channel][statLevel(raw)] : '')
|
|
301
|
+
setText(channel + 'Note', available ? STAT_NOTES[channel][statLevel(raw)] : '')
|
|
302
|
+
setBar(channel + 'Bar', raw)
|
|
303
|
+
var channelDelta = available ? historyDelta(data, channel) : '—'
|
|
304
|
+
paintArrow(channel + 'Arrow', channelDelta)
|
|
305
|
+
paintDelta(channel + 'Delta', channelDelta)
|
|
306
|
+
}
|
|
307
|
+
|
|
308
|
+
var averages = [
|
|
309
|
+
['avgTension', 'tension'], ['avgWarmth', 'warmth'],
|
|
310
|
+
['avgHeaviness', 'heaviness'], ['avgClarity', 'clarity'],
|
|
314
311
|
]
|
|
315
|
-
for (var
|
|
316
|
-
var
|
|
317
|
-
|
|
318
|
-
var label = document.createElement('span')
|
|
319
|
-
label.className = 'relation-label'
|
|
320
|
-
label.textContent = columns[i][0]
|
|
321
|
-
var value = document.createElement('strong')
|
|
322
|
-
value.className = 'relation-value'
|
|
323
|
-
value.textContent = available && columns[i][1] !== null ? fmt(columns[i][1]) : '–'
|
|
324
|
-
var note = document.createElement('p')
|
|
325
|
-
note.className = 'relation-note'
|
|
326
|
-
note.textContent = available ? columns[i][2] : '—'
|
|
327
|
-
column.appendChild(label)
|
|
328
|
-
column.appendChild(value)
|
|
329
|
-
column.appendChild(note)
|
|
330
|
-
grid.appendChild(column)
|
|
312
|
+
for (var a = 0; a < averages.length; a += 1) {
|
|
313
|
+
var value = Number(intero[averages[a][1]])
|
|
314
|
+
setText(averages[a][0], available && Number.isFinite(value) ? fmt(value) : '—')
|
|
331
315
|
}
|
|
332
|
-
|
|
316
|
+
setText('avgHint', available && Array.isArray(data.history) && data.history.length
|
|
317
|
+
? data.history.length + ' 个采样' : '—')
|
|
333
318
|
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
foot.textContent = available
|
|
339
|
-
? '认识 ' + knownDays() + ' 天 · 冲突 ' + count + ' 次 · 依恋 ' + attachmentOf(safeTrust || 0, safeCloseness || 0, safeConflict)
|
|
340
|
-
: '认识 — 天 · 冲突 — 次 · 依恋 —'
|
|
341
|
-
box.appendChild(foot)
|
|
319
|
+
setText('toneWord', available ? (scalarLabel(data.tone, '') || '正常') : '–')
|
|
320
|
+
setText('relationWord', available ? (scalarLabel(data.relation, '') || '–') : '–')
|
|
321
|
+
setText('autonomyWord', available ? (scalarLabel(data.autonomy, '') || '–') : '–')
|
|
322
|
+
if (available) setText('motivation', data.motivation || '安静待着,等你开口')
|
|
342
323
|
}
|
|
343
324
|
|
|
344
|
-
function
|
|
325
|
+
function renderNow(data) {
|
|
345
326
|
var available = data && data.available !== false
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
setText('
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
if (fill) fill.style.width = Math.round(capacity * 100) + '%'
|
|
355
|
-
setText('capText', total + ' / 200')
|
|
327
|
+
var narrative = (available && data.narrative) || {}
|
|
328
|
+
setText('nowIntent', available ? (String(narrative.intent || '').trim() || '—') : '—')
|
|
329
|
+
setText('nowLength', available && narrative.length_factor !== undefined
|
|
330
|
+
? fmt(narrative.length_factor) : '—')
|
|
331
|
+
setText('nowInitiative', available && narrative.initiative !== undefined
|
|
332
|
+
? fmt(narrative.initiative) : '—')
|
|
333
|
+
setText('nowBand', available ? (String(narrative.metacog_band || '').trim() || '—') : '—')
|
|
334
|
+
setText('nowHint', available ? '只读' : '无数据')
|
|
356
335
|
}
|
|
357
336
|
|
|
358
|
-
function
|
|
337
|
+
function renderToday(data) {
|
|
359
338
|
var available = data && data.available !== false
|
|
360
339
|
var rows = available && Array.isArray(data.memory_recent) ? data.memory_recent : []
|
|
361
|
-
var
|
|
362
|
-
var
|
|
363
|
-
var dayStart = new Date()
|
|
364
|
-
dayStart.setHours(0, 0, 0, 0)
|
|
340
|
+
var dayStart = startOfToday()
|
|
341
|
+
var fresh = 0
|
|
365
342
|
for (var i = 0; i < rows.length; i += 1) {
|
|
366
343
|
var ts = Number(rows[i] && rows[i].ts)
|
|
367
|
-
if (Number.isFinite(ts) && ts
|
|
344
|
+
if (Number.isFinite(ts) && ts >= dayStart) fresh += 1
|
|
368
345
|
}
|
|
369
|
-
setText('
|
|
370
|
-
setText('
|
|
371
|
-
|
|
346
|
+
setText('todayMem', available ? String(fresh) : '—')
|
|
347
|
+
setText('todayTurns', available && Array.isArray(data.history) ? String(data.history.length) : '—')
|
|
348
|
+
var proactive = available ? Number(data.proactive_sent_today) : NaN
|
|
349
|
+
setText('todayProactive', Number.isFinite(proactive) ? String(Math.round(proactive)) : '—')
|
|
372
350
|
}
|
|
373
351
|
|
|
374
|
-
function
|
|
375
|
-
var
|
|
376
|
-
var
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
352
|
+
function mergePoints(data) {
|
|
353
|
+
var extras = data && data.available !== false ? data : {}
|
|
354
|
+
var history = Array.isArray(extras.history) ? extras.history : []
|
|
355
|
+
if (history.length) {
|
|
356
|
+
return history.map(function (item) {
|
|
357
|
+
var ts = Number(item.ts) || 0
|
|
358
|
+
return { ts: ts < 1e12 ? ts * 1000 : ts, v: Number(item.v) || 0 }
|
|
359
|
+
})
|
|
360
|
+
}
|
|
361
|
+
if (seriesPoints && seriesPoints.length) {
|
|
362
|
+
return seriesPoints.map(function (item) {
|
|
363
|
+
var ts = Number(item.ts) || 0
|
|
364
|
+
return { ts: ts < 1e12 ? ts * 1000 : ts, v: Number(item.v) || 0 }
|
|
365
|
+
})
|
|
366
|
+
}
|
|
367
|
+
return []
|
|
384
368
|
}
|
|
385
369
|
|
|
370
|
+
function drawChartThrough(canvas, points) {
|
|
371
|
+
if (!canvas) return
|
|
372
|
+
var width = Math.max(120, Math.round(canvas.clientWidth || 320))
|
|
373
|
+
var height = Math.max(90, Math.round(canvas.clientHeight || 150))
|
|
374
|
+
var dpr = window.devicePixelRatio || 1
|
|
375
|
+
canvas.width = Math.round(width * dpr)
|
|
376
|
+
canvas.height = Math.round(height * dpr)
|
|
377
|
+
var ctx = canvas.getContext('2d')
|
|
378
|
+
if (!ctx) return
|
|
379
|
+
var palette = chartPalette()
|
|
380
|
+
ctx.setTransform(dpr, 0, 0, dpr, 0, 0)
|
|
381
|
+
ctx.clearRect(0, 0, width, height)
|
|
382
|
+
|
|
383
|
+
ctx.strokeStyle = palette.grid
|
|
384
|
+
ctx.globalAlpha = 0.45
|
|
385
|
+
ctx.lineWidth = 1
|
|
386
|
+
for (var g = 1; g <= 3; g += 1) {
|
|
387
|
+
var y = Math.round((height / 4) * g) + 0.5
|
|
388
|
+
ctx.beginPath()
|
|
389
|
+
ctx.moveTo(0, y)
|
|
390
|
+
ctx.lineTo(width, y)
|
|
391
|
+
ctx.stroke()
|
|
392
|
+
}
|
|
393
|
+
|
|
394
|
+
if (!points.length) return
|
|
395
|
+
|
|
396
|
+
var minTs = points[0].ts
|
|
397
|
+
var maxTs = points[points.length - 1].ts
|
|
398
|
+
var span = Math.max(1, maxTs - minTs)
|
|
399
|
+
var pad = 6
|
|
400
|
+
var usable = height - pad * 2
|
|
401
|
+
|
|
402
|
+
function xOf(ts) { return ((ts - minTs) / span) * (width - 2) + 1 }
|
|
403
|
+
function yOf(v) { return pad + (1 - (Math.max(-1, Math.min(1, v)) + 1) / 2) * usable }
|
|
404
|
+
|
|
405
|
+
ctx.globalAlpha = 1
|
|
406
|
+
|
|
407
|
+
ctx.beginPath()
|
|
408
|
+
ctx.moveTo(xOf(points[0].ts), yOf(points[0].v))
|
|
409
|
+
for (var i = 1; i < points.length; i += 1) ctx.lineTo(xOf(points[i].ts), yOf(points[i].v))
|
|
410
|
+
ctx.lineTo(xOf(points[points.length - 1].ts), height)
|
|
411
|
+
ctx.lineTo(xOf(points[0].ts), height)
|
|
412
|
+
ctx.closePath()
|
|
413
|
+
ctx.globalAlpha = 0.14
|
|
414
|
+
ctx.fillStyle = palette.accent
|
|
415
|
+
ctx.fill()
|
|
416
|
+
ctx.globalAlpha = 1
|
|
417
|
+
|
|
418
|
+
ctx.beginPath()
|
|
419
|
+
ctx.moveTo(xOf(points[0].ts), yOf(points[0].v))
|
|
420
|
+
for (var j = 1; j < points.length; j += 1) ctx.lineTo(xOf(points[j].ts), yOf(points[j].v))
|
|
421
|
+
ctx.strokeStyle = palette.accent
|
|
422
|
+
ctx.lineWidth = 2
|
|
423
|
+
ctx.lineJoin = 'round'
|
|
424
|
+
ctx.lineCap = 'round'
|
|
425
|
+
ctx.stroke()
|
|
426
|
+
|
|
427
|
+
var last = points[points.length - 1]
|
|
428
|
+
ctx.beginPath()
|
|
429
|
+
ctx.arc(xOf(last.ts), yOf(last.v), 3.4, 0, Math.PI * 2)
|
|
430
|
+
ctx.fillStyle = palette.ink
|
|
431
|
+
ctx.fill()
|
|
432
|
+
}
|
|
433
|
+
|
|
434
|
+
function renderChart(data) {
|
|
435
|
+
var canvas = $('moodChart')
|
|
436
|
+
if (!canvas) return
|
|
437
|
+
var points = mergePoints(data)
|
|
438
|
+
var empty = $('chartEmpty')
|
|
439
|
+
setText('chartHint', points.length ? points.length + ' 个采样点' : '—')
|
|
440
|
+
if (empty) empty.hidden = points.length > 0
|
|
441
|
+
canvas.hidden = !points.length
|
|
442
|
+
if (!points.length) return
|
|
443
|
+
drawChartThrough(canvas, points)
|
|
444
|
+
}
|
|
445
|
+
|
|
446
|
+
/* -------------------------------------------------------------- 记忆 --- */
|
|
447
|
+
|
|
386
448
|
function renderMemories(data) {
|
|
387
449
|
var list = $('memoryList')
|
|
388
|
-
var hint = $('memoryHint')
|
|
389
450
|
if (!list) return
|
|
390
451
|
list.textContent = ''
|
|
391
452
|
var rows = data && data.available !== false && Array.isArray(data.memory_recent)
|
|
@@ -402,344 +463,278 @@
|
|
|
402
463
|
}
|
|
403
464
|
for (var i = 0; i < rows.length; i += 1) {
|
|
404
465
|
var item = rows[i] || {}
|
|
405
|
-
var text = String(item.text || '').slice(0, 40)
|
|
406
466
|
var li = document.createElement('li')
|
|
407
|
-
var head = document.createElement('div')
|
|
408
|
-
head.className = 'memory-head'
|
|
409
467
|
var time = document.createElement('time')
|
|
410
468
|
time.textContent = relativeTime(Number(item.ts))
|
|
411
|
-
var
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
head.appendChild(time)
|
|
415
|
-
head.appendChild(tag)
|
|
469
|
+
var kind = document.createElement('span')
|
|
470
|
+
kind.className = 'memory-kind'
|
|
471
|
+
kind.textContent = memoryKind(item.text)
|
|
416
472
|
var body = document.createElement('span')
|
|
417
473
|
body.className = 'memory-text'
|
|
418
|
-
body.textContent = text
|
|
419
|
-
li.appendChild(
|
|
474
|
+
body.textContent = String(item.text || '').slice(0, 80)
|
|
475
|
+
li.appendChild(time)
|
|
476
|
+
li.appendChild(kind)
|
|
420
477
|
li.appendChild(body)
|
|
421
478
|
list.appendChild(li)
|
|
422
479
|
}
|
|
423
480
|
}
|
|
424
481
|
|
|
425
|
-
function
|
|
426
|
-
var
|
|
427
|
-
var
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
return seriesPoints.map(function (item) {
|
|
435
|
-
return { ts: Number(item.ts) / 1000, v: Number(item.value) || 0 }
|
|
436
|
-
})
|
|
437
|
-
}
|
|
438
|
-
if (data && data.available !== false) {
|
|
439
|
-
localPoints.push({ ts: Date.now() / 1000, v: Number(data.v) || 0 })
|
|
440
|
-
if (localPoints.length > 120) localPoints = localPoints.slice(-120)
|
|
482
|
+
function renderMemorySummary(data) {
|
|
483
|
+
var available = data && data.available !== false
|
|
484
|
+
var rows = available && Array.isArray(data.memory_recent) ? data.memory_recent : []
|
|
485
|
+
var total = available && typeof data.memory_count === 'number' ? data.memory_count : rows.length
|
|
486
|
+
var dayStart = startOfToday()
|
|
487
|
+
var today = 0
|
|
488
|
+
for (var i = 0; i < rows.length; i += 1) {
|
|
489
|
+
var ts = Number(rows[i] && rows[i].ts)
|
|
490
|
+
if (Number.isFinite(ts) && ts >= dayStart) today += 1
|
|
441
491
|
}
|
|
442
|
-
|
|
492
|
+
setText('memTotal', String(total))
|
|
493
|
+
setText('memToday', String(available ? today : '—'))
|
|
494
|
+
var recall = available ? Number(data.recall_count) : NaN
|
|
495
|
+
setText('memRecall', Number.isFinite(recall) && recall > 0 ? String(Math.round(recall)) : '—')
|
|
496
|
+
var ratio = clamp01(total / 200)
|
|
497
|
+
setBar('capFill', ratio)
|
|
498
|
+
setText('capFillText', pct(ratio))
|
|
499
|
+
setText('capText', total + ' / 200')
|
|
443
500
|
}
|
|
444
501
|
|
|
445
|
-
|
|
446
|
-
if (!canvas) return
|
|
447
|
-
var points = mergePoints(data)
|
|
448
|
-
var wrapWidth = canvas.clientWidth || canvas.parentElement.clientWidth || 600
|
|
449
|
-
var wrapHeight = 170
|
|
450
|
-
var dpr = window.devicePixelRatio || 1
|
|
451
|
-
canvas.width = Math.round(wrapWidth * dpr)
|
|
452
|
-
canvas.height = Math.round(wrapHeight * dpr)
|
|
453
|
-
var ctx = canvas.getContext('2d')
|
|
454
|
-
if (!ctx) return
|
|
455
|
-
ctx.setTransform(dpr, 0, 0, dpr, 0, 0)
|
|
456
|
-
ctx.clearRect(0, 0, wrapWidth, wrapHeight)
|
|
457
|
-
|
|
458
|
-
var styles = window.getComputedStyle(document.documentElement)
|
|
459
|
-
var lineColor = styles.getPropertyValue('--accent').trim() || '#4176e6'
|
|
460
|
-
var gridColor = styles.getPropertyValue('--line').trim() || 'rgba(127,127,127,.2)'
|
|
461
|
-
var mutedColor = styles.getPropertyValue('--text-3').trim() || '#81858c'
|
|
462
|
-
var amber = styles.getPropertyValue('--amber').trim() || '#f7ad31'
|
|
463
|
-
|
|
464
|
-
var padL = 34
|
|
465
|
-
var padR = 12
|
|
466
|
-
var padT = 12
|
|
467
|
-
var padB = 20
|
|
468
|
-
var plotW = Math.max(10, wrapWidth - padL - padR)
|
|
469
|
-
var plotH = Math.max(10, wrapHeight - padT - padB)
|
|
470
|
-
|
|
471
|
-
ctx.strokeStyle = gridColor
|
|
472
|
-
ctx.lineWidth = 1
|
|
473
|
-
ctx.font = '10px -apple-system, "Microsoft YaHei", sans-serif'
|
|
474
|
-
ctx.fillStyle = mutedColor
|
|
475
|
-
for (var g = 0; g < 5; g += 1) {
|
|
476
|
-
var y = padT + (plotH * g) / 4
|
|
477
|
-
ctx.beginPath()
|
|
478
|
-
ctx.moveTo(padL, y)
|
|
479
|
-
ctx.lineTo(padL + plotW, y)
|
|
480
|
-
ctx.stroke()
|
|
481
|
-
if (g === 0 || g === 4) {
|
|
482
|
-
var label = g === 0 ? '1.0' : '-1.0'
|
|
483
|
-
ctx.fillText(label, 6, y + 3)
|
|
484
|
-
}
|
|
485
|
-
}
|
|
486
|
-
if (points.length > 1) {
|
|
487
|
-
var minTs = points[0].ts
|
|
488
|
-
var maxTs = points[points.length - 1].ts
|
|
489
|
-
var span = maxTs - minTs || 1
|
|
490
|
-
var toX = function (ts) { return padL + ((ts - minTs) / span) * plotW }
|
|
491
|
-
var toY = function (v) { return padT + ((1 - Math.max(-1, Math.min(1, v))) / 2) * plotH }
|
|
492
|
-
|
|
493
|
-
ctx.beginPath()
|
|
494
|
-
for (var i = 0; i < points.length; i += 1) {
|
|
495
|
-
var x = toX(points[i].ts)
|
|
496
|
-
var py = toY(points[i].v)
|
|
497
|
-
if (i === 0) ctx.moveTo(x, py)
|
|
498
|
-
else ctx.lineTo(x, py)
|
|
499
|
-
}
|
|
500
|
-
ctx.strokeStyle = lineColor
|
|
501
|
-
ctx.lineWidth = 2
|
|
502
|
-
ctx.lineJoin = 'round'
|
|
503
|
-
ctx.stroke()
|
|
502
|
+
/* -------------------------------------------------------------- 关系 --- */
|
|
504
503
|
|
|
505
|
-
|
|
506
|
-
|
|
507
|
-
|
|
508
|
-
|
|
509
|
-
|
|
510
|
-
|
|
511
|
-
|
|
512
|
-
|
|
513
|
-
|
|
514
|
-
|
|
515
|
-
var eventTs = Number(events[e].ts) / 1000
|
|
516
|
-
if (!Number.isFinite(eventTs) || eventTs < minTs || eventTs > maxTs) continue
|
|
517
|
-
var eventY = toY(last.v)
|
|
518
|
-
for (var p = 0; p < points.length; p += 1) {
|
|
519
|
-
if (points[p].ts >= eventTs) { eventY = toY(points[p].v); break }
|
|
520
|
-
}
|
|
521
|
-
ctx.beginPath()
|
|
522
|
-
ctx.arc(toX(eventTs), eventY, 2.6, 0, Math.PI * 2)
|
|
523
|
-
ctx.fill()
|
|
524
|
-
}
|
|
525
|
-
setText(canvas.id === 'deskChart' ? 'deskChartHint' : 'chartHint', '单线 + ' + events.length + ' 个事件点')
|
|
526
|
-
} else {
|
|
527
|
-
ctx.strokeStyle = lineColor
|
|
528
|
-
ctx.lineWidth = 2
|
|
529
|
-
var midY = padT + plotH / 2
|
|
530
|
-
ctx.beginPath()
|
|
531
|
-
ctx.moveTo(padL, midY)
|
|
532
|
-
ctx.lineTo(padL + plotW, midY)
|
|
533
|
-
ctx.stroke()
|
|
534
|
-
if (points.length === 1) {
|
|
535
|
-
ctx.fillStyle = lineColor
|
|
536
|
-
ctx.beginPath()
|
|
537
|
-
ctx.arc(padL + plotW - 3, padT + ((1 - Math.max(-1, Math.min(1, points[0].v))) / 2) * plotH, 3, 0, Math.PI * 2)
|
|
538
|
-
ctx.fill()
|
|
539
|
-
}
|
|
540
|
-
setText(canvas.id === 'deskChart' ? 'deskChartHint' : 'chartHint', '积累中 · 单点')
|
|
504
|
+
function relationRow(label, value, note, available) {
|
|
505
|
+
var row = document.createElement('div')
|
|
506
|
+
var dt = document.createElement('dt')
|
|
507
|
+
dt.textContent = label
|
|
508
|
+
var dd = document.createElement('dd')
|
|
509
|
+
dd.textContent = available ? fmt(value) : '—'
|
|
510
|
+
if (note && available) {
|
|
511
|
+
var small = document.createElement('small')
|
|
512
|
+
small.textContent = note
|
|
513
|
+
dd.appendChild(small)
|
|
541
514
|
}
|
|
542
|
-
|
|
543
|
-
|
|
544
|
-
|
|
545
|
-
|
|
546
|
-
function renderChart(data) {
|
|
547
|
-
drawChartInto($('moodChart'), data)
|
|
548
|
-
drawChartInto($('deskChart'), data)
|
|
549
|
-
}
|
|
550
|
-
|
|
551
|
-
var DESK_HIDDEN_KEY = 'biocore-desk-hidden'
|
|
552
|
-
var RAIL_KEY = 'biocore-rail-collapsed'
|
|
553
|
-
|
|
554
|
-
function copyText(fromId, toId) {
|
|
555
|
-
var from = $(fromId)
|
|
556
|
-
var to = $(toId)
|
|
557
|
-
if (from && to) to.textContent = from.textContent
|
|
515
|
+
row.appendChild(dt)
|
|
516
|
+
row.appendChild(dd)
|
|
517
|
+
return row
|
|
558
518
|
}
|
|
559
519
|
|
|
560
|
-
function
|
|
561
|
-
var
|
|
562
|
-
|
|
563
|
-
|
|
564
|
-
}
|
|
565
|
-
|
|
566
|
-
/** Desktop grid mirrors the mobile DOM (single data source, no duplicate state). */
|
|
567
|
-
function deskSync(data) {
|
|
568
|
-
copyText('motivation', 'deskMotivation')
|
|
569
|
-
copyText('statusText', 'deskStatusText')
|
|
570
|
-
var mobileStatus = $('status')
|
|
571
|
-
var deskStatus = $('deskStatus')
|
|
572
|
-
if (mobileStatus && deskStatus) {
|
|
573
|
-
deskStatus.className = 'status' + (mobileStatus.classList.contains('on') ? ' on' : (mobileStatus.classList.contains('off') ? ' off' : ''))
|
|
574
|
-
}
|
|
575
|
-
copyText('num-v', 'deskNumV')
|
|
576
|
-
copyText('moodNote', 'deskWordV')
|
|
577
|
-
copyText('arrow-v', 'deskArrowV')
|
|
578
|
-
copyText('delta-v', 'deskDeltaV')
|
|
579
|
-
copyBar('bar-v', 'deskBarV')
|
|
580
|
-
copyText('num-emotion-tension', 'deskNumTension')
|
|
581
|
-
copyText('status-tension', 'deskWordTension')
|
|
582
|
-
copyText('arrow-emotion-tension', 'deskArrowTension')
|
|
583
|
-
copyText('delta-emotion-tension', 'deskDeltaTension')
|
|
584
|
-
copyBar('bar-emotion-tension', 'deskBarTension')
|
|
585
|
-
copyText('runtimeUptime', 'deskUptime')
|
|
586
|
-
copyText('runtimeTurns', 'deskTurns')
|
|
587
|
-
copyText('runtimeVector', 'deskVector')
|
|
588
|
-
copyText('capText', 'deskCapText')
|
|
589
|
-
copyBar('capFill', 'deskCapFill')
|
|
590
|
-
copyText('dataDirText', 'deskDataDir')
|
|
591
|
-
copyText('memTotal', 'deskTodayMem')
|
|
592
|
-
copyText('runtimeTurns', 'deskTodayTurns')
|
|
593
|
-
copyText('memoryHint', 'deskMemoryHint')
|
|
594
|
-
var memoryFrom = $('memoryList')
|
|
595
|
-
var memoryTo = $('deskMemoryList')
|
|
596
|
-
if (memoryFrom && memoryTo) memoryTo.innerHTML = memoryFrom.innerHTML
|
|
520
|
+
function renderRelation(data) {
|
|
521
|
+
var box = $('metrics')
|
|
522
|
+
if (!box) return
|
|
523
|
+
box.textContent = ''
|
|
597
524
|
var available = data && data.available !== false
|
|
598
|
-
var intero = (available && data.intero) || {}
|
|
599
|
-
var channels = [['tension', 'deskAvgTension'], ['warmth', 'deskAvgWarmth'], ['heaviness', 'deskAvgHeaviness'], ['clarity', 'deskAvgClarity']]
|
|
600
|
-
for (var i = 0; i < channels.length; i += 1) {
|
|
601
|
-
var value = Number(intero[channels[i][0]])
|
|
602
|
-
setText(channels[i][1], Number.isFinite(value) ? fmt(value) : '—')
|
|
603
|
-
}
|
|
604
525
|
var rel = (available && data.relationship_metrics) || {}
|
|
605
|
-
|
|
606
|
-
|
|
607
|
-
|
|
608
|
-
|
|
609
|
-
var
|
|
610
|
-
var
|
|
611
|
-
setText('deskPersona', personaChars ? '已载入' : '未载入')
|
|
612
|
-
setText('deskPersonaNote', personaChars ? (personaChars + ' 字 · 编译后只改倾向') : '还没有写人设')
|
|
613
|
-
}
|
|
526
|
+
var closeness = Number(rel.closeness)
|
|
527
|
+
var trust = Number(rel.trust)
|
|
528
|
+
var conflict = Number(rel.conflict)
|
|
529
|
+
var safeCloseness = Number.isFinite(closeness) ? closeness : 0
|
|
530
|
+
var safeTrust = Number.isFinite(trust) ? trust : 0
|
|
531
|
+
var safeConflict = Number.isFinite(conflict) ? conflict : 0
|
|
614
532
|
|
|
615
|
-
|
|
616
|
-
|
|
617
|
-
|
|
533
|
+
var list = document.createElement('dl')
|
|
534
|
+
list.className = 'data-list'
|
|
535
|
+
list.appendChild(relationRow('亲近', safeCloseness, '', available))
|
|
536
|
+
list.appendChild(relationRow('信任', safeTrust, '', available))
|
|
537
|
+
list.appendChild(relationRow('冲突', safeConflict, '', available))
|
|
538
|
+
box.appendChild(list)
|
|
618
539
|
|
|
619
|
-
|
|
620
|
-
if (!
|
|
621
|
-
|
|
540
|
+
var count = Number(rel.conflict_count)
|
|
541
|
+
if (!Number.isFinite(count)) count = safeConflict > 0 ? Math.round(safeConflict * 10) : 0
|
|
542
|
+
var foot = document.createElement('div')
|
|
543
|
+
foot.className = 'relation-foot'
|
|
544
|
+
var facts = [
|
|
545
|
+
['认识', available ? knownDays() + ' 天' : '— 天'],
|
|
546
|
+
['冲突', available ? count + ' 次' : '— 次'],
|
|
547
|
+
['依恋', available ? attachmentOf(safeTrust, safeCloseness, safeConflict) : '—'],
|
|
548
|
+
]
|
|
549
|
+
for (var i = 0; i < facts.length; i += 1) {
|
|
550
|
+
var cell = document.createElement('span')
|
|
551
|
+
cell.textContent = facts[i][0]
|
|
552
|
+
var value = document.createElement('b')
|
|
553
|
+
value.textContent = facts[i][1]
|
|
554
|
+
cell.appendChild(value)
|
|
555
|
+
foot.appendChild(cell)
|
|
556
|
+
}
|
|
557
|
+
box.appendChild(foot)
|
|
558
|
+
setText('relationHint', available ? '只读' : '无数据')
|
|
622
559
|
}
|
|
623
560
|
|
|
624
|
-
function
|
|
625
|
-
|
|
626
|
-
|
|
627
|
-
|
|
628
|
-
|
|
629
|
-
|
|
561
|
+
function renderNarrative(data) {
|
|
562
|
+
var box = $('narrative')
|
|
563
|
+
if (!box) return
|
|
564
|
+
box.textContent = ''
|
|
565
|
+
var available = data && data.available !== false
|
|
566
|
+
var narrative = (available && data.narrative) || {}
|
|
567
|
+
var rows = [
|
|
568
|
+
['意图', available ? (String(narrative.intent || '').trim() || '—') : '—'],
|
|
569
|
+
['主动性', available && narrative.initiative !== undefined ? fmt(narrative.initiative) : '—'],
|
|
570
|
+
['长度因子', available && narrative.length_factor !== undefined ? fmt(narrative.length_factor) : '—'],
|
|
571
|
+
['元认知带', available ? (String(narrative.metacog_band || '').trim() || '—') : '—'],
|
|
572
|
+
['允许纠偏', available ? (narrative.allow_correction === true ? '是' : '否') : '—'],
|
|
573
|
+
]
|
|
574
|
+
for (var i = 0; i < rows.length; i += 1) {
|
|
575
|
+
var wrap = document.createElement('div')
|
|
576
|
+
var dt = document.createElement('dt')
|
|
577
|
+
dt.textContent = rows[i][0]
|
|
578
|
+
var dd = document.createElement('dd')
|
|
579
|
+
dd.textContent = rows[i][1]
|
|
580
|
+
wrap.appendChild(dt)
|
|
581
|
+
wrap.appendChild(dd)
|
|
582
|
+
box.appendChild(wrap)
|
|
583
|
+
}
|
|
584
|
+
setText('narrativeHint', available ? '上一轮' : '无数据')
|
|
630
585
|
}
|
|
631
586
|
|
|
632
|
-
|
|
633
|
-
try { window.localStorage.setItem(DESK_HIDDEN_KEY, JSON.stringify(list)) } catch (e) { /* ignore */ }
|
|
634
|
-
}
|
|
587
|
+
/* -------------------------------------------------------------- 工具 --- */
|
|
635
588
|
|
|
636
|
-
function
|
|
637
|
-
var
|
|
638
|
-
|
|
639
|
-
|
|
640
|
-
|
|
589
|
+
function renderRuntime(data) {
|
|
590
|
+
var available = data && data.available !== false
|
|
591
|
+
var seconds = Math.max(0, Math.round((Date.now() - PAGE_START) / 1000))
|
|
592
|
+
var uptime = seconds < 60 ? seconds + ' 秒'
|
|
593
|
+
: (seconds < 3600 ? Math.round(seconds / 60) + ' 分钟' : Math.round(seconds / 3600) + ' 小时')
|
|
594
|
+
setText('runtimeUptime', uptime)
|
|
595
|
+
setText('runtimeTurns', available && Array.isArray(data.history) ? String(data.history.length) : '—')
|
|
596
|
+
var mode = $('embeddingMode')
|
|
597
|
+
setText('runtimeVector', mode ? (mode.value || 'auto') : '—')
|
|
598
|
+
setText('runSession', '会话 ' + shortSession())
|
|
599
|
+
setText('diagSession', SESSION || '未指定')
|
|
641
600
|
}
|
|
642
601
|
|
|
643
|
-
function
|
|
644
|
-
|
|
645
|
-
|
|
646
|
-
|
|
647
|
-
|
|
648
|
-
|
|
649
|
-
button.className = 'desk-hide'
|
|
650
|
-
button.setAttribute('data-desk-hide', '')
|
|
651
|
-
button.setAttribute('aria-label', '隐藏这张卡片')
|
|
652
|
-
button.textContent = '−'
|
|
653
|
-
card.appendChild(button)
|
|
602
|
+
function renderDiagnostics(data) {
|
|
603
|
+
var available = data && data.available !== false
|
|
604
|
+
var degraded = available && Array.isArray(data.degraded) ? data.degraded : []
|
|
605
|
+
var errors = available && Array.isArray(data.errors) ? data.errors : []
|
|
606
|
+
var parts = degraded.concat(errors).filter(Boolean).map(function (item) {
|
|
607
|
+
return String(item).slice(0, 40)
|
|
654
608
|
})
|
|
609
|
+
setText('diagDegraded', parts.length ? parts.join(',') : '无')
|
|
610
|
+
var generated = available ? Number(data.generated_at) : NaN
|
|
611
|
+
setText('diagGenerated', Number.isFinite(generated) && generated > 0
|
|
612
|
+
? new Date(generated).toLocaleTimeString() : '—')
|
|
613
|
+
setText('diagAdapter', available && data.adapter && data.adapter.version
|
|
614
|
+
? String(data.adapter.version) : '—')
|
|
615
|
+
}
|
|
616
|
+
|
|
617
|
+
function loadStats() {
|
|
618
|
+
var rate = $('cacheStatText')
|
|
619
|
+
var detail = $('cacheDetail')
|
|
620
|
+
if (!rate) return
|
|
621
|
+
fetch('/api/biocore/stats', { headers: { accept: 'application/json' } })
|
|
622
|
+
.then(function (response) { return response.ok ? response.json() : null })
|
|
623
|
+
.then(function (body) {
|
|
624
|
+
var cache = body && body.cache ? body.cache : null
|
|
625
|
+
if (cache && Number(cache.turns) > 0) {
|
|
626
|
+
var hitRate = Number(cache.hit_rate) || 0
|
|
627
|
+
rate.textContent = (Math.round(hitRate * 1000) / 10) + '%'
|
|
628
|
+
if (detail) {
|
|
629
|
+
detail.textContent = (Number(cache.cache_read) || 0) + ' / ' + (Number(cache.prompt_tokens) || 0)
|
|
630
|
+
}
|
|
631
|
+
return
|
|
632
|
+
}
|
|
633
|
+
var embed = body && body.embed ? body.embed : null
|
|
634
|
+
var hit = embed ? (Number(embed.hit) || 0) : 0
|
|
635
|
+
var miss = embed ? (Number(embed.miss) || 0) : 0
|
|
636
|
+
if (hit + miss > 0) {
|
|
637
|
+
rate.textContent = (Math.round(hit / (hit + miss) * 1000) / 10) + '%'
|
|
638
|
+
if (detail) detail.textContent = 'hit ' + hit + ' / miss ' + miss
|
|
639
|
+
return
|
|
640
|
+
}
|
|
641
|
+
rate.textContent = '暂无数据'
|
|
642
|
+
if (detail) detail.textContent = '—'
|
|
643
|
+
})
|
|
644
|
+
.catch(function () {
|
|
645
|
+
rate.textContent = '暂无数据'
|
|
646
|
+
if (detail) detail.textContent = '—'
|
|
647
|
+
})
|
|
655
648
|
}
|
|
656
649
|
|
|
657
|
-
function
|
|
658
|
-
var named = card.getAttribute('data-card-label')
|
|
659
|
-
if (named) return named
|
|
660
|
-
var title = card.querySelector('h2')
|
|
661
|
-
return title ? title.textContent.trim() : card.id
|
|
662
|
-
}
|
|
663
|
-
|
|
664
|
-
function restoreDeskCard(id) {
|
|
665
|
-
writeDeskHidden(readDeskHidden().filter(function (item) { return item !== id }))
|
|
666
|
-
applyDeskHidden()
|
|
667
|
-
renderAddCards()
|
|
668
|
-
layoutToast('已恢复卡片')
|
|
669
|
-
}
|
|
650
|
+
var traceFormatter = function () { return window.BioTraceView || null }
|
|
670
651
|
|
|
671
|
-
function
|
|
672
|
-
var
|
|
673
|
-
|
|
674
|
-
|
|
675
|
-
|
|
676
|
-
|
|
652
|
+
function renderTrace(body) {
|
|
653
|
+
var list = $('traceList')
|
|
654
|
+
if (!list) return
|
|
655
|
+
list.textContent = ''
|
|
656
|
+
var view = traceFormatter()
|
|
657
|
+
var failed = !body || body.ok === false
|
|
658
|
+
var rows = !failed && Array.isArray(body.rows) ? body.rows.slice(0, TRACE_LIMIT) : []
|
|
659
|
+
var formatted = {
|
|
660
|
+
rows: view ? rows.map(function (row) { return view.formatRow(row) }) : rows,
|
|
661
|
+
empty: !rows.length,
|
|
662
|
+
error: failed,
|
|
663
|
+
}
|
|
664
|
+
setText('traceHint', rows.length ? '最近 ' + rows.length + ' 轮' : '还没有记录')
|
|
665
|
+
if (formatted.empty) {
|
|
666
|
+
var empty = document.createElement('p')
|
|
667
|
+
empty.className = 'state-note'
|
|
668
|
+
empty.textContent = failed ? '读取失败,稍后自动重试' : '还没有记录'
|
|
669
|
+
list.appendChild(empty)
|
|
677
670
|
return
|
|
678
671
|
}
|
|
679
|
-
var
|
|
680
|
-
|
|
681
|
-
|
|
682
|
-
|
|
683
|
-
|
|
684
|
-
|
|
685
|
-
|
|
686
|
-
|
|
687
|
-
|
|
688
|
-
|
|
689
|
-
|
|
690
|
-
|
|
691
|
-
|
|
692
|
-
|
|
693
|
-
|
|
694
|
-
|
|
695
|
-
|
|
696
|
-
|
|
697
|
-
|
|
698
|
-
|
|
699
|
-
|
|
700
|
-
|
|
701
|
-
|
|
702
|
-
|
|
703
|
-
|
|
704
|
-
var storage = layoutStorage()
|
|
705
|
-
var collapsed = false
|
|
706
|
-
try { collapsed = !!storage && storage.getItem(RAIL_KEY) === '1' } catch (e) { collapsed = false }
|
|
707
|
-
railApply(collapsed)
|
|
708
|
-
var button = $('railToggle')
|
|
709
|
-
if (!button) return
|
|
710
|
-
button.addEventListener('click', function () {
|
|
711
|
-
var next = !document.body.classList.contains('rail-collapsed')
|
|
712
|
-
railApply(next)
|
|
713
|
-
try { if (storage) storage.setItem(RAIL_KEY, next ? '1' : '0') } catch (e) { /* ignore */ }
|
|
672
|
+
for (var i = 0; i < formatted.rows.length; i += 1) {
|
|
673
|
+
var row = formatted.rows[i] || {}
|
|
674
|
+
var item = document.createElement('div')
|
|
675
|
+
item.className = 'trace-row'
|
|
676
|
+
var values = [row.time, row.changes, row.intent, row.length, row.gate, row.memory]
|
|
677
|
+
for (var c = 0; c < values.length; c += 1) {
|
|
678
|
+
var cell = document.createElement('span')
|
|
679
|
+
if (c === 0) cell.className = 'trace-time'
|
|
680
|
+
var cellText = String(values[c] || '—').split(' · ').join(',')
|
|
681
|
+
cell.textContent = cellText
|
|
682
|
+
cell.title = cellText
|
|
683
|
+
cell.setAttribute('data-label', ['时间', '状态变化', '意图', '长度来源', '闸命中', '记忆'][c])
|
|
684
|
+
item.appendChild(cell)
|
|
685
|
+
}
|
|
686
|
+
list.appendChild(item)
|
|
687
|
+
}
|
|
688
|
+
}
|
|
689
|
+
|
|
690
|
+
function fetchTrace() {
|
|
691
|
+
if (!SESSION) {
|
|
692
|
+
renderTrace({ ok: true, rows: [] })
|
|
693
|
+
return Promise.resolve(null)
|
|
694
|
+
}
|
|
695
|
+
return fetch('/api/biocore/trace?session=' + encodeURIComponent(SESSION) + '&limit=' + TRACE_LIMIT, {
|
|
696
|
+
headers: { accept: 'application/json' },
|
|
714
697
|
})
|
|
698
|
+
.then(function (response) { return response.json() })
|
|
699
|
+
.then(function (body) { renderTrace(body); return body })
|
|
700
|
+
.catch(function () { renderTrace(null); return null })
|
|
715
701
|
}
|
|
716
702
|
|
|
717
|
-
|
|
718
|
-
|
|
719
|
-
|
|
720
|
-
|
|
703
|
+
/* ------------------------------------------------------------ 状态轮询 --- */
|
|
704
|
+
|
|
705
|
+
function finishBoot() {
|
|
706
|
+
if (bootDone) return
|
|
707
|
+
bootDone = true
|
|
708
|
+
document.body.classList.remove('is-booting')
|
|
721
709
|
}
|
|
722
710
|
|
|
723
711
|
function render(data) {
|
|
724
712
|
lastData = data
|
|
725
|
-
renderNumbers(data)
|
|
726
713
|
renderMetrics(data)
|
|
714
|
+
renderNow(data)
|
|
715
|
+
renderToday(data)
|
|
727
716
|
renderMemories(data)
|
|
728
717
|
renderMemorySummary(data)
|
|
729
|
-
|
|
718
|
+
renderRelation(data)
|
|
719
|
+
renderNarrative(data)
|
|
730
720
|
renderRuntime(data)
|
|
721
|
+
renderDiagnostics(data)
|
|
731
722
|
renderChart(data)
|
|
732
|
-
|
|
733
|
-
|
|
734
|
-
|
|
723
|
+
var memory = data && data.available !== false && typeof data.memory_count === 'number'
|
|
724
|
+
? '记忆 ' + data.memory_count : '记忆 -'
|
|
725
|
+
setText('footerMeta', '会话 ' + (SESSION || '-') + ' ' + memory + ' 每 ' + (POLL_MS / 1000) + ' 秒刷新')
|
|
726
|
+
var live = !!(data && data.available !== false)
|
|
727
|
+
document.body.classList.toggle('is-empty', !live)
|
|
735
728
|
if (!data) renderEmpty('request_failed')
|
|
736
729
|
else if (data.available === false) renderEmpty(String(data.reason || 'request_failed'))
|
|
737
730
|
else clearEmpty()
|
|
731
|
+
finishBoot()
|
|
738
732
|
}
|
|
739
733
|
|
|
740
734
|
function fetchState() {
|
|
741
735
|
if (!SESSION) {
|
|
742
736
|
setStatus(false, '未连接')
|
|
737
|
+
render(null)
|
|
743
738
|
setText('motivation', '没有会话参数,先在聊天会话里打开')
|
|
744
739
|
return Promise.resolve(null)
|
|
745
740
|
}
|
|
@@ -749,7 +744,8 @@
|
|
|
749
744
|
.then(function (response) { return response.json() })
|
|
750
745
|
.then(function (data) {
|
|
751
746
|
lastSuccess = Date.now()
|
|
752
|
-
|
|
747
|
+
var live = data && data.available !== false
|
|
748
|
+
setStatus(live, live ? '已连接 0 秒前' : '还没有状态')
|
|
753
749
|
render(data)
|
|
754
750
|
return data
|
|
755
751
|
})
|
|
@@ -767,13 +763,20 @@
|
|
|
767
763
|
})
|
|
768
764
|
.then(function (response) { return response.ok ? response.json() : null })
|
|
769
765
|
.then(function (body) {
|
|
770
|
-
if (body &&
|
|
771
|
-
seriesPoints = body.
|
|
766
|
+
if (body && Array.isArray(body.points) && body.points.length) {
|
|
767
|
+
seriesPoints = body.points
|
|
768
|
+
} else if (body && body.series && Array.isArray(body.series.v)
|
|
769
|
+
&& Array.isArray(body.series.ts) && body.series.v.length === body.series.ts.length) {
|
|
770
|
+
seriesPoints = body.series.ts.map(function (ts, index) {
|
|
771
|
+
return { ts: ts, v: body.series.v[index] }
|
|
772
|
+
})
|
|
772
773
|
}
|
|
773
774
|
})
|
|
774
775
|
.catch(function () { /* optional: state extras already cover the chart */ })
|
|
775
776
|
}
|
|
776
777
|
|
|
778
|
+
/* -------------------------------------------------------------- 开关 --- */
|
|
779
|
+
|
|
777
780
|
function switchPost(key, value) {
|
|
778
781
|
return fetch('/api/biocore/switch', {
|
|
779
782
|
method: 'POST',
|
|
@@ -782,15 +785,9 @@
|
|
|
782
785
|
}).then(function (response) { return response.json() })
|
|
783
786
|
}
|
|
784
787
|
|
|
785
|
-
function paintSwitchState(key, checked) {
|
|
786
|
-
var states = document.querySelectorAll('[data-switch-state="' + key + '"]')
|
|
787
|
-
for (var i = 0; i < states.length; i += 1) states[i].textContent = '当前:' + (checked ? '开' : '关')
|
|
788
|
-
}
|
|
789
|
-
|
|
790
788
|
function setSwitchInputs(key, checked) {
|
|
791
789
|
var inputs = document.querySelectorAll('input[data-switch="' + key + '"]')
|
|
792
790
|
for (var i = 0; i < inputs.length; i += 1) inputs[i].checked = !!checked
|
|
793
|
-
paintSwitchState(key, checked)
|
|
794
791
|
}
|
|
795
792
|
|
|
796
793
|
function applySwitchValues(values) {
|
|
@@ -805,8 +802,16 @@
|
|
|
805
802
|
var box = $('switches')
|
|
806
803
|
if (!box) return
|
|
807
804
|
box.textContent = ''
|
|
805
|
+
var lastGroup = ''
|
|
808
806
|
for (var i = 0; i < SWITCHES.length; i += 1) {
|
|
809
807
|
var meta = SWITCHES[i]
|
|
808
|
+
if (meta.group && meta.group !== lastGroup) {
|
|
809
|
+
lastGroup = meta.group
|
|
810
|
+
var heading = document.createElement('p')
|
|
811
|
+
heading.className = 'switch-group'
|
|
812
|
+
heading.textContent = meta.group
|
|
813
|
+
box.appendChild(heading)
|
|
814
|
+
}
|
|
810
815
|
var row = document.createElement('label')
|
|
811
816
|
row.className = 'switch-row'
|
|
812
817
|
var textWrap = document.createElement('span')
|
|
@@ -817,13 +822,8 @@
|
|
|
817
822
|
var desc = document.createElement('span')
|
|
818
823
|
desc.className = 'switch-desc'
|
|
819
824
|
desc.textContent = meta.desc
|
|
820
|
-
var state = document.createElement('span')
|
|
821
|
-
state.className = 'switch-state'
|
|
822
|
-
state.setAttribute('data-switch-state', meta.key)
|
|
823
|
-
state.textContent = '当前:关'
|
|
824
825
|
textWrap.appendChild(title)
|
|
825
826
|
textWrap.appendChild(desc)
|
|
826
|
-
textWrap.appendChild(state)
|
|
827
827
|
var input = document.createElement('input')
|
|
828
828
|
input.type = 'checkbox'
|
|
829
829
|
input.setAttribute('data-switch', meta.key)
|
|
@@ -842,20 +842,20 @@
|
|
|
842
842
|
var next = !!input.checked
|
|
843
843
|
switchBusy = true
|
|
844
844
|
input.disabled = true
|
|
845
|
-
setText('switchHint', '
|
|
845
|
+
setText('switchHint', '写入中')
|
|
846
846
|
switchPost(key, next)
|
|
847
847
|
.then(function (body) {
|
|
848
848
|
if (body && body.ok === true && body.switches) {
|
|
849
849
|
applySwitchValues(body.switches)
|
|
850
|
-
setText('switchHint', '
|
|
850
|
+
setText('switchHint', '已写入 flags')
|
|
851
851
|
} else {
|
|
852
852
|
setSwitchInputs(key, !next)
|
|
853
|
-
setText('switchHint', '
|
|
853
|
+
setText('switchHint', '写入失败 ' + ((body && body.reason) || 'unknown'))
|
|
854
854
|
}
|
|
855
855
|
})
|
|
856
856
|
.catch(function () {
|
|
857
857
|
setSwitchInputs(key, !next)
|
|
858
|
-
setText('switchHint', '
|
|
858
|
+
setText('switchHint', '写入失败 未连接')
|
|
859
859
|
})
|
|
860
860
|
.then(function () {
|
|
861
861
|
switchBusy = false
|
|
@@ -863,8 +863,16 @@
|
|
|
863
863
|
})
|
|
864
864
|
}
|
|
865
865
|
|
|
866
|
-
|
|
867
|
-
|
|
866
|
+
function loadSwitches() {
|
|
867
|
+
return fetch('/api/biocore/switch', { headers: { accept: 'application/json' } })
|
|
868
|
+
.then(function (response) { return response.json() })
|
|
869
|
+
.then(function (body) {
|
|
870
|
+
if (body && body.ok === true && body.switches) applySwitchValues(body.switches)
|
|
871
|
+
})
|
|
872
|
+
.catch(function () { /* keep defaults, POST still tries */ })
|
|
873
|
+
}
|
|
874
|
+
|
|
875
|
+
/* -------------------------------------------------------------- 人设 --- */
|
|
868
876
|
|
|
869
877
|
function personaStatus(message, tone) {
|
|
870
878
|
var el = $('personaStatus')
|
|
@@ -874,22 +882,13 @@
|
|
|
874
882
|
el.classList.toggle('persona-status-ok', tone === 'ok')
|
|
875
883
|
}
|
|
876
884
|
|
|
877
|
-
|
|
878
|
-
|
|
879
|
-
|
|
880
|
-
|
|
881
|
-
|
|
882
|
-
|
|
883
|
-
|
|
884
|
-
var item = document.createElement('div')
|
|
885
|
-
var label = document.createElement('span')
|
|
886
|
-
label.textContent = String(row.label || row.key || '')
|
|
887
|
-
var value = document.createElement('strong')
|
|
888
|
-
value.textContent = row.value === null || row.value === undefined ? '–' : String(row.value)
|
|
889
|
-
item.appendChild(label)
|
|
890
|
-
item.appendChild(value)
|
|
891
|
-
box.appendChild(item)
|
|
892
|
-
}
|
|
885
|
+
/** One status line for the persona card: file, compile and persist state. */
|
|
886
|
+
function personaPersistHint(message, tone) {
|
|
887
|
+
var el = $('personaHint')
|
|
888
|
+
if (!el) return
|
|
889
|
+
el.textContent = message
|
|
890
|
+
el.classList.toggle('persona-status-error', tone === 'error')
|
|
891
|
+
el.classList.toggle('persona-status-ok', tone === 'ok')
|
|
893
892
|
}
|
|
894
893
|
|
|
895
894
|
function loadPersona() {
|
|
@@ -897,19 +896,18 @@
|
|
|
897
896
|
.then(function (response) { return response.json() })
|
|
898
897
|
.then(function (body) {
|
|
899
898
|
if (!body || body.ok !== true) {
|
|
900
|
-
personaStatus('
|
|
899
|
+
personaStatus('人设文件坏了 ' + ((body && body.reason) || 'bad_profile'), 'error')
|
|
901
900
|
return body
|
|
902
901
|
}
|
|
903
902
|
personaText = String(body.text || '')
|
|
904
903
|
var area = $('personaText')
|
|
905
904
|
if (area) area.value = personaText
|
|
906
905
|
personaDirty = false
|
|
907
|
-
|
|
908
|
-
personaStatus(body.text ? '已载入' : '未设置人设', body.text ? 'ok' : '')
|
|
906
|
+
personaStatus(body.text ? '已载入' : '未设置', body.text ? 'ok' : '')
|
|
909
907
|
return body
|
|
910
908
|
})
|
|
911
909
|
.catch(function () {
|
|
912
|
-
personaStatus('
|
|
910
|
+
personaStatus('连不上宿主', 'error')
|
|
913
911
|
return null
|
|
914
912
|
})
|
|
915
913
|
}
|
|
@@ -919,11 +917,11 @@
|
|
|
919
917
|
var button = $('personaSave')
|
|
920
918
|
var text = area ? area.value : ''
|
|
921
919
|
if (!text.trim()) {
|
|
922
|
-
personaStatus('
|
|
920
|
+
personaStatus('人设不能为空', 'error')
|
|
923
921
|
return
|
|
924
922
|
}
|
|
925
923
|
if (!window.confirm('确定保存并编译人设吗?这会覆盖当前数值画像。')) return
|
|
926
|
-
personaStatus('
|
|
924
|
+
personaStatus('编译中')
|
|
927
925
|
if (button) button.disabled = true
|
|
928
926
|
fetch('/api/biocore/persona', {
|
|
929
927
|
method: 'POST',
|
|
@@ -933,31 +931,20 @@
|
|
|
933
931
|
.then(function (response) { return response.json() })
|
|
934
932
|
.then(function (body) {
|
|
935
933
|
if (!body || body.ok !== true) {
|
|
936
|
-
personaStatus('
|
|
934
|
+
personaStatus('编译失败 ' + ((body && body.reason) || 'unknown'), 'error')
|
|
937
935
|
if (body && body.detail) setText('personaHint', String(body.detail).slice(0, 120))
|
|
938
936
|
return body
|
|
939
937
|
}
|
|
940
938
|
personaDirty = false
|
|
941
939
|
personaText = String(body.text || text)
|
|
942
|
-
|
|
943
|
-
personaStatus('已编译 · ' + new Date().toLocaleTimeString(), 'ok')
|
|
940
|
+
personaStatus('已编译 ' + new Date().toLocaleTimeString(), 'ok')
|
|
944
941
|
return body
|
|
945
942
|
})
|
|
946
943
|
.catch(function () {
|
|
947
|
-
personaStatus('
|
|
944
|
+
personaStatus('编译失败 连不上宿主', 'error')
|
|
948
945
|
return null
|
|
949
946
|
})
|
|
950
|
-
.then(function () {
|
|
951
|
-
if (button) button.disabled = false
|
|
952
|
-
})
|
|
953
|
-
}
|
|
954
|
-
|
|
955
|
-
function personaPersistHint(message, tone) {
|
|
956
|
-
var el = $('personaPersistHint')
|
|
957
|
-
if (!el) return
|
|
958
|
-
el.textContent = message
|
|
959
|
-
el.classList.toggle('persona-status-error', tone === 'error')
|
|
960
|
-
el.classList.toggle('persona-status-ok', tone === 'ok')
|
|
947
|
+
.then(function () { if (button) button.disabled = false })
|
|
961
948
|
}
|
|
962
949
|
|
|
963
950
|
function loadPersonaPersist() {
|
|
@@ -967,31 +954,34 @@
|
|
|
967
954
|
var applyButton = $('personaPersist')
|
|
968
955
|
var rollbackButton = $('personaRollback')
|
|
969
956
|
if (!body || body.ok !== true) {
|
|
970
|
-
personaPersistHint('
|
|
957
|
+
personaPersistHint('常驻不可用 ' + ((body && body.reason) || 'unknown'), 'error')
|
|
971
958
|
if (rollbackButton) rollbackButton.hidden = true
|
|
972
959
|
return body
|
|
973
960
|
}
|
|
974
961
|
if (body.applied) {
|
|
975
|
-
personaPersistHint('
|
|
962
|
+
personaPersistHint('常驻已应用 预设 ' + String(body.preset_id || '') + ' '
|
|
963
|
+
+ Number(body.chars || 0) + ' 字'
|
|
964
|
+
+ (body.matches_preset ? '' : ' 预设已变更'), body.matches_preset ? 'ok' : 'error')
|
|
976
965
|
if (rollbackButton) rollbackButton.hidden = false
|
|
977
966
|
if (applyButton) applyButton.textContent = '重新应用常驻'
|
|
978
967
|
} else {
|
|
979
|
-
|
|
968
|
+
var reason = String(body.reason || 'not_applied')
|
|
969
|
+
personaPersistHint(reason === 'not_applied' ? '已保存人设,常驻未启用' : '常驻未应用 ' + reason, '')
|
|
980
970
|
if (rollbackButton) rollbackButton.hidden = !body.has_backup
|
|
981
|
-
if (applyButton) applyButton.textContent = '
|
|
971
|
+
if (applyButton) applyButton.textContent = '设为常驻'
|
|
982
972
|
}
|
|
983
973
|
return body
|
|
984
974
|
})
|
|
985
975
|
.catch(function () {
|
|
986
|
-
personaPersistHint('
|
|
976
|
+
personaPersistHint('常驻 连不上宿主', 'error')
|
|
987
977
|
return null
|
|
988
978
|
})
|
|
989
979
|
}
|
|
990
980
|
|
|
991
981
|
function applyPersonaPersist() {
|
|
992
982
|
var button = $('personaPersist')
|
|
993
|
-
if (!window.confirm('把人设写入 DSH
|
|
994
|
-
personaPersistHint('
|
|
983
|
+
if (!window.confirm('把人设写入 DSH 默认预设并常驻?应用后由预设提供人设,可随时恢复。')) return
|
|
984
|
+
personaPersistHint('正在写入预设')
|
|
995
985
|
if (button) button.disabled = true
|
|
996
986
|
fetch('/api/biocore/persona/apply', {
|
|
997
987
|
method: 'POST',
|
|
@@ -1001,26 +991,23 @@
|
|
|
1001
991
|
.then(function (response) { return response.json() })
|
|
1002
992
|
.then(function (body) {
|
|
1003
993
|
if (!body || body.ok !== true) {
|
|
1004
|
-
personaPersistHint('
|
|
1005
|
-
if (body && body.detail) personaStatus(String(body.detail).slice(0, 120), 'error')
|
|
994
|
+
personaPersistHint('常驻应用失败 ' + ((body && body.reason) || 'unknown'), 'error')
|
|
1006
995
|
return body
|
|
1007
996
|
}
|
|
1008
997
|
personaStatus('已写入常驻预设', 'ok')
|
|
1009
998
|
return loadPersonaPersist()
|
|
1010
999
|
})
|
|
1011
1000
|
.catch(function () {
|
|
1012
|
-
personaPersistHint('
|
|
1001
|
+
personaPersistHint('常驻应用失败 连不上宿主', 'error')
|
|
1013
1002
|
return null
|
|
1014
1003
|
})
|
|
1015
|
-
.then(function () {
|
|
1016
|
-
if (button) button.disabled = false
|
|
1017
|
-
})
|
|
1004
|
+
.then(function () { if (button) button.disabled = false })
|
|
1018
1005
|
}
|
|
1019
1006
|
|
|
1020
1007
|
function rollbackPersonaPersist() {
|
|
1021
1008
|
var button = $('personaRollback')
|
|
1022
1009
|
if (!window.confirm('恢复应用常驻前的预设人设?')) return
|
|
1023
|
-
personaPersistHint('
|
|
1010
|
+
personaPersistHint('正在恢复')
|
|
1024
1011
|
if (button) button.disabled = true
|
|
1025
1012
|
fetch('/api/biocore/persona/apply', {
|
|
1026
1013
|
method: 'POST',
|
|
@@ -1030,19 +1017,17 @@
|
|
|
1030
1017
|
.then(function (response) { return response.json() })
|
|
1031
1018
|
.then(function (body) {
|
|
1032
1019
|
if (!body || body.ok !== true) {
|
|
1033
|
-
personaPersistHint('
|
|
1020
|
+
personaPersistHint('恢复失败 ' + ((body && body.reason) || 'unknown'), 'error')
|
|
1034
1021
|
return body
|
|
1035
1022
|
}
|
|
1036
1023
|
personaStatus('已恢复原预设人设', 'ok')
|
|
1037
1024
|
return loadPersonaPersist()
|
|
1038
1025
|
})
|
|
1039
1026
|
.catch(function () {
|
|
1040
|
-
personaPersistHint('
|
|
1027
|
+
personaPersistHint('恢复失败 连不上宿主', 'error')
|
|
1041
1028
|
return null
|
|
1042
1029
|
})
|
|
1043
|
-
.then(function () {
|
|
1044
|
-
if (button) button.disabled = false
|
|
1045
|
-
})
|
|
1030
|
+
.then(function () { if (button) button.disabled = false })
|
|
1046
1031
|
}
|
|
1047
1032
|
|
|
1048
1033
|
function bindPersona() {
|
|
@@ -1062,7 +1047,7 @@
|
|
|
1062
1047
|
loadPersonaPersist()
|
|
1063
1048
|
}
|
|
1064
1049
|
|
|
1065
|
-
|
|
1050
|
+
/* -------------------------------------------------------- 模型与接口 --- */
|
|
1066
1051
|
|
|
1067
1052
|
function modelInputs() {
|
|
1068
1053
|
return {
|
|
@@ -1070,7 +1055,6 @@
|
|
|
1070
1055
|
baseUrl: $('modelBaseUrl'),
|
|
1071
1056
|
model: $('modelName'),
|
|
1072
1057
|
apiKey: $('modelApiKey'),
|
|
1073
|
-
localPath: $('modelLocalPath'),
|
|
1074
1058
|
}
|
|
1075
1059
|
}
|
|
1076
1060
|
|
|
@@ -1081,7 +1065,6 @@
|
|
|
1081
1065
|
base_url: fields.baseUrl ? fields.baseUrl.value.trim() : '',
|
|
1082
1066
|
model: fields.model ? fields.model.value.trim() : '',
|
|
1083
1067
|
api_key: fields.apiKey ? fields.apiKey.value : '',
|
|
1084
|
-
local_path: fields.localPath ? fields.localPath.value.trim() : '',
|
|
1085
1068
|
}
|
|
1086
1069
|
}
|
|
1087
1070
|
|
|
@@ -1107,16 +1090,6 @@
|
|
|
1107
1090
|
}
|
|
1108
1091
|
}
|
|
1109
1092
|
|
|
1110
|
-
function fillRerank(value) {
|
|
1111
|
-
var fields = rerankInputs()
|
|
1112
|
-
var data = value && typeof value === 'object' ? value : {}
|
|
1113
|
-
if (fields.enabled) fields.enabled.checked = data.enabled === true
|
|
1114
|
-
if (fields.baseUrl) fields.baseUrl.value = String(data.base_url || 'https://api.jina.ai/v1')
|
|
1115
|
-
if (fields.model) fields.model.value = String(data.model || 'jina-reranker-v2-base-multilingual')
|
|
1116
|
-
if (fields.apiKey && data.api_key) fields.apiKey.value = '••••••••'
|
|
1117
|
-
if (fields.topN) fields.topN.value = String(data.top_n || 20)
|
|
1118
|
-
}
|
|
1119
|
-
|
|
1120
1093
|
function fillModel(values) {
|
|
1121
1094
|
var fields = modelInputs()
|
|
1122
1095
|
var data = values || {}
|
|
@@ -1124,13 +1097,20 @@
|
|
|
1124
1097
|
if (fields.baseUrl) fields.baseUrl.value = String(data.base_url || '')
|
|
1125
1098
|
if (fields.model) fields.model.value = String(data.model || '')
|
|
1126
1099
|
if (fields.apiKey && data.api_key) fields.apiKey.value = '••••••••'
|
|
1127
|
-
if (fields.localPath) fields.localPath.value = String(data.local_path || '')
|
|
1128
1100
|
}
|
|
1129
1101
|
|
|
1130
|
-
function
|
|
1131
|
-
|
|
1102
|
+
function fillRerank(value) {
|
|
1103
|
+
var fields = rerankInputs()
|
|
1104
|
+
var data = value && typeof value === 'object' ? value : {}
|
|
1105
|
+
if (fields.enabled) fields.enabled.checked = data.enabled === true
|
|
1106
|
+
if (fields.baseUrl) fields.baseUrl.value = String(data.base_url || 'https://api.jina.ai/v1')
|
|
1107
|
+
if (fields.model) fields.model.value = String(data.model || 'jina-reranker-v2-base-multilingual')
|
|
1108
|
+
if (fields.apiKey && data.api_key) fields.apiKey.value = '••••••••'
|
|
1109
|
+
if (fields.topN) fields.topN.value = String(data.top_n || 20)
|
|
1132
1110
|
}
|
|
1133
1111
|
|
|
1112
|
+
function setModelStatus(message) { setText('modelStatus', message) }
|
|
1113
|
+
|
|
1134
1114
|
function saveModelDraft(values) {
|
|
1135
1115
|
try { window.localStorage.setItem(MODEL_DRAFT_KEY, JSON.stringify(values)) } catch { /* ignore */ }
|
|
1136
1116
|
}
|
|
@@ -1139,9 +1119,7 @@
|
|
|
1139
1119
|
try {
|
|
1140
1120
|
var raw = window.localStorage.getItem(MODEL_DRAFT_KEY)
|
|
1141
1121
|
return raw ? JSON.parse(raw) : null
|
|
1142
|
-
} catch {
|
|
1143
|
-
return null
|
|
1144
|
-
}
|
|
1122
|
+
} catch { return null }
|
|
1145
1123
|
}
|
|
1146
1124
|
|
|
1147
1125
|
function expressionInputs() {
|
|
@@ -1151,7 +1129,6 @@
|
|
|
1151
1129
|
target: $('lengthTarget'),
|
|
1152
1130
|
max: $('lengthMax'),
|
|
1153
1131
|
status: $('expressionStatus'),
|
|
1154
|
-
hint: $('expressionHint'),
|
|
1155
1132
|
save: $('expressionSave'),
|
|
1156
1133
|
}
|
|
1157
1134
|
}
|
|
@@ -1161,8 +1138,7 @@
|
|
|
1161
1138
|
if (!inputs.toggle) return
|
|
1162
1139
|
var on = inputs.toggle.checked === true
|
|
1163
1140
|
if (inputs.fields) inputs.fields.hidden = !on
|
|
1164
|
-
if (inputs.status) inputs.status.textContent = on ? '
|
|
1165
|
-
if (inputs.hint) inputs.hint.textContent = on ? '打开后按目标/上限字数约束表达' : '关闭时长度由人设决定'
|
|
1141
|
+
if (inputs.status) inputs.status.textContent = on ? '按目标与上限约束' : '长度由人设决定'
|
|
1166
1142
|
}
|
|
1167
1143
|
|
|
1168
1144
|
function fillExpression(value) {
|
|
@@ -1193,7 +1169,7 @@
|
|
|
1193
1169
|
var values = expressionValues()
|
|
1194
1170
|
if (inputs.save.disabled) return
|
|
1195
1171
|
inputs.save.disabled = true
|
|
1196
|
-
if (inputs.status) inputs.status.textContent = '
|
|
1172
|
+
if (inputs.status) inputs.status.textContent = '保存中'
|
|
1197
1173
|
fetch('/api/biocore/settings', {
|
|
1198
1174
|
method: 'POST',
|
|
1199
1175
|
headers: { 'content-type': 'application/json', accept: 'application/json' },
|
|
@@ -1206,10 +1182,10 @@
|
|
|
1206
1182
|
if (inputs.status) inputs.status.textContent = '已保存,重启后生效'
|
|
1207
1183
|
return
|
|
1208
1184
|
}
|
|
1209
|
-
if (inputs.status) inputs.status.textContent = '
|
|
1185
|
+
if (inputs.status) inputs.status.textContent = '保存失败 ' + ((body && body.reason) || 'unknown')
|
|
1210
1186
|
})
|
|
1211
1187
|
.catch(function () {
|
|
1212
|
-
if (inputs.status) inputs.status.textContent = '
|
|
1188
|
+
if (inputs.status) inputs.status.textContent = '保存失败 连不上宿主'
|
|
1213
1189
|
})
|
|
1214
1190
|
.then(function () { inputs.save.disabled = false })
|
|
1215
1191
|
})
|
|
@@ -1239,40 +1215,31 @@
|
|
|
1239
1215
|
})
|
|
1240
1216
|
}
|
|
1241
1217
|
|
|
1242
|
-
function bindAdvanced() {
|
|
1243
|
-
var reset = $('resetLayout')
|
|
1244
|
-
if (reset) reset.addEventListener('click', function () { resetLayout() })
|
|
1245
|
-
var clear = $('clearDraft')
|
|
1246
|
-
if (clear) clear.addEventListener('click', function () {
|
|
1247
|
-
try { window.localStorage.removeItem(MODEL_DRAFT_KEY) } catch { /* ignore */ }
|
|
1248
|
-
setText('advancedHint', '已清空本地草稿')
|
|
1249
|
-
layoutToast('已清空本地草稿')
|
|
1250
|
-
})
|
|
1251
|
-
}
|
|
1252
|
-
|
|
1253
1218
|
function bindModelActions() {
|
|
1254
1219
|
var button = $('modelSave')
|
|
1255
1220
|
var test = $('modelTest')
|
|
1256
1221
|
var rerankTest = $('rerankTest')
|
|
1222
|
+
|
|
1257
1223
|
function runConnectionTest(kind, buttonEl) {
|
|
1258
|
-
setModelStatus((kind === 'rerank' ? 'Rerank ' : 'Embedding ') + '
|
|
1224
|
+
setModelStatus((kind === 'rerank' ? 'Rerank ' : 'Embedding ') + '测试中')
|
|
1259
1225
|
if (buttonEl) buttonEl.disabled = true
|
|
1260
1226
|
fetch('/api/biocore/settings/test', {
|
|
1261
1227
|
method: 'POST',
|
|
1262
1228
|
headers: { 'content-type': 'application/json', accept: 'application/json' },
|
|
1263
|
-
body: JSON.stringify({ kind }),
|
|
1229
|
+
body: JSON.stringify({ kind: kind }),
|
|
1264
1230
|
})
|
|
1265
1231
|
.then(function (response) { return response.ok ? response.json() : null })
|
|
1266
1232
|
.then(function (body) {
|
|
1267
1233
|
if (body && body.ok === true) {
|
|
1268
|
-
setModelStatus(kind + '
|
|
1234
|
+
setModelStatus(kind + ' 正常 ' + (body.elapsed_ms || 0) + 'ms')
|
|
1269
1235
|
return
|
|
1270
1236
|
}
|
|
1271
|
-
setModelStatus(kind + '
|
|
1237
|
+
setModelStatus(kind + ' 失败 ' + ((body && (body.detail || body.reason)) || 'no_response'))
|
|
1272
1238
|
})
|
|
1273
|
-
.catch(function () { setModelStatus(kind + '
|
|
1239
|
+
.catch(function () { setModelStatus(kind + ' 失败 连不上宿主') })
|
|
1274
1240
|
.then(function () { if (buttonEl) buttonEl.disabled = false })
|
|
1275
1241
|
}
|
|
1242
|
+
|
|
1276
1243
|
if (test) test.addEventListener('click', function () { runConnectionTest('embedding', test) })
|
|
1277
1244
|
if (rerankTest) rerankTest.addEventListener('click', function () { runConnectionTest('rerank', rerankTest) })
|
|
1278
1245
|
if (!button) return
|
|
@@ -1281,12 +1248,12 @@
|
|
|
1281
1248
|
if (values.api_key.indexOf('•') === 0) delete values.api_key
|
|
1282
1249
|
var rerank = rerankValues()
|
|
1283
1250
|
if (String(rerank.api_key || '').indexOf('•') === 0) delete rerank.api_key
|
|
1284
|
-
setModelStatus('
|
|
1251
|
+
setModelStatus('保存中')
|
|
1285
1252
|
button.disabled = true
|
|
1286
1253
|
fetch('/api/biocore/settings', {
|
|
1287
1254
|
method: 'POST',
|
|
1288
1255
|
headers: { 'content-type': 'application/json', accept: 'application/json' },
|
|
1289
|
-
body: JSON.stringify({ confirm: true, embedding: values, rerank }),
|
|
1256
|
+
body: JSON.stringify({ confirm: true, embedding: values, rerank: rerank }),
|
|
1290
1257
|
})
|
|
1291
1258
|
.then(function (response) { return response.ok ? response.json() : null })
|
|
1292
1259
|
.then(function (body) {
|
|
@@ -1295,136 +1262,54 @@
|
|
|
1295
1262
|
return
|
|
1296
1263
|
}
|
|
1297
1264
|
saveModelDraft(values)
|
|
1298
|
-
setModelStatus('
|
|
1265
|
+
setModelStatus('接口未开启 已存本地草稿')
|
|
1299
1266
|
})
|
|
1300
1267
|
.catch(function () {
|
|
1301
1268
|
saveModelDraft(values)
|
|
1302
|
-
setModelStatus('
|
|
1269
|
+
setModelStatus('接口未开启 已存本地草稿')
|
|
1303
1270
|
})
|
|
1304
1271
|
.then(function () { button.disabled = false })
|
|
1305
1272
|
})
|
|
1306
1273
|
}
|
|
1307
1274
|
|
|
1308
|
-
function
|
|
1309
|
-
|
|
1310
|
-
|
|
1311
|
-
|
|
1312
|
-
|
|
1313
|
-
|
|
1314
|
-
|
|
1315
|
-
|
|
1316
|
-
}
|
|
1317
|
-
|
|
1318
|
-
var TRACE_LIMIT = 3
|
|
1319
|
-
|
|
1320
|
-
function traceFormatter() {
|
|
1321
|
-
return window.BioTraceView || null
|
|
1322
|
-
}
|
|
1323
|
-
|
|
1324
|
-
function renderTrace(body) {
|
|
1325
|
-
var list = $('traceList')
|
|
1326
|
-
var hint = $('traceHint')
|
|
1327
|
-
if (!list) return
|
|
1328
|
-
list.textContent = ''
|
|
1329
|
-
var view = traceFormatter()
|
|
1330
|
-
var formatted = view
|
|
1331
|
-
? view.formatPayload(body, TRACE_LIMIT)
|
|
1332
|
-
: { rows: [], empty: true, error: false, hint: '还没有记录' }
|
|
1333
|
-
setText('traceHint', formatted.hint)
|
|
1334
|
-
if (formatted.empty) {
|
|
1335
|
-
var empty = document.createElement('p')
|
|
1336
|
-
empty.className = 'empty'
|
|
1337
|
-
empty.textContent = formatted.error ? '读取失败' : '还没有记录'
|
|
1338
|
-
list.appendChild(empty)
|
|
1339
|
-
return
|
|
1340
|
-
}
|
|
1341
|
-
var labels = ['状态变化', '意图', '长度来源', '闸命中', '记忆']
|
|
1342
|
-
for (var i = 0; i < formatted.rows.length; i += 1) {
|
|
1343
|
-
var row = formatted.rows[i] || {}
|
|
1344
|
-
var item = document.createElement('article')
|
|
1345
|
-
item.className = 'trace-row'
|
|
1346
|
-
var head = document.createElement('div')
|
|
1347
|
-
head.className = 'trace-head'
|
|
1348
|
-
var time = document.createElement('span')
|
|
1349
|
-
time.textContent = row.time || '--:--'
|
|
1350
|
-
var badge = document.createElement('span')
|
|
1351
|
-
badge.className = 'trace-badge'
|
|
1352
|
-
badge.textContent = '#' + (formatted.rows.length - i)
|
|
1353
|
-
head.appendChild(time)
|
|
1354
|
-
head.appendChild(badge)
|
|
1355
|
-
var grid = document.createElement('dl')
|
|
1356
|
-
grid.className = 'trace-grid'
|
|
1357
|
-
var values = [row.changes, row.intent, row.length, row.gate, row.memory]
|
|
1358
|
-
for (var index = 0; index < labels.length; index += 1) {
|
|
1359
|
-
var dt = document.createElement('dt')
|
|
1360
|
-
dt.textContent = labels[index]
|
|
1361
|
-
var dd = document.createElement('dd')
|
|
1362
|
-
dd.textContent = values[index] || '—'
|
|
1363
|
-
grid.appendChild(dt)
|
|
1364
|
-
grid.appendChild(dd)
|
|
1365
|
-
}
|
|
1366
|
-
item.appendChild(head)
|
|
1367
|
-
item.appendChild(grid)
|
|
1368
|
-
list.appendChild(item)
|
|
1369
|
-
}
|
|
1370
|
-
}
|
|
1371
|
-
|
|
1372
|
-
function fetchTrace() {
|
|
1373
|
-
if (!SESSION) {
|
|
1374
|
-
renderTrace({ ok: true, rows: [] })
|
|
1375
|
-
return Promise.resolve(null)
|
|
1376
|
-
}
|
|
1377
|
-
return fetch('/api/biocore/trace?session=' + encodeURIComponent(SESSION) + '&limit=' + TRACE_LIMIT, {
|
|
1378
|
-
headers: { accept: 'application/json' },
|
|
1275
|
+
function bindAdvanced() {
|
|
1276
|
+
var reset = $('resetLayout')
|
|
1277
|
+
if (reset) reset.addEventListener('click', function () { resetLayout() })
|
|
1278
|
+
var clear = $('clearDraft')
|
|
1279
|
+
if (clear) clear.addEventListener('click', function () {
|
|
1280
|
+
try { window.localStorage.removeItem(MODEL_DRAFT_KEY) } catch { /* ignore */ }
|
|
1281
|
+
setText('advancedHint', '草稿已清空')
|
|
1282
|
+
layoutToast('已清空本地草稿')
|
|
1379
1283
|
})
|
|
1380
|
-
.then(function (response) { return response.json() })
|
|
1381
|
-
.then(function (body) {
|
|
1382
|
-
renderTrace(body)
|
|
1383
|
-
return body
|
|
1384
|
-
})
|
|
1385
|
-
.catch(function () {
|
|
1386
|
-
renderTrace(null)
|
|
1387
|
-
return null
|
|
1388
|
-
})
|
|
1389
1284
|
}
|
|
1390
1285
|
|
|
1391
|
-
|
|
1286
|
+
/* ------------------------------------------------------------ 布局/编辑 --- */
|
|
1287
|
+
|
|
1392
1288
|
var layoutApi = typeof window !== 'undefined' ? window.BioCorePanelLayout : null
|
|
1393
1289
|
var layoutState = null
|
|
1394
1290
|
var layoutEditing = false
|
|
1395
|
-
var
|
|
1291
|
+
var activeTab = 'dashboard'
|
|
1396
1292
|
var drag = null
|
|
1397
1293
|
var pressTimer = null
|
|
1398
1294
|
var pressStart = null
|
|
1399
|
-
var LONG_PRESS_MS = 400
|
|
1400
|
-
var ENTRY_LONG_PRESS_MS = 600
|
|
1401
|
-
var MOVE_CANCEL_PX = 8
|
|
1402
|
-
var entryTimer = null
|
|
1403
|
-
var entryStart = null
|
|
1404
1295
|
|
|
1405
1296
|
function layoutStorage() {
|
|
1406
1297
|
try { return window.localStorage } catch { return null }
|
|
1407
1298
|
}
|
|
1408
1299
|
|
|
1409
1300
|
function layoutCards() {
|
|
1410
|
-
var container = $('
|
|
1301
|
+
var container = $('grid')
|
|
1411
1302
|
if (!container) return []
|
|
1412
1303
|
return Array.prototype.slice.call(container.querySelectorAll('[data-card-id]'))
|
|
1413
1304
|
}
|
|
1414
1305
|
|
|
1415
|
-
/** Movable DOM slot for a card: grouped cards share one slot (channel grid). */
|
|
1416
|
-
function layoutSlotOf(card) {
|
|
1417
|
-
var slot = card && card.closest ? card.closest('[data-layout-slot]') : null
|
|
1418
|
-
return slot || card
|
|
1419
|
-
}
|
|
1420
|
-
|
|
1421
1306
|
function layoutToast(message) {
|
|
1422
1307
|
var el = $('layoutToast')
|
|
1423
1308
|
if (!el) return
|
|
1424
1309
|
el.textContent = String(message || '')
|
|
1425
1310
|
el.hidden = false
|
|
1426
1311
|
if (toastTimer) window.clearTimeout(toastTimer)
|
|
1427
|
-
toastTimer = window.setTimeout(function () { el.hidden = true },
|
|
1312
|
+
toastTimer = window.setTimeout(function () { el.hidden = true }, 2400)
|
|
1428
1313
|
}
|
|
1429
1314
|
|
|
1430
1315
|
function ensureHideButton(card) {
|
|
@@ -1438,22 +1323,24 @@
|
|
|
1438
1323
|
card.appendChild(hide)
|
|
1439
1324
|
}
|
|
1440
1325
|
|
|
1441
|
-
|
|
1326
|
+
function cardLabel(card) {
|
|
1327
|
+
var named = card.getAttribute('data-card-label')
|
|
1328
|
+
if (named) return named
|
|
1329
|
+
var title = card.querySelector('h2')
|
|
1330
|
+
return title ? title.textContent.trim() : card.getAttribute('data-card-id')
|
|
1331
|
+
}
|
|
1442
1332
|
|
|
1443
1333
|
function applyTabGroup() {
|
|
1444
1334
|
if (!layoutApi) return
|
|
1335
|
+
var body = document.body
|
|
1336
|
+
body.dataset.tab = activeTab
|
|
1337
|
+
body.classList.toggle('layout-editing', layoutEditing)
|
|
1338
|
+
setText('pageTitle', layoutApi.TAB_LABELS[activeTab] || 'BioCore')
|
|
1445
1339
|
var group = layoutEditing ? null : (layoutApi.TAB_GROUPS[activeTab] || [])
|
|
1446
|
-
var panel = $('panel')
|
|
1447
|
-
if (panel) {
|
|
1448
|
-
var tabsKnown = ['dashboard', 'memory', 'relation', 'settings', 'tools']
|
|
1449
|
-
for (var t = 0; t < tabsKnown.length; t += 1) {
|
|
1450
|
-
panel.classList.toggle('tab-' + tabsKnown[t], activeTab === tabsKnown[t])
|
|
1451
|
-
}
|
|
1452
|
-
}
|
|
1453
1340
|
var cards = layoutCards()
|
|
1454
1341
|
for (var i = 0; i < cards.length; i += 1) {
|
|
1455
1342
|
var id = cards[i].getAttribute('data-card-id')
|
|
1456
|
-
cards[i].classList.toggle('tab-
|
|
1343
|
+
cards[i].classList.toggle('tab-off', !!group && group.indexOf(id) < 0)
|
|
1457
1344
|
}
|
|
1458
1345
|
var items = document.querySelectorAll('[data-tab]')
|
|
1459
1346
|
for (var j = 0; j < items.length; j += 1) {
|
|
@@ -1467,6 +1354,8 @@
|
|
|
1467
1354
|
if (!layoutApi || !layoutApi.TAB_GROUPS[tab]) return
|
|
1468
1355
|
activeTab = tab
|
|
1469
1356
|
applyTabGroup()
|
|
1357
|
+
if (tab === 'tools') loadStats()
|
|
1358
|
+
if (layoutEditing) renderAddCards()
|
|
1470
1359
|
}
|
|
1471
1360
|
|
|
1472
1361
|
function bindTabBar() {
|
|
@@ -1484,7 +1373,7 @@
|
|
|
1484
1373
|
if (!layoutApi) return
|
|
1485
1374
|
layoutState = layoutApi.normalizeLayout(next)
|
|
1486
1375
|
if (persist !== false) layoutApi.writeLayout(layoutStorage(), layoutState)
|
|
1487
|
-
var container = $('
|
|
1376
|
+
var container = $('grid')
|
|
1488
1377
|
if (!container) return
|
|
1489
1378
|
var cards = layoutCards()
|
|
1490
1379
|
var byId = {}
|
|
@@ -1494,14 +1383,11 @@
|
|
|
1494
1383
|
byId[card.getAttribute('data-card-id')] = card
|
|
1495
1384
|
}
|
|
1496
1385
|
var ordered = layoutApi.sortCards(Object.keys(byId), layoutState.order)
|
|
1497
|
-
var orderedSlots = []
|
|
1498
1386
|
for (var j = 0; j < ordered.length; j += 1) {
|
|
1499
|
-
var
|
|
1500
|
-
var node = byId[id]
|
|
1387
|
+
var node = byId[ordered[j]]
|
|
1501
1388
|
if (!node) continue
|
|
1502
|
-
node.classList.toggle('is-hidden', layoutState.hidden.indexOf(
|
|
1503
|
-
|
|
1504
|
-
if (orderedSlots.indexOf(slot) < 0) { orderedSlots.push(slot); container.appendChild(slot) }
|
|
1389
|
+
node.classList.toggle('is-hidden', layoutState.hidden.indexOf(ordered[j]) >= 0)
|
|
1390
|
+
container.appendChild(node)
|
|
1505
1391
|
}
|
|
1506
1392
|
renderAddCards()
|
|
1507
1393
|
applyTabGroup()
|
|
@@ -1519,41 +1405,6 @@
|
|
|
1519
1405
|
layoutApi.writeLayout(layoutStorage(), layoutState)
|
|
1520
1406
|
}
|
|
1521
1407
|
|
|
1522
|
-
function setLayoutEditing(on) {
|
|
1523
|
-
layoutEditing = !!on
|
|
1524
|
-
var panel = $('panel')
|
|
1525
|
-
if (panel) panel.classList.toggle('layout-editing', layoutEditing)
|
|
1526
|
-
var title = $('pageTitle')
|
|
1527
|
-
if (title) title.textContent = layoutEditing ? '编辑仪表盘' : 'BioCore'
|
|
1528
|
-
var fab = $('layoutFab')
|
|
1529
|
-
if (fab) fab.hidden = layoutEditing
|
|
1530
|
-
var done = $('layoutDone')
|
|
1531
|
-
if (done) done.hidden = !layoutEditing
|
|
1532
|
-
var reset = $('layoutReset')
|
|
1533
|
-
if (reset) reset.hidden = !layoutEditing
|
|
1534
|
-
var cards = layoutCards()
|
|
1535
|
-
for (var i = 0; i < cards.length; i += 1) ensureHideButton(cards[i])
|
|
1536
|
-
ensureDeskHides()
|
|
1537
|
-
applyDeskHidden()
|
|
1538
|
-
renderAddCards()
|
|
1539
|
-
applyTabGroup()
|
|
1540
|
-
if (!layoutEditing) persistDomOrder()
|
|
1541
|
-
}
|
|
1542
|
-
|
|
1543
|
-
function resetLayout(message) {
|
|
1544
|
-
if (layoutApi) layoutApi.clearLayout(layoutStorage())
|
|
1545
|
-
layoutState = layoutApi ? layoutApi.defaultLayout() : null
|
|
1546
|
-
applyLayout(layoutState, false)
|
|
1547
|
-
layoutToast(message || '已恢复默认布局')
|
|
1548
|
-
}
|
|
1549
|
-
|
|
1550
|
-
function cardLabel(card) {
|
|
1551
|
-
var named = card.getAttribute('data-card-label')
|
|
1552
|
-
if (named) return named
|
|
1553
|
-
var title = card.querySelector('h1, h2')
|
|
1554
|
-
return title ? title.textContent.trim() : card.getAttribute('data-card-id')
|
|
1555
|
-
}
|
|
1556
|
-
|
|
1557
1408
|
function hiddenCards() {
|
|
1558
1409
|
var out = []
|
|
1559
1410
|
if (!layoutApi || !layoutState) return out
|
|
@@ -1571,17 +1422,8 @@
|
|
|
1571
1422
|
var section = $('addCards')
|
|
1572
1423
|
var list = $('addList')
|
|
1573
1424
|
if (!section || !list) return
|
|
1574
|
-
|
|
1575
|
-
|
|
1576
|
-
list.textContent = ''
|
|
1577
|
-
return
|
|
1578
|
-
}
|
|
1579
|
-
var hidden = hiddenCards()
|
|
1580
|
-
var deskHidden = readDeskHidden()
|
|
1581
|
-
var deskHiddenCards = deskCards().filter(function (card, index) {
|
|
1582
|
-
return deskHidden.indexOf(deskCardId(card, index)) >= 0
|
|
1583
|
-
})
|
|
1584
|
-
if (!hidden.length && !deskHiddenCards.length) {
|
|
1425
|
+
var hidden = layoutEditing ? hiddenCards() : []
|
|
1426
|
+
if (!hidden.length) {
|
|
1585
1427
|
section.hidden = true
|
|
1586
1428
|
list.textContent = ''
|
|
1587
1429
|
return
|
|
@@ -1596,14 +1438,6 @@
|
|
|
1596
1438
|
chip.textContent = '+ ' + hidden[i].label
|
|
1597
1439
|
list.appendChild(chip)
|
|
1598
1440
|
}
|
|
1599
|
-
for (var d = 0; d < deskHiddenCards.length; d += 1) {
|
|
1600
|
-
var deskChip = document.createElement('button')
|
|
1601
|
-
deskChip.type = 'button'
|
|
1602
|
-
deskChip.className = 'add-chip'
|
|
1603
|
-
deskChip.setAttribute('data-desk-add', deskCardId(deskHiddenCards[d], 0))
|
|
1604
|
-
deskChip.textContent = '+ ' + deskLabel(deskHiddenCards[d])
|
|
1605
|
-
list.appendChild(deskChip)
|
|
1606
|
-
}
|
|
1607
1441
|
}
|
|
1608
1442
|
|
|
1609
1443
|
function showHiddenCard(id) {
|
|
@@ -1615,59 +1449,64 @@
|
|
|
1615
1449
|
for (var i = 0; i < cards.length; i += 1) {
|
|
1616
1450
|
if (cards[i].getAttribute('data-card-id') === id) label = cardLabel(cards[i])
|
|
1617
1451
|
}
|
|
1618
|
-
layoutToast('
|
|
1452
|
+
layoutToast('已恢复「' + (label || id) + '」')
|
|
1619
1453
|
}
|
|
1620
1454
|
|
|
1621
|
-
function
|
|
1622
|
-
|
|
1623
|
-
|
|
1624
|
-
|
|
1625
|
-
|
|
1626
|
-
|
|
1455
|
+
function setLayoutEditing(on) {
|
|
1456
|
+
layoutEditing = !!on
|
|
1457
|
+
var fab = $('layoutFab')
|
|
1458
|
+
if (fab) fab.hidden = layoutEditing
|
|
1459
|
+
var done = $('layoutDone')
|
|
1460
|
+
if (done) done.hidden = !layoutEditing
|
|
1461
|
+
var reset = $('layoutReset')
|
|
1462
|
+
if (reset) reset.hidden = !layoutEditing
|
|
1463
|
+
var cards = layoutCards()
|
|
1464
|
+
for (var i = 0; i < cards.length; i += 1) ensureHideButton(cards[i])
|
|
1465
|
+
renderAddCards()
|
|
1466
|
+
applyTabGroup()
|
|
1467
|
+
if (layoutEditing) layoutToast('编辑模式:拖动排序,红色 − 隐藏卡片')
|
|
1468
|
+
else persistDomOrder()
|
|
1627
1469
|
}
|
|
1628
1470
|
|
|
1629
|
-
function
|
|
1630
|
-
if (
|
|
1631
|
-
|
|
1632
|
-
|
|
1633
|
-
|
|
1634
|
-
|
|
1635
|
-
|
|
1636
|
-
|
|
1637
|
-
|
|
1638
|
-
|
|
1639
|
-
if (!entryStart) return
|
|
1640
|
-
entryStart = null
|
|
1641
|
-
setLayoutEditing(true)
|
|
1642
|
-
layoutToast('已进入编辑模式,长按卡片可拖动')
|
|
1643
|
-
}, ENTRY_LONG_PRESS_MS)
|
|
1644
|
-
}
|
|
1645
|
-
|
|
1646
|
-
function beginDrag(card, event) {
|
|
1471
|
+
function resetLayout(message) {
|
|
1472
|
+
if (layoutApi) layoutApi.clearLayout(layoutStorage())
|
|
1473
|
+
layoutState = layoutApi ? layoutApi.defaultLayout() : null
|
|
1474
|
+
applyLayout(layoutState, false)
|
|
1475
|
+
layoutToast(message || '已恢复默认布局')
|
|
1476
|
+
}
|
|
1477
|
+
|
|
1478
|
+
/* --- drag & drop ------------------------------------------------------- */
|
|
1479
|
+
|
|
1480
|
+
function beginDrag(card, pointerId) {
|
|
1647
1481
|
if (!layoutEditing || !layoutApi || !card) return
|
|
1648
|
-
drag = { card: card, pointerId:
|
|
1482
|
+
drag = { card: card, pointerId: pointerId }
|
|
1649
1483
|
card.classList.add('dragging')
|
|
1650
|
-
try { if (card.setPointerCapture) card.setPointerCapture(
|
|
1484
|
+
try { if (card.setPointerCapture) card.setPointerCapture(pointerId) } catch { /* ignore */ }
|
|
1651
1485
|
}
|
|
1652
1486
|
|
|
1653
1487
|
function updateDrag(event) {
|
|
1654
1488
|
if (!drag || event.pointerId !== drag.pointerId) return
|
|
1489
|
+
var container = $('grid')
|
|
1490
|
+
if (!container) return
|
|
1655
1491
|
var cards = layoutCards()
|
|
1492
|
+
var x = event.clientX
|
|
1656
1493
|
var y = event.clientY
|
|
1494
|
+
var reference = null
|
|
1495
|
+
var last = null
|
|
1657
1496
|
for (var i = 0; i < cards.length; i += 1) {
|
|
1658
1497
|
var candidate = cards[i]
|
|
1659
|
-
if (candidate === drag.card
|
|
1498
|
+
if (candidate === drag.card) continue
|
|
1499
|
+
if (candidate.classList.contains('tab-off') || candidate.classList.contains('is-hidden')) continue
|
|
1660
1500
|
var rect = candidate.getBoundingClientRect()
|
|
1661
|
-
|
|
1662
|
-
|
|
1663
|
-
|
|
1664
|
-
|
|
1665
|
-
|
|
1666
|
-
|
|
1667
|
-
|
|
1668
|
-
return
|
|
1669
|
-
}
|
|
1501
|
+
last = rect
|
|
1502
|
+
if (y < rect.top) { reference = candidate; break }
|
|
1503
|
+
if (y <= rect.bottom && x < rect.left + rect.width / 2) { reference = candidate; break }
|
|
1504
|
+
}
|
|
1505
|
+
if (reference) {
|
|
1506
|
+
container.insertBefore(drag.card, reference)
|
|
1507
|
+
return
|
|
1670
1508
|
}
|
|
1509
|
+
if (last && y > last.bottom) container.appendChild(drag.card)
|
|
1671
1510
|
}
|
|
1672
1511
|
|
|
1673
1512
|
function endDrag() {
|
|
@@ -1679,41 +1518,50 @@
|
|
|
1679
1518
|
persistDomOrder()
|
|
1680
1519
|
}
|
|
1681
1520
|
|
|
1682
|
-
function
|
|
1521
|
+
function onGridPointerDown(event) {
|
|
1683
1522
|
if (!layoutEditing) return
|
|
1684
1523
|
var target = event.target
|
|
1685
|
-
|
|
1686
|
-
if (!card) return
|
|
1524
|
+
if (!target || !target.closest) return
|
|
1687
1525
|
if (target.closest('[data-layout-hide]')) return
|
|
1526
|
+
var card = target.closest('[data-card-id]')
|
|
1527
|
+
if (!card) return
|
|
1688
1528
|
var intent = layoutApi && layoutApi.dragIntent
|
|
1689
1529
|
? layoutApi.dragIntent({ editing: layoutEditing, pointerType: event.pointerType })
|
|
1690
1530
|
: { hold: true }
|
|
1691
1531
|
if (!intent.hold) return
|
|
1692
1532
|
if (event.cancelable) event.preventDefault()
|
|
1693
|
-
pressStart = {
|
|
1533
|
+
pressStart = {
|
|
1534
|
+
x: event.clientX,
|
|
1535
|
+
y: event.clientY,
|
|
1536
|
+
card: card,
|
|
1537
|
+
pointerId: event.pointerId,
|
|
1538
|
+
touch: event.pointerType !== 'mouse',
|
|
1539
|
+
}
|
|
1694
1540
|
if (pressTimer) window.clearTimeout(pressTimer)
|
|
1695
|
-
|
|
1696
|
-
pressTimer =
|
|
1697
|
-
|
|
1698
|
-
|
|
1699
|
-
|
|
1700
|
-
|
|
1701
|
-
|
|
1702
|
-
|
|
1703
|
-
|
|
1704
|
-
function onCardsPointerMove(event) {
|
|
1705
|
-
if (entryTimer && entryStart) {
|
|
1706
|
-
var edx = event.clientX - entryStart.x
|
|
1707
|
-
var edy = event.clientY - entryStart.y
|
|
1708
|
-
if (edx * edx + edy * edy > MOVE_CANCEL_PX * MOVE_CANCEL_PX) cancelEntryPress()
|
|
1541
|
+
if (pressStart.touch) {
|
|
1542
|
+
pressTimer = window.setTimeout(function () {
|
|
1543
|
+
pressTimer = null
|
|
1544
|
+
if (!pressStart) return
|
|
1545
|
+
var pending = pressStart
|
|
1546
|
+
pressStart = null
|
|
1547
|
+
beginDrag(pending.card, pending.pointerId)
|
|
1548
|
+
}, LONG_PRESS_MS)
|
|
1709
1549
|
}
|
|
1710
|
-
|
|
1550
|
+
}
|
|
1551
|
+
|
|
1552
|
+
function onDocPointerMove(event) {
|
|
1553
|
+
if (pressStart && !drag) {
|
|
1711
1554
|
var dx = event.clientX - pressStart.x
|
|
1712
1555
|
var dy = event.clientY - pressStart.y
|
|
1713
1556
|
if (dx * dx + dy * dy > MOVE_CANCEL_PX * MOVE_CANCEL_PX) {
|
|
1714
|
-
window.clearTimeout(pressTimer)
|
|
1715
|
-
|
|
1557
|
+
if (pressTimer) { window.clearTimeout(pressTimer); pressTimer = null }
|
|
1558
|
+
if (pressStart.touch) {
|
|
1559
|
+
pressStart = null
|
|
1560
|
+
return
|
|
1561
|
+
}
|
|
1562
|
+
var pending = pressStart
|
|
1716
1563
|
pressStart = null
|
|
1564
|
+
beginDrag(pending.card, pending.pointerId)
|
|
1717
1565
|
}
|
|
1718
1566
|
return
|
|
1719
1567
|
}
|
|
@@ -1723,25 +1571,22 @@
|
|
|
1723
1571
|
}
|
|
1724
1572
|
}
|
|
1725
1573
|
|
|
1726
|
-
function
|
|
1727
|
-
|
|
1728
|
-
if (pressTimer) {
|
|
1729
|
-
window.clearTimeout(pressTimer)
|
|
1730
|
-
pressTimer = null
|
|
1731
|
-
}
|
|
1574
|
+
function onDocPointerUp() {
|
|
1575
|
+
if (pressTimer) { window.clearTimeout(pressTimer); pressTimer = null }
|
|
1732
1576
|
pressStart = null
|
|
1733
1577
|
endDrag()
|
|
1734
1578
|
}
|
|
1735
1579
|
|
|
1736
|
-
function
|
|
1580
|
+
function onGridClick(event) {
|
|
1737
1581
|
var target = event.target
|
|
1738
|
-
|
|
1582
|
+
if (!target || !target.closest) return
|
|
1583
|
+
var add = target.closest('[data-add-card]')
|
|
1739
1584
|
if (add) {
|
|
1740
1585
|
event.preventDefault()
|
|
1741
1586
|
showHiddenCard(add.getAttribute('data-add-card'))
|
|
1742
1587
|
return
|
|
1743
1588
|
}
|
|
1744
|
-
var hide = target
|
|
1589
|
+
var hide = target.closest('[data-layout-hide]')
|
|
1745
1590
|
if (!hide) return
|
|
1746
1591
|
event.preventDefault()
|
|
1747
1592
|
var card = hide.closest('[data-card-id]')
|
|
@@ -1753,7 +1598,7 @@
|
|
|
1753
1598
|
return
|
|
1754
1599
|
}
|
|
1755
1600
|
applyLayout(next)
|
|
1756
|
-
layoutToast('已隐藏「' + cardLabel(card) + '
|
|
1601
|
+
layoutToast('已隐藏「' + cardLabel(card) + '」')
|
|
1757
1602
|
}
|
|
1758
1603
|
|
|
1759
1604
|
function initLayout() {
|
|
@@ -1766,23 +1611,48 @@
|
|
|
1766
1611
|
if (fab) fab.addEventListener('click', function () { setLayoutEditing(true) })
|
|
1767
1612
|
if (done) done.addEventListener('click', function () { setLayoutEditing(false) })
|
|
1768
1613
|
if (reset) reset.addEventListener('click', function () { resetLayout() })
|
|
1769
|
-
var
|
|
1770
|
-
if (
|
|
1771
|
-
|
|
1772
|
-
cards.addEventListener('click', onCardsClick)
|
|
1773
|
-
}
|
|
1614
|
+
var grid = $('grid')
|
|
1615
|
+
if (grid) grid.addEventListener('pointerdown', onGridPointerDown)
|
|
1616
|
+
document.addEventListener('click', onGridClick)
|
|
1774
1617
|
bindTabBar()
|
|
1775
|
-
document.addEventListener('
|
|
1776
|
-
document.addEventListener('
|
|
1777
|
-
document.addEventListener('
|
|
1778
|
-
|
|
1779
|
-
|
|
1780
|
-
|
|
1781
|
-
|
|
1618
|
+
document.addEventListener('pointermove', onDocPointerMove, { passive: false })
|
|
1619
|
+
document.addEventListener('pointerup', onDocPointerUp)
|
|
1620
|
+
document.addEventListener('pointercancel', onDocPointerUp)
|
|
1621
|
+
}
|
|
1622
|
+
|
|
1623
|
+
/* ---------------------------------------------------------------- 侧栏 --- */
|
|
1624
|
+
|
|
1625
|
+
function railApply(collapsed) {
|
|
1626
|
+
document.body.classList.toggle('rail-collapsed', !!collapsed)
|
|
1627
|
+
var button = $('railToggle')
|
|
1628
|
+
if (!button) return
|
|
1629
|
+
var label = collapsed ? '展开导航' : '收起导航'
|
|
1630
|
+
button.setAttribute('aria-expanded', String(!collapsed))
|
|
1631
|
+
button.setAttribute('aria-label', label)
|
|
1632
|
+
button.setAttribute('title', label)
|
|
1633
|
+
}
|
|
1634
|
+
|
|
1635
|
+
function railInit() {
|
|
1636
|
+
var storage = layoutStorage()
|
|
1637
|
+
var key = layoutApi && layoutApi.RAIL_KEY ? layoutApi.RAIL_KEY : 'biocore-panel-rail-v1'
|
|
1638
|
+
var collapsed = false
|
|
1639
|
+
try { collapsed = !!storage && storage.getItem(key) === '1' } catch { collapsed = false }
|
|
1640
|
+
railApply(collapsed)
|
|
1641
|
+
var button = $('railToggle')
|
|
1642
|
+
if (!button) return
|
|
1643
|
+
button.addEventListener('click', function () {
|
|
1644
|
+
var next = !document.body.classList.contains('rail-collapsed')
|
|
1645
|
+
railApply(next)
|
|
1646
|
+
try { if (storage) storage.setItem(key, next ? '1' : '0') } catch { /* ignore */ }
|
|
1647
|
+
})
|
|
1782
1648
|
}
|
|
1783
1649
|
|
|
1650
|
+
/* ---------------------------------------------------------------- boot --- */
|
|
1651
|
+
|
|
1784
1652
|
function boot() {
|
|
1653
|
+
applyTheme()
|
|
1785
1654
|
railInit()
|
|
1655
|
+
if (layoutApi && layoutApi.TAB_GROUPS[INITIAL_TAB]) activeTab = INITIAL_TAB
|
|
1786
1656
|
initLayout()
|
|
1787
1657
|
bindAdvanced()
|
|
1788
1658
|
bindPersona()
|
|
@@ -1791,17 +1661,21 @@
|
|
|
1791
1661
|
bindExpression()
|
|
1792
1662
|
loadModelSettings()
|
|
1793
1663
|
buildSwitches()
|
|
1664
|
+
|
|
1794
1665
|
if (!SESSION) {
|
|
1795
1666
|
setStatus(false, '未连接')
|
|
1796
|
-
|
|
1797
|
-
setText('
|
|
1667
|
+
render(null)
|
|
1668
|
+
setText('motivation', '没有会话参数,先在聊天会话里打开')
|
|
1798
1669
|
renderTrace({ ok: true, rows: [] })
|
|
1670
|
+
window.setTimeout(finishBoot, 600)
|
|
1799
1671
|
return
|
|
1800
1672
|
}
|
|
1801
1673
|
loadSwitches()
|
|
1802
1674
|
loadSeries()
|
|
1803
1675
|
fetchState()
|
|
1804
1676
|
fetchTrace()
|
|
1677
|
+
loadStats()
|
|
1678
|
+
|
|
1805
1679
|
function pollTick() {
|
|
1806
1680
|
if (document.hidden) return
|
|
1807
1681
|
fetchState()
|
|
@@ -1809,17 +1683,17 @@
|
|
|
1809
1683
|
}
|
|
1810
1684
|
window.setInterval(pollTick, POLL_MS)
|
|
1811
1685
|
window.setInterval(updateStatusClock, 1000)
|
|
1686
|
+
window.setInterval(loadStats, STATS_MS)
|
|
1812
1687
|
document.addEventListener('visibilitychange', function () {
|
|
1813
1688
|
if (document.hidden) return
|
|
1814
1689
|
fetchState()
|
|
1815
1690
|
fetchTrace()
|
|
1691
|
+
loadStats()
|
|
1816
1692
|
})
|
|
1817
1693
|
window.addEventListener('resize', function () { renderChart(lastData) })
|
|
1694
|
+
window.setTimeout(finishBoot, 3000)
|
|
1818
1695
|
}
|
|
1819
1696
|
|
|
1820
|
-
if (document.readyState === 'loading')
|
|
1821
|
-
|
|
1822
|
-
|
|
1823
|
-
boot()
|
|
1824
|
-
}
|
|
1825
|
-
})();
|
|
1697
|
+
if (document.readyState === 'loading') document.addEventListener('DOMContentLoaded', boot)
|
|
1698
|
+
else boot()
|
|
1699
|
+
})();
|