@zaofan/dsh-qqbot 1.11.1 → 1.11.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/client/qqbot-settings.js +46 -0
- package/docs/extension-contract.md +29 -1
- package/examples/ext-ui/demo/dock/card.css +51 -48
- package/examples/ext-ui/demo/dock/card.html +3 -2
- package/examples/ext-ui/demo/dock/card.js +7 -6
- package/examples/ext-ui/demo/settings/card.css +53 -50
- package/package.json +1 -1
- package/settings-host.js +42 -1
package/client/qqbot-settings.js
CHANGED
|
@@ -693,6 +693,15 @@ window.__ModuleLoader__.load({
|
|
|
693
693
|
* dock.members() 群友台账(谁说过话、什么印象分)
|
|
694
694
|
* 这些走 /api/qqbot-settings/dock-data,scope=当前数据根(每实例一份,天然隔离)。
|
|
695
695
|
*/
|
|
696
|
+
/** 宿主是不是暗色主题(宿主 ThemePresenter 给 body 加 data-ds-dark-theme 属性) */
|
|
697
|
+
function extUiIsDark() {
|
|
698
|
+
try {
|
|
699
|
+
if (document.body && document.body.hasAttribute('data-ds-dark-theme')) return true
|
|
700
|
+
// 兜底:宿主没给属性时看系统偏好
|
|
701
|
+
if (window.matchMedia && window.matchMedia('(prefers-color-scheme: dark)').matches) return true
|
|
702
|
+
} catch (e) { /* ignore */ }
|
|
703
|
+
return false
|
|
704
|
+
}
|
|
696
705
|
/** 读当前 web 会话 id(宿主的 localStorage,和「当前会话」检测同一个源) */
|
|
697
706
|
function extUiCurrentSid() {
|
|
698
707
|
try {
|
|
@@ -749,6 +758,35 @@ window.__ModuleLoader__.load({
|
|
|
749
758
|
.catch(function () { return null })
|
|
750
759
|
},
|
|
751
760
|
toast: function (m) { try { window.alert(String(m)) } catch (e) { /* ignore */ } },
|
|
761
|
+
|
|
762
|
+
/**
|
|
763
|
+
* 主题(2026-10-27 主人问:自定义 UI 能跟随宿主主题吗?—— 能)
|
|
764
|
+
*
|
|
765
|
+
* 两条路,**优先用 CSS 变量**:
|
|
766
|
+
* ① CSS 变量(推荐):插件在 body 上定义了 --qqs-* token,亮/暗两套自动切换。
|
|
767
|
+
* 自定义属性会**穿透 Shadow DOM**,卡片里直接写 var(--qqs-text) 就行。
|
|
768
|
+
* 常用:--qqs-card / --qqs-panel / --qqs-text / --qqs-text2 / --qqs-text3
|
|
769
|
+
* --qqs-border / --qqs-input / --qqs-toolbar / --qqs-primary-fg
|
|
770
|
+
* ② JS 判断(要按主题做逻辑分支时):theme / isDark() / onThemeChange(fn)
|
|
771
|
+
* 宿主切主题时给 body 加 data-ds-dark-theme 属性,这里监听它。
|
|
772
|
+
*/
|
|
773
|
+
get theme() { return extUiIsDark() ? 'dark' : 'light' },
|
|
774
|
+
isDark: function () { return extUiIsDark() },
|
|
775
|
+
onThemeChange: function (fn) {
|
|
776
|
+
if (typeof fn !== 'function') return function () {}
|
|
777
|
+
var handler = function () { try { fn(extUiIsDark() ? 'dark' : 'light') } catch (e) { /* ignore */ } }
|
|
778
|
+
try {
|
|
779
|
+
var ob = new MutationObserver(handler)
|
|
780
|
+
ob.observe(document.body, { attributes: true, attributeFilter: ['data-ds-dark-theme', 'class'] })
|
|
781
|
+
var mq = window.matchMedia ? window.matchMedia('(prefers-color-scheme: dark)') : null
|
|
782
|
+
if (mq && mq.addEventListener) mq.addEventListener('change', handler)
|
|
783
|
+
return function () {
|
|
784
|
+
try { ob.disconnect() } catch (e) { /* ignore */ }
|
|
785
|
+
try { if (mq && mq.removeEventListener) mq.removeEventListener('change', handler) } catch (e) { /* ignore */ }
|
|
786
|
+
}
|
|
787
|
+
} catch (e) { return function () {} }
|
|
788
|
+
},
|
|
789
|
+
|
|
752
790
|
/**
|
|
753
791
|
* 能力调用(2026-10-27 主人要求:UI 卡片要和其余扩展对齐能力)
|
|
754
792
|
*
|
|
@@ -857,6 +895,14 @@ window.__ModuleLoader__.load({
|
|
|
857
895
|
context: function () { return fetch(q('context')).then(function (r) { return r.json() }).catch(function () { return null }) },
|
|
858
896
|
sessions: function () { return fetch(q('sessions')).then(function (r) { return r.json() }).catch(function () { return null }) },
|
|
859
897
|
history: function (n) { return fetch(q('history') + '&limit=' + (n || 20)).then(function (r) { return r.json() }).catch(function () { return null }) },
|
|
898
|
+
/**
|
|
899
|
+
* 当前会话 **AI 最后一个正文块**(2026-10-27)
|
|
900
|
+
* 不是 QQ 消息流水,是 agent 真正输出的那段文字。
|
|
901
|
+
* 实现:宿主 kernel.get('sessions').get(sid).deriveMessages() 从后往前找 assistant。
|
|
902
|
+
*/
|
|
903
|
+
lastAssistantText: function () {
|
|
904
|
+
return fetch(q('lastAssistantText')).then(function (r) { return r.json() }).catch(function () { return null })
|
|
905
|
+
},
|
|
860
906
|
members: function () { return fetch(q('members')).then(function (r) { return r.json() }).catch(function () { return null }) },
|
|
861
907
|
}
|
|
862
908
|
}
|
|
@@ -93,7 +93,8 @@ qqbotExt.dock.peerId / scope / ns
|
|
|
93
93
|
qqbotExt.dock.dataRoot / cwd
|
|
94
94
|
qqbotExt.dock.context() // 实例 + 会话信息
|
|
95
95
|
qqbotExt.dock.sessions() // 这个机器人管过哪些会话
|
|
96
|
-
qqbotExt.dock.history(n) // 当前会话最近 n
|
|
96
|
+
qqbotExt.dock.history(n) // 当前会话最近 n 条消息(QQ 消息流水)
|
|
97
|
+
qqbotExt.dock.lastAssistantText() // ★ AI 最后一个正文块(她真正输出的那段话)
|
|
97
98
|
qqbotExt.dock.members() // 群友台账
|
|
98
99
|
|
|
99
100
|
// 能力(与代码扩展对齐,走白名单 RPC)
|
|
@@ -109,8 +110,35 @@ qqbotExt.cap('text' | 'markdown', 文本) // 发消息
|
|
|
109
110
|
|
|
110
111
|
// 自己的后端(见 §7)
|
|
111
112
|
qqbotExt.api('/你的路由') // → /api/ext/dispatch?id=<你>&p=/你的路由
|
|
113
|
+
|
|
114
|
+
// 主题
|
|
115
|
+
qqbotExt.theme // 'light' | 'dark'
|
|
116
|
+
qqbotExt.isDark()
|
|
117
|
+
qqbotExt.onThemeChange(cb) // 返回取消函数
|
|
118
|
+
```
|
|
119
|
+
|
|
120
|
+
**让卡片跟着宿主主题变**:插件在 `body` 上定义了一套 `--qqs-*` token,亮/暗两套自动切换,
|
|
121
|
+
而 **CSS 自定义属性会穿透 Shadow DOM** —— 所以卡片里直接用变量就行,不用自己判断:
|
|
122
|
+
|
|
123
|
+
```css
|
|
124
|
+
.card {
|
|
125
|
+
background: var(--qqs-card, #fff);
|
|
126
|
+
color: var(--qqs-text, #1f2329);
|
|
127
|
+
border: 1px solid var(--qqs-border, #e6e8ec);
|
|
128
|
+
}
|
|
112
129
|
```
|
|
113
130
|
|
|
131
|
+
可用 token:
|
|
132
|
+
|
|
133
|
+
| 类别 | 变量 |
|
|
134
|
+
|---|---|
|
|
135
|
+
| 背景 | `--qqs-card` `--qqs-panel` `--qqs-card-solid` `--qqs-input` `--qqs-toolbar` |
|
|
136
|
+
| 文字 | `--qqs-text` `--qqs-text2` `--qqs-text3` |
|
|
137
|
+
| 边框 | `--qqs-border` `--qqs-border-light` |
|
|
138
|
+
| 主题色 | `--qqs-primary-bg` `--qqs-primary-border` `--qqs-primary-fg` |
|
|
139
|
+
|
|
140
|
+
**一定要带 fallback**(`var(--qqs-text, #1f2329)`)—— 老版本宿主的 token 可能不全。
|
|
141
|
+
|
|
114
142
|
**写卡片的几条硬边界**(都是设计如此,不是 bug):
|
|
115
143
|
|
|
116
144
|
1. **样式双向隔离**:Shadow DOM 让卡片样式不污染面板,但**面板的 CSS 变量/组件样式也进不来**,得自己写。
|
|
@@ -1,55 +1,58 @@
|
|
|
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
|
-
|
|
1
|
+
/* 颜色全部走 --qqs-* 变量(带 fallback)——
|
|
2
|
+
宿主的亮/暗 token 定义在 body 上,自定义属性会穿透 Shadow DOM,
|
|
3
|
+
所以卡片跟着宿主主题自动切换,不用自己判断。 */
|
|
4
|
+
/* 扩展示例 · 两处卡片共用的样式(Shadow DOM 内,不影响面板其他地方) */
|
|
5
|
+
:host { display: block; }
|
|
6
|
+
.card {
|
|
7
|
+
background: var(--qqs-card, #fff); border: 1px solid var(--qqs-border, #e6e8ec); border-radius: 12px;
|
|
8
|
+
padding: 10px 12px; font: 13px -apple-system, 'PingFang SC', 'Microsoft YaHei', sans-serif;
|
|
9
|
+
color: var(--qqs-text, #1f2329); box-shadow: 0 1px 2px rgba(20,30,60,.05);
|
|
10
|
+
}
|
|
11
|
+
.hd { display: flex; align-items: center; gap: 6px; font-weight: 700; margin-bottom: 8px; }
|
|
12
|
+
.dot { width: 7px; height: 7px; border-radius: 50%; background: #bbb; flex: none; }
|
|
13
|
+
.dot.on { background: #16a34a; }
|
|
14
|
+
.dot.err { background: #dc2626; }
|
|
15
|
+
.row { display: flex; align-items: center; gap: 6px; margin: 6px 0; font-size: 12px; }
|
|
16
|
+
.lbl { color: var(--qqs-text3, #6b7280); flex: none; }
|
|
17
|
+
.val { color: var(--qqs-text, #1f2329); }
|
|
18
|
+
.hint { color: var(--qqs-text3, #9aa0a8); font-size: 11px; line-height: 1.5; }
|
|
19
|
+
.txt {
|
|
20
|
+
flex: 1; min-width: 160px; font: inherit; padding: 6px 9px;
|
|
21
|
+
border: 1px solid var(--qqs-border, #d0d5dd); border-radius: 8px; outline: none; color: var(--qqs-text, #1f2329);
|
|
22
|
+
}
|
|
23
|
+
.txt:focus { border-color: #7c6cf0; }
|
|
24
|
+
.acts { display: flex; gap: 6px; margin-top: 10px; flex-wrap: wrap; }
|
|
25
|
+
.btn {
|
|
26
|
+
font: inherit; font-size: 12px; padding: 5px 12px; border-radius: 8px;
|
|
27
|
+
border: 1px solid var(--qqs-primary-border, #d9c6ff); background: var(--qqs-primary-bg, #f1ecff); color: var(--qqs-primary-fg, #4a3a9f); cursor: pointer;
|
|
28
|
+
}
|
|
29
|
+
.btn:hover { background: #e9e1ff; }
|
|
30
|
+
.btn:disabled { opacity: .5; cursor: default; }
|
|
31
|
+
.btn.ghost { background: var(--qqs-card, #fff); border-color: #d0d5dd; color: #4b5563; }
|
|
32
|
+
|
|
33
|
+
/* dock 卡片专用:顶部一排子标签 */
|
|
34
|
+
.subtabs { display: flex; gap: 4px; border-bottom: 1px solid #eee; margin-bottom: 10px; }
|
|
35
|
+
.subtabs button {
|
|
36
|
+
font: inherit; font-size: 13px; padding: 6px 14px; border: none; background: none;
|
|
37
|
+
cursor: pointer; color: #666; border-bottom: 2px solid transparent;
|
|
38
|
+
}
|
|
39
|
+
.subtabs button.on { color: var(--qqs-primary-fg, #4a3a9f); font-weight: 700; border-bottom-color: #7c6cf0; }
|
|
40
|
+
.pane { display: none; }
|
|
41
|
+
.pane.on { display: block; }
|
|
42
|
+
pre.info {
|
|
43
|
+
background: var(--qqs-card-solid, #f6f7f9); border: 1px solid var(--qqs-border, #e6e8ec); border-radius: 8px;
|
|
44
|
+
padding: 8px 10px; font-size: 11px; line-height: 1.6; max-height: 180px; overflow: auto;
|
|
45
|
+
white-space: pre-wrap; word-break: break-all; margin: 4px 0 0;
|
|
46
|
+
}
|
|
44
47
|
|
|
45
48
|
/* 说明块(2026-10-27):告诉用户这两段文字会出现在 /扩展示例 里 */
|
|
46
49
|
.tip {
|
|
47
|
-
background: #f4f7ff; border: 1px solid #dbe6ff; border-radius: 9px;
|
|
48
|
-
padding: 8px 10px; font-size: 11.5px; line-height: 1.65; color: #3a4a6b; margin: 2px 0 10px;
|
|
50
|
+
background: var(--qqs-primary-bg, #f4f7ff); border: 1px solid var(--qqs-primary-border, #dbe6ff); border-radius: 9px;
|
|
51
|
+
padding: 8px 10px; font-size: 11.5px; line-height: 1.65; color: var(--qqs-text2, #3a4a6b); margin: 2px 0 10px;
|
|
49
52
|
}
|
|
50
|
-
.tip b { color: #2b4a8f; }
|
|
53
|
+
.tip b { color: var(--qqs-primary-fg, #2b4a8f); }
|
|
51
54
|
.code {
|
|
52
55
|
font-family: ui-monospace, Consolas, monospace; font-size: 11px;
|
|
53
|
-
background: #fff; border: 1px dashed #c9d8f5; border-radius: 6px;
|
|
54
|
-
padding: 3px 7px; margin: 5px 0; color: #1f3b73;
|
|
56
|
+
background: var(--qqs-card, #fff); border: 1px dashed var(--qqs-primary-border, #c9d8f5); border-radius: 6px;
|
|
57
|
+
padding: 3px 7px; margin: 5px 0; color: var(--qqs-primary-fg, #1f3b73);
|
|
55
58
|
}
|
|
@@ -37,10 +37,11 @@
|
|
|
37
37
|
|
|
38
38
|
<!-- 子标签 2:展示 dock 能拿到的会话数据(这是 dock 独有能力的演示) -->
|
|
39
39
|
<div class="pane" data-pane="ctx">
|
|
40
|
-
<div class="row"><span class="hint">下面这些是 dock 卡片独有的能力 ——
|
|
40
|
+
<div class="row"><span class="hint">下面这些是 dock 卡片独有的能力 —— 能直接读到当前实例和会话的真实数据。<br />
|
|
41
|
+
「读 AI 最后正文」取的是她 <b>最后一次输出的那段话</b>(不是 QQ 消息流水)。</span></div>
|
|
41
42
|
<div class="acts">
|
|
42
43
|
<button class="btn" id="ctxBtn">读上下文</button>
|
|
43
|
-
<button class="btn ghost" id="hisBtn"
|
|
44
|
+
<button class="btn ghost" id="hisBtn">读 AI 最后正文</button>
|
|
44
45
|
</div>
|
|
45
46
|
<pre class="info" id="info">点上面按钮看看</pre>
|
|
46
47
|
</div>
|
|
@@ -64,12 +64,13 @@
|
|
|
64
64
|
hisBtn.addEventListener('click', function () {
|
|
65
65
|
if (!qqbotExt.dock) return setInfo('这个卡片没有 dock 上下文')
|
|
66
66
|
setInfo('读取中…')
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
setInfo(
|
|
67
|
+
// ★ 2026-10-27(主人要求):读 **AI 最后一个正文块**,不是 QQ 消息流水。
|
|
68
|
+
// 实现抄 session_watch_lib.mjs 的 lastAssistantText():
|
|
69
|
+
// 宿主 kernel.get('sessions').get(sid).deriveMessages() 从后往前找 assistant。
|
|
70
|
+
qqbotExt.dock.lastAssistantText().then(function (r) {
|
|
71
|
+
if (!r) return setInfo('没有响应')
|
|
72
|
+
if (!r.ok) return setInfo('读不到:' + (r.reason || r.via || '未知') + (r.sid ? ('\nsid=' + r.sid) : ''))
|
|
73
|
+
setInfo('(AI 最后一段正文,' + r.text.length + ' 字符)\n\n' + r.text)
|
|
73
74
|
})
|
|
74
75
|
})
|
|
75
76
|
}
|
|
@@ -1,61 +1,64 @@
|
|
|
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
|
-
|
|
1
|
+
/* 颜色全部走 --qqs-* 变量(带 fallback)——
|
|
2
|
+
宿主的亮/暗 token 定义在 body 上,自定义属性会穿透 Shadow DOM,
|
|
3
|
+
所以卡片跟着宿主主题自动切换,不用自己判断。 */
|
|
4
|
+
/* 扩展示例 · 两处卡片共用的样式(Shadow DOM 内,不影响面板其他地方) */
|
|
5
|
+
:host { display: block; }
|
|
6
|
+
.card {
|
|
7
|
+
background: var(--qqs-card, #fff); border: 1px solid var(--qqs-border, #e6e8ec); border-radius: 12px;
|
|
8
|
+
padding: 10px 12px; font: 13px -apple-system, 'PingFang SC', 'Microsoft YaHei', sans-serif;
|
|
9
|
+
color: var(--qqs-text, #1f2329); box-shadow: 0 1px 2px rgba(20,30,60,.05);
|
|
10
|
+
}
|
|
11
|
+
.hd { display: flex; align-items: center; gap: 6px; font-weight: 700; margin-bottom: 8px; }
|
|
12
|
+
.dot { width: 7px; height: 7px; border-radius: 50%; background: #bbb; flex: none; }
|
|
13
|
+
.dot.on { background: #16a34a; }
|
|
14
|
+
.dot.err { background: #dc2626; }
|
|
15
|
+
.row { display: flex; align-items: center; gap: 6px; margin: 6px 0; font-size: 12px; }
|
|
16
|
+
.lbl { color: var(--qqs-text3, #6b7280); flex: none; }
|
|
17
|
+
.val { color: var(--qqs-text, #1f2329); }
|
|
18
|
+
.hint { color: var(--qqs-text3, #9aa0a8); font-size: 11px; line-height: 1.5; }
|
|
19
|
+
.txt {
|
|
20
|
+
flex: 1; min-width: 160px; font: inherit; padding: 6px 9px;
|
|
21
|
+
border: 1px solid var(--qqs-border, #d0d5dd); border-radius: 8px; outline: none; color: var(--qqs-text, #1f2329);
|
|
22
|
+
}
|
|
23
|
+
.txt:focus { border-color: #7c6cf0; }
|
|
24
|
+
.acts { display: flex; gap: 6px; margin-top: 10px; flex-wrap: wrap; }
|
|
25
|
+
.btn {
|
|
26
|
+
font: inherit; font-size: 12px; padding: 5px 12px; border-radius: 8px;
|
|
27
|
+
border: 1px solid var(--qqs-primary-border, #d9c6ff); background: var(--qqs-primary-bg, #f1ecff); color: var(--qqs-primary-fg, #4a3a9f); cursor: pointer;
|
|
28
|
+
}
|
|
29
|
+
.btn:hover { background: #e9e1ff; }
|
|
30
|
+
.btn:disabled { opacity: .5; cursor: default; }
|
|
31
|
+
.btn.ghost { background: var(--qqs-card, #fff); border-color: #d0d5dd; color: #4b5563; }
|
|
32
|
+
|
|
33
|
+
/* dock 卡片专用:顶部一排子标签 */
|
|
34
|
+
.subtabs { display: flex; gap: 4px; border-bottom: 1px solid #eee; margin-bottom: 10px; }
|
|
35
|
+
.subtabs button {
|
|
36
|
+
font: inherit; font-size: 13px; padding: 6px 14px; border: none; background: none;
|
|
37
|
+
cursor: pointer; color: #666; border-bottom: 2px solid transparent;
|
|
38
|
+
}
|
|
39
|
+
.subtabs button.on { color: var(--qqs-primary-fg, #4a3a9f); font-weight: 700; border-bottom-color: #7c6cf0; }
|
|
40
|
+
.pane { display: none; }
|
|
41
|
+
.pane.on { display: block; }
|
|
42
|
+
pre.info {
|
|
43
|
+
background: var(--qqs-card-solid, #f6f7f9); border: 1px solid var(--qqs-border, #e6e8ec); border-radius: 8px;
|
|
44
|
+
padding: 8px 10px; font-size: 11px; line-height: 1.6; max-height: 180px; overflow: auto;
|
|
45
|
+
white-space: pre-wrap; word-break: break-all; margin: 4px 0 0;
|
|
46
|
+
}
|
|
44
47
|
|
|
45
48
|
/* 说明块(2026-10-27):告诉用户这两段文字会出现在 /扩展示例 里 */
|
|
46
49
|
.tip {
|
|
47
|
-
background: #f4f7ff; border: 1px solid #dbe6ff; border-radius: 9px;
|
|
48
|
-
padding: 8px 10px; font-size: 11.5px; line-height: 1.65; color: #3a4a6b; margin: 2px 0 10px;
|
|
50
|
+
background: var(--qqs-primary-bg, #f4f7ff); border: 1px solid var(--qqs-primary-border, #dbe6ff); border-radius: 9px;
|
|
51
|
+
padding: 8px 10px; font-size: 11.5px; line-height: 1.65; color: var(--qqs-text2, #3a4a6b); margin: 2px 0 10px;
|
|
49
52
|
}
|
|
50
|
-
.tip b { color: #2b4a8f; }
|
|
53
|
+
.tip b { color: var(--qqs-primary-fg, #2b4a8f); }
|
|
51
54
|
.code {
|
|
52
55
|
font-family: ui-monospace, Consolas, monospace; font-size: 11px;
|
|
53
|
-
background: #fff; border: 1px dashed #c9d8f5; border-radius: 6px;
|
|
54
|
-
padding: 3px 7px; margin: 5px 0; color: #1f3b73;
|
|
56
|
+
background: var(--qqs-card, #fff); border: 1px dashed var(--qqs-primary-border, #c9d8f5); border-radius: 6px;
|
|
57
|
+
padding: 3px 7px; margin: 5px 0; color: var(--qqs-primary-fg, #1f3b73);
|
|
55
58
|
}
|
|
56
59
|
|
|
57
60
|
/* 作用域切换(2026-10-27):全局 / 当前账号 */
|
|
58
61
|
.seg { gap: 14px; flex-wrap: wrap; }
|
|
59
|
-
.seg label { display: inline-flex; align-items: center; gap: 5px; cursor: pointer; font-size: 12px; color: #374151; }
|
|
62
|
+
.seg label { display: inline-flex; align-items: center; gap: 5px; cursor: pointer; font-size: 12px; color: var(--qqs-text2, #374151); }
|
|
60
63
|
.seg input[type="radio"] { margin: 0; cursor: pointer; }
|
|
61
|
-
.seg b { color: #2b4a8f; font-weight: 600; }
|
|
64
|
+
.seg b { color: var(--qqs-primary-fg, #2b4a8f); font-weight: 600; }
|
package/package.json
CHANGED
package/settings-host.js
CHANGED
|
@@ -5481,6 +5481,43 @@ const WHY_MAP = { busy: '目标会话回合活跃,已等待至回合结束仍超
|
|
|
5481
5481
|
return writeJson(res, 200, { ok: true, messages: rows });
|
|
5482
5482
|
}
|
|
5483
5483
|
|
|
5484
|
+
// ★ 2026-10-27(主人要求):取当前会话「AI 最后一个正文块」。
|
|
5485
|
+
// 原来卡片读的是 .qqbot/msg-history(QQ 消息流水),跟 AI 的正文不是一回事。
|
|
5486
|
+
// 实现抄 examples/ext-tools/lib/session_watch_lib.mjs 的 lastAssistantText():
|
|
5487
|
+
// 宿主 kernel.get('sessions').get(sid).deriveMessages() 从后往前找 assistant 角色。
|
|
5488
|
+
// (那条路还支持 turn/end 事件累积,但这里只要"最后一次的正文",直接取更省事。)
|
|
5489
|
+
if (what === 'lastAssistantText') {
|
|
5490
|
+
const sid = String(q?.get('sid') || '').trim();
|
|
5491
|
+
if (!sid) return writeJson(res, 200, { ok: false, reason: '缺 sid(当前会话 id)', text: '' });
|
|
5492
|
+
let text = '';
|
|
5493
|
+
let via = '';
|
|
5494
|
+
try {
|
|
5495
|
+
const sessions = ctx.get?.('sessions');
|
|
5496
|
+
const sess = sessions && typeof sessions.get === 'function' ? sessions.get(sid) : null;
|
|
5497
|
+
if (sess && typeof sess.deriveMessages === 'function') {
|
|
5498
|
+
const msgs = sess.deriveMessages() || [];
|
|
5499
|
+
for (let i = msgs.length - 1; i >= 0; i--) {
|
|
5500
|
+
const m = msgs[i];
|
|
5501
|
+
const role = String(m?.role ?? m?.type ?? '');
|
|
5502
|
+
if (!role.includes('assistant')) continue;
|
|
5503
|
+
// content 可能是字符串、数组(每项带 text)、或对象
|
|
5504
|
+
const c = m?.content;
|
|
5505
|
+
let t = '';
|
|
5506
|
+
if (typeof c === 'string') t = c;
|
|
5507
|
+
else if (Array.isArray(c)) t = c.map((x) => (typeof x === 'string' ? x : String(x?.text ?? ''))).join('');
|
|
5508
|
+
else if (c && typeof c === 'object') t = String(c.text ?? '');
|
|
5509
|
+
t = t.trim();
|
|
5510
|
+
if (t) { text = t; via = 'deriveMessages'; break; }
|
|
5511
|
+
}
|
|
5512
|
+
} else {
|
|
5513
|
+
via = 'sessions.get 拿不到会话';
|
|
5514
|
+
}
|
|
5515
|
+
} catch (e) {
|
|
5516
|
+
via = '异常: ' + (e instanceof Error ? e.message : String(e));
|
|
5517
|
+
}
|
|
5518
|
+
return writeJson(res, 200, { ok: !!text, text, via, sid });
|
|
5519
|
+
}
|
|
5520
|
+
|
|
5484
5521
|
if (what === 'members') {
|
|
5485
5522
|
// 群友台账(people-memo 落的那份)
|
|
5486
5523
|
let out = {};
|
|
@@ -5703,7 +5740,11 @@ const WHY_MAP = { busy: '目标会话回合活跃,已等待至回合结束仍超
|
|
|
5703
5740
|
dataRoot: dr,
|
|
5704
5741
|
logger: console,
|
|
5705
5742
|
selfName: id,
|
|
5706
|
-
kind
|
|
5743
|
+
// ★ 2026-10-27:kind 不能传 'ui' —— store 会落到 .qqbot-extensions/ui/data/,
|
|
5744
|
+
// 而 listExtUi 正是扫 ui/ 下每个子目录找 card.html,会把 data/ 当成一个扩展目录。
|
|
5745
|
+
// 用点开头:路径变成 .qqbot-extensions/.ui-data/data/<extId>.json,
|
|
5746
|
+
// 既不在 ui/ 下,也不会被 EXT_ID_RE(不允许 '.')当成扩展 id。
|
|
5747
|
+
kind: '.ui-data',
|
|
5707
5748
|
appId: String(viewOf(ctx.settings, capNs)?.value?.appId || ''),
|
|
5708
5749
|
owners: (() => {
|
|
5709
5750
|
try {
|