visualknowledge 0.2.2 → 0.2.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.
|
@@ -6,72 +6,18 @@
|
|
|
6
6
|
*/
|
|
7
7
|
|
|
8
8
|
import { html } from 'htm/react';
|
|
9
|
-
import {
|
|
9
|
+
import { useEffect, useRef } from 'react';
|
|
10
10
|
import { WidgetContainer } from './WidgetContainer.jsx';
|
|
11
11
|
|
|
12
|
-
const IFRAME_TIMEOUT_MS = 10000;
|
|
13
|
-
|
|
14
12
|
export function HtmlWidget({ html: htmlContent, onFullscreen }) {
|
|
15
13
|
const iframeRef = useRef(null);
|
|
16
|
-
const [status, setStatus] = useState('loading');
|
|
17
|
-
const timerRef = useRef(null);
|
|
18
|
-
const stableContentRef = useRef('');
|
|
19
14
|
|
|
20
|
-
//
|
|
15
|
+
// 流式写入:内容到达即写入 iframe
|
|
21
16
|
useEffect(() => {
|
|
22
17
|
if (!htmlContent) return;
|
|
23
|
-
|
|
24
|
-
// 内容变化时重置状态
|
|
25
|
-
if (htmlContent !== stableContentRef.current) {
|
|
26
|
-
setStatus('loading');
|
|
27
|
-
if (timerRef.current) clearTimeout(timerRef.current);
|
|
28
|
-
}
|
|
29
|
-
|
|
30
|
-
stableContentRef.current = htmlContent;
|
|
31
|
-
}, [htmlContent]);
|
|
32
|
-
|
|
33
|
-
// iframe 初始化 + 写入内容
|
|
34
|
-
useEffect(() => {
|
|
35
18
|
const iframe = iframeRef.current;
|
|
36
|
-
if (!iframe
|
|
37
|
-
|
|
38
|
-
let settled = false;
|
|
39
|
-
|
|
40
|
-
const write = () => {
|
|
41
|
-
_writeHtml(iframe, htmlContent);
|
|
42
|
-
setStatus('done');
|
|
43
|
-
settled = true;
|
|
44
|
-
if (timerRef.current) clearTimeout(timerRef.current);
|
|
45
|
-
};
|
|
46
|
-
|
|
47
|
-
// 首次加载或 src 变化时等待 load 事件
|
|
48
|
-
const handler = () => {
|
|
49
|
-
write();
|
|
50
|
-
};
|
|
51
|
-
|
|
52
|
-
// 超时保护:即使 load 事件没触发也强制写入
|
|
53
|
-
timerRef.current = setTimeout(() => {
|
|
54
|
-
if (!settled) {
|
|
55
|
-
console.warn('[HtmlWidget] iframe load timeout, forcing write');
|
|
56
|
-
write();
|
|
57
|
-
}
|
|
58
|
-
}, IFRAME_TIMEOUT_MS);
|
|
59
|
-
|
|
60
|
-
iframe.addEventListener('load', handler);
|
|
61
|
-
|
|
62
|
-
// 如果 iframe 已经加载完成,直接写入
|
|
63
|
-
try {
|
|
64
|
-
if (iframe.contentDocument && iframe.contentDocument.readyState === 'complete') {
|
|
65
|
-
write();
|
|
66
|
-
}
|
|
67
|
-
} catch (_) {
|
|
68
|
-
// cross-origin or not ready yet, wait for load event
|
|
69
|
-
}
|
|
70
|
-
|
|
71
|
-
return () => {
|
|
72
|
-
iframe.removeEventListener('load', handler);
|
|
73
|
-
if (timerRef.current) clearTimeout(timerRef.current);
|
|
74
|
-
};
|
|
19
|
+
if (!iframe) return;
|
|
20
|
+
_writeHtml(iframe, htmlContent);
|
|
75
21
|
}, [htmlContent]);
|
|
76
22
|
|
|
77
23
|
const handleFullscreen = () => {
|
|
@@ -80,11 +26,13 @@ export function HtmlWidget({ html: htmlContent, onFullscreen }) {
|
|
|
80
26
|
}
|
|
81
27
|
};
|
|
82
28
|
|
|
29
|
+
const hasContent = htmlContent && htmlContent.trim().length > 0;
|
|
30
|
+
|
|
83
31
|
return html`
|
|
84
32
|
<${WidgetContainer}
|
|
85
33
|
badge="visualize"
|
|
86
|
-
typeLabel=${
|
|
87
|
-
status=${
|
|
34
|
+
typeLabel=${hasContent ? '交互式可视化' : '正在生成可视化...'}
|
|
35
|
+
status=${hasContent ? 'done' : 'loading'}
|
|
88
36
|
onZoom=${handleFullscreen}
|
|
89
37
|
>
|
|
90
38
|
<iframe
|
|
@@ -126,6 +126,14 @@ export class StreamProcessor {
|
|
|
126
126
|
// 返回 segments 的快照,包含当前活跃段
|
|
127
127
|
const result = [...this._segments];
|
|
128
128
|
|
|
129
|
+
// 更新活跃的 codeblock segment,将流式缓冲区内容暴露给组件
|
|
130
|
+
if (this._phase === 'codeblock') {
|
|
131
|
+
const lastSeg = result[result.length - 1];
|
|
132
|
+
if (lastSeg && (lastSeg.type === 'mermaid' || lastSeg.type === 'html' || lastSeg.type === 'svg')) {
|
|
133
|
+
lastSeg.content = this._buffer;
|
|
134
|
+
}
|
|
135
|
+
}
|
|
136
|
+
|
|
129
137
|
// 如果有活跃的文字段且不在 segments 中,追加它
|
|
130
138
|
if (this._phase === 'text' && this._activeText) {
|
|
131
139
|
// 检查是否已有未完成的 text segment(最后一个 segment 可能是待更新的 text)
|