@jetecho/dsh-csv-and-image-preview 0.1.1 → 0.1.2
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/Agent.md +41 -41
- package/LICENSE +21 -21
- package/README.en.md +79 -79
- package/README.md +79 -79
- package/cordis.patch.yml +6 -6
- package/lib/client.js +190 -190
- package/lib/index.js +434 -434
- package/lib/types/index.d.ts +54 -54
- package/package.json +67 -67
package/lib/client.js
CHANGED
|
@@ -1,190 +1,190 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* dsh-csv-and-image-preview 浏览器端 bundle(单文件,经 __ModuleLoader__ 加载)。
|
|
3
|
-
*
|
|
4
|
-
* 为两个工具注册键控 toolview(`tool.call.toolview`, key = 工具名):
|
|
5
|
-
* - `preview_image`:从结果节点 meta 读取 { src, mime, label },渲染浏览器
|
|
6
|
-
* 原生 <img>;
|
|
7
|
-
* - `preview_csv`:从结果节点 meta 读取 { label, delimiter, rows, ... },
|
|
8
|
-
* 渲染原生 <table>(粘性表头、控件限高 320px 局部滚动、表头点击排序)。
|
|
9
|
-
*
|
|
10
|
-
* 这样图片/表格都不经过聊天渲染器的 markdown/genui 白名单,直接以真实 DOM
|
|
11
|
-
* 显示。样式使用 --dsw-* 主题变量,跟随全局亮/暗主题。
|
|
12
|
-
* @module dsh-csv-and-image-preview/client
|
|
13
|
-
*/
|
|
14
|
-
|
|
15
|
-
window.__ModuleLoader__.load({
|
|
16
|
-
id: 'dsh-csv-and-image-preview',
|
|
17
|
-
factory: (require) => {
|
|
18
|
-
var module = { exports: {} }
|
|
19
|
-
var exports = module.exports
|
|
20
|
-
Object.defineProperty(exports, Symbol.toStringTag, { value: 'Module' })
|
|
21
|
-
|
|
22
|
-
const React = require('react')
|
|
23
|
-
|
|
24
|
-
// ── 样式 ────────────────────────────────────────────────────────────────
|
|
25
|
-
const css = [
|
|
26
|
-
'/* dsh-csv-and-image-preview */',
|
|
27
|
-
'.dip-root{display:flex;flex-direction:column;gap:6px;align-items:flex-start;max-width:100%}',
|
|
28
|
-
'.dip-label{font-size:12px;color:var(--dsw-alias-label-secondary);line-height:20px}',
|
|
29
|
-
'.dip-figure{margin:0;padding:8px;border:1px solid var(--dsw-alias-border-l1);border-radius:12px;background:var(--dsw-alias-bg-layer-1);max-width:100%}',
|
|
30
|
-
'.dip-img{display:block;max-width:100%;height:auto;border-radius:8px}',
|
|
31
|
-
'.dip-img.svg{background:#fff}',
|
|
32
|
-
'.dip-missing{font-size:12px;color:var(--dsw-alias-label-tertiary)}',
|
|
33
|
-
'/* preview_csv 表格:控件限高,内部滚动,不撑爆聊天卡片 */',
|
|
34
|
-
'.dip-table-wrap{max-height:320px;max-width:100%;overflow:auto;border:1px solid var(--dsw-alias-border-l1);border-radius:8px}',
|
|
35
|
-
'.dip-table{border-collapse:separate;border-spacing:0;font-size:12px;line-height:20px;white-space:nowrap}',
|
|
36
|
-
'.dip-table th{position:sticky;top:0;z-index:1;background:var(--dsw-alias-bg-layer-1);color:var(--dsw-alias-label-secondary);font-weight:600;text-align:left;cursor:pointer;user-select:none;-webkit-user-select:none}',
|
|
37
|
-
'.dip-table th,.dip-table td{padding:4px 10px;border-bottom:1px solid var(--dsw-alias-border-l1);border-right:1px solid var(--dsw-alias-border-l1);overflow:hidden;text-overflow:ellipsis}',
|
|
38
|
-
'.dip-table th:last-child,.dip-table td:last-child{border-right:none}',
|
|
39
|
-
'.dip-table tbody tr:nth-child(even) td{background:rgba(127,127,127,0.08)}',
|
|
40
|
-
'.dip-table td.num{text-align:right;font-variant-numeric:tabular-nums}',
|
|
41
|
-
'.dip-csv-note{font-size:12px;color:var(--dsw-alias-label-tertiary);line-height:18px}',
|
|
42
|
-
].join('\n')
|
|
43
|
-
if (typeof document !== 'undefined') {
|
|
44
|
-
const key = 'dip-' + 'dsh-csv-and-image-preview'
|
|
45
|
-
if (document.querySelector('style[data-plugin-css="' + key + '"]') === null) {
|
|
46
|
-
const el = document.createElement('style')
|
|
47
|
-
el.dataset.plugin = 'dsh-csv-and-image-preview'
|
|
48
|
-
el.dataset.pluginCss = key
|
|
49
|
-
el.textContent = css
|
|
50
|
-
document.head.appendChild(el)
|
|
51
|
-
}
|
|
52
|
-
}
|
|
53
|
-
|
|
54
|
-
/** 从 props.block 取 meta;工具调用 running 或回退时 meta 不存在。 */
|
|
55
|
-
function metaOf(props) {
|
|
56
|
-
const block = props && props.block
|
|
57
|
-
return block !== null && typeof block === 'object' && 'meta' in block ? block.meta : undefined
|
|
58
|
-
}
|
|
59
|
-
|
|
60
|
-
/**
|
|
61
|
-
* 键控 toolview(preview_image):从 meta 读取 { src, mime, label } 渲染
|
|
62
|
-
* 原生 <img>。
|
|
63
|
-
*/
|
|
64
|
-
function PreviewToolView(props) {
|
|
65
|
-
const meta = metaOf(props)
|
|
66
|
-
const label = typeof meta === 'object' && meta !== null && typeof meta.label === 'string' ? meta.label : '预览'
|
|
67
|
-
const src = typeof meta === 'object' && meta !== null && typeof meta.src === 'string' ? meta.src : null
|
|
68
|
-
const isSvg = typeof meta === 'object' && meta !== null && meta.mime === 'image/svg+xml'
|
|
69
|
-
if (src === null) {
|
|
70
|
-
return React.createElement('div', { className: 'dip-root', 'data-dip-preview': true },
|
|
71
|
-
React.createElement('span', { className: 'dip-missing' }, '图片预览缺少数据(meta 缺失)'))
|
|
72
|
-
}
|
|
73
|
-
return React.createElement('div', { className: 'dip-root', 'data-dip-preview': true },
|
|
74
|
-
React.createElement('span', { className: 'dip-label' }, label),
|
|
75
|
-
React.createElement('figure', { className: 'dip-figure' },
|
|
76
|
-
React.createElement('img', {
|
|
77
|
-
className: 'dip-img' + (isSvg ? ' svg' : ''),
|
|
78
|
-
src: src,
|
|
79
|
-
alt: label,
|
|
80
|
-
})))
|
|
81
|
-
}
|
|
82
|
-
|
|
83
|
-
const NUMERIC_RE = /^-?\d+(\.\d+)?$/
|
|
84
|
-
|
|
85
|
-
/** 截断/统计脚注:「显示前 50 / 1234 行 · 前 5 / 8 列 · 分隔符 ","」。 */
|
|
86
|
-
function csvNote(meta) {
|
|
87
|
-
const parts = []
|
|
88
|
-
parts.push(meta.rowsShown < meta.totalRows
|
|
89
|
-
? `显示前 ${meta.rowsShown} / ${meta.totalRows} 行`
|
|
90
|
-
: `共 ${meta.totalRows} 行`)
|
|
91
|
-
if (meta.colsShown < meta.totalCols) parts.push(`前 ${meta.colsShown} / ${meta.totalCols} 列`)
|
|
92
|
-
parts.push(`分隔符 "${meta.delimiter === '\t' ? 'Tab' : meta.delimiter}"`)
|
|
93
|
-
return parts.join(' · ')
|
|
94
|
-
}
|
|
95
|
-
|
|
96
|
-
/**
|
|
97
|
-
* 对数据行(不含表头)按列排序:整列(除空串)可解析为数字时按数值比较,
|
|
98
|
-
* 否则按中文 locale 比较;空值恒沉底。dir=1 升序,-1 降序。
|
|
99
|
-
*/
|
|
100
|
-
function sortBodyRows(meta, col, dir) {
|
|
101
|
-
const body = meta.rows.slice(1)
|
|
102
|
-
const numeric = body.every((r) => {
|
|
103
|
-
const v = col < r.length ? r[col] : ''
|
|
104
|
-
return v === '' || NUMERIC_RE.test(v)
|
|
105
|
-
}) && body.some((r) => col < r.length && NUMERIC_RE.test(r[col]))
|
|
106
|
-
const cmp = numeric
|
|
107
|
-
? (a, b) => (parseFloat(a) || 0) - (parseFloat(b) || 0)
|
|
108
|
-
: (a, b) => String(a).localeCompare(String(b), 'zh-Hans-CN')
|
|
109
|
-
return body
|
|
110
|
-
.map((_, i) => i)
|
|
111
|
-
.sort((x, y) => {
|
|
112
|
-
const a = x < body.length && col < body[x].length ? body[x][col] : ''
|
|
113
|
-
const b = y < body.length && col < body[y].length ? body[y][col] : ''
|
|
114
|
-
if ((a === '') !== (b === '')) return a === '' ? 1 : -1
|
|
115
|
-
return cmp(a, b) * dir
|
|
116
|
-
})
|
|
117
|
-
.map((i) => body[i])
|
|
118
|
-
}
|
|
119
|
-
|
|
120
|
-
/**
|
|
121
|
-
* 键控 toolview(preview_csv):从 meta 读取 { label, delimiter, rows,
|
|
122
|
-
* totalRows, totalCols, rowsShown, colsShown },渲染原生 <table>。
|
|
123
|
-
* rows[0] 为表头;数字单元格右对齐;表头点击循环排序(升→降→取消)。
|
|
124
|
-
*/
|
|
125
|
-
function CsvToolView(props) {
|
|
126
|
-
const meta = metaOf(props)
|
|
127
|
-
const ok = meta !== null && typeof meta === 'object' && Array.isArray(meta.rows) && meta.rows.length > 0
|
|
128
|
-
// hooks 必须无条件调用,放在提前 return 之前。
|
|
129
|
-
const sortPair = React.useState(null) // null | { col, dir }
|
|
130
|
-
const sort = sortPair[0]
|
|
131
|
-
const setSort = sortPair[1]
|
|
132
|
-
if (!ok) {
|
|
133
|
-
return React.createElement('div', { className: 'dip-root', 'data-dip-preview': true },
|
|
134
|
-
React.createElement('span', { className: 'dip-missing' }, 'CSV 预览缺少数据(meta 缺失)'))
|
|
135
|
-
}
|
|
136
|
-
const label = typeof meta.label === 'string' && meta.label !== '' ? meta.label : 'CSV 预览'
|
|
137
|
-
|
|
138
|
-
const headerClick = (ci) => {
|
|
139
|
-
setSort((prev) => (prev !== null && prev.col === ci ? (prev.dir === 1 ? { col: ci, dir: -1 } : null) : { col: ci, dir: 1 }))
|
|
140
|
-
}
|
|
141
|
-
|
|
142
|
-
const thead = React.createElement('thead', null,
|
|
143
|
-
React.createElement('tr', null, meta.rows[0].map((c, i) => {
|
|
144
|
-
const indicator = sort !== null && sort.col === i ? (sort.dir === 1 ? ' ▲' : ' ▼') : ''
|
|
145
|
-
return React.createElement('th', {
|
|
146
|
-
key: i,
|
|
147
|
-
onClick: () => headerClick(i),
|
|
148
|
-
title: String(c),
|
|
149
|
-
}, String(c) + indicator)
|
|
150
|
-
})))
|
|
151
|
-
const bodyRows = sort !== null ? sortBodyRows(meta, sort.col, sort.dir) : meta.rows.slice(1)
|
|
152
|
-
const tbody = React.createElement('tbody', null, bodyRows.map((r, ri) =>
|
|
153
|
-
React.createElement('tr', { key: ri }, r.map((c, ci) => {
|
|
154
|
-
const s = String(c)
|
|
155
|
-
return React.createElement('td', {
|
|
156
|
-
key: ci,
|
|
157
|
-
className: NUMERIC_RE.test(s) ? 'num' : undefined,
|
|
158
|
-
title: s,
|
|
159
|
-
}, s)
|
|
160
|
-
}))))
|
|
161
|
-
return React.createElement('div', { className: 'dip-root', 'data-dip-preview': true },
|
|
162
|
-
React.createElement('span', { className: 'dip-label' }, label),
|
|
163
|
-
React.createElement('div', { className: 'dip-table-wrap' },
|
|
164
|
-
React.createElement('table', { className: 'dip-table' }, thead, tbody)),
|
|
165
|
-
React.createElement('span', { className: 'dip-csv-note' }, csvNote(meta)))
|
|
166
|
-
}
|
|
167
|
-
|
|
168
|
-
// ── 服务接入 ─────────────────────────────────────────────────────────────
|
|
169
|
-
const inject = []
|
|
170
|
-
function apply(ctx) {
|
|
171
|
-
const slots = ctx.get('slots')
|
|
172
|
-
if (slots === undefined) return
|
|
173
|
-
const disposals = [
|
|
174
|
-
slots.inject('tool.call.toolview', () => slots.register(
|
|
175
|
-
{ name: 'tool.call.toolview', key: 'preview_image' },
|
|
176
|
-
PreviewToolView,
|
|
177
|
-
)),
|
|
178
|
-
slots.inject('tool.call.toolview', () => slots.register(
|
|
179
|
-
{ name: 'tool.call.toolview', key: 'preview_csv' },
|
|
180
|
-
CsvToolView,
|
|
181
|
-
)),
|
|
182
|
-
]
|
|
183
|
-
return () => { for (const dispose of disposals) dispose() }
|
|
184
|
-
}
|
|
185
|
-
|
|
186
|
-
exports.apply = apply
|
|
187
|
-
exports.inject = inject
|
|
188
|
-
return module.exports
|
|
189
|
-
},
|
|
190
|
-
})
|
|
1
|
+
/**
|
|
2
|
+
* dsh-csv-and-image-preview 浏览器端 bundle(单文件,经 __ModuleLoader__ 加载)。
|
|
3
|
+
*
|
|
4
|
+
* 为两个工具注册键控 toolview(`tool.call.toolview`, key = 工具名):
|
|
5
|
+
* - `preview_image`:从结果节点 meta 读取 { src, mime, label },渲染浏览器
|
|
6
|
+
* 原生 <img>;
|
|
7
|
+
* - `preview_csv`:从结果节点 meta 读取 { label, delimiter, rows, ... },
|
|
8
|
+
* 渲染原生 <table>(粘性表头、控件限高 320px 局部滚动、表头点击排序)。
|
|
9
|
+
*
|
|
10
|
+
* 这样图片/表格都不经过聊天渲染器的 markdown/genui 白名单,直接以真实 DOM
|
|
11
|
+
* 显示。样式使用 --dsw-* 主题变量,跟随全局亮/暗主题。
|
|
12
|
+
* @module dsh-csv-and-image-preview/client
|
|
13
|
+
*/
|
|
14
|
+
|
|
15
|
+
window.__ModuleLoader__.load({
|
|
16
|
+
id: '@jetecho/dsh-csv-and-image-preview',
|
|
17
|
+
factory: (require) => {
|
|
18
|
+
var module = { exports: {} }
|
|
19
|
+
var exports = module.exports
|
|
20
|
+
Object.defineProperty(exports, Symbol.toStringTag, { value: 'Module' })
|
|
21
|
+
|
|
22
|
+
const React = require('react')
|
|
23
|
+
|
|
24
|
+
// ── 样式 ────────────────────────────────────────────────────────────────
|
|
25
|
+
const css = [
|
|
26
|
+
'/* dsh-csv-and-image-preview */',
|
|
27
|
+
'.dip-root{display:flex;flex-direction:column;gap:6px;align-items:flex-start;max-width:100%}',
|
|
28
|
+
'.dip-label{font-size:12px;color:var(--dsw-alias-label-secondary);line-height:20px}',
|
|
29
|
+
'.dip-figure{margin:0;padding:8px;border:1px solid var(--dsw-alias-border-l1);border-radius:12px;background:var(--dsw-alias-bg-layer-1);max-width:100%}',
|
|
30
|
+
'.dip-img{display:block;max-width:100%;height:auto;border-radius:8px}',
|
|
31
|
+
'.dip-img.svg{background:#fff}',
|
|
32
|
+
'.dip-missing{font-size:12px;color:var(--dsw-alias-label-tertiary)}',
|
|
33
|
+
'/* preview_csv 表格:控件限高,内部滚动,不撑爆聊天卡片 */',
|
|
34
|
+
'.dip-table-wrap{max-height:320px;max-width:100%;overflow:auto;border:1px solid var(--dsw-alias-border-l1);border-radius:8px}',
|
|
35
|
+
'.dip-table{border-collapse:separate;border-spacing:0;font-size:12px;line-height:20px;white-space:nowrap}',
|
|
36
|
+
'.dip-table th{position:sticky;top:0;z-index:1;background:var(--dsw-alias-bg-layer-1);color:var(--dsw-alias-label-secondary);font-weight:600;text-align:left;cursor:pointer;user-select:none;-webkit-user-select:none}',
|
|
37
|
+
'.dip-table th,.dip-table td{padding:4px 10px;border-bottom:1px solid var(--dsw-alias-border-l1);border-right:1px solid var(--dsw-alias-border-l1);overflow:hidden;text-overflow:ellipsis}',
|
|
38
|
+
'.dip-table th:last-child,.dip-table td:last-child{border-right:none}',
|
|
39
|
+
'.dip-table tbody tr:nth-child(even) td{background:rgba(127,127,127,0.08)}',
|
|
40
|
+
'.dip-table td.num{text-align:right;font-variant-numeric:tabular-nums}',
|
|
41
|
+
'.dip-csv-note{font-size:12px;color:var(--dsw-alias-label-tertiary);line-height:18px}',
|
|
42
|
+
].join('\n')
|
|
43
|
+
if (typeof document !== 'undefined') {
|
|
44
|
+
const key = 'dip-' + 'dsh-csv-and-image-preview'
|
|
45
|
+
if (document.querySelector('style[data-plugin-css="' + key + '"]') === null) {
|
|
46
|
+
const el = document.createElement('style')
|
|
47
|
+
el.dataset.plugin = 'dsh-csv-and-image-preview'
|
|
48
|
+
el.dataset.pluginCss = key
|
|
49
|
+
el.textContent = css
|
|
50
|
+
document.head.appendChild(el)
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
/** 从 props.block 取 meta;工具调用 running 或回退时 meta 不存在。 */
|
|
55
|
+
function metaOf(props) {
|
|
56
|
+
const block = props && props.block
|
|
57
|
+
return block !== null && typeof block === 'object' && 'meta' in block ? block.meta : undefined
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
/**
|
|
61
|
+
* 键控 toolview(preview_image):从 meta 读取 { src, mime, label } 渲染
|
|
62
|
+
* 原生 <img>。
|
|
63
|
+
*/
|
|
64
|
+
function PreviewToolView(props) {
|
|
65
|
+
const meta = metaOf(props)
|
|
66
|
+
const label = typeof meta === 'object' && meta !== null && typeof meta.label === 'string' ? meta.label : '预览'
|
|
67
|
+
const src = typeof meta === 'object' && meta !== null && typeof meta.src === 'string' ? meta.src : null
|
|
68
|
+
const isSvg = typeof meta === 'object' && meta !== null && meta.mime === 'image/svg+xml'
|
|
69
|
+
if (src === null) {
|
|
70
|
+
return React.createElement('div', { className: 'dip-root', 'data-dip-preview': true },
|
|
71
|
+
React.createElement('span', { className: 'dip-missing' }, '图片预览缺少数据(meta 缺失)'))
|
|
72
|
+
}
|
|
73
|
+
return React.createElement('div', { className: 'dip-root', 'data-dip-preview': true },
|
|
74
|
+
React.createElement('span', { className: 'dip-label' }, label),
|
|
75
|
+
React.createElement('figure', { className: 'dip-figure' },
|
|
76
|
+
React.createElement('img', {
|
|
77
|
+
className: 'dip-img' + (isSvg ? ' svg' : ''),
|
|
78
|
+
src: src,
|
|
79
|
+
alt: label,
|
|
80
|
+
})))
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
const NUMERIC_RE = /^-?\d+(\.\d+)?$/
|
|
84
|
+
|
|
85
|
+
/** 截断/统计脚注:「显示前 50 / 1234 行 · 前 5 / 8 列 · 分隔符 ","」。 */
|
|
86
|
+
function csvNote(meta) {
|
|
87
|
+
const parts = []
|
|
88
|
+
parts.push(meta.rowsShown < meta.totalRows
|
|
89
|
+
? `显示前 ${meta.rowsShown} / ${meta.totalRows} 行`
|
|
90
|
+
: `共 ${meta.totalRows} 行`)
|
|
91
|
+
if (meta.colsShown < meta.totalCols) parts.push(`前 ${meta.colsShown} / ${meta.totalCols} 列`)
|
|
92
|
+
parts.push(`分隔符 "${meta.delimiter === '\t' ? 'Tab' : meta.delimiter}"`)
|
|
93
|
+
return parts.join(' · ')
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
/**
|
|
97
|
+
* 对数据行(不含表头)按列排序:整列(除空串)可解析为数字时按数值比较,
|
|
98
|
+
* 否则按中文 locale 比较;空值恒沉底。dir=1 升序,-1 降序。
|
|
99
|
+
*/
|
|
100
|
+
function sortBodyRows(meta, col, dir) {
|
|
101
|
+
const body = meta.rows.slice(1)
|
|
102
|
+
const numeric = body.every((r) => {
|
|
103
|
+
const v = col < r.length ? r[col] : ''
|
|
104
|
+
return v === '' || NUMERIC_RE.test(v)
|
|
105
|
+
}) && body.some((r) => col < r.length && NUMERIC_RE.test(r[col]))
|
|
106
|
+
const cmp = numeric
|
|
107
|
+
? (a, b) => (parseFloat(a) || 0) - (parseFloat(b) || 0)
|
|
108
|
+
: (a, b) => String(a).localeCompare(String(b), 'zh-Hans-CN')
|
|
109
|
+
return body
|
|
110
|
+
.map((_, i) => i)
|
|
111
|
+
.sort((x, y) => {
|
|
112
|
+
const a = x < body.length && col < body[x].length ? body[x][col] : ''
|
|
113
|
+
const b = y < body.length && col < body[y].length ? body[y][col] : ''
|
|
114
|
+
if ((a === '') !== (b === '')) return a === '' ? 1 : -1
|
|
115
|
+
return cmp(a, b) * dir
|
|
116
|
+
})
|
|
117
|
+
.map((i) => body[i])
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
/**
|
|
121
|
+
* 键控 toolview(preview_csv):从 meta 读取 { label, delimiter, rows,
|
|
122
|
+
* totalRows, totalCols, rowsShown, colsShown },渲染原生 <table>。
|
|
123
|
+
* rows[0] 为表头;数字单元格右对齐;表头点击循环排序(升→降→取消)。
|
|
124
|
+
*/
|
|
125
|
+
function CsvToolView(props) {
|
|
126
|
+
const meta = metaOf(props)
|
|
127
|
+
const ok = meta !== null && typeof meta === 'object' && Array.isArray(meta.rows) && meta.rows.length > 0
|
|
128
|
+
// hooks 必须无条件调用,放在提前 return 之前。
|
|
129
|
+
const sortPair = React.useState(null) // null | { col, dir }
|
|
130
|
+
const sort = sortPair[0]
|
|
131
|
+
const setSort = sortPair[1]
|
|
132
|
+
if (!ok) {
|
|
133
|
+
return React.createElement('div', { className: 'dip-root', 'data-dip-preview': true },
|
|
134
|
+
React.createElement('span', { className: 'dip-missing' }, 'CSV 预览缺少数据(meta 缺失)'))
|
|
135
|
+
}
|
|
136
|
+
const label = typeof meta.label === 'string' && meta.label !== '' ? meta.label : 'CSV 预览'
|
|
137
|
+
|
|
138
|
+
const headerClick = (ci) => {
|
|
139
|
+
setSort((prev) => (prev !== null && prev.col === ci ? (prev.dir === 1 ? { col: ci, dir: -1 } : null) : { col: ci, dir: 1 }))
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
const thead = React.createElement('thead', null,
|
|
143
|
+
React.createElement('tr', null, meta.rows[0].map((c, i) => {
|
|
144
|
+
const indicator = sort !== null && sort.col === i ? (sort.dir === 1 ? ' ▲' : ' ▼') : ''
|
|
145
|
+
return React.createElement('th', {
|
|
146
|
+
key: i,
|
|
147
|
+
onClick: () => headerClick(i),
|
|
148
|
+
title: String(c),
|
|
149
|
+
}, String(c) + indicator)
|
|
150
|
+
})))
|
|
151
|
+
const bodyRows = sort !== null ? sortBodyRows(meta, sort.col, sort.dir) : meta.rows.slice(1)
|
|
152
|
+
const tbody = React.createElement('tbody', null, bodyRows.map((r, ri) =>
|
|
153
|
+
React.createElement('tr', { key: ri }, r.map((c, ci) => {
|
|
154
|
+
const s = String(c)
|
|
155
|
+
return React.createElement('td', {
|
|
156
|
+
key: ci,
|
|
157
|
+
className: NUMERIC_RE.test(s) ? 'num' : undefined,
|
|
158
|
+
title: s,
|
|
159
|
+
}, s)
|
|
160
|
+
}))))
|
|
161
|
+
return React.createElement('div', { className: 'dip-root', 'data-dip-preview': true },
|
|
162
|
+
React.createElement('span', { className: 'dip-label' }, label),
|
|
163
|
+
React.createElement('div', { className: 'dip-table-wrap' },
|
|
164
|
+
React.createElement('table', { className: 'dip-table' }, thead, tbody)),
|
|
165
|
+
React.createElement('span', { className: 'dip-csv-note' }, csvNote(meta)))
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
// ── 服务接入 ─────────────────────────────────────────────────────────────
|
|
169
|
+
const inject = []
|
|
170
|
+
function apply(ctx) {
|
|
171
|
+
const slots = ctx.get('slots')
|
|
172
|
+
if (slots === undefined) return
|
|
173
|
+
const disposals = [
|
|
174
|
+
slots.inject('tool.call.toolview', () => slots.register(
|
|
175
|
+
{ name: 'tool.call.toolview', key: 'preview_image' },
|
|
176
|
+
PreviewToolView,
|
|
177
|
+
)),
|
|
178
|
+
slots.inject('tool.call.toolview', () => slots.register(
|
|
179
|
+
{ name: 'tool.call.toolview', key: 'preview_csv' },
|
|
180
|
+
CsvToolView,
|
|
181
|
+
)),
|
|
182
|
+
]
|
|
183
|
+
return () => { for (const dispose of disposals) dispose() }
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
exports.apply = apply
|
|
187
|
+
exports.inject = inject
|
|
188
|
+
return module.exports
|
|
189
|
+
},
|
|
190
|
+
})
|