Spaces:
Runtime error
Runtime error
代码评审;功能改进
Browse files- client/src/causal_flow.html +15 -5
- client/src/pages/causal_flow/index.ts +218 -92
- client/src/shared/lang/translations.ts +12 -5
- client/src/shared/prediction_attribution/causal_flow/genAttributeDagStepPlayback.ts +5 -2
- client/src/shared/prediction_attribution/causal_flow/genAttributeDagTextMeasure.ts +17 -14
- client/src/shared/prediction_attribution/causal_flow/genAttributeDagView.ts +12 -9
- client/src/shared/prediction_attribution/core/excludePromptPatternsUi.ts +11 -4
- client/src/shared/storage/genAttributeRunCache.ts +5 -0
- client/src/tests/prediction_attribution/genAttributeDagPreprocess.test.ts +49 -0
client/src/causal_flow.html
CHANGED
|
@@ -326,7 +326,7 @@
|
|
| 326 |
<div class="semantic-submode-row attribution-delete-prompt-patterns-header">
|
| 327 |
<span class="semantic-submode-group">
|
| 328 |
<label class="attribution-use-mapping-label"
|
| 329 |
-
title="When enabled, each line below is a regex with the global flag, matched only within
|
| 330 |
data-i18n="title">
|
| 331 |
<input type="checkbox" id="gen_attr_delete_prompt_patterns_enable">
|
| 332 |
<span></span>
|
|
@@ -340,14 +340,14 @@
|
|
| 340 |
placeholder="One regex per line — matched prompt tokens are removed from layout"
|
| 341 |
spellcheck="false"
|
| 342 |
autocomplete="off"
|
| 343 |
-
title="One regex per line (global flag), matched only within
|
| 344 |
data-i18n="placeholder,title"></textarea>
|
| 345 |
</div>
|
| 346 |
<div class="attribution-exclude-prompt-patterns-row">
|
| 347 |
<div class="semantic-submode-row attribution-exclude-prompt-patterns-header">
|
| 348 |
<span class="semantic-submode-group">
|
| 349 |
<label class="attribution-use-mapping-label"
|
| 350 |
-
title="When enabled, each line below is a regex with the global flag, matched only within
|
| 351 |
data-i18n="title">
|
| 352 |
<input type="checkbox" id="gen_attr_exclude_prompt_patterns_enable" checked>
|
| 353 |
<span></span>
|
|
@@ -358,10 +358,10 @@
|
|
| 358 |
</span>
|
| 359 |
</div>
|
| 360 |
<textarea id="gen_attr_exclude_prompt_patterns" class="attribution-exclude-prompt-patterns-input" rows="1"
|
| 361 |
-
placeholder="One regex per line (
|
| 362 |
spellcheck="false"
|
| 363 |
autocomplete="off"
|
| 364 |
-
title="One regex per line (global flag), matched only within
|
| 365 |
data-i18n="placeholder,title"></textarea>
|
| 366 |
</div>
|
| 367 |
|
|
@@ -433,6 +433,16 @@
|
|
| 433 |
</span>
|
| 434 |
</span>
|
| 435 |
</div>
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 436 |
</section>
|
| 437 |
</div>
|
| 438 |
</section>
|
|
|
|
| 326 |
<div class="semantic-submode-row attribution-delete-prompt-patterns-header">
|
| 327 |
<span class="semantic-submode-group">
|
| 328 |
<label class="attribution-use-mapping-label"
|
| 329 |
+
title="When enabled, each line below is a regex with the global flag, matched only within input areas (excluding generated continuation). Matched prompt tokens are physically removed from the DAG — they neither appear nor occupy layout space (stricter than Exclude + Hide)."
|
| 330 |
data-i18n="title">
|
| 331 |
<input type="checkbox" id="gen_attr_delete_prompt_patterns_enable">
|
| 332 |
<span></span>
|
|
|
|
| 340 |
placeholder="One regex per line — matched prompt tokens are removed from layout"
|
| 341 |
spellcheck="false"
|
| 342 |
autocomplete="off"
|
| 343 |
+
title="One regex per line (global flag), matched only within input areas (excluding generated continuation); matched tokens are deleted from the DAG and do not occupy layout space."
|
| 344 |
data-i18n="placeholder,title"></textarea>
|
| 345 |
</div>
|
| 346 |
<div class="attribution-exclude-prompt-patterns-row">
|
| 347 |
<div class="semantic-submode-row attribution-exclude-prompt-patterns-header">
|
| 348 |
<span class="semantic-submode-group">
|
| 349 |
<label class="attribution-use-mapping-label"
|
| 350 |
+
title="When enabled, each line below is a regex with the global flag, matched only within input areas (excluding generated continuation). If a token offset lies fully inside a match, its score is treated as 0."
|
| 351 |
data-i18n="title">
|
| 352 |
<input type="checkbox" id="gen_attr_exclude_prompt_patterns_enable" checked>
|
| 353 |
<span></span>
|
|
|
|
| 358 |
</span>
|
| 359 |
</div>
|
| 360 |
<textarea id="gen_attr_exclude_prompt_patterns" class="attribution-exclude-prompt-patterns-input" rows="1"
|
| 361 |
+
placeholder="One regex per line (input areas only)"
|
| 362 |
spellcheck="false"
|
| 363 |
autocomplete="off"
|
| 364 |
+
title="One regex per line (global flag), matched only within input areas (excluding generated continuation); if a token offset lies fully inside a match, its score is treated as 0."
|
| 365 |
data-i18n="placeholder,title"></textarea>
|
| 366 |
</div>
|
| 367 |
|
|
|
|
| 433 |
</span>
|
| 434 |
</span>
|
| 435 |
</div>
|
| 436 |
+
<div class="gen-attr-dag-measure-width-row semantic-submode-row gen-attr-dag-replay-auto-zoom-row">
|
| 437 |
+
<span class="semantic-submode-group">
|
| 438 |
+
<label class="semantic-submode-label">
|
| 439 |
+
<input type="checkbox" id="gen_attr_dag_replay_auto_zoom"
|
| 440 |
+
title="When checked, step replay (▶) fits the viewport after each step (stops if you pan or zoom). Saved locally."
|
| 441 |
+
data-i18n="title">
|
| 442 |
+
<span data-i18n>Auto zoom</span>
|
| 443 |
+
</label>
|
| 444 |
+
</span>
|
| 445 |
+
</div>
|
| 446 |
</section>
|
| 447 |
</div>
|
| 448 |
</section>
|
client/src/pages/causal_flow/index.ts
CHANGED
|
@@ -150,6 +150,7 @@ const GEN_ATTR_DAG_MEASURE_WIDTH_STORAGE_KEY = 'info_radar_gen_attr_dag_measure_
|
|
| 150 |
const GEN_ATTR_DAG_LAYOUT_MODE_STORAGE_KEY = 'info_radar_gen_attr_dag_layout_mode';
|
| 151 |
const GEN_ATTR_DAG_PLAYBACK_STEP_MS_STORAGE_KEY = 'info_radar_gen_attr_dag_playback_step_ms';
|
| 152 |
const GEN_ATTR_DAG_REPLAY_PACING_MODE_STORAGE_KEY = 'info_radar_gen_attr_dag_replay_pacing_mode';
|
|
|
|
| 153 |
const GEN_ATTR_DAG_PLAYBACK_TOTAL_S_STORAGE_KEY = 'info_radar_gen_attr_dag_playback_total_s';
|
| 154 |
const GEN_ATTR_DAG_NODE_CI_VISUAL_SCALE_STORAGE_KEY = 'info_radar_gen_attr_dag_node_ci_visual_scale';
|
| 155 |
const GEN_ATTR_DAG_DECAY_ATTRIBUTION_HIGH_SURPRISAL_STORAGE_KEY =
|
|
@@ -199,6 +200,8 @@ const GEN_ATTR_DAG_PLAYBACK_STEP_MS_MAX = 10000;
|
|
| 199 |
const GEN_ATTR_DAG_PLAYBACK_TOTAL_S_DEFAULT = 7;
|
| 200 |
const GEN_ATTR_DAG_PLAYBACK_TOTAL_S_MIN = 1;
|
| 201 |
const GEN_ATTR_DAG_PLAYBACK_TOTAL_S_MAX = 3600;
|
|
|
|
|
|
|
| 202 |
|
| 203 |
/** 与无 demoUiOptions 本地缓存时「读出默认」对齐,供重置与可读性单一的来源 */
|
| 204 |
const DEFAULT_GEN_ATTR_DEMO_UI_OPTIONS: GenAttrDemoUiOptions = {
|
|
@@ -219,6 +222,7 @@ const DEFAULT_GEN_ATTR_DEMO_UI_OPTIONS: GenAttrDemoUiOptions = {
|
|
| 219 |
recursiveEdgeBatchAnimationDirection: 'forward',
|
| 220 |
showTokenInfoOnSelected: false,
|
| 221 |
replayPacingMode: 'total',
|
|
|
|
| 222 |
playbackTotalS: GEN_ATTR_DAG_PLAYBACK_TOTAL_S_DEFAULT,
|
| 223 |
playbackStepMs: GEN_ATTR_DAG_PLAYBACK_STEP_MS_DEFAULT,
|
| 224 |
excludePromptPatternsEnabled: true,
|
|
@@ -290,17 +294,36 @@ function readStoredDagPlaybackStepMs(): number {
|
|
| 290 |
}
|
| 291 |
|
| 292 |
function clampDagPlaybackTotalS(n: number): number {
|
|
|
|
|
|
|
| 293 |
return Math.max(
|
| 294 |
GEN_ATTR_DAG_PLAYBACK_TOTAL_S_MIN,
|
| 295 |
-
Math.min(GEN_ATTR_DAG_PLAYBACK_TOTAL_S_MAX,
|
| 296 |
);
|
| 297 |
}
|
| 298 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 299 |
function readStoredDagPlaybackTotalS(): number {
|
| 300 |
return lsReadNumber(
|
| 301 |
GEN_ATTR_DAG_PLAYBACK_TOTAL_S_STORAGE_KEY,
|
| 302 |
GEN_ATTR_DAG_PLAYBACK_TOTAL_S_DEFAULT,
|
| 303 |
-
{ clamp: clampDagPlaybackTotalS },
|
| 304 |
);
|
| 305 |
}
|
| 306 |
|
|
@@ -312,6 +335,14 @@ function readStoredDagReplayPacingMode(): DagReplayPacingMode {
|
|
| 312 |
);
|
| 313 |
}
|
| 314 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 315 |
function readStoredDagLayoutMode(): DagLayoutMode {
|
| 316 |
return lsReadEnum(
|
| 317 |
GEN_ATTR_DAG_LAYOUT_MODE_STORAGE_KEY,
|
|
@@ -406,6 +437,9 @@ const dagPlaybackTotalSInput = document.getElementById(
|
|
| 406 |
) as HTMLInputElement | null;
|
| 407 |
const dagReplayTotalWrap = document.getElementById('gen_attr_dag_replay_total_wrap');
|
| 408 |
const dagReplayStepWrap = document.getElementById('gen_attr_dag_replay_step_wrap');
|
|
|
|
|
|
|
|
|
|
| 409 |
|
| 410 |
/** 与 `#gen_attr_dag_replay_mode` 同步;非法或缺失时视为 `total`。 */
|
| 411 |
function currentDagReplayPacingMode(): DagReplayPacingMode {
|
|
@@ -418,13 +452,13 @@ function readDagReplayPacingFromControls(options?: { writeBack?: boolean }): Dag
|
|
| 418 |
const stepMs = Number.isFinite(rawStep)
|
| 419 |
? clampDagPlaybackStepMs(rawStep)
|
| 420 |
: readStoredDagPlaybackStepMs();
|
| 421 |
-
const rawS =
|
| 422 |
const totalS = Number.isFinite(rawS)
|
| 423 |
? clampDagPlaybackTotalS(rawS)
|
| 424 |
: readStoredDagPlaybackTotalS();
|
| 425 |
if (options?.writeBack) {
|
| 426 |
if (dagPlaybackStepMsInput) dagPlaybackStepMsInput.value = String(stepMs);
|
| 427 |
-
if (dagPlaybackTotalSInput) dagPlaybackTotalSInput.value =
|
| 428 |
}
|
| 429 |
return { mode: currentDagReplayPacingMode(), stepMs, totalS };
|
| 430 |
}
|
|
@@ -542,6 +576,7 @@ bindExcludePatternsUi({
|
|
| 542 |
onEffectiveChange: onExcludePatternsEffectiveChange,
|
| 543 |
defaultTextWhenKeyAbsent: '',
|
| 544 |
defaultEnabledWhenKeyAbsent: false,
|
|
|
|
| 545 |
});
|
| 546 |
bindExcludePatternsUi({
|
| 547 |
storageKeys: {
|
|
@@ -552,6 +587,7 @@ bindExcludePatternsUi({
|
|
| 552 |
enableCheckbox: genAttrExcludePromptPatternsEnable,
|
| 553 |
onEffectiveChange: onExcludePatternsEffectiveChange,
|
| 554 |
defaultTextWhenKeyAbsent: DEFAULT_EXCLUDE_PROMPT_PATTERNS_TEXT,
|
|
|
|
| 555 |
});
|
| 556 |
bindExcludePatternsUi({
|
| 557 |
storageKeys: {
|
|
@@ -562,6 +598,7 @@ bindExcludePatternsUi({
|
|
| 562 |
enableCheckbox: genAttrExcludeGeneratedPatternsEnable,
|
| 563 |
onEffectiveChange: onExcludePatternsEffectiveChange,
|
| 564 |
defaultTextWhenKeyAbsent: DEFAULT_EXCLUDE_GENERATED_PATTERNS_TEXT,
|
|
|
|
| 565 |
});
|
| 566 |
|
| 567 |
/** 与 DAG 同源:DAG 预处理按当前控件即时读取,不读 Attribution 的 localStorage。 */
|
|
@@ -598,7 +635,9 @@ if (dagPlaybackStepMsInput) dagPlaybackStepMsInput.value = String(initialDagPlay
|
|
| 598 |
const initialDagReplayPacingMode = readStoredDagReplayPacingMode();
|
| 599 |
if (dagReplayModeSelect) dagReplayModeSelect.value = initialDagReplayPacingMode;
|
| 600 |
const initialDagPlaybackTotalS = readStoredDagPlaybackTotalS();
|
| 601 |
-
if (dagPlaybackTotalSInput) dagPlaybackTotalSInput.value =
|
|
|
|
|
|
|
| 602 |
applyDagReplaySpeedUi();
|
| 603 |
|
| 604 |
const genAttrResultsNode = genAttrResultsEl.node() as HTMLElement | null;
|
|
@@ -613,9 +652,7 @@ const initialDagNodeCiVisualScale = readStoredDagNodeCiVisualScale();
|
|
| 613 |
if (dagNodeCiVisualScaleInput) dagNodeCiVisualScaleInput.checked = initialDagNodeCiVisualScale;
|
| 614 |
setDagNodeCiVisualScaleEnabled(initialDagNodeCiVisualScale);
|
| 615 |
dagNodeCiVisualScaleInput?.addEventListener('change', () => {
|
| 616 |
-
|
| 617 |
-
lsWriteBool(GEN_ATTR_DAG_NODE_CI_VISUAL_SCALE_STORAGE_KEY, enabled, '1');
|
| 618 |
-
setDagNodeCiVisualScaleEnabled(enabled);
|
| 619 |
tryResetAndReplayDag();
|
| 620 |
});
|
| 621 |
|
|
@@ -632,9 +669,7 @@ if (dagDecayAttributionHighSurprisalInput) {
|
|
| 632 |
}
|
| 633 |
setDagDecayAttributionToHighSurprisalTargetEnabled(initialDagDecayAttributionHighSurprisal);
|
| 634 |
dagDecayAttributionHighSurprisalInput?.addEventListener('change', () => {
|
| 635 |
-
|
| 636 |
-
lsWriteBool(GEN_ATTR_DAG_DECAY_ATTRIBUTION_HIGH_SURPRISAL_STORAGE_KEY, enabled, '1');
|
| 637 |
-
setDagDecayAttributionToHighSurprisalTargetEnabled(enabled);
|
| 638 |
tryResetAndReplayDag({ refit: false });
|
| 639 |
});
|
| 640 |
|
|
@@ -653,9 +688,7 @@ const initialDagHideInactiveEdges = readStoredDagHideInactiveEdges();
|
|
| 653 |
if (dagHideInactiveEdgesInput) dagHideInactiveEdgesInput.checked = initialDagHideInactiveEdges;
|
| 654 |
applyDagHideInactiveEdges(initialDagHideInactiveEdges);
|
| 655 |
dagHideInactiveEdgesInput?.addEventListener('change', () => {
|
| 656 |
-
|
| 657 |
-
lsWriteBool(GEN_ATTR_DAG_HIDE_INACTIVE_EDGES_STORAGE_KEY, hide, '1');
|
| 658 |
-
applyDagHideInactiveEdges(hide);
|
| 659 |
});
|
| 660 |
|
| 661 |
function readStoredDagShowDownstreamInfluence(): boolean {
|
|
@@ -670,9 +703,7 @@ if (dagShowDownstreamInfluenceInput) {
|
|
| 670 |
dagShowDownstreamInfluenceInput.checked = initialDagShowDownstreamInfluence;
|
| 671 |
}
|
| 672 |
dagShowDownstreamInfluenceInput?.addEventListener('change', () => {
|
| 673 |
-
|
| 674 |
-
lsWriteBool(GEN_ATTR_DAG_SHOW_DOWNSTREAM_INFLUENCE_STORAGE_KEY, show, '1');
|
| 675 |
-
dagHandle.setShowDownstreamInfluence(show);
|
| 676 |
});
|
| 677 |
|
| 678 |
function syncDimInactiveTokensThresholdInputUi(): void {
|
|
@@ -790,24 +821,18 @@ setDimInactiveTokensThresholdControlFromFraction(initialDagDimInactiveTokensThre
|
|
| 790 |
applyDagRecursiveAttributionSubmodeUi();
|
| 791 |
syncDimInactiveTokensThresholdInputUi();
|
| 792 |
dagRecursiveAttributionInput?.addEventListener('change', () => {
|
| 793 |
-
const enabled = dagRecursiveAttributionInput.checked;
|
| 794 |
-
lsWriteBool(GEN_ATTR_DAG_RECURSIVE_ATTRIBUTION_STORAGE_KEY, enabled, '1');
|
| 795 |
applyDagRecursiveAttributionSubmodeUi();
|
| 796 |
-
dagHandle.setRecursiveAttributionEnabled(
|
| 797 |
});
|
| 798 |
dagRecursiveEdgeAnimationDirectionSelect?.addEventListener('change', () => {
|
| 799 |
const direction = currentDagRecursiveEdgeAnimationDirection();
|
| 800 |
dagRecursiveEdgeAnimationDirectionSelect.value = direction;
|
| 801 |
-
lsWriteString(GEN_ATTR_DAG_RECURSIVE_EDGE_ANIMATION_DIRECTION_STORAGE_KEY, direction);
|
| 802 |
dagHandle.setRecursiveEdgeBatchAnimationDirection(direction);
|
| 803 |
});
|
| 804 |
|
| 805 |
dagDimInactiveTokensInput?.addEventListener('change', () => {
|
| 806 |
-
const enabled = dagDimInactiveTokensInput.checked;
|
| 807 |
-
lsWriteBool(GEN_ATTR_DAG_DIM_INACTIVE_TOKENS_STORAGE_KEY, enabled, '1');
|
| 808 |
syncDimInactiveTokensThresholdInputUi();
|
| 809 |
applyDagDimInactiveTokensFromControls();
|
| 810 |
-
syncGenAttrResetUiOptionsButtonState();
|
| 811 |
});
|
| 812 |
|
| 813 |
dagDimInactiveTokensThresholdInput?.addEventListener('input', () => {
|
|
@@ -816,16 +841,11 @@ dagDimInactiveTokensThresholdInput?.addEventListener('input', () => {
|
|
| 816 |
dagDimInactiveTokensThresholdInput?.addEventListener('change', () => {
|
| 817 |
const t = readDimInactiveTokensThresholdFromControl();
|
| 818 |
setDimInactiveTokensThresholdControlFromFraction(t);
|
| 819 |
-
lsSet(GEN_ATTR_DAG_DIM_INACTIVE_TOKENS_THRESHOLD_STORAGE_KEY, String(t));
|
| 820 |
applyDagDimInactiveTokensFromControls();
|
| 821 |
-
syncGenAttrResetUiOptionsButtonState();
|
| 822 |
});
|
| 823 |
|
| 824 |
dagDimInactiveNotInAnimationInput?.addEventListener('change', () => {
|
| 825 |
-
const enabled = dagDimInactiveNotInAnimationInput.checked;
|
| 826 |
-
lsWriteBool(GEN_ATTR_DAG_DIM_INACTIVE_NOT_IN_ANIMATION_STORAGE_KEY, enabled, '1');
|
| 827 |
applyDagDimInactiveTokensFromControls();
|
| 828 |
-
syncGenAttrResetUiOptionsButtonState();
|
| 829 |
});
|
| 830 |
|
| 831 |
function readStoredDagHideExcludedTokens(): boolean {
|
|
@@ -847,15 +867,11 @@ function readStoredDagShowTopkOnSelected(): boolean {
|
|
| 847 |
const initialDagShowTopkOnSelected = readStoredDagShowTopkOnSelected();
|
| 848 |
if (dagShowTopkOnSelectedInput) dagShowTopkOnSelectedInput.checked = initialDagShowTopkOnSelected;
|
| 849 |
dagHideExcludedTokensInput?.addEventListener('change', () => {
|
| 850 |
-
|
| 851 |
-
lsWriteBool(GEN_ATTR_DAG_HIDE_EXCLUDED_TOKENS_STORAGE_KEY, hide, '1');
|
| 852 |
-
dagHandle.setHideExcludedTokens(hide);
|
| 853 |
});
|
| 854 |
|
| 855 |
dagShowTopkOnSelectedInput?.addEventListener('change', () => {
|
| 856 |
-
|
| 857 |
-
lsWriteBool(GEN_ATTR_DAG_SHOW_TOPK_ON_SELECTED_STORAGE_KEY, show, '1');
|
| 858 |
-
dagHandle.setShowTokenInfoOnSelected(show);
|
| 859 |
});
|
| 860 |
|
| 861 |
setPageOptsGetter(() => {
|
|
@@ -878,22 +894,28 @@ dagPlaybackStepMsInput?.addEventListener('change', () => {
|
|
| 878 |
? clampDagPlaybackStepMs(raw)
|
| 879 |
: GEN_ATTR_DAG_PLAYBACK_STEP_MS_DEFAULT;
|
| 880 |
dagPlaybackStepMsInput.value = String(ms);
|
| 881 |
-
lsSet(GEN_ATTR_DAG_PLAYBACK_STEP_MS_STORAGE_KEY, String(ms));
|
| 882 |
});
|
| 883 |
|
| 884 |
dagReplayModeSelect?.addEventListener('change', () => {
|
| 885 |
-
const mode = currentDagReplayPacingMode();
|
| 886 |
-
lsWriteString(GEN_ATTR_DAG_REPLAY_PACING_MODE_STORAGE_KEY, mode);
|
| 887 |
applyDagReplaySpeedUi();
|
| 888 |
});
|
| 889 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 890 |
dagPlaybackTotalSInput?.addEventListener('change', () => {
|
| 891 |
-
|
| 892 |
-
|
| 893 |
-
|
| 894 |
-
|
| 895 |
-
|
| 896 |
-
lsSet(GEN_ATTR_DAG_PLAYBACK_TOTAL_S_STORAGE_KEY, String(s));
|
| 897 |
});
|
| 898 |
|
| 899 |
function isSkipChatTemplate(): boolean {
|
|
@@ -1210,7 +1232,7 @@ function handleDagPlaybackToggle(wantPlay: boolean): void {
|
|
| 1210 |
cancelDagLastTokenDwell();
|
| 1211 |
const steps = h.getAllSteps();
|
| 1212 |
if (dagPlaybackNextIndex >= steps.length) {
|
| 1213 |
-
//
|
| 1214 |
dagHandle.reset(true);
|
| 1215 |
dagPlaybackNextIndex = 0;
|
| 1216 |
}
|
|
@@ -1261,15 +1283,19 @@ function handleDagPlaybackToggle(wantPlay: boolean): void {
|
|
| 1261 |
if (visible) toolCallingPendingLine?.show();
|
| 1262 |
else toolCallingPendingLine?.hide();
|
| 1263 |
},
|
| 1264 |
-
showPrompt: (
|
| 1265 |
const firstStep = steps[0]!;
|
| 1266 |
syncDagInputLayerAtStep({
|
| 1267 |
catalogSpans: currentRunPromptSpans,
|
| 1268 |
layoutWire: firstStep.context,
|
| 1269 |
inputRanges: firstStep.inputRanges,
|
| 1270 |
-
fitViewport,
|
| 1271 |
});
|
| 1272 |
},
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1273 |
showToolResponse: (stepIndex) => {
|
| 1274 |
const step = steps[stepIndex]!;
|
| 1275 |
syncDagInputLayerAtStep({
|
|
@@ -1342,10 +1368,8 @@ toolCallingPendingLine = attachToolCallingPendingLine(
|
|
| 1342 |
);
|
| 1343 |
|
| 1344 |
dagLayoutModeSelect?.addEventListener('change', () => {
|
| 1345 |
-
const mode = currentDagLayoutMode();
|
| 1346 |
-
lsWriteString(GEN_ATTR_DAG_LAYOUT_MODE_STORAGE_KEY, mode);
|
| 1347 |
applyDagLayoutModeUi();
|
| 1348 |
-
dagHandle.setLayoutMode(
|
| 1349 |
});
|
| 1350 |
|
| 1351 |
/**
|
|
@@ -1397,7 +1421,6 @@ dagMeasureWidthInput?.addEventListener('change', () => {
|
|
| 1397 |
? clampDagMeasureWidth(raw)
|
| 1398 |
: GEN_ATTR_DAG_MEASURE_WIDTH_DEFAULT;
|
| 1399 |
dagMeasureWidthInput.value = String(w);
|
| 1400 |
-
lsSet(GEN_ATTR_DAG_MEASURE_WIDTH_STORAGE_KEY, String(w));
|
| 1401 |
dagHandle.setMeasureWidthPx(w);
|
| 1402 |
tryResetAndReplayDag();
|
| 1403 |
});
|
|
@@ -1406,7 +1429,6 @@ dagCompactnessInput?.addEventListener('change', () => {
|
|
| 1406 |
const raw = parseFloat(dagCompactnessInput.value);
|
| 1407 |
const c = Number.isFinite(raw) ? clampDagCompactness(raw) : DAG_COMPACTNESS_DEFAULT;
|
| 1408 |
dagCompactnessInput.value = String(c);
|
| 1409 |
-
lsSet(GEN_ATTR_DAG_COMPACTNESS_STORAGE_KEY, String(c));
|
| 1410 |
dagHandle.setDagCompactness(c);
|
| 1411 |
tryResetAndReplayDag();
|
| 1412 |
});
|
|
@@ -1417,7 +1439,6 @@ dagEdgeTopPCoverageInput?.addEventListener('change', () => {
|
|
| 1417 |
? clampDagEdgeTopPCoverage(raw)
|
| 1418 |
: DAG_EDGE_TOP_P_COVERAGE_DEFAULT;
|
| 1419 |
dagEdgeTopPCoverageInput.value = String(c);
|
| 1420 |
-
lsSet(GEN_ATTR_DAG_EDGE_TOP_P_COVERAGE_STORAGE_KEY, String(c));
|
| 1421 |
dagHandle.setEdgeTopPCoverage(c);
|
| 1422 |
tryResetAndReplayDag({ refit: false });
|
| 1423 |
});
|
|
@@ -1428,7 +1449,6 @@ dagLinearArcIntervalInput?.addEventListener('change', () => {
|
|
| 1428 |
? clampLinearArcAdjacentGap(raw)
|
| 1429 |
: LINEAR_ARC_ADJACENT_GAP_DEFAULT;
|
| 1430 |
dagLinearArcIntervalInput.value = String(n);
|
| 1431 |
-
lsSet(GEN_ATTR_DAG_LINEAR_ARC_GAP_STORAGE_KEY, String(n));
|
| 1432 |
dagHandle.setLinearArcAdjacentGapPx(n, { skipRefit: isDagBusy() });
|
| 1433 |
});
|
| 1434 |
|
|
@@ -1476,6 +1496,7 @@ function readGenAttrDemoUiOptionsFromControls(): GenAttrDemoUiOptions {
|
|
| 1476 |
recursiveEdgeBatchAnimationDirection: currentDagRecursiveEdgeAnimationDirection(),
|
| 1477 |
showTokenInfoOnSelected: dagShowTopkOnSelectedInput?.checked ?? false,
|
| 1478 |
replayPacingMode,
|
|
|
|
| 1479 |
playbackTotalS,
|
| 1480 |
playbackStepMs,
|
| 1481 |
excludePromptPatternsEnabled: genAttrExcludePromptPatternsEnable?.checked ?? true,
|
|
@@ -1510,8 +1531,133 @@ function syncGenAttrResetUiOptionsButtonState(): void {
|
|
| 1510 |
}
|
| 1511 |
|
| 1512 |
/**
|
| 1513 |
-
*
|
|
|
|
|
|
|
|
|
|
| 1514 |
*/
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1515 |
function applyGenAttrExcludePatternsFromDemoUiSnap(snap: Partial<GenAttrDemoUiOptions>): void {
|
| 1516 |
const {
|
| 1517 |
deletePromptPatternsEnabled,
|
|
@@ -1663,9 +1809,12 @@ function applyGenAttrDemoUiOptionsSnap(snap: Partial<GenAttrDemoUiOptions>): voi
|
|
| 1663 |
if (dagReplayModeSelect) dagReplayModeSelect.value = snap.replayPacingMode;
|
| 1664 |
applyDagReplaySpeedUi();
|
| 1665 |
}
|
|
|
|
|
|
|
|
|
|
| 1666 |
if (snap.playbackTotalS !== undefined) {
|
| 1667 |
const s = clampDagPlaybackTotalS(snap.playbackTotalS);
|
| 1668 |
-
if (dagPlaybackTotalSInput) dagPlaybackTotalSInput.value =
|
| 1669 |
}
|
| 1670 |
if (snap.playbackStepMs !== undefined) {
|
| 1671 |
const ms = clampDagPlaybackStepMs(snap.playbackStepMs);
|
|
@@ -1673,6 +1822,7 @@ function applyGenAttrDemoUiOptionsSnap(snap: Partial<GenAttrDemoUiOptions>): voi
|
|
| 1673 |
}
|
| 1674 |
|
| 1675 |
applyGenAttrExcludePatternsFromDemoUiSnap(snap);
|
|
|
|
| 1676 |
syncGenAttrResetUiOptionsButtonState();
|
| 1677 |
}
|
| 1678 |
|
|
@@ -1695,38 +1845,6 @@ function applyGenAttrDemoUiOptionsFromRecord(rec: GenAttrCachedRun): void {
|
|
| 1695 |
applyGenAttrDemoUiOptionsSnap(rec.demoUiOptions);
|
| 1696 |
}
|
| 1697 |
|
| 1698 |
-
/** Gen Attribute demo-UI scope:与 {@link readGenAttrDemoUiOptionsFromControls} / IndexedDB demo 快照一致(不含 Model、Max tokens、prompt 正文)。 */
|
| 1699 |
-
const GEN_ATTR_DEMO_UI_LOCAL_STORAGE_KEYS: readonly string[] = [
|
| 1700 |
-
GEN_ATTR_DAG_MEASURE_WIDTH_STORAGE_KEY,
|
| 1701 |
-
GEN_ATTR_DAG_LAYOUT_MODE_STORAGE_KEY,
|
| 1702 |
-
GEN_ATTR_DAG_PLAYBACK_STEP_MS_STORAGE_KEY,
|
| 1703 |
-
GEN_ATTR_DAG_REPLAY_PACING_MODE_STORAGE_KEY,
|
| 1704 |
-
GEN_ATTR_DAG_PLAYBACK_TOTAL_S_STORAGE_KEY,
|
| 1705 |
-
GEN_ATTR_DAG_NODE_CI_VISUAL_SCALE_STORAGE_KEY,
|
| 1706 |
-
GEN_ATTR_DAG_DECAY_ATTRIBUTION_HIGH_SURPRISAL_STORAGE_KEY,
|
| 1707 |
-
GEN_ATTR_DAG_HIDE_INACTIVE_EDGES_STORAGE_KEY,
|
| 1708 |
-
GEN_ATTR_DAG_SHOW_DOWNSTREAM_INFLUENCE_STORAGE_KEY,
|
| 1709 |
-
GEN_ATTR_DAG_RECURSIVE_ATTRIBUTION_STORAGE_KEY,
|
| 1710 |
-
GEN_ATTR_DAG_RECURSIVE_EDGE_ANIMATION_DIRECTION_STORAGE_KEY,
|
| 1711 |
-
GEN_ATTR_DAG_HIDE_EXCLUDED_TOKENS_STORAGE_KEY,
|
| 1712 |
-
GEN_ATTR_DAG_SHOW_TOPK_ON_SELECTED_STORAGE_KEY,
|
| 1713 |
-
GEN_ATTR_DAG_LINEAR_ARC_GAP_STORAGE_KEY,
|
| 1714 |
-
GEN_ATTR_DAG_COMPACTNESS_STORAGE_KEY,
|
| 1715 |
-
GEN_ATTR_DAG_EDGE_TOP_P_COVERAGE_STORAGE_KEY,
|
| 1716 |
-
GEN_ATTR_DELETE_PROMPT_PATTERNS_STORAGE_KEY,
|
| 1717 |
-
GEN_ATTR_DELETE_PROMPT_PATTERNS_ENABLED_STORAGE_KEY,
|
| 1718 |
-
GEN_ATTR_EXCLUDE_PROMPT_PATTERNS_STORAGE_KEY,
|
| 1719 |
-
GEN_ATTR_EXCLUDE_PROMPT_PATTERNS_ENABLED_STORAGE_KEY,
|
| 1720 |
-
GEN_ATTR_EXCLUDE_GENERATED_PATTERNS_STORAGE_KEY,
|
| 1721 |
-
GEN_ATTR_EXCLUDE_GENERATED_PATTERNS_ENABLED_STORAGE_KEY,
|
| 1722 |
-
];
|
| 1723 |
-
|
| 1724 |
-
function removeGenAttrDemoUiOptionsFromLocalStorage(): void {
|
| 1725 |
-
for (const k of GEN_ATTR_DEMO_UI_LOCAL_STORAGE_KEYS) {
|
| 1726 |
-
lsRemove(k);
|
| 1727 |
-
}
|
| 1728 |
-
}
|
| 1729 |
-
|
| 1730 |
/** 重置「DAG 演示用 UI」:清 LS 后以 {@link DEFAULT_GEN_ATTR_DEMO_UI_OPTIONS} 全量套用。 */
|
| 1731 |
function resetGenAttrDemoUiOptionsToDefaults(): void {
|
| 1732 |
stopDagPlayback();
|
|
@@ -1740,10 +1858,18 @@ genAttrResetUiOptionsBtn?.addEventListener('click', resetGenAttrDemoUiOptionsToD
|
|
| 1740 |
(() => {
|
| 1741 |
const panel = document.querySelector('.gen-attribute-page .input-section');
|
| 1742 |
if (!panel) return;
|
| 1743 |
-
const
|
| 1744 |
-
|
| 1745 |
-
|
| 1746 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1747 |
})();
|
| 1748 |
|
| 1749 |
window.addEventListener('pagehide', (ev) => {
|
|
|
|
| 150 |
const GEN_ATTR_DAG_LAYOUT_MODE_STORAGE_KEY = 'info_radar_gen_attr_dag_layout_mode';
|
| 151 |
const GEN_ATTR_DAG_PLAYBACK_STEP_MS_STORAGE_KEY = 'info_radar_gen_attr_dag_playback_step_ms';
|
| 152 |
const GEN_ATTR_DAG_REPLAY_PACING_MODE_STORAGE_KEY = 'info_radar_gen_attr_dag_replay_pacing_mode';
|
| 153 |
+
const GEN_ATTR_DAG_REPLAY_AUTO_ZOOM_STORAGE_KEY = 'info_radar_gen_attr_dag_replay_auto_zoom';
|
| 154 |
const GEN_ATTR_DAG_PLAYBACK_TOTAL_S_STORAGE_KEY = 'info_radar_gen_attr_dag_playback_total_s';
|
| 155 |
const GEN_ATTR_DAG_NODE_CI_VISUAL_SCALE_STORAGE_KEY = 'info_radar_gen_attr_dag_node_ci_visual_scale';
|
| 156 |
const GEN_ATTR_DAG_DECAY_ATTRIBUTION_HIGH_SURPRISAL_STORAGE_KEY =
|
|
|
|
| 200 |
const GEN_ATTR_DAG_PLAYBACK_TOTAL_S_DEFAULT = 7;
|
| 201 |
const GEN_ATTR_DAG_PLAYBACK_TOTAL_S_MIN = 1;
|
| 202 |
const GEN_ATTR_DAG_PLAYBACK_TOTAL_S_MAX = 3600;
|
| 203 |
+
/** 手输总量化的步长;原生 step=1 箭头在 `input` 里另取整。 */
|
| 204 |
+
const GEN_ATTR_DAG_PLAYBACK_TOTAL_S_STEP = 0.1;
|
| 205 |
|
| 206 |
/** 与无 demoUiOptions 本地缓存时「读出默认」对齐,供重置与可读性单一的来源 */
|
| 207 |
const DEFAULT_GEN_ATTR_DEMO_UI_OPTIONS: GenAttrDemoUiOptions = {
|
|
|
|
| 222 |
recursiveEdgeBatchAnimationDirection: 'forward',
|
| 223 |
showTokenInfoOnSelected: false,
|
| 224 |
replayPacingMode: 'total',
|
| 225 |
+
replayAutoZoom: false,
|
| 226 |
playbackTotalS: GEN_ATTR_DAG_PLAYBACK_TOTAL_S_DEFAULT,
|
| 227 |
playbackStepMs: GEN_ATTR_DAG_PLAYBACK_STEP_MS_DEFAULT,
|
| 228 |
excludePromptPatternsEnabled: true,
|
|
|
|
| 294 |
}
|
| 295 |
|
| 296 |
function clampDagPlaybackTotalS(n: number): number {
|
| 297 |
+
const stepped =
|
| 298 |
+
Math.round(n / GEN_ATTR_DAG_PLAYBACK_TOTAL_S_STEP) * GEN_ATTR_DAG_PLAYBACK_TOTAL_S_STEP;
|
| 299 |
return Math.max(
|
| 300 |
GEN_ATTR_DAG_PLAYBACK_TOTAL_S_MIN,
|
| 301 |
+
Math.min(GEN_ATTR_DAG_PLAYBACK_TOTAL_S_MAX, stepped),
|
| 302 |
);
|
| 303 |
}
|
| 304 |
|
| 305 |
+
function formatDagPlaybackTotalS(n: number): string {
|
| 306 |
+
const s = clampDagPlaybackTotalS(n);
|
| 307 |
+
return Number.isInteger(s) ? String(s) : s.toFixed(1);
|
| 308 |
+
}
|
| 309 |
+
|
| 310 |
+
function readDagPlaybackTotalSFromInput(): number {
|
| 311 |
+
const raw = parseFloat(dagPlaybackTotalSInput?.value ?? '');
|
| 312 |
+
return Number.isFinite(raw)
|
| 313 |
+
? clampDagPlaybackTotalS(raw)
|
| 314 |
+
: GEN_ATTR_DAG_PLAYBACK_TOTAL_S_DEFAULT;
|
| 315 |
+
}
|
| 316 |
+
|
| 317 |
+
function commitDagPlaybackTotalSInput(): void {
|
| 318 |
+
if (!dagPlaybackTotalSInput) return;
|
| 319 |
+
dagPlaybackTotalSInput.value = formatDagPlaybackTotalS(readDagPlaybackTotalSFromInput());
|
| 320 |
+
}
|
| 321 |
+
|
| 322 |
function readStoredDagPlaybackTotalS(): number {
|
| 323 |
return lsReadNumber(
|
| 324 |
GEN_ATTR_DAG_PLAYBACK_TOTAL_S_STORAGE_KEY,
|
| 325 |
GEN_ATTR_DAG_PLAYBACK_TOTAL_S_DEFAULT,
|
| 326 |
+
{ parse: 'float', clamp: clampDagPlaybackTotalS },
|
| 327 |
);
|
| 328 |
}
|
| 329 |
|
|
|
|
| 335 |
);
|
| 336 |
}
|
| 337 |
|
| 338 |
+
function readStoredDagReplayAutoZoom(): boolean {
|
| 339 |
+
return lsReadBool(
|
| 340 |
+
GEN_ATTR_DAG_REPLAY_AUTO_ZOOM_STORAGE_KEY,
|
| 341 |
+
DEFAULT_GEN_ATTR_DEMO_UI_OPTIONS.replayAutoZoom,
|
| 342 |
+
{ encoding: '1' },
|
| 343 |
+
);
|
| 344 |
+
}
|
| 345 |
+
|
| 346 |
function readStoredDagLayoutMode(): DagLayoutMode {
|
| 347 |
return lsReadEnum(
|
| 348 |
GEN_ATTR_DAG_LAYOUT_MODE_STORAGE_KEY,
|
|
|
|
| 437 |
) as HTMLInputElement | null;
|
| 438 |
const dagReplayTotalWrap = document.getElementById('gen_attr_dag_replay_total_wrap');
|
| 439 |
const dagReplayStepWrap = document.getElementById('gen_attr_dag_replay_step_wrap');
|
| 440 |
+
const dagReplayAutoZoomInput = document.getElementById(
|
| 441 |
+
'gen_attr_dag_replay_auto_zoom',
|
| 442 |
+
) as HTMLInputElement | null;
|
| 443 |
|
| 444 |
/** 与 `#gen_attr_dag_replay_mode` 同步;非法或缺失时视为 `total`。 */
|
| 445 |
function currentDagReplayPacingMode(): DagReplayPacingMode {
|
|
|
|
| 452 |
const stepMs = Number.isFinite(rawStep)
|
| 453 |
? clampDagPlaybackStepMs(rawStep)
|
| 454 |
: readStoredDagPlaybackStepMs();
|
| 455 |
+
const rawS = parseFloat(dagPlaybackTotalSInput?.value ?? '');
|
| 456 |
const totalS = Number.isFinite(rawS)
|
| 457 |
? clampDagPlaybackTotalS(rawS)
|
| 458 |
: readStoredDagPlaybackTotalS();
|
| 459 |
if (options?.writeBack) {
|
| 460 |
if (dagPlaybackStepMsInput) dagPlaybackStepMsInput.value = String(stepMs);
|
| 461 |
+
if (dagPlaybackTotalSInput) dagPlaybackTotalSInput.value = formatDagPlaybackTotalS(totalS);
|
| 462 |
}
|
| 463 |
return { mode: currentDagReplayPacingMode(), stepMs, totalS };
|
| 464 |
}
|
|
|
|
| 576 |
onEffectiveChange: onExcludePatternsEffectiveChange,
|
| 577 |
defaultTextWhenKeyAbsent: '',
|
| 578 |
defaultEnabledWhenKeyAbsent: false,
|
| 579 |
+
skipLocalStoragePersist: true,
|
| 580 |
});
|
| 581 |
bindExcludePatternsUi({
|
| 582 |
storageKeys: {
|
|
|
|
| 587 |
enableCheckbox: genAttrExcludePromptPatternsEnable,
|
| 588 |
onEffectiveChange: onExcludePatternsEffectiveChange,
|
| 589 |
defaultTextWhenKeyAbsent: DEFAULT_EXCLUDE_PROMPT_PATTERNS_TEXT,
|
| 590 |
+
skipLocalStoragePersist: true,
|
| 591 |
});
|
| 592 |
bindExcludePatternsUi({
|
| 593 |
storageKeys: {
|
|
|
|
| 598 |
enableCheckbox: genAttrExcludeGeneratedPatternsEnable,
|
| 599 |
onEffectiveChange: onExcludePatternsEffectiveChange,
|
| 600 |
defaultTextWhenKeyAbsent: DEFAULT_EXCLUDE_GENERATED_PATTERNS_TEXT,
|
| 601 |
+
skipLocalStoragePersist: true,
|
| 602 |
});
|
| 603 |
|
| 604 |
/** 与 DAG 同源:DAG 预处理按当前控件即时读取,不读 Attribution 的 localStorage。 */
|
|
|
|
| 635 |
const initialDagReplayPacingMode = readStoredDagReplayPacingMode();
|
| 636 |
if (dagReplayModeSelect) dagReplayModeSelect.value = initialDagReplayPacingMode;
|
| 637 |
const initialDagPlaybackTotalS = readStoredDagPlaybackTotalS();
|
| 638 |
+
if (dagPlaybackTotalSInput) dagPlaybackTotalSInput.value = formatDagPlaybackTotalS(initialDagPlaybackTotalS);
|
| 639 |
+
const initialDagReplayAutoZoom = readStoredDagReplayAutoZoom();
|
| 640 |
+
if (dagReplayAutoZoomInput) dagReplayAutoZoomInput.checked = initialDagReplayAutoZoom;
|
| 641 |
applyDagReplaySpeedUi();
|
| 642 |
|
| 643 |
const genAttrResultsNode = genAttrResultsEl.node() as HTMLElement | null;
|
|
|
|
| 652 |
if (dagNodeCiVisualScaleInput) dagNodeCiVisualScaleInput.checked = initialDagNodeCiVisualScale;
|
| 653 |
setDagNodeCiVisualScaleEnabled(initialDagNodeCiVisualScale);
|
| 654 |
dagNodeCiVisualScaleInput?.addEventListener('change', () => {
|
| 655 |
+
setDagNodeCiVisualScaleEnabled(dagNodeCiVisualScaleInput.checked);
|
|
|
|
|
|
|
| 656 |
tryResetAndReplayDag();
|
| 657 |
});
|
| 658 |
|
|
|
|
| 669 |
}
|
| 670 |
setDagDecayAttributionToHighSurprisalTargetEnabled(initialDagDecayAttributionHighSurprisal);
|
| 671 |
dagDecayAttributionHighSurprisalInput?.addEventListener('change', () => {
|
| 672 |
+
setDagDecayAttributionToHighSurprisalTargetEnabled(dagDecayAttributionHighSurprisalInput.checked);
|
|
|
|
|
|
|
| 673 |
tryResetAndReplayDag({ refit: false });
|
| 674 |
});
|
| 675 |
|
|
|
|
| 688 |
if (dagHideInactiveEdgesInput) dagHideInactiveEdgesInput.checked = initialDagHideInactiveEdges;
|
| 689 |
applyDagHideInactiveEdges(initialDagHideInactiveEdges);
|
| 690 |
dagHideInactiveEdgesInput?.addEventListener('change', () => {
|
| 691 |
+
applyDagHideInactiveEdges(dagHideInactiveEdgesInput.checked);
|
|
|
|
|
|
|
| 692 |
});
|
| 693 |
|
| 694 |
function readStoredDagShowDownstreamInfluence(): boolean {
|
|
|
|
| 703 |
dagShowDownstreamInfluenceInput.checked = initialDagShowDownstreamInfluence;
|
| 704 |
}
|
| 705 |
dagShowDownstreamInfluenceInput?.addEventListener('change', () => {
|
| 706 |
+
dagHandle.setShowDownstreamInfluence(dagShowDownstreamInfluenceInput.checked);
|
|
|
|
|
|
|
| 707 |
});
|
| 708 |
|
| 709 |
function syncDimInactiveTokensThresholdInputUi(): void {
|
|
|
|
| 821 |
applyDagRecursiveAttributionSubmodeUi();
|
| 822 |
syncDimInactiveTokensThresholdInputUi();
|
| 823 |
dagRecursiveAttributionInput?.addEventListener('change', () => {
|
|
|
|
|
|
|
| 824 |
applyDagRecursiveAttributionSubmodeUi();
|
| 825 |
+
dagHandle.setRecursiveAttributionEnabled(dagRecursiveAttributionInput.checked);
|
| 826 |
});
|
| 827 |
dagRecursiveEdgeAnimationDirectionSelect?.addEventListener('change', () => {
|
| 828 |
const direction = currentDagRecursiveEdgeAnimationDirection();
|
| 829 |
dagRecursiveEdgeAnimationDirectionSelect.value = direction;
|
|
|
|
| 830 |
dagHandle.setRecursiveEdgeBatchAnimationDirection(direction);
|
| 831 |
});
|
| 832 |
|
| 833 |
dagDimInactiveTokensInput?.addEventListener('change', () => {
|
|
|
|
|
|
|
| 834 |
syncDimInactiveTokensThresholdInputUi();
|
| 835 |
applyDagDimInactiveTokensFromControls();
|
|
|
|
| 836 |
});
|
| 837 |
|
| 838 |
dagDimInactiveTokensThresholdInput?.addEventListener('input', () => {
|
|
|
|
| 841 |
dagDimInactiveTokensThresholdInput?.addEventListener('change', () => {
|
| 842 |
const t = readDimInactiveTokensThresholdFromControl();
|
| 843 |
setDimInactiveTokensThresholdControlFromFraction(t);
|
|
|
|
| 844 |
applyDagDimInactiveTokensFromControls();
|
|
|
|
| 845 |
});
|
| 846 |
|
| 847 |
dagDimInactiveNotInAnimationInput?.addEventListener('change', () => {
|
|
|
|
|
|
|
| 848 |
applyDagDimInactiveTokensFromControls();
|
|
|
|
| 849 |
});
|
| 850 |
|
| 851 |
function readStoredDagHideExcludedTokens(): boolean {
|
|
|
|
| 867 |
const initialDagShowTopkOnSelected = readStoredDagShowTopkOnSelected();
|
| 868 |
if (dagShowTopkOnSelectedInput) dagShowTopkOnSelectedInput.checked = initialDagShowTopkOnSelected;
|
| 869 |
dagHideExcludedTokensInput?.addEventListener('change', () => {
|
| 870 |
+
dagHandle.setHideExcludedTokens(dagHideExcludedTokensInput.checked);
|
|
|
|
|
|
|
| 871 |
});
|
| 872 |
|
| 873 |
dagShowTopkOnSelectedInput?.addEventListener('change', () => {
|
| 874 |
+
dagHandle.setShowTokenInfoOnSelected(dagShowTopkOnSelectedInput.checked);
|
|
|
|
|
|
|
| 875 |
});
|
| 876 |
|
| 877 |
setPageOptsGetter(() => {
|
|
|
|
| 894 |
? clampDagPlaybackStepMs(raw)
|
| 895 |
: GEN_ATTR_DAG_PLAYBACK_STEP_MS_DEFAULT;
|
| 896 |
dagPlaybackStepMsInput.value = String(ms);
|
|
|
|
| 897 |
});
|
| 898 |
|
| 899 |
dagReplayModeSelect?.addEventListener('change', () => {
|
|
|
|
|
|
|
| 900 |
applyDagReplaySpeedUi();
|
| 901 |
});
|
| 902 |
|
| 903 |
+
dagPlaybackTotalSInput?.addEventListener('input', (e) => {
|
| 904 |
+
if (!dagPlaybackTotalSInput) return;
|
| 905 |
+
if (!(e instanceof InputEvent) || (e.inputType !== 'increment' && e.inputType !== 'decrement')) {
|
| 906 |
+
return;
|
| 907 |
+
}
|
| 908 |
+
const raw = parseFloat(dagPlaybackTotalSInput.value);
|
| 909 |
+
if (!Number.isFinite(raw)) return;
|
| 910 |
+
dagPlaybackTotalSInput.value = formatDagPlaybackTotalS(clampDagPlaybackTotalS(Math.round(raw)));
|
| 911 |
+
});
|
| 912 |
+
|
| 913 |
dagPlaybackTotalSInput?.addEventListener('change', () => {
|
| 914 |
+
commitDagPlaybackTotalSInput();
|
| 915 |
+
});
|
| 916 |
+
|
| 917 |
+
dagPlaybackTotalSInput?.addEventListener('blur', () => {
|
| 918 |
+
commitDagPlaybackTotalSInput();
|
|
|
|
| 919 |
});
|
| 920 |
|
| 921 |
function isSkipChatTemplate(): boolean {
|
|
|
|
| 1232 |
cancelDagLastTokenDwell();
|
| 1233 |
const steps = h.getAllSteps();
|
| 1234 |
if (dagPlaybackNextIndex >= steps.length) {
|
| 1235 |
+
// 保留 `layoutDirty`:用户 pan/zoom 后 Auto zoom 仍应停止 fit。
|
| 1236 |
dagHandle.reset(true);
|
| 1237 |
dagPlaybackNextIndex = 0;
|
| 1238 |
}
|
|
|
|
| 1283 |
if (visible) toolCallingPendingLine?.show();
|
| 1284 |
else toolCallingPendingLine?.hide();
|
| 1285 |
},
|
| 1286 |
+
showPrompt: () => {
|
| 1287 |
const firstStep = steps[0]!;
|
| 1288 |
syncDagInputLayerAtStep({
|
| 1289 |
catalogSpans: currentRunPromptSpans,
|
| 1290 |
layoutWire: firstStep.context,
|
| 1291 |
inputRanges: firstStep.inputRanges,
|
|
|
|
| 1292 |
});
|
| 1293 |
},
|
| 1294 |
+
afterStepShown: () => {
|
| 1295 |
+
if (dagReplayAutoZoomInput?.checked) {
|
| 1296 |
+
dagHandle.fitViewportToContent();
|
| 1297 |
+
}
|
| 1298 |
+
},
|
| 1299 |
showToolResponse: (stepIndex) => {
|
| 1300 |
const step = steps[stepIndex]!;
|
| 1301 |
syncDagInputLayerAtStep({
|
|
|
|
| 1368 |
);
|
| 1369 |
|
| 1370 |
dagLayoutModeSelect?.addEventListener('change', () => {
|
|
|
|
|
|
|
| 1371 |
applyDagLayoutModeUi();
|
| 1372 |
+
dagHandle.setLayoutMode(currentDagLayoutMode());
|
| 1373 |
});
|
| 1374 |
|
| 1375 |
/**
|
|
|
|
| 1421 |
? clampDagMeasureWidth(raw)
|
| 1422 |
: GEN_ATTR_DAG_MEASURE_WIDTH_DEFAULT;
|
| 1423 |
dagMeasureWidthInput.value = String(w);
|
|
|
|
| 1424 |
dagHandle.setMeasureWidthPx(w);
|
| 1425 |
tryResetAndReplayDag();
|
| 1426 |
});
|
|
|
|
| 1429 |
const raw = parseFloat(dagCompactnessInput.value);
|
| 1430 |
const c = Number.isFinite(raw) ? clampDagCompactness(raw) : DAG_COMPACTNESS_DEFAULT;
|
| 1431 |
dagCompactnessInput.value = String(c);
|
|
|
|
| 1432 |
dagHandle.setDagCompactness(c);
|
| 1433 |
tryResetAndReplayDag();
|
| 1434 |
});
|
|
|
|
| 1439 |
? clampDagEdgeTopPCoverage(raw)
|
| 1440 |
: DAG_EDGE_TOP_P_COVERAGE_DEFAULT;
|
| 1441 |
dagEdgeTopPCoverageInput.value = String(c);
|
|
|
|
| 1442 |
dagHandle.setEdgeTopPCoverage(c);
|
| 1443 |
tryResetAndReplayDag({ refit: false });
|
| 1444 |
});
|
|
|
|
| 1449 |
? clampLinearArcAdjacentGap(raw)
|
| 1450 |
: LINEAR_ARC_ADJACENT_GAP_DEFAULT;
|
| 1451 |
dagLinearArcIntervalInput.value = String(n);
|
|
|
|
| 1452 |
dagHandle.setLinearArcAdjacentGapPx(n, { skipRefit: isDagBusy() });
|
| 1453 |
});
|
| 1454 |
|
|
|
|
| 1496 |
recursiveEdgeBatchAnimationDirection: currentDagRecursiveEdgeAnimationDirection(),
|
| 1497 |
showTokenInfoOnSelected: dagShowTopkOnSelectedInput?.checked ?? false,
|
| 1498 |
replayPacingMode,
|
| 1499 |
+
replayAutoZoom: dagReplayAutoZoomInput?.checked ?? false,
|
| 1500 |
playbackTotalS,
|
| 1501 |
playbackStepMs,
|
| 1502 |
excludePromptPatternsEnabled: genAttrExcludePromptPatternsEnable?.checked ?? true,
|
|
|
|
| 1531 |
}
|
| 1532 |
|
| 1533 |
/**
|
| 1534 |
+
* 演示 UI 控件 id ↔ localStorage 键:面板委托识别、批量清除 LS。
|
| 1535 |
+
* 新增控件须同步改:本表、`persistGenAttrDemoUiOptionsToLocalStorage`、
|
| 1536 |
+
* {@link readGenAttrDemoUiOptionsFromControls}、`applyGenAttrDemoUiOptionsSnap`、
|
| 1537 |
+
* {@link DEFAULT_GEN_ATTR_DEMO_UI_OPTIONS}(不含 Model、Max tokens、prompt 正文)。
|
| 1538 |
*/
|
| 1539 |
+
const GEN_ATTR_DEMO_UI_PERSIST_SPECS: ReadonlyArray<{
|
| 1540 |
+
readonly controlId: string;
|
| 1541 |
+
readonly storageKey: string;
|
| 1542 |
+
}> = [
|
| 1543 |
+
{ controlId: 'gen_attr_dag_layout_mode', storageKey: GEN_ATTR_DAG_LAYOUT_MODE_STORAGE_KEY },
|
| 1544 |
+
{ controlId: 'gen_attr_dag_compactness', storageKey: GEN_ATTR_DAG_COMPACTNESS_STORAGE_KEY },
|
| 1545 |
+
{ controlId: 'gen_attr_dag_measure_width', storageKey: GEN_ATTR_DAG_MEASURE_WIDTH_STORAGE_KEY },
|
| 1546 |
+
{ controlId: 'gen_attr_dag_linear_arc_interval', storageKey: GEN_ATTR_DAG_LINEAR_ARC_GAP_STORAGE_KEY },
|
| 1547 |
+
{ controlId: 'gen_attr_dag_node_ci_visual_scale', storageKey: GEN_ATTR_DAG_NODE_CI_VISUAL_SCALE_STORAGE_KEY },
|
| 1548 |
+
{
|
| 1549 |
+
controlId: 'gen_attr_dag_decay_attribution_high_surprisal',
|
| 1550 |
+
storageKey: GEN_ATTR_DAG_DECAY_ATTRIBUTION_HIGH_SURPRISAL_STORAGE_KEY,
|
| 1551 |
+
},
|
| 1552 |
+
{ controlId: 'gen_attr_dag_recursive_attribution', storageKey: GEN_ATTR_DAG_RECURSIVE_ATTRIBUTION_STORAGE_KEY },
|
| 1553 |
+
{
|
| 1554 |
+
controlId: 'gen_attr_dag_recursive_edge_animation_direction',
|
| 1555 |
+
storageKey: GEN_ATTR_DAG_RECURSIVE_EDGE_ANIMATION_DIRECTION_STORAGE_KEY,
|
| 1556 |
+
},
|
| 1557 |
+
{ controlId: 'gen_attr_dag_dim_inactive_tokens', storageKey: GEN_ATTR_DAG_DIM_INACTIVE_TOKENS_STORAGE_KEY },
|
| 1558 |
+
{
|
| 1559 |
+
controlId: 'gen_attr_dag_dim_inactive_tokens_threshold',
|
| 1560 |
+
storageKey: GEN_ATTR_DAG_DIM_INACTIVE_TOKENS_THRESHOLD_STORAGE_KEY,
|
| 1561 |
+
},
|
| 1562 |
+
{
|
| 1563 |
+
controlId: 'gen_attr_dag_dim_inactive_not_in_animation',
|
| 1564 |
+
storageKey: GEN_ATTR_DAG_DIM_INACTIVE_NOT_IN_ANIMATION_STORAGE_KEY,
|
| 1565 |
+
},
|
| 1566 |
+
{
|
| 1567 |
+
controlId: 'gen_attr_dag_show_downstream_influence',
|
| 1568 |
+
storageKey: GEN_ATTR_DAG_SHOW_DOWNSTREAM_INFLUENCE_STORAGE_KEY,
|
| 1569 |
+
},
|
| 1570 |
+
{ controlId: 'gen_attr_dag_edge_top_p_coverage', storageKey: GEN_ATTR_DAG_EDGE_TOP_P_COVERAGE_STORAGE_KEY },
|
| 1571 |
+
{ controlId: 'gen_attr_dag_hide_inactive_edges', storageKey: GEN_ATTR_DAG_HIDE_INACTIVE_EDGES_STORAGE_KEY },
|
| 1572 |
+
{ controlId: 'gen_attr_dag_hide_excluded_tokens', storageKey: GEN_ATTR_DAG_HIDE_EXCLUDED_TOKENS_STORAGE_KEY },
|
| 1573 |
+
{ controlId: 'gen_attr_dag_show_topk_on_selected', storageKey: GEN_ATTR_DAG_SHOW_TOPK_ON_SELECTED_STORAGE_KEY },
|
| 1574 |
+
{ controlId: 'gen_attr_dag_replay_mode', storageKey: GEN_ATTR_DAG_REPLAY_PACING_MODE_STORAGE_KEY },
|
| 1575 |
+
{ controlId: 'gen_attr_dag_replay_auto_zoom', storageKey: GEN_ATTR_DAG_REPLAY_AUTO_ZOOM_STORAGE_KEY },
|
| 1576 |
+
{ controlId: 'gen_attr_dag_playback_total_s', storageKey: GEN_ATTR_DAG_PLAYBACK_TOTAL_S_STORAGE_KEY },
|
| 1577 |
+
{ controlId: 'gen_attr_dag_playback_step_ms', storageKey: GEN_ATTR_DAG_PLAYBACK_STEP_MS_STORAGE_KEY },
|
| 1578 |
+
{
|
| 1579 |
+
controlId: 'gen_attr_delete_prompt_patterns_enable',
|
| 1580 |
+
storageKey: GEN_ATTR_DELETE_PROMPT_PATTERNS_ENABLED_STORAGE_KEY,
|
| 1581 |
+
},
|
| 1582 |
+
{ controlId: 'gen_attr_delete_prompt_patterns', storageKey: GEN_ATTR_DELETE_PROMPT_PATTERNS_STORAGE_KEY },
|
| 1583 |
+
{
|
| 1584 |
+
controlId: 'gen_attr_exclude_prompt_patterns_enable',
|
| 1585 |
+
storageKey: GEN_ATTR_EXCLUDE_PROMPT_PATTERNS_ENABLED_STORAGE_KEY,
|
| 1586 |
+
},
|
| 1587 |
+
{ controlId: 'gen_attr_exclude_prompt_patterns', storageKey: GEN_ATTR_EXCLUDE_PROMPT_PATTERNS_STORAGE_KEY },
|
| 1588 |
+
{
|
| 1589 |
+
controlId: 'gen_attr_exclude_generated_patterns_enable',
|
| 1590 |
+
storageKey: GEN_ATTR_EXCLUDE_GENERATED_PATTERNS_ENABLED_STORAGE_KEY,
|
| 1591 |
+
},
|
| 1592 |
+
{ controlId: 'gen_attr_exclude_generated_patterns', storageKey: GEN_ATTR_EXCLUDE_GENERATED_PATTERNS_STORAGE_KEY },
|
| 1593 |
+
];
|
| 1594 |
+
|
| 1595 |
+
const GEN_ATTR_DEMO_UI_CONTROL_IDS = new Set(GEN_ATTR_DEMO_UI_PERSIST_SPECS.map((s) => s.controlId));
|
| 1596 |
+
|
| 1597 |
+
const GEN_ATTR_DEMO_UI_LOCAL_STORAGE_KEYS: readonly string[] = GEN_ATTR_DEMO_UI_PERSIST_SPECS.map(
|
| 1598 |
+
(s) => s.storageKey,
|
| 1599 |
+
);
|
| 1600 |
+
|
| 1601 |
+
function isGenAttrDemoUiControl(target: EventTarget | null): boolean {
|
| 1602 |
+
return target instanceof HTMLElement && GEN_ATTR_DEMO_UI_CONTROL_IDS.has(target.id);
|
| 1603 |
+
}
|
| 1604 |
+
|
| 1605 |
+
function removeGenAttrDemoUiOptionsFromLocalStorage(): void {
|
| 1606 |
+
for (const k of GEN_ATTR_DEMO_UI_LOCAL_STORAGE_KEYS) {
|
| 1607 |
+
lsRemove(k);
|
| 1608 |
+
}
|
| 1609 |
+
}
|
| 1610 |
+
|
| 1611 |
+
function persistGenAttrDemoUiOptionsToLocalStorage(snap: GenAttrDemoUiOptions): void {
|
| 1612 |
+
lsWriteString(GEN_ATTR_DAG_LAYOUT_MODE_STORAGE_KEY, snap.layoutMode);
|
| 1613 |
+
lsSet(GEN_ATTR_DAG_MEASURE_WIDTH_STORAGE_KEY, String(snap.measureWidthPx));
|
| 1614 |
+
lsSet(GEN_ATTR_DAG_COMPACTNESS_STORAGE_KEY, String(snap.dagCompactness));
|
| 1615 |
+
lsSet(GEN_ATTR_DAG_LINEAR_ARC_GAP_STORAGE_KEY, String(snap.linearArcAdjacentGapPx));
|
| 1616 |
+
lsSet(GEN_ATTR_DAG_EDGE_TOP_P_COVERAGE_STORAGE_KEY, String(snap.edgeTopPCoverage));
|
| 1617 |
+
lsWriteBool(GEN_ATTR_DAG_HIDE_EXCLUDED_TOKENS_STORAGE_KEY, snap.hideExcludedTokens, '1');
|
| 1618 |
+
lsWriteBool(GEN_ATTR_DAG_DIM_INACTIVE_TOKENS_STORAGE_KEY, snap.dimInactiveTokens, '1');
|
| 1619 |
+
lsSet(GEN_ATTR_DAG_DIM_INACTIVE_TOKENS_THRESHOLD_STORAGE_KEY, String(snap.dimInactiveTokensThreshold));
|
| 1620 |
+
lsWriteBool(
|
| 1621 |
+
GEN_ATTR_DAG_DIM_INACTIVE_NOT_IN_ANIMATION_STORAGE_KEY,
|
| 1622 |
+
snap.dimInactiveNotDuringAnimation,
|
| 1623 |
+
'1',
|
| 1624 |
+
);
|
| 1625 |
+
lsWriteBool(GEN_ATTR_DAG_NODE_CI_VISUAL_SCALE_STORAGE_KEY, snap.nodeCiVisualScaleEnabled, '1');
|
| 1626 |
+
lsWriteBool(
|
| 1627 |
+
GEN_ATTR_DAG_DECAY_ATTRIBUTION_HIGH_SURPRISAL_STORAGE_KEY,
|
| 1628 |
+
snap.decayAttributionToHighSurprisalTargetEnabled,
|
| 1629 |
+
'1',
|
| 1630 |
+
);
|
| 1631 |
+
lsWriteBool(GEN_ATTR_DAG_HIDE_INACTIVE_EDGES_STORAGE_KEY, snap.hideInactiveEdges, '1');
|
| 1632 |
+
lsWriteBool(GEN_ATTR_DAG_SHOW_DOWNSTREAM_INFLUENCE_STORAGE_KEY, snap.showDownstreamInfluence, '1');
|
| 1633 |
+
lsWriteBool(GEN_ATTR_DAG_RECURSIVE_ATTRIBUTION_STORAGE_KEY, snap.recursiveAttributionEnabled, '1');
|
| 1634 |
+
lsWriteString(
|
| 1635 |
+
GEN_ATTR_DAG_RECURSIVE_EDGE_ANIMATION_DIRECTION_STORAGE_KEY,
|
| 1636 |
+
snap.recursiveEdgeBatchAnimationDirection,
|
| 1637 |
+
);
|
| 1638 |
+
lsWriteBool(GEN_ATTR_DAG_SHOW_TOPK_ON_SELECTED_STORAGE_KEY, snap.showTokenInfoOnSelected, '1');
|
| 1639 |
+
lsWriteString(GEN_ATTR_DAG_REPLAY_PACING_MODE_STORAGE_KEY, snap.replayPacingMode);
|
| 1640 |
+
lsWriteBool(GEN_ATTR_DAG_REPLAY_AUTO_ZOOM_STORAGE_KEY, snap.replayAutoZoom, '1');
|
| 1641 |
+
lsSet(GEN_ATTR_DAG_PLAYBACK_TOTAL_S_STORAGE_KEY, String(snap.playbackTotalS));
|
| 1642 |
+
lsSet(GEN_ATTR_DAG_PLAYBACK_STEP_MS_STORAGE_KEY, String(snap.playbackStepMs));
|
| 1643 |
+
lsSet(GEN_ATTR_DELETE_PROMPT_PATTERNS_STORAGE_KEY, snap.deletePromptPatternsText);
|
| 1644 |
+
lsWriteBool(GEN_ATTR_DELETE_PROMPT_PATTERNS_ENABLED_STORAGE_KEY, snap.deletePromptPatternsEnabled, '1');
|
| 1645 |
+
lsSet(GEN_ATTR_EXCLUDE_PROMPT_PATTERNS_STORAGE_KEY, snap.excludePromptPatternsText);
|
| 1646 |
+
lsWriteBool(GEN_ATTR_EXCLUDE_PROMPT_PATTERNS_ENABLED_STORAGE_KEY, snap.excludePromptPatternsEnabled, '1');
|
| 1647 |
+
lsSet(GEN_ATTR_EXCLUDE_GENERATED_PATTERNS_STORAGE_KEY, snap.excludeGeneratedPatternsText);
|
| 1648 |
+
lsWriteBool(
|
| 1649 |
+
GEN_ATTR_EXCLUDE_GENERATED_PATTERNS_ENABLED_STORAGE_KEY,
|
| 1650 |
+
snap.excludeGeneratedPatternsEnabled,
|
| 1651 |
+
'1',
|
| 1652 |
+
);
|
| 1653 |
+
}
|
| 1654 |
+
|
| 1655 |
+
/** 演示用 UI:DOM → localStorage 的唯一写路径(用户改控件经面板委托;程序化 apply 末尾调用)。 */
|
| 1656 |
+
function syncGenAttrDemoUiOptionsToLocalStorage(): void {
|
| 1657 |
+
persistGenAttrDemoUiOptionsToLocalStorage(readGenAttrDemoUiOptionsFromControls());
|
| 1658 |
+
}
|
| 1659 |
+
|
| 1660 |
+
/** 从 `demoUiOptions` 还原排除控件(仅 DOM);`replay` 读当前控件生效。 */
|
| 1661 |
function applyGenAttrExcludePatternsFromDemoUiSnap(snap: Partial<GenAttrDemoUiOptions>): void {
|
| 1662 |
const {
|
| 1663 |
deletePromptPatternsEnabled,
|
|
|
|
| 1809 |
if (dagReplayModeSelect) dagReplayModeSelect.value = snap.replayPacingMode;
|
| 1810 |
applyDagReplaySpeedUi();
|
| 1811 |
}
|
| 1812 |
+
if (snap.replayAutoZoom !== undefined) {
|
| 1813 |
+
if (dagReplayAutoZoomInput) dagReplayAutoZoomInput.checked = snap.replayAutoZoom;
|
| 1814 |
+
}
|
| 1815 |
if (snap.playbackTotalS !== undefined) {
|
| 1816 |
const s = clampDagPlaybackTotalS(snap.playbackTotalS);
|
| 1817 |
+
if (dagPlaybackTotalSInput) dagPlaybackTotalSInput.value = formatDagPlaybackTotalS(s);
|
| 1818 |
}
|
| 1819 |
if (snap.playbackStepMs !== undefined) {
|
| 1820 |
const ms = clampDagPlaybackStepMs(snap.playbackStepMs);
|
|
|
|
| 1822 |
}
|
| 1823 |
|
| 1824 |
applyGenAttrExcludePatternsFromDemoUiSnap(snap);
|
| 1825 |
+
syncGenAttrDemoUiOptionsToLocalStorage();
|
| 1826 |
syncGenAttrResetUiOptionsButtonState();
|
| 1827 |
}
|
| 1828 |
|
|
|
|
| 1845 |
applyGenAttrDemoUiOptionsSnap(rec.demoUiOptions);
|
| 1846 |
}
|
| 1847 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1848 |
/** 重置「DAG 演示用 UI」:清 LS 后以 {@link DEFAULT_GEN_ATTR_DEMO_UI_OPTIONS} 全量套用。 */
|
| 1849 |
function resetGenAttrDemoUiOptionsToDefaults(): void {
|
| 1850 |
stopDagPlayback();
|
|
|
|
| 1858 |
(() => {
|
| 1859 |
const panel = document.querySelector('.gen-attribute-page .input-section');
|
| 1860 |
if (!panel) return;
|
| 1861 |
+
const onDemoUiPersist = (e: Event) => {
|
| 1862 |
+
if (!isGenAttrDemoUiControl(e.target)) return;
|
| 1863 |
+
syncGenAttrDemoUiOptionsToLocalStorage();
|
| 1864 |
+
syncGenAttrResetUiOptionsButtonState();
|
| 1865 |
+
};
|
| 1866 |
+
panel.addEventListener('change', onDemoUiPersist);
|
| 1867 |
+
panel.addEventListener('blur', onDemoUiPersist, true);
|
| 1868 |
+
panel.addEventListener('input', (e) => {
|
| 1869 |
+
if (!isGenAttrDemoUiControl(e.target)) return;
|
| 1870 |
+
syncGenAttrResetUiOptionsButtonState();
|
| 1871 |
+
});
|
| 1872 |
+
syncGenAttrResetUiOptionsButtonState();
|
| 1873 |
})();
|
| 1874 |
|
| 1875 |
window.addEventListener('pagehide', (ev) => {
|
client/src/shared/lang/translations.ts
CHANGED
|
@@ -40,11 +40,15 @@ export const translations: Translations = {
|
|
| 40 |
'Max new tokens must not exceed {limit}': 'Max new tokens 不得超过 {limit}',
|
| 41 |
'Max new tokens must be a positive integer': 'Max new tokens 须为正整数',
|
| 42 |
// LLM Causal Flow(gen_attribute)页:placeholder / title 文案
|
| 43 |
-
'When enabled, each line below is a regex with the global flag, matched only within
|
| 44 |
-
'启用后仅在
|
| 45 |
-
'One regex per line (
|
| 46 |
-
|
| 47 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
| 48 |
'When enabled, each line below is a regex with the global flag, matched only within the model-generated continuation (excluding the initial static prompt). If a token offset lies fully inside a match, its score is treated as 0.':
|
| 49 |
'启用后仅在模型已生成的 continuation(不含初始静态 prompt)内按下列正则匹配;token 的 offset 完全落在某次匹配区间内则 score 视为 0。',
|
| 50 |
'One regex per line (generated continuation only)': '每行一条正则(仅已生成 continuation)',
|
|
@@ -127,6 +131,9 @@ export const translations: Translations = {
|
|
| 127 |
'Restore DAG options, play speed, exclusions, etc. to defaults and clear saved preferences for those controls.':
|
| 128 |
'将 DAG 参数、播放速度、排除正则等恢复为默认值,并清除这些控件的本地保存项。',
|
| 129 |
'Play speed': '播放速度',
|
|
|
|
|
|
|
|
|
|
| 130 |
'Causal Flow Mode': '因果流模式',
|
| 131 |
'Unchecked: direct attribution — immediate predecessors only (default). Checked: Causal Flow Mode (↯) — trace from the focused token back to information sources. Sources: prompt; surprising or teacher-forced generated tokens (MI decay stops the chain). Conduits: high-confidence middle tokens—attribution passes through. Blue edges: propagated share; node ring: attribution stay (strong where explanation lands). Use with Decay attribution to high-surprisal targets. On the DAG, ↯ plays the focus propagation chain; ▶ replays generation steps when no token is focused.':
|
| 132 |
'未勾选:原始直接归因(仅一跳前驱,默认)。勾选:因果流模式(↯)— 从焦点 token 向上追溯到信息来源。来源:prompt;高惊讶或 teacher-forced 的生成 token(MI 衰减可截断链)。传导:高置信中间 token,归因穿过。蓝边:传播份额;节点环:归因停留(解释落点处更强)。建议与「向高惊讶目标衰减归因」配合使用。DAG 上:↯ 播放焦点传��链;无焦点时 ▶ 步进重放生成过程。',
|
|
|
|
| 40 |
'Max new tokens must not exceed {limit}': 'Max new tokens 不得超过 {limit}',
|
| 41 |
'Max new tokens must be a positive integer': 'Max new tokens 须为正整数',
|
| 42 |
// LLM Causal Flow(gen_attribute)页:placeholder / title 文案
|
| 43 |
+
'When enabled, each line below is a regex with the global flag, matched only within input areas (excluding generated continuation). Matched prompt tokens are physically removed from the DAG — they neither appear nor occupy layout space (stricter than Exclude + Hide).':
|
| 44 |
+
'启用后仅在 input 区(不含已生成 continuation)内按下列正则匹配;命中的 prompt token 从 DAG 物理移除,不占布局(比 Exclude + Hide 更严格)。',
|
| 45 |
+
'One regex per line (global flag), matched only within input areas (excluding generated continuation); matched tokens are deleted from the DAG and do not occupy layout space.':
|
| 46 |
+
'每行一条正则,`g`,仅在 input 区(不含已生成 continuation)内匹配;命中的 token 从 DAG 删除且不占布局空间。',
|
| 47 |
+
'When enabled, each line below is a regex with the global flag, matched only within input areas (excluding generated continuation). If a token offset lies fully inside a match, its score is treated as 0.':
|
| 48 |
+
'启用后仅在 input 区(不含已生成 continuation)内按下列正则匹配;token 的 offset 完全落在某次匹配区间内则 score 视为 0。',
|
| 49 |
+
'One regex per line (input areas only)': '每行一条正则(仅 input 区)',
|
| 50 |
+
'One regex per line (global flag), matched only within input areas (excluding generated continuation); if a token offset lies fully inside a match, its score is treated as 0.':
|
| 51 |
+
'每行一条正则,`g`,仅在 input 区(不含已生成 continuation)内匹配;token 的 offset 完全落在某次匹配区间内则 score 视为 0。',
|
| 52 |
'When enabled, each line below is a regex with the global flag, matched only within the model-generated continuation (excluding the initial static prompt). If a token offset lies fully inside a match, its score is treated as 0.':
|
| 53 |
'启用后仅在模型已生成的 continuation(不含初始静态 prompt)内按下列正则匹配;token 的 offset 完全落在某次匹配区间内则 score 视为 0。',
|
| 54 |
'One regex per line (generated continuation only)': '每行一条正则(仅已生成 continuation)',
|
|
|
|
| 131 |
'Restore DAG options, play speed, exclusions, etc. to defaults and clear saved preferences for those controls.':
|
| 132 |
'将 DAG 参数、播放速度、排除正则等恢复为默认值,并清除这些控件的本地保存项。',
|
| 133 |
'Play speed': '播放速度',
|
| 134 |
+
'Auto zoom': '自动缩放',
|
| 135 |
+
'When checked, step replay (▶) fits the viewport after each step (stops if you pan or zoom). Saved locally.':
|
| 136 |
+
'勾选后,步进重放(▶)每步自动适配视口(手动平移/缩放后停止)。本地保存。',
|
| 137 |
'Causal Flow Mode': '因果流模式',
|
| 138 |
'Unchecked: direct attribution — immediate predecessors only (default). Checked: Causal Flow Mode (↯) — trace from the focused token back to information sources. Sources: prompt; surprising or teacher-forced generated tokens (MI decay stops the chain). Conduits: high-confidence middle tokens—attribution passes through. Blue edges: propagated share; node ring: attribution stay (strong where explanation lands). Use with Decay attribution to high-surprisal targets. On the DAG, ↯ plays the focus propagation chain; ▶ replays generation steps when no token is focused.':
|
| 139 |
'未勾选:原始直接归因(仅一跳前驱,默认)。勾选:因果流模式(↯)— 从焦点 token 向上追溯到信息来源。来源:prompt;高惊讶或 teacher-forced 的生成 token(MI 衰减可截断链)。传导:高置信中间 token,归因穿过。蓝边:传播份额;节点环:归因停留(解释落点处更强)。建议与「向高惊讶目标衰减归因」配合使用。DAG 上:↯ 播放焦点传��链;无焦点时 ▶ 步进重放生成过程。',
|
client/src/shared/prediction_attribution/causal_flow/genAttributeDagStepPlayback.ts
CHANGED
|
@@ -140,9 +140,11 @@ export type RunDagStepPlaybackLoopOptions = {
|
|
| 140 |
isStale: () => boolean;
|
| 141 |
setTimer: (cb: () => void, delayMs: number) => void;
|
| 142 |
setToolPendingVisible: (visible: boolean) => void;
|
| 143 |
-
showPrompt: (
|
| 144 |
showToolResponse: (stepIndex: number) => void;
|
| 145 |
showOutputGen: (stepIndex: number) => void;
|
|
|
|
|
|
|
| 146 |
onOutputGenShown: (stepIndex: number) => void;
|
| 147 |
onAllOutputGensShown: () => void;
|
| 148 |
};
|
|
@@ -168,7 +170,7 @@ export function runDagStepPlaybackLoop(opts: RunDagStepPlaybackLoopOptions): voi
|
|
| 168 |
|
| 169 |
switch (event.kind) {
|
| 170 |
case 'prompt':
|
| 171 |
-
opts.showPrompt(
|
| 172 |
break;
|
| 173 |
case 'toolResponse':
|
| 174 |
opts.showToolResponse(event.stepIndex);
|
|
@@ -182,6 +184,7 @@ export function runDagStepPlaybackLoop(opts: RunDagStepPlaybackLoopOptions): voi
|
|
| 182 |
void _exhaustive;
|
| 183 |
}
|
| 184 |
}
|
|
|
|
| 185 |
|
| 186 |
const nextIndex = eventIndex + 1;
|
| 187 |
if (nextIndex >= opts.events.length) {
|
|
|
|
| 140 |
isStale: () => boolean;
|
| 141 |
setTimer: (cb: () => void, delayMs: number) => void;
|
| 142 |
setToolPendingVisible: (visible: boolean) => void;
|
| 143 |
+
showPrompt: () => void;
|
| 144 |
showToolResponse: (stepIndex: number) => void;
|
| 145 |
showOutputGen: (stepIndex: number) => void;
|
| 146 |
+
/** 每段内容展示后调用(如步进重放开启 Auto zoom 时 fit 视口)。 */
|
| 147 |
+
afterStepShown?: () => void;
|
| 148 |
onOutputGenShown: (stepIndex: number) => void;
|
| 149 |
onAllOutputGensShown: () => void;
|
| 150 |
};
|
|
|
|
| 170 |
|
| 171 |
switch (event.kind) {
|
| 172 |
case 'prompt':
|
| 173 |
+
opts.showPrompt();
|
| 174 |
break;
|
| 175 |
case 'toolResponse':
|
| 176 |
opts.showToolResponse(event.stepIndex);
|
|
|
|
| 184 |
void _exhaustive;
|
| 185 |
}
|
| 186 |
}
|
| 187 |
+
opts.afterStepShown?.();
|
| 188 |
|
| 189 |
const nextIndex = eventIndex + 1;
|
| 190 |
if (nextIndex >= opts.events.length) {
|
client/src/shared/prediction_attribution/causal_flow/genAttributeDagTextMeasure.ts
CHANGED
|
@@ -199,12 +199,15 @@ export function createGenAttributeDagTextMeasure(
|
|
| 199 |
} {
|
| 200 |
// 合并重叠区间,保证 originalToCompacted 与 compactText 行为一致。
|
| 201 |
let mergedDeleteIntervals = mergeDeleteIntervals(deleteIntervals);
|
| 202 |
-
/** 当前测量层中的完整文本(prompt
|
| 203 |
let fullText = '';
|
| 204 |
-
/**
|
| 205 |
-
|
| 206 |
-
|
| 207 |
-
|
|
|
|
|
|
|
|
|
|
| 208 |
let bpeStrings: FrontendToken[] = [];
|
| 209 |
const calculator = new TokenPositionCalculator(measureRoot);
|
| 210 |
|
|
@@ -229,8 +232,8 @@ export function createGenAttributeDagTextMeasure(
|
|
| 229 |
return {
|
| 230 |
reset(): void {
|
| 231 |
fullText = '';
|
| 232 |
-
|
| 233 |
-
|
| 234 |
bpeStrings = [];
|
| 235 |
while (measureRoot.firstChild) measureRoot.removeChild(measureRoot.firstChild);
|
| 236 |
calculator.resetIndex();
|
|
@@ -247,8 +250,8 @@ export function createGenAttributeDagTextMeasure(
|
|
| 247 |
setPrompt(promptText: string, spans: PromptTokenSpan[]): Map<string, GenAttrDagTokenGeom> {
|
| 248 |
// 将被删区间字符物理移除,DOM 里不留空洞。
|
| 249 |
const compactedPrompt = compactText(promptText, mergedDeleteIntervals);
|
| 250 |
-
|
| 251 |
-
|
| 252 |
fullText = compactedPrompt;
|
| 253 |
|
| 254 |
// 过滤掉被删 span(调用方也会跳过,双重保险)。
|
|
@@ -291,8 +294,8 @@ export function createGenAttributeDagTextMeasure(
|
|
| 291 |
'genAttributeDagTextMeasure: appendInputSpans layoutText must extend current fullText prefix',
|
| 292 |
);
|
| 293 |
}
|
| 294 |
-
|
| 295 |
-
|
| 296 |
fullText = compactedLayout;
|
| 297 |
|
| 298 |
const keptNew = newSpans.filter(
|
|
@@ -328,9 +331,9 @@ export function createGenAttributeDagTextMeasure(
|
|
| 328 |
|
| 329 |
appendGeneratedToken(token: string, offset: [number, number]): GenAttrDagTokenGeom {
|
| 330 |
// offset[0] 是原始坐标系下该 token 在全文中的起点(= context.length at generation time)。
|
| 331 |
-
// 映射到压缩坐标系:
|
| 332 |
-
const generatedRelative = offset[0] -
|
| 333 |
-
const compactedStart =
|
| 334 |
const compactedOffset: [number, number] = [compactedStart, compactedStart + token.length];
|
| 335 |
|
| 336 |
fullText += token;
|
|
|
|
| 199 |
} {
|
| 200 |
// 合并重叠区间,保证 originalToCompacted 与 compactText 行为一致。
|
| 201 |
let mergedDeleteIntervals = mergeDeleteIntervals(deleteIntervals);
|
| 202 |
+
/** 当前测量层中的完整文本(prompt 前缀已压缩 + 生成 token 原样拼接)。 */
|
| 203 |
let fullText = '';
|
| 204 |
+
/**
|
| 205 |
+
* 最近一次 input 同步时 `layoutWire` 在原始坐标系下的长度(`setPrompt` 为 prompt 长度;
|
| 206 |
+
* `appendInputSpans` 为含 tool response 等的全长)。供 {@link appendGeneratedToken} 锚定生成区起点。
|
| 207 |
+
*/
|
| 208 |
+
let layoutAnchorLength = 0;
|
| 209 |
+
/** 与 {@link layoutAnchorLength} 同锚点、经 delete 压缩后的坐标长度。 */
|
| 210 |
+
let compactedLayoutAnchorLength = 0;
|
| 211 |
let bpeStrings: FrontendToken[] = [];
|
| 212 |
const calculator = new TokenPositionCalculator(measureRoot);
|
| 213 |
|
|
|
|
| 232 |
return {
|
| 233 |
reset(): void {
|
| 234 |
fullText = '';
|
| 235 |
+
layoutAnchorLength = 0;
|
| 236 |
+
compactedLayoutAnchorLength = 0;
|
| 237 |
bpeStrings = [];
|
| 238 |
while (measureRoot.firstChild) measureRoot.removeChild(measureRoot.firstChild);
|
| 239 |
calculator.resetIndex();
|
|
|
|
| 250 |
setPrompt(promptText: string, spans: PromptTokenSpan[]): Map<string, GenAttrDagTokenGeom> {
|
| 251 |
// 将被删区间字符物理移除,DOM 里不留空洞。
|
| 252 |
const compactedPrompt = compactText(promptText, mergedDeleteIntervals);
|
| 253 |
+
layoutAnchorLength = promptText.length;
|
| 254 |
+
compactedLayoutAnchorLength = compactedPrompt.length;
|
| 255 |
fullText = compactedPrompt;
|
| 256 |
|
| 257 |
// 过滤掉被删 span(调用方也会跳过,双重保险)。
|
|
|
|
| 294 |
'genAttributeDagTextMeasure: appendInputSpans layoutText must extend current fullText prefix',
|
| 295 |
);
|
| 296 |
}
|
| 297 |
+
layoutAnchorLength = layoutText.length;
|
| 298 |
+
compactedLayoutAnchorLength = compactedLayout.length;
|
| 299 |
fullText = compactedLayout;
|
| 300 |
|
| 301 |
const keptNew = newSpans.filter(
|
|
|
|
| 331 |
|
| 332 |
appendGeneratedToken(token: string, offset: [number, number]): GenAttrDagTokenGeom {
|
| 333 |
// offset[0] 是原始坐标系下该 token 在全文中的起点(= context.length at generation time)。
|
| 334 |
+
// 映射到压缩坐标系:自 layoutAnchor 之后的生成区无 delete,按锚点相对偏移平移。
|
| 335 |
+
const generatedRelative = offset[0] - layoutAnchorLength;
|
| 336 |
+
const compactedStart = compactedLayoutAnchorLength + generatedRelative;
|
| 337 |
const compactedOffset: [number, number] = [compactedStart, compactedStart + token.length];
|
| 338 |
|
| 339 |
fullText += token;
|
client/src/shared/prediction_attribution/causal_flow/genAttributeDagView.ts
CHANGED
|
@@ -592,8 +592,8 @@ export type GenAttributeDagHandle = {
|
|
| 592 |
/**
|
| 593 |
* 清空图与测量状态;不修改当前 SVG 上的 d3 zoom 变换(视口平移/缩放由 `layoutDirty` 与
|
| 594 |
* `fitViewportToContent` 控制)。
|
| 595 |
-
* @param preserveUserViewport 为 `true` 时保留调用前的 `layoutDirty`:
|
| 596 |
-
*
|
| 597 |
*/
|
| 598 |
reset(preserveUserViewport?: boolean): void;
|
| 599 |
/**
|
|
@@ -1358,6 +1358,9 @@ export function initGenAttributeDagView(
|
|
| 1358 |
);
|
| 1359 |
}
|
| 1360 |
|
|
|
|
|
|
|
|
|
|
| 1361 |
/**
|
| 1362 |
* Hide exclude/inactive 时,参与布局的节点集随焦点 / dim 阈值变化;须重算 linear-arc / spiral 几何。
|
| 1363 |
*/
|
|
@@ -1372,14 +1375,14 @@ export function initGenAttributeDagView(
|
|
| 1372 |
if (layoutIncludedNodeIdsKey === LAYOUT_INCLUDED_ALL_KEY) return;
|
| 1373 |
layoutIncludedNodeIdsKey = LAYOUT_INCLUDED_ALL_KEY;
|
| 1374 |
paint();
|
| 1375 |
-
if (!layoutDirty) fitViewportToContent(true);
|
| 1376 |
return;
|
| 1377 |
}
|
| 1378 |
const key = computeLayoutIncludedNodeIdsKey(focusId, focusState);
|
| 1379 |
if (key === layoutIncludedNodeIdsKey) return;
|
| 1380 |
layoutIncludedNodeIdsKey = key;
|
| 1381 |
paint();
|
| 1382 |
-
if (!layoutDirty) fitViewportToContent(true);
|
| 1383 |
}
|
| 1384 |
|
| 1385 |
function invalidateLayoutIncludedNodeIdsKey(): void {
|
|
@@ -1425,7 +1428,7 @@ export function initGenAttributeDagView(
|
|
| 1425 |
*/
|
| 1426 |
let dagExcludeIntervals: [number, number][] = [];
|
| 1427 |
/**
|
| 1428 |
-
* 每次 {@link setPromptTokenSpans} 按
|
| 1429 |
* 命中区间内的 prompt token 不进入图也不进入测量层(textMeasure 物理压缩布局空间)。
|
| 1430 |
*/
|
| 1431 |
let dagDeleteIntervals: [number, number][] = [];
|
|
@@ -2199,9 +2202,10 @@ export function initGenAttributeDagView(
|
|
| 2199 |
recursiveEdgeAnimation.stopPlayback();
|
| 2200 |
if (batchDepth === 0) {
|
| 2201 |
syncGraphToSvg();
|
| 2202 |
-
// 生成
|
| 2203 |
-
|
| 2204 |
-
|
|
|
|
| 2205 |
}
|
| 2206 |
}
|
| 2207 |
|
|
@@ -2330,7 +2334,6 @@ export function initGenAttributeDagView(
|
|
| 2330 |
});
|
| 2331 |
ro.observe(stackEl);
|
| 2332 |
|
| 2333 |
-
let dagPlaybackPlaying = false;
|
| 2334 |
const playBtn = resultsRoot
|
| 2335 |
.append('button')
|
| 2336 |
.attr('type', 'button')
|
|
|
|
| 592 |
/**
|
| 593 |
* 清空图与测量状态;不修改当前 SVG 上的 d3 zoom 变换(视口平移/缩放由 `layoutDirty` 与
|
| 594 |
* `fitViewportToContent` 控制)。
|
| 595 |
+
* @param preserveUserViewport 为 `true` 时保留调用前的 `layoutDirty`:设置项切换后重放、
|
| 596 |
+
* 步进重放从末尾重头播放等保留用户 pan/zoom。默认 `false`(新一次 run 等场景仍从干净视口起算)。
|
| 597 |
*/
|
| 598 |
reset(preserveUserViewport?: boolean): void;
|
| 599 |
/**
|
|
|
|
| 1358 |
);
|
| 1359 |
}
|
| 1360 |
|
| 1361 |
+
/** 步进重放(▶)期间为 true;fit 由页面 `afterStepShown` + Auto zoom 统一处理,见 {@link syncLayoutForLowVisibilityMembership}。 */
|
| 1362 |
+
let dagPlaybackPlaying = false;
|
| 1363 |
+
|
| 1364 |
/**
|
| 1365 |
* Hide exclude/inactive 时,参与布局的节点集随焦点 / dim 阈值变化;须重算 linear-arc / spiral 几何。
|
| 1366 |
*/
|
|
|
|
| 1375 |
if (layoutIncludedNodeIdsKey === LAYOUT_INCLUDED_ALL_KEY) return;
|
| 1376 |
layoutIncludedNodeIdsKey = LAYOUT_INCLUDED_ALL_KEY;
|
| 1377 |
paint();
|
| 1378 |
+
if (!layoutDirty && !dagPlaybackPlaying) fitViewportToContent(true);
|
| 1379 |
return;
|
| 1380 |
}
|
| 1381 |
const key = computeLayoutIncludedNodeIdsKey(focusId, focusState);
|
| 1382 |
if (key === layoutIncludedNodeIdsKey) return;
|
| 1383 |
layoutIncludedNodeIdsKey = key;
|
| 1384 |
paint();
|
| 1385 |
+
if (!layoutDirty && !dagPlaybackPlaying) fitViewportToContent(true);
|
| 1386 |
}
|
| 1387 |
|
| 1388 |
function invalidateLayoutIncludedNodeIdsKey(): void {
|
|
|
|
| 1428 |
*/
|
| 1429 |
let dagExcludeIntervals: [number, number][] = [];
|
| 1430 |
/**
|
| 1431 |
+
* 每次 {@link setPromptTokenSpans} 按 `layoutWire` + `inputRanges` 重算(与 exclude 一致;多轮追加 input 区时扩展)。
|
| 1432 |
* 命中区间内的 prompt token 不进入图也不进入测量层(textMeasure 物理压缩布局空间)。
|
| 1433 |
*/
|
| 1434 |
let dagDeleteIntervals: [number, number][] = [];
|
|
|
|
| 2202 |
recursiveEdgeAnimation.stopPlayback();
|
| 2203 |
if (batchDepth === 0) {
|
| 2204 |
syncGraphToSvg();
|
| 2205 |
+
// 生成时每步 fit;步进重放(▶)由页面按 Auto zoom 在 `afterStepShown` 统一处理。
|
| 2206 |
+
if (!dagPlaybackPlaying) {
|
| 2207 |
+
fitViewportToContent();
|
| 2208 |
+
}
|
| 2209 |
}
|
| 2210 |
}
|
| 2211 |
|
|
|
|
| 2334 |
});
|
| 2335 |
ro.observe(stackEl);
|
| 2336 |
|
|
|
|
| 2337 |
const playBtn = resultsRoot
|
| 2338 |
.append('button')
|
| 2339 |
.attr('type', 'button')
|
client/src/shared/prediction_attribution/core/excludePromptPatternsUi.ts
CHANGED
|
@@ -20,6 +20,8 @@ export type BindExcludePatternsUiOptions = {
|
|
| 20 |
defaultTextWhenKeyAbsent?: string;
|
| 21 |
/** enable 键从未写入时的默认勾选态;缺省 `true`(与 Attribution Exclude 一致) */
|
| 22 |
defaultEnabledWhenKeyAbsent?: boolean;
|
|
|
|
|
|
|
| 23 |
};
|
| 24 |
|
| 25 |
export type BindExcludePromptPatternsUiOptions = Omit<
|
|
@@ -46,6 +48,7 @@ export function bindExcludePatternsUi(options: BindExcludePatternsUiOptions): vo
|
|
| 46 |
onEffectiveChange,
|
| 47 |
defaultTextWhenKeyAbsent,
|
| 48 |
defaultEnabledWhenKeyAbsent = true,
|
|
|
|
| 49 |
} = options;
|
| 50 |
const { textKey, enabledKey } = storageKeys;
|
| 51 |
|
|
@@ -70,16 +73,20 @@ export function bindExcludePatternsUi(options: BindExcludePatternsUiOptions): vo
|
|
| 70 |
syncEnableGatedTextInputVisibility(enableCheckbox, textInput);
|
| 71 |
|
| 72 |
enableCheckbox?.addEventListener('change', () => {
|
| 73 |
-
if (
|
| 74 |
-
|
|
|
|
|
|
|
|
|
|
| 75 |
}
|
| 76 |
-
lsWriteBool(enabledKey, enableCheckbox.checked, '1');
|
| 77 |
syncEnableGatedTextInputVisibility(enableCheckbox, textInput);
|
| 78 |
onEffectiveChange();
|
| 79 |
});
|
| 80 |
|
| 81 |
textInput?.addEventListener('blur', () => {
|
| 82 |
-
|
|
|
|
|
|
|
| 83 |
onEffectiveChange();
|
| 84 |
});
|
| 85 |
|
|
|
|
| 20 |
defaultTextWhenKeyAbsent?: string;
|
| 21 |
/** enable 键从未写入时的默认勾选态;缺省 `true`(与 Attribution Exclude 一致) */
|
| 22 |
defaultEnabledWhenKeyAbsent?: boolean;
|
| 23 |
+
/** 为 true 时不写 localStorage(由调用方统一 sync,如 Gen Attribute demo UI 面板委托) */
|
| 24 |
+
skipLocalStoragePersist?: boolean;
|
| 25 |
};
|
| 26 |
|
| 27 |
export type BindExcludePromptPatternsUiOptions = Omit<
|
|
|
|
| 48 |
onEffectiveChange,
|
| 49 |
defaultTextWhenKeyAbsent,
|
| 50 |
defaultEnabledWhenKeyAbsent = true,
|
| 51 |
+
skipLocalStoragePersist = false,
|
| 52 |
} = options;
|
| 53 |
const { textKey, enabledKey } = storageKeys;
|
| 54 |
|
|
|
|
| 73 |
syncEnableGatedTextInputVisibility(enableCheckbox, textInput);
|
| 74 |
|
| 75 |
enableCheckbox?.addEventListener('change', () => {
|
| 76 |
+
if (!skipLocalStoragePersist) {
|
| 77 |
+
if (textInput) {
|
| 78 |
+
lsSet(textKey, textInput.value);
|
| 79 |
+
}
|
| 80 |
+
lsWriteBool(enabledKey, enableCheckbox.checked, '1');
|
| 81 |
}
|
|
|
|
| 82 |
syncEnableGatedTextInputVisibility(enableCheckbox, textInput);
|
| 83 |
onEffectiveChange();
|
| 84 |
});
|
| 85 |
|
| 86 |
textInput?.addEventListener('blur', () => {
|
| 87 |
+
if (!skipLocalStoragePersist) {
|
| 88 |
+
lsSet(textKey, textInput.value);
|
| 89 |
+
}
|
| 90 |
onEffectiveChange();
|
| 91 |
});
|
| 92 |
|
client/src/shared/storage/genAttributeRunCache.ts
CHANGED
|
@@ -91,6 +91,8 @@ export type GenAttrDemoUiOptions = {
|
|
| 91 |
/** 是否显示 token tooltip(UI: Show token tooltip;`showTokenInfoOnSelected`)。 */
|
| 92 |
showTokenInfoOnSelected: boolean;
|
| 93 |
replayPacingMode: 'total' | 'step';
|
|
|
|
|
|
|
| 94 |
playbackTotalS: number;
|
| 95 |
playbackStepMs: number;
|
| 96 |
/** 删除 prompt token(物理移除,不占布局):使能与正则文本(`info_radar_gen_attr_delete_prompt_*`)。 */
|
|
@@ -332,6 +334,9 @@ function isValidDemoUiOptionsPayload(v: unknown): v is Partial<GenAttrDemoUiOpti
|
|
| 332 |
if (d.replayPacingMode !== undefined && d.replayPacingMode !== 'total' && d.replayPacingMode !== 'step') {
|
| 333 |
return false;
|
| 334 |
}
|
|
|
|
|
|
|
|
|
|
| 335 |
if (d.playbackTotalS !== undefined && (typeof d.playbackTotalS !== 'number' || !Number.isFinite(d.playbackTotalS))) {
|
| 336 |
return false;
|
| 337 |
}
|
|
|
|
| 91 |
/** 是否显示 token tooltip(UI: Show token tooltip;`showTokenInfoOnSelected`)。 */
|
| 92 |
showTokenInfoOnSelected: boolean;
|
| 93 |
replayPacingMode: 'total' | 'step';
|
| 94 |
+
/** 步进重放(▶)每步是否自动 fit 视口。 */
|
| 95 |
+
replayAutoZoom: boolean;
|
| 96 |
playbackTotalS: number;
|
| 97 |
playbackStepMs: number;
|
| 98 |
/** 删除 prompt token(物理移除,不占布局):使能与正则文本(`info_radar_gen_attr_delete_prompt_*`)。 */
|
|
|
|
| 334 |
if (d.replayPacingMode !== undefined && d.replayPacingMode !== 'total' && d.replayPacingMode !== 'step') {
|
| 335 |
return false;
|
| 336 |
}
|
| 337 |
+
if (d.replayAutoZoom !== undefined && typeof d.replayAutoZoom !== 'boolean') {
|
| 338 |
+
return false;
|
| 339 |
+
}
|
| 340 |
if (d.playbackTotalS !== undefined && (typeof d.playbackTotalS !== 'number' || !Number.isFinite(d.playbackTotalS))) {
|
| 341 |
return false;
|
| 342 |
}
|
client/src/tests/prediction_attribution/genAttributeDagPreprocess.test.ts
ADDED
|
@@ -0,0 +1,49 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
/**
|
| 2 |
+
* DAG 预处理:delete prompt 区间收集
|
| 3 |
+
* 运行: cd client/src && npx tsx tests/prediction_attribution/genAttributeDagPreprocess.test.ts
|
| 4 |
+
*/
|
| 5 |
+
import { collectDeletePromptIntervals } from '../../shared/prediction_attribution/causal_flow/genAttributeDagPreprocess';
|
| 6 |
+
|
| 7 |
+
let passed = 0;
|
| 8 |
+
let failed = 0;
|
| 9 |
+
|
| 10 |
+
function assert(label: string, cond: boolean): void {
|
| 11 |
+
if (cond) {
|
| 12 |
+
passed++;
|
| 13 |
+
console.log(` ✓ ${label}`);
|
| 14 |
+
} else {
|
| 15 |
+
failed++;
|
| 16 |
+
console.log(` ✗ ${label}`);
|
| 17 |
+
}
|
| 18 |
+
}
|
| 19 |
+
|
| 20 |
+
function assertEq<T>(label: string, actual: T, expected: T): void {
|
| 21 |
+
assert(label, JSON.stringify(actual) === JSON.stringify(expected));
|
| 22 |
+
}
|
| 23 |
+
|
| 24 |
+
const wire = 'PROMPT_END|TOOL_RESPONSE';
|
| 25 |
+
const inputRanges: [number, number][] = [
|
| 26 |
+
[0, 11],
|
| 27 |
+
[11, wire.length],
|
| 28 |
+
];
|
| 29 |
+
|
| 30 |
+
console.log('collectDeletePromptIntervals');
|
| 31 |
+
|
| 32 |
+
assertEq('empty inputRanges → []', collectDeletePromptIntervals(wire, [], 'TOOL'), []);
|
| 33 |
+
|
| 34 |
+
assertEq(
|
| 35 |
+
'match only in inputRanges[0]',
|
| 36 |
+
collectDeletePromptIntervals(wire, inputRanges, 'PROMPT'),
|
| 37 |
+
[[0, 6]],
|
| 38 |
+
);
|
| 39 |
+
|
| 40 |
+
assertEq(
|
| 41 |
+
'match in later input range',
|
| 42 |
+
collectDeletePromptIntervals(wire, inputRanges, 'TOOL'),
|
| 43 |
+
[[11, 15]],
|
| 44 |
+
);
|
| 45 |
+
|
| 46 |
+
assertEq('empty pattern → []', collectDeletePromptIntervals(wire, inputRanges, ''), []);
|
| 47 |
+
|
| 48 |
+
console.log(`\n${passed} passed, ${failed} failed`);
|
| 49 |
+
if (failed > 0) process.exit(1);
|