-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathindex.html
More file actions
666 lines (635 loc) · 40.9 KB
/
Copy pathindex.html
File metadata and controls
666 lines (635 loc) · 40.9 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
<!doctype html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width,initial-scale=1" />
<meta name="description" content="从一个问题开始,分支探索,再汇总为新主线的 AI 思考画布" />
<title>Thought Canvas</title>
<link rel="stylesheet" href="styles.css" />
</head>
<body>
<div id="app">
<div id="topChrome" class="top-chrome home-mode" aria-label="项目状态栏">
<header class="project-bar" aria-label="项目栏">
<div class="project-logo" aria-hidden="true">T</div>
<div class="project-copy">
<strong>Thought Canvas</strong>
<span>对话形成结构,本地自动保存</span>
</div>
<button id="homeBtn" class="icon-button" aria-label="返回项目首页" title="项目首页">
<svg viewBox="0 0 24 24" aria-hidden="true"><path d="M3 11.5 12 4l9 7.5M5.5 10v10h13V10M9 20v-6h6v6"/></svg>
</button>
<button id="newProjectBtn" class="icon-button" aria-label="新建项目" title="新建项目">
<svg viewBox="0 0 24 24" aria-hidden="true"><path d="M12 5v14M5 12h14"/></svg>
</button>
<button id="settingsBtn" class="icon-button" aria-label="打开设置" title="设置">
<svg viewBox="0 0 24 24" aria-hidden="true"><path d="M12 15.5a3.5 3.5 0 1 0 0-7 3.5 3.5 0 0 0 0 7Z"/><path d="M19.4 15a1.7 1.7 0 0 0 .3 1.9l.1.1-2.8 2.8-.1-.1a1.7 1.7 0 0 0-1.9-.3 1.7 1.7 0 0 0-1 1.6v.2h-4v-.2a1.7 1.7 0 0 0-1-1.6 1.7 1.7 0 0 0-1.9.3l-.1.1L4.2 17l.1-.1a1.7 1.7 0 0 0 .3-1.9A1.7 1.7 0 0 0 3 14H2.8v-4H3a1.7 1.7 0 0 0 1.6-1 1.7 1.7 0 0 0-.3-1.9L4.2 7 7 4.2l.1.1a1.7 1.7 0 0 0 1.9.3A1.7 1.7 0 0 0 10 3V2.8h4V3a1.7 1.7 0 0 0 1 1.6 1.7 1.7 0 0 0 1.9-.3l.1-.1L19.8 7l-.1.1a1.7 1.7 0 0 0-.3 1.9 1.7 1.7 0 0 0 1.6 1h.2v4H21a1.7 1.7 0 0 0-1.6 1Z"/></svg>
</button>
</header>
<div class="goal-cluster">
<div class="goal-bar" title="最终目标是方向性约束。AI 的推测必须由你确认后才会进入模型上下文。">
<div class="goal-main-row">
<span class="goal-label">最终目标</span>
<input id="goalInput" aria-label="最终目标" placeholder="AI 可提出建议,但只有你确认后才会生效" />
<span id="goalBadge" class="goal-badge">未设定</span>
</div>
</div>
<div id="goalSuggestionCard" class="goal-suggestion-card hidden" role="status" aria-live="polite">
<div>
<span class="goal-suggestion-kicker">AI 推测你的目标</span>
<strong id="goalSuggestionText"></strong>
</div>
<div class="goal-suggestion-actions">
<button type="button" id="acceptGoalSuggestionBtn" class="primary-compact">采用</button>
<button type="button" id="editGoalSuggestionBtn">编辑后采用</button>
<button type="button" id="rejectGoalSuggestionBtn" class="bare-button">暂不设置</button>
</div>
</div>
</div>
<div id="saveStatus" class="save-status hidden" role="status" aria-live="polite">
<i aria-hidden="true"></i>
<span>已保存</span>
<time>本地文件</time>
</div>
</div>
<nav class="tool-rail" aria-label="画布工具">
<button id="selectToolBtn" class="tool-button active" data-tooltip="框选" aria-label="框选">
<svg viewBox="0 0 24 24"><path d="m5 3 12 8-6 1-2 6Z"/></svg>
</button>
<button id="handToolBtn" class="tool-button" data-tooltip="拖动画布" aria-label="拖动画布">
<svg viewBox="0 0 24 24"><path d="M8 11V6a2 2 0 0 1 4 0v4-6a2 2 0 0 1 4 0v6-4a2 2 0 0 1 4 0v8c0 4-3 7-7 7h-1c-3 0-5-1-7-4l-2-3a2 2 0 0 1 3-2l2 2"/></svg>
</button>
<span class="rail-separator"></span>
<button id="fitBtn" class="tool-button" data-tooltip="适配全部" aria-label="适配全部节点">
<svg viewBox="0 0 24 24"><path d="M8 3H3v5M16 3h5v5M8 21H3v-5M16 21h5v-5"/></svg>
</button>
<button id="autoLayoutBtn" class="tool-button" data-tooltip="自动排布" aria-label="自动排布节点">
<svg viewBox="0 0 24 24"><path d="M4 5h5v5H4zM15 5h5v5h-5zM9 14h6v5H9zM9 7.5h6M6.5 10v2.5h5.5M17.5 10v2.5H12"/></svg>
</button>
<button id="toggleArchiveBtn" class="tool-button" data-tooltip="显示归档" aria-label="显示归档">
<svg viewBox="0 0 24 24"><path d="M3 6h18v4H3zM5 10h14v10H5zM9 14h6"/></svg>
</button>
<span class="rail-separator"></span>
<button id="historyBtn" class="tool-button" data-tooltip="版本记录" aria-label="查看版本记录">
<svg viewBox="0 0 24 24"><path d="M4 12a8 8 0 1 0 2.3-5.7L4 8.6M4 4v4.6h4.6M12 7v5l3.2 2"/></svg>
</button>
<button id="exportBtn" class="tool-button" data-tooltip="导出" aria-label="导出画布">
<svg viewBox="0 0 24 24"><path d="M12 3v12M8 7l4-4 4 4M5 13v7h14v-7"/></svg>
</button>
<label class="tool-button file-button" data-tooltip="导入" aria-label="导入画布">
<svg viewBox="0 0 24 24"><path d="M12 15V3M8 11l4 4 4-4M5 17v4h14v-4"/></svg>
<input id="importInput" type="file" accept="application/json" />
</label>
</nav>
<section id="homeView" class="home-view hidden" aria-label="项目首页">
<div class="home-shell">
<header class="home-header">
<div>
<span class="home-kicker">THOUGHT CANVAS</span>
<h1>今天想理清什么?</h1>
<p>从一个问题开始,分支探索,再把有价值的内容汇成新的主线。</p>
</div>
<button id="homeNewProjectBtn" class="home-new-button">+ 新建项目</button>
</header>
<section class="home-composer-card">
<textarea id="homeQuestionInput" placeholder="输入你想探索、学习或做出决定的问题…"></textarea>
<div class="home-composer-bottom">
<div class="home-model-selectors">
<select id="homeProviderSelect" aria-label="首页供应商"></select>
<select id="homeModelSelect" aria-label="首页模型"></select>
<select id="homeReasoningSelect" aria-label="首页思考等级"></select>
<span id="homeModelConnectionHint">使用全局默认,可在设置中修改</span>
<button type="button" id="homeConnectProviderBtn" class="home-model-connect hidden">连接模型</button>
</div>
<button id="homeStartBtn" class="home-start-button" aria-label="创建项目并开始">开始</button>
</div>
</section>
<section class="recent-projects-section">
<div class="recent-projects-head">
<div><h2>最近项目</h2><p>项目与画布自动保存到本机项目目录;模型配置与密钥也仅保存在本机。</p></div>
<span id="projectCountBadge" class="project-count-badge">0 个项目</span>
</div>
<div id="recentProjectsGrid" class="recent-projects-grid"></div>
</section>
</div>
</section>
<main id="workspace" class="workspace">
<section id="viewport" class="viewport mode-select" aria-label="无限思考画布">
<div id="world" class="world">
<svg id="edgesSvg" class="edges-svg" width="12000" height="12000"></svg>
<div id="nodesLayer" class="nodes-layer"></div>
</div>
<div id="selectionRect" class="selection-rect hidden"></div>
<div id="selectionActions" class="selection-actions hidden">
<span id="selectionLabel">已选择 0 个节点</span>
<button id="mergeSelectedBtn" class="primary-compact">汇总</button>
<button id="compareSelectedBtn">比较</button>
<button id="completeSelectedBtn">完成</button>
<button id="archiveSelectedBtn">归档</button>
<button id="clearSelectionBtn" class="bare-button" aria-label="取消选择">×</button>
</div>
<section id="canvasNavigator" class="canvas-navigator hidden" aria-label="画布导航">
<div class="canvas-search-field">
<svg viewBox="0 0 24 24" aria-hidden="true"><circle cx="11" cy="11" r="6.5"/><path d="m16 16 4 4"/></svg>
<input id="nodeSearchInput" type="search" autocomplete="off" placeholder="搜索节点、观点或问题" aria-label="搜索画布节点" aria-controls="nodeSearchResults" />
<button type="button" id="clearNodeSearchBtn" class="canvas-search-clear hidden" aria-label="清除搜索">×</button>
</div>
<div class="canvas-nav-actions">
<button type="button" id="pathViewBtn" aria-pressed="false" title="只显示当前路径、当前节点与直接下一步">当前路径</button>
<button type="button" id="reasoningPanelBtn" title="查看观点、证据、假设与它们的关系">推理对象</button>
<button type="button" id="returnToSourceBtn" class="hidden" title="返回当前节点的来源">返回来源</button>
</div>
<div id="nodeSearchResults" class="node-search-results hidden" role="listbox"></div>
</section>
<div class="canvas-hint">
<span>拖动空白处框选</span>
<span>空格拖动画布</span>
<span>滚轮平滑缩放</span>
</div>
<aside id="miniMap" class="mini-map hidden" aria-label="画布小地图">
<div class="mini-map-head"><span>地图</span><button type="button" id="fitMiniMapBtn" title="适配全部节点">适配</button></div>
<div id="miniMapStage" class="mini-map-stage"></div>
</aside>
<div class="zoom-controls" aria-label="缩放控制">
<button id="zoomOutBtn" aria-label="缩小">−</button>
<span id="zoomValue">100%</span>
<button id="zoomInBtn" aria-label="放大">+</button>
</div>
</section>
<div id="sidebarResizer" class="sidebar-resizer" title="拖动调整面板宽度"></div>
<aside id="sidebar" class="sidebar" aria-label="节点对话面板"></aside>
</main>
</div>
<dialog id="settingsDialog" class="modal settings-modal provider-settings-modal">
<form method="dialog" id="settingsForm">
<header class="modal-header">
<div>
<span class="eyebrow">设置</span>
<h2>模型供应商与上下文</h2>
<p>为不同模型服务分别维护 Base URL、API Key 与模型列表。</p>
</div>
<button type="button" class="icon-button modal-close" aria-label="关闭">×</button>
</header>
<div class="settings-tabs" role="tablist">
<button type="button" class="settings-tab active" data-settings-tab="general">通用</button>
<button type="button" class="settings-tab" data-settings-tab="providers">供应商</button>
<button type="button" class="settings-tab" data-settings-tab="oauth">OAuth</button>
</div>
<div class="settings-tab-panel active" data-settings-panel="general">
<div class="settings-grid compact-settings-grid">
<section class="settings-card settings-wide language-settings-card">
<div class="settings-title-row"><h3>界面语言</h3><span>切换应用界面;项目标题、用户输入和模型回答不会被自动翻译</span></div>
<div class="language-setting-row">
<label><span>语言</span><select id="uiLanguageSelect" data-no-i18n></select></label>
<p class="policy-copy">切换后立即预览,并在保存设置后写入本机配置。新生成的系统提示会使用所选语言。</p>
</div>
</section>
<section class="settings-card">
<div class="settings-title-row"><h3>默认回答模型</h3><span>应用到所有未单独覆盖的聊天框</span></div>
<div class="field-triplet model-triplet">
<label><span>供应商</span><select id="settingsDefaultProvider"></select></label>
<label><span>模型</span><select id="settingsDefaultModel"></select></label>
<label><span>思考等级</span><select id="settingsDefaultReasoning"></select></label>
</div>
<label class="inline-check"><input id="resetComposerOverridesInput" type="checkbox" checked /><span>保存时清除已有聊天框的临时模型选择</span></label>
</section>
<section class="settings-card">
<h3>汇总模型</h3>
<div class="field-triplet model-triplet">
<label><span>供应商</span><select id="settingsMergeProvider"></select></label>
<label><span>模型</span><select id="settingsMergeModel"></select></label>
<label><span>思考等级</span><select id="settingsMergeReasoning"></select></label>
</div>
</section>
<section class="settings-card settings-wide">
<h3>长期约束</h3>
<textarea id="constraintsInput" placeholder="每行一条,例如:不想出镜;预算有限;目标是单人起步"></textarea>
</section>
<section class="settings-card settings-wide">
<div class="settings-title-row"><h3>回答拆解方式</h3><span>拆现有内容,不生成后续问题</span></div>
<div class="field-pair model-pair">
<select id="decomposePresetSelect">
<option value="structure">按内容结构</option>
<option value="chapters">按章节主题</option>
<option value="arguments">按观点与依据</option>
<option value="steps">按执行步骤</option>
<option value="learning">按学习难点</option>
<option value="custom">完全自定义</option>
</select>
<span class="field-help">每条回答下方可手动拆解,也可在提问中说“帮我拆解”自动创建节点;模糊意图会先预览确认。</span>
</div>
<textarea id="decomposePromptInput" placeholder="可选:补充你的拆解要求,例如:每个模块保留原文案例,不要超过5个模块。"></textarea>
</section>
<section class="settings-card settings-wide">
<div class="settings-title-row"><h3>Compact</h3><span>只压缩发给模型的上下文,不删除原始消息</span></div>
<label class="inline-check"><input id="autoCompactEnabledInput" type="checkbox" /><span>自动 Compact</span></label>
<label><span>消息数量触发阈值</span><input id="autoCompactMessageLimitInput" type="number" min="4" max="100" step="1" /></label>
<p class="policy-copy">也可在节点顶部手动执行、查看、重新生成或删除 Compact。删除 Compact 不影响任何原始聊天记录。</p>
</section>
<section class="settings-card settings-wide">
<div class="settings-title-row"><h3>画布连线</h3><span>控制普通节点之间的连接方式</span></div>
<div class="field-pair model-pair">
<label><span>线条形状</span><select id="connectionShapeInput"><option value="curve">弧线</option><option value="orthogonal">直角线</option></select></label>
<label><span>线条样式</span><select id="connectionStrokeInput"><option value="solid">实线</option><option value="dashed">虚线</option></select></label>
</div>
<p class="policy-copy">生成中的节点仍会临时使用流动虚线,完成后恢复这里选择的样式。</p>
</section>
<section class="settings-card settings-wide">
<div class="settings-title-row">
<h3>最终目标的作用</h3>
<span>方向约束,不覆盖当前追问</span>
</div>
<p class="policy-copy">优先级固定为:当前明确追问 → 当前节点与祖先路径 → 用户长期约束 → 最终目标。目标修改只影响之后的新分支和汇总,历史节点保留创建时的目标版本。</p>
<div id="goalHistory" class="goal-history"></div>
</section>
</div>
</div>
<div class="settings-tab-panel" data-settings-panel="providers">
<div class="provider-manager">
<aside class="provider-list-pane">
<div class="provider-list-head">
<div><strong>供应商</strong><span>分为预设与自定义两类</span></div>
<button type="button" id="addProviderBtn" class="primary-compact">+ 自定义</button>
</div>
<input id="providerSearchInput" class="provider-search" placeholder="搜索预设或自定义供应商" />
<div id="providerProfileList" class="provider-profile-list"></div>
</aside>
<section class="provider-editor-pane">
<div class="provider-editor-head">
<div><span class="eyebrow">当前配置</span><h3 id="providerEditorTitle">供应商</h3></div>
<div class="provider-head-actions">
<span id="providerConnectionState" class="connection-state neutral">未连接</span>
<button type="button" id="connectProviderBtn" class="primary-compact">连接并同步模型</button>
</div>
</div>
<div class="provider-editor-scroll">
<div class="provider-form-grid provider-core-fields">
<label><span>名称</span><input id="providerNameInput" /></label>
<label><span>接口协议</span><select id="providerProtocolSelect"></select></label>
<label class="provider-wide"><span>Base URL</span><input id="providerBaseUrlInput" placeholder="https://api.example.com/v1" /></label>
<label class="provider-wide provider-key-field">
<span>API Key <small id="providerKeyStatus">未保存</small></span>
<div class="provider-key-row"><input id="providerApiKeyInput" type="password" autocomplete="off" placeholder="仅用于连接验证;成功后保存到 .env.local" /><button type="button" id="clearProviderKeyBtn">清除</button></div>
</label>
</div>
<section class="model-list-section">
<div class="model-list-head"><div><strong>模型</strong><span>一行一个模型 ID;显示名称可选</span></div><button type="button" id="addModelRowBtn">+ 新增模型</button></div>
<div id="providerModelRows" class="provider-model-rows"></div>
</section>
<details class="provider-advanced">
<summary>高级设置</summary>
<p>认证方式和自定义请求头只在部分代理商要求特殊 Header 时使用。普通 OpenAI 兼容接口通常保持默认即可。</p>
<div class="provider-form-grid">
<label><span>认证方式</span><select id="providerAuthModeSelect"></select></label>
<label><span>思考参数适配</span><select id="providerReasoningModeSelect"></select></label>
<label><span>连接测试模型</span><select id="providerTestModelSelect"></select></label>
<label class="provider-wide"><span>自定义请求头(JSON,可选)</span><textarea id="providerHeadersInput" class="provider-headers" placeholder='{"HTTP-Referer":"https://example.com","X-Title":"Thought Canvas"}'></textarea></label>
<label class="provider-check provider-wide"><input id="providerEnabledInput" type="checkbox" /><span>在聊天模型下拉框中启用</span></label>
</div>
</details>
<div id="providerTestDetail" class="provider-test-detail">输入 API Key 后点击“连接并同步模型”:验证凭据、读取可用模型,并把模型与思考等级同步到所有选择器。</div>
</div>
<div class="provider-editor-actions">
<button type="button" id="testProviderBtn">仅测试当前模型</button>
<button type="button" id="syncModelsBtn">重新同步模型</button>
<button type="button" id="setDefaultProviderBtn">设为默认回答</button>
<button type="button" id="duplicateProviderBtn">复制为自定义</button>
<button type="button" id="resetProviderBtn">恢复预设</button>
<button type="button" id="deleteProviderBtn" class="danger-soft">删除</button>
<button type="button" id="saveProviderBtn" class="primary-action">保存非敏感配置</button>
</div>
</section>
</div>
</div>
<div class="settings-tab-panel" data-settings-panel="oauth">
<div class="oauth-grid">
<article class="oauth-card oauth-card-wide codex-oauth-card">
<div class="oauth-card-title"><strong>ChatGPT / Codex App Server</strong><span id="codexOAuthBadge" class="oauth-badge caution">检测中</span></div>
<p>点击后由本机官方 Codex App Server 完成 ChatGPT 授权、账号状态读取和模型同步。Thought Canvas 不读取、不复制,也不保存 OAuth Token。</p>
<div class="codex-login-methods" aria-label="Codex 登录方式">
<button type="button" id="codexBrowserLoginBtn" class="oauth-method-button recommended">
<span>连接 Codex <em>推荐</em></span>
<small>打开官方 ChatGPT 授权页面;完成后自动同步当前账号可用模型与思考等级。</small>
</button>
<button type="button" id="codexDeviceLoginBtn" class="oauth-method-button">
<span>设备码连接</span>
<small>浏览器回调不便时,打开一次性验证网址并输入设备码。</small>
</button>
</div>
<div id="codexOAuthDetail" class="oauth-detail">正在检测本机 Codex App Server…</div>
<div id="codexAccountSummary" class="oauth-account-summary hidden"></div>
<div id="codexModelSummary" class="oauth-model-summary hidden"></div>
<pre id="codexOAuthLog" class="oauth-log hidden" aria-live="polite"></pre>
<div class="oauth-actions">
<button type="button" id="codexDetectBtn">刷新状态与模型</button>
<button type="button" id="codexUseNowBtn" class="primary-action hidden">立即使用 Codex</button>
<button type="button" id="codexCancelLoginBtn" class="danger-soft hidden">取消授权</button>
<button type="button" id="codexLogoutBtn">退出登录</button>
</div>
</article>
<article class="oauth-card"><div><strong>OpenClaw</strong><span class="oauth-badge possible">可作为本地桥接</span></div><p>OpenClaw 可以自己管理 OpenAI OAuth。当前版本不读取其令牌;可把 OpenClaw 暴露的兼容端点作为自定义供应商接入。</p></article>
<article class="oauth-card"><div><strong>Claude CLI / Claude.ai</strong><span class="oauth-badge caution">暂不内置</span></div><p>订阅认证与第三方调用边界仍在变化。正式产品优先使用 Anthropic API Key,避免直接搬运用户令牌。</p></article>
<article class="oauth-card"><div><strong>Gemini OAuth</strong><span class="oauth-badge possible">需要自建 OAuth Client</span></div><p>需要 Google Cloud OAuth Client、Redirect URI、令牌刷新与安全存储,不适合无后端静态页面。</p></article>
</div>
</div>
<footer class="modal-actions">
<button type="button" id="closeSettingsBtn">完成并关闭</button>
<button type="submit" id="saveSettingsBtn" class="primary-action">保存全部设置</button>
</footer>
</form>
</dialog>
<dialog id="newProjectDialog" class="modal compact-modal new-project-modal">
<form method="dialog" id="newProjectForm">
<header class="modal-header">
<div>
<span class="eyebrow">新建项目</span>
<h2>建立一张新的思考画布</h2>
<p>项目名称可以留空;最终目标也可以在首问后由 AI 提议。</p>
</div>
<button type="button" class="icon-button modal-close" aria-label="关闭">×</button>
</header>
<div class="new-project-fields">
<label><span>项目名称</span><input id="newProjectTitleInput" placeholder="例如:研究新的商业方向" /></label>
<label><span>最终目标(可选)</span><textarea id="newProjectGoalInput" placeholder="例如:在四周内选出一个可以低成本验证的方向"></textarea></label>
<div class="field-triplet model-triplet">
<label><span>供应商</span><select id="newProjectProviderSelect"></select></label>
<label><span>模型</span><select id="newProjectModelSelect"></select></label>
<label><span>思考等级</span><select id="newProjectReasoningSelect"></select></label>
</div>
<div class="new-project-model-status"><span id="newProjectModelHint">模型与思考等级会作为根节点的初始配置。</span><button type="button" id="newProjectConnectProviderBtn" class="text-action hidden">连接模型</button></div>
</div>
<footer class="modal-actions">
<button type="button" id="cancelNewProjectBtn">取消</button>
<button type="submit" id="confirmNewProjectBtn" class="primary-action">创建项目</button>
</footer>
</form>
</dialog>
<dialog id="branchComposerDialog" class="modal compact-modal branch-composer-modal">
<form method="dialog" id="branchComposerForm">
<header class="modal-header">
<div>
<span class="eyebrow">新建分支</span>
<h2>从当前节点平行延伸</h2>
<p id="branchComposerContext">这次提问会创建新的子节点;当前聊天记录不会被修改。</p>
</div>
<button type="button" class="icon-button modal-close" aria-label="关闭">×</button>
</header>
<div class="branch-composer-fields">
<textarea id="branchMessageDraft" placeholder="输入要在新分支中讨论的问题。Enter 发送,Shift + Enter 换行"></textarea>
<div class="field-pair model-pair">
<label><span>供应商</span><select id="branchProviderSelect"></select><small id="branchProviderMeta" class="branch-field-meta"></small></label>
<label><span>模型</span><select id="branchModelSelect"></select><small id="branchModelMeta" class="branch-field-meta"></small></label>
<label><span>思考等级</span><select id="branchReasoningSelect"></select><small id="branchReasoningMeta" class="branch-field-meta"></small></label>
</div>
</div>
<footer class="modal-actions">
<button type="button" class="dialog-cancel">取消</button>
<button type="submit" id="confirmBranchBtn" class="primary-action">创建分支并发送</button>
</footer>
</form>
</dialog>
<dialog id="contextInspectorDialog" class="modal context-inspector-modal">
<form method="dialog" id="contextInspectorForm">
<header class="modal-header">
<div>
<span class="eyebrow">本次上下文</span>
<h2 id="contextInspectorTitle">模型会看到什么</h2>
<p id="contextInspectorSubtitle">查看当前节点最近一次请求的不可变上下文快照,或预览下一次请求。</p>
</div>
<button type="button" class="icon-button modal-close" aria-label="关闭">×</button>
</header>
<div class="context-inspector-summary">
<div><span>预计输入</span><strong id="contextTokenTotal">0 tokens</strong></div>
<div><span>快照</span><strong id="contextSnapshotLabel">预览</strong></div>
<div><span>目标</span><strong id="contextGoalLabel">未确认</strong></div>
</div>
<div id="contextPreferenceControls" class="context-preference-controls"></div>
<div id="contextInspectorSections" class="context-inspector-sections"></div>
<footer class="modal-actions">
<span class="context-inspector-note">当前问题与当前节点消息始终包含;其他层可以为下一次请求单独开关。</span>
<button type="button" id="closeContextInspectorBtn" class="primary-action">完成</button>
</footer>
</form>
</dialog>
<dialog id="annotationDialog" class="modal compact-modal annotation-modal" aria-labelledby="annotationDialogTitle">
<form method="dialog" id="annotationForm">
<header class="modal-header">
<div>
<span class="eyebrow">思维导图标注</span>
<h2 id="annotationDialogTitle">新建标注模块</h2>
<p id="annotationSourceLabel">标注会通过密集虚线连接到当前节点,不会影响主分支完成度。</p>
</div>
<button type="button" class="icon-button modal-close" aria-label="关闭">×</button>
</header>
<div class="annotation-form-grid">
<label><span>类型</span><select id="annotationTypeSelect">
<option value="note">标注</option>
<option value="idea">想法</option>
<option value="question">问题</option>
<option value="risk">风险</option>
<option value="action">行动</option>
</select></label>
<fieldset class="annotation-color-field">
<legend>背景色</legend>
<input type="hidden" id="annotationColorInput" value="auto" />
<div class="annotation-color-options" role="radiogroup" aria-label="标注背景色">
<button type="button" class="annotation-color-swatch selected" data-annotation-color="auto" aria-pressed="true" title="跟随类型"><span class="annotation-color-dot is-auto"></span><span>跟随类型</span></button>
<button type="button" class="annotation-color-swatch" data-annotation-color="violet" aria-pressed="false" title="雾紫"><span class="annotation-color-dot" style="--swatch-color:#a98cff"></span><span>雾紫</span></button>
<button type="button" class="annotation-color-swatch" data-annotation-color="slate" aria-pressed="false" title="石板"><span class="annotation-color-dot" style="--swatch-color:#839ab9"></span><span>石板</span></button>
<button type="button" class="annotation-color-swatch" data-annotation-color="blue" aria-pressed="false" title="晴蓝"><span class="annotation-color-dot" style="--swatch-color:#6299da"></span><span>晴蓝</span></button>
<button type="button" class="annotation-color-swatch" data-annotation-color="mint" aria-pressed="false" title="薄荷"><span class="annotation-color-dot" style="--swatch-color:#66be9c"></span><span>薄荷</span></button>
<button type="button" class="annotation-color-swatch" data-annotation-color="amber" aria-pressed="false" title="琥珀"><span class="annotation-color-dot" style="--swatch-color:#d8b45b"></span><span>琥珀</span></button>
<button type="button" class="annotation-color-swatch" data-annotation-color="rose" aria-pressed="false" title="玫瑰"><span class="annotation-color-dot" style="--swatch-color:#da7d8b"></span><span>玫瑰</span></button>
</div>
</fieldset>
<label class="annotation-wide"><span>标题</span><input id="annotationTitleInput" maxlength="120" placeholder="例如:这里需要验证用户成本" /></label>
<label class="annotation-wide"><span>内容</span><textarea id="annotationContentInput" rows="8" placeholder="记录批注、补充说明、风险、下一步或任何不想混进主对话的内容。"></textarea></label>
</div>
<footer class="modal-actions">
<button type="button" id="deleteAnnotationBtn" class="danger-soft hidden">删除标注</button>
<span class="modal-action-spacer"></span>
<button type="button" class="dialog-cancel">取消</button>
<button type="submit" class="primary-action">保存标注</button>
</footer>
</form>
</dialog>
<dialog id="artifactDialog" class="modal artifact-modal">
<form method="dialog" id="artifactForm">
<header class="modal-header">
<div>
<span class="eyebrow">提炼为推理对象</span>
<h2 id="artifactDialogTitle">从原回答中保留可追溯的观点</h2>
<p id="artifactSourceLabel">对象会保留节点、消息与原文范围。</p>
</div>
<button type="button" class="icon-button modal-close" aria-label="关闭">×</button>
</header>
<div class="artifact-form-grid">
<label><span>类型</span><select id="artifactKindSelect">
<option value="claim">观点</option><option value="evidence">证据</option><option value="assumption">假设</option><option value="question">问题</option>
<option value="option">方案</option><option value="risk">风险</option><option value="action">行动</option>
</select></label>
<label><span>可信状态</span><select id="artifactConfidenceSelect">
<option value="unverified">未验证</option><option value="partial">部分证据</option><option value="verified">已验证</option><option value="contested">有争议</option>
</select></label>
<label class="artifact-wide"><span>标题</span><input id="artifactTitleInput" maxlength="120" placeholder="用一句话说明这个对象" /></label>
<label class="artifact-wide"><span>原文内容</span><textarea id="artifactContentInput" rows="7"></textarea></label>
</div>
<footer class="modal-actions">
<span class="context-inspector-note">提炼不会修改原回答;后续可以把对象设置为支持、反驳或依赖关系。</span>
<button type="button" class="dialog-cancel">取消</button>
<button type="submit" class="primary-action">保存推理对象</button>
</footer>
</form>
</dialog>
<dialog id="reasoningDialog" class="modal reasoning-modal">
<form method="dialog">
<header class="modal-header">
<div>
<span class="eyebrow">推理对象库</span>
<h2>观点、证据、假设与决策</h2>
<p>工作是否完成和内容是否可信分开记录;所有对象都能回到原节点与原消息。</p>
</div>
<button type="button" class="icon-button modal-close" aria-label="关闭">×</button>
</header>
<div class="reasoning-toolbar">
<div id="reasoningSummary" class="reasoning-summary"></div>
<label><span>类型</span><select id="reasoningKindFilter"><option value="all">全部类型</option></select></label>
<label><span>可信</span><select id="reasoningConfidenceFilter"><option value="all">全部状态</option></select></label>
</div>
<div class="reasoning-layout">
<section><h3>对象</h3><div id="reasoningArtifactList" class="reasoning-artifact-list"></div></section>
<section><h3>关系</h3><div id="reasoningRelationList" class="reasoning-relation-list"></div></section>
</div>
<footer class="modal-actions"><button type="button" class="dialog-cancel primary-action">完成</button></footer>
</form>
</dialog>
<dialog id="relationDialog" class="modal compact-modal relation-modal">
<form method="dialog" id="relationForm">
<header class="modal-header">
<div><span class="eyebrow">建立语义关系</span><h2 id="relationSourceLabel">连接推理对象</h2><p>关系方向为“当前对象 → 目标对象”。</p></div>
<button type="button" class="icon-button modal-close" aria-label="关闭">×</button>
</header>
<div class="relation-form-fields">
<label><span>关系</span><select id="relationTypeSelect"><option value="supports">支持</option><option value="refutes">反驳</option><option value="depends_on">依赖</option></select></label>
<label><span>目标对象</span><select id="relationTargetSelect"></select></label>
</div>
<footer class="modal-actions"><button type="button" class="dialog-cancel">取消</button><button type="submit" class="primary-action">建立关系</button></footer>
</form>
</dialog>
<dialog id="compareDialog" class="modal compare-modal">
<form method="dialog">
<header class="modal-header">
<div><span class="eyebrow">分支比较</span><h2>并排检查上下文、结论与风险</h2><p>比较不调用模型,先展示可验证的结构差异与来源。</p></div>
<button type="button" class="icon-button modal-close" aria-label="关闭">×</button>
</header>
<div id="compareContent" class="compare-content"></div>
<footer class="modal-actions"><button type="button" class="dialog-cancel primary-action">完成</button></footer>
</form>
</dialog>
<dialog id="mergeDialog" class="modal merge-modal">
<form method="dialog" id="mergeForm">
<header class="modal-header">
<div>
<span class="eyebrow">汇总</span>
<h2>确认输入范围</h2>
<p>系统不会静默漏掉节点。重复、结构节点和压缩规则都会在这里说明。</p>
</div>
<button type="button" class="icon-button modal-close" aria-label="关闭">×</button>
</header>
<div id="mergePlan" class="merge-plan"></div>
<label class="focus-field">
<span>这次最想解决什么,可留空</span>
<textarea id="mergeFocus" placeholder="例如:基于这些分支,我现在最应该优先选择哪条路径?"></textarea>
</label>
<footer class="modal-actions">
<button type="button" class="dialog-cancel">取消</button>
<button type="submit" id="confirmMergeBtn" class="primary-action">生成汇总节点</button>
</footer>
</form>
</dialog>
<dialog id="backupDialog" class="modal backup-modal">
<header class="modal-header">
<div>
<span class="eyebrow">本地版本</span>
<h2>版本记录与恢复</h2>
<p>关键操作完成时会保留本地恢复版本;恢复前也会先备份当前版本。</p>
</div>
<button type="button" class="icon-button modal-close" aria-label="关闭">×</button>
</header>
<div class="backup-toolbar">
<span id="backupSummary">正在读取本地备份…</span>
<button type="button" id="refreshBackupsBtn">刷新</button>
</div>
<div id="backupList" class="backup-list" aria-live="polite"></div>
</dialog>
<dialog id="exportDialog" class="modal export-modal">
<header class="modal-header">
<div>
<span class="eyebrow">开放导出</span>
<h2>选择导出格式</h2>
<p>项目原始格式用于完整恢复;JSON Canvas 和 Markdown 便于迁移与长期阅读。</p>
</div>
<button type="button" class="icon-button modal-close" aria-label="关闭">×</button>
</header>
<div class="export-format-grid">
<button type="button" data-export-format="native">
<strong>Thought Canvas JSON</strong>
<span>完整项目、上下文快照、生成记录和推理关系</span>
<em>.json</em>
</button>
<button type="button" data-export-format="canvas">
<strong>JSON Canvas</strong>
<span>节点、分支与推理关系,可导入兼容画布工具</span>
<em>.canvas</em>
</button>
<button type="button" data-export-format="markdown">
<strong>Markdown</strong>
<span>目标、对话、观点、证据、关系与最终决策</span>
<em>.md</em>
</button>
</div>
<footer class="modal-actions export-footer">
<span id="exportSummary"></span>
<button type="button" class="dialog-cancel">取消</button>
</footer>
</dialog>
<dialog id="archiveDialog" class="modal compact-modal">
<form method="dialog" id="archiveForm">
<header class="modal-header">
<div>
<span class="eyebrow">归档</span>
<h2>归档所选分支</h2>
<p>归档父节点时会一起归档其后代,但内容和关系仍然保留。</p>
</div>
<button type="button" class="icon-button modal-close" aria-label="关闭">×</button>
</header>
<label class="focus-field">
<span>归档原因</span>
<textarea id="archiveReason">已完成或暂时不进入主线</textarea>
</label>
<footer class="modal-actions">
<button type="button" class="dialog-cancel">取消</button>
<button type="submit" class="primary-action">确认归档</button>
</footer>
</form>
</dialog>
<dialog id="confirmDialog" class="modal compact-modal confirm-modal" aria-labelledby="confirmDialogTitle">
<form method="dialog" id="confirmDialogForm">
<header class="modal-header confirm-modal-header">
<div>
<span id="confirmDialogEyebrow" class="eyebrow">请确认</span>
<h2 id="confirmDialogTitle">确认操作</h2>
<p id="confirmDialogMessage"></p>
</div>
<button type="button" id="confirmDialogCloseBtn" class="icon-button" aria-label="关闭">×</button>
</header>
<div id="confirmDialogDetail" class="confirm-dialog-detail hidden"></div>
<footer class="modal-actions">
<button type="button" id="confirmDialogCancelBtn">取消</button>
<button type="submit" id="confirmDialogConfirmBtn" class="primary-action">确认</button>
</footer>
</form>
</dialog>
<script type="module" src="app.js"></script>
</body>
</html>