ds-markdown 0.0.13 → 0.0.14-beta.1
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/README.en.md +12 -8
- package/README.ja.md +10 -8
- package/README.ko.md +10 -8
- package/README.md +13 -8
- package/dist/cjs/Markdown/index.d.ts +1 -1
- package/dist/cjs/Markdown/index.js +12 -4
- package/dist/cjs/Markdown/index.js.map +1 -1
- package/dist/cjs/MarkdownCMD/index.d.ts +2 -7
- package/dist/cjs/MarkdownCMD/index.js +64 -207
- package/dist/cjs/MarkdownCMD/index.js.map +1 -1
- package/dist/cjs/defined.d.ts +34 -12
- package/dist/cjs/hooks/useTypingTask.d.ts +8 -8
- package/dist/cjs/hooks/useTypingTask.js +42 -10
- package/dist/cjs/hooks/useTypingTask.js.map +1 -1
- package/dist/cjs/index.d.ts +4 -2
- package/dist/cjs/index.js.map +1 -1
- package/dist/cjs/utils/Tokenizer.js +1 -1
- package/dist/cjs/utils/Tokenizer.js.map +1 -1
- package/dist/cjs/utils/compiler.js +9 -0
- package/dist/cjs/utils/compiler.js.map +1 -1
- package/dist/esm/Markdown/index.d.ts +1 -1
- package/dist/esm/Markdown/index.js +13 -5
- package/dist/esm/Markdown/index.js.map +1 -1
- package/dist/esm/MarkdownCMD/index.d.ts +2 -7
- package/dist/esm/MarkdownCMD/index.js +65 -208
- package/dist/esm/MarkdownCMD/index.js.map +1 -1
- package/dist/esm/defined.d.ts +34 -12
- package/dist/esm/hooks/useTypingTask.d.ts +8 -8
- package/dist/esm/hooks/useTypingTask.js +42 -10
- package/dist/esm/hooks/useTypingTask.js.map +1 -1
- package/dist/esm/index.d.ts +4 -2
- package/dist/esm/index.js.map +1 -1
- package/dist/esm/utils/Tokenizer.js +1 -1
- package/dist/esm/utils/Tokenizer.js.map +1 -1
- package/dist/esm/utils/compiler.js +9 -0
- package/dist/esm/utils/compiler.js.map +1 -1
- package/package.json +2 -2
package/README.en.md
CHANGED
|
@@ -12,7 +12,9 @@ A React component designed specifically for modern AI applications, providing sm
|
|
|
12
12
|
[](https://react.dev)
|
|
13
13
|
[](https://www.typescriptlang.org/)
|
|
14
14
|
|
|
15
|
-
[📖 Live Demo](https://onshinpei.github.io/ds-markdown/)
|
|
15
|
+
[📖 Live Demo](https://onshinpei.github.io/ds-markdown/)
|
|
16
|
+
|
|
17
|
+
[DEMO:🔧 StackBlitz Experience](https://stackblitz.com/edit/vitejs-vite-ddfw8avb?file=src%2FApp.tsx)
|
|
16
18
|
|
|
17
19
|
---
|
|
18
20
|
|
|
@@ -178,9 +180,9 @@ Let's explore these new features together!`);
|
|
|
178
180
|
### Imperative API (Recommended for Streaming Scenarios)
|
|
179
181
|
|
|
180
182
|
```typescript
|
|
181
|
-
import { MarkdownCMD,
|
|
183
|
+
import { MarkdownCMD, MarkdownCMDRef } from 'ds-markdown';
|
|
182
184
|
|
|
183
|
-
interface
|
|
185
|
+
interface MarkdownCMDRef {
|
|
184
186
|
push: (content: string, answerType: AnswerType) => void;
|
|
185
187
|
clear: () => void;
|
|
186
188
|
triggerWholeEnd: () => void;
|
|
@@ -246,12 +248,14 @@ High-frequency recommends `requestAnimationFrame`, low-frequency recommends `set
|
|
|
246
248
|
|
|
247
249
|
### 📝 AI Streaming Conversation
|
|
248
250
|
|
|
251
|
+
[DEMO: 🔧 StackBlitz 体验](https://stackblitz.com/edit/vitejs-vite-2ri8kex3?file=src%2FApp.tsx)
|
|
252
|
+
|
|
249
253
|
````tsx
|
|
250
254
|
import { useRef } from 'react';
|
|
251
|
-
import { MarkdownCMD,
|
|
255
|
+
import { MarkdownCMD, MarkdownCMDRef } from 'ds-markdown';
|
|
252
256
|
|
|
253
257
|
function StreamingChat() {
|
|
254
|
-
const markdownRef = useRef<
|
|
258
|
+
const markdownRef = useRef<MarkdownCMDRef>(null);
|
|
255
259
|
|
|
256
260
|
// Simulate AI streaming response
|
|
257
261
|
const simulateAIResponse = async () => {
|
|
@@ -378,7 +382,7 @@ const handleStreamingMarkdown = () => {
|
|
|
378
382
|
|
|
379
383
|
```tsx
|
|
380
384
|
// ✅ Recommended: Imperative API
|
|
381
|
-
const ref = useRef<
|
|
385
|
+
const ref = useRef<MarkdownCMDRef>(null);
|
|
382
386
|
useEffect(() => {
|
|
383
387
|
ref.current?.push(newChunk, 'answer');
|
|
384
388
|
}, [newChunk]);
|
|
@@ -391,9 +395,9 @@ const [content, setContent] = useState('');
|
|
|
391
395
|
### 3. Type Safety
|
|
392
396
|
|
|
393
397
|
```tsx
|
|
394
|
-
import {
|
|
398
|
+
import { MarkdownCMDRef } from 'ds-markdown';
|
|
395
399
|
|
|
396
|
-
const ref = useRef<
|
|
400
|
+
const ref = useRef<MarkdownCMDRef>(null);
|
|
397
401
|
// Complete TypeScript type hints
|
|
398
402
|
```
|
|
399
403
|
|
package/README.ja.md
CHANGED
|
@@ -12,7 +12,9 @@
|
|
|
12
12
|
[](https://react.dev)
|
|
13
13
|
[](https://www.typescriptlang.org/)
|
|
14
14
|
|
|
15
|
-
[📖
|
|
15
|
+
[📖 オンラインデモ](https://onshinpei.github.io/ds-markdown/)
|
|
16
|
+
|
|
17
|
+
[DEMO:🔧 StackBlitz 体験](https://stackblitz.com/edit/vitejs-vite-ddfw8avb?file=src%2FApp.tsx)
|
|
16
18
|
|
|
17
19
|
---
|
|
18
20
|
|
|
@@ -178,9 +180,9 @@ React 19 は多くのエキサイティングな新機能をもたらします
|
|
|
178
180
|
### 命令的 API(ストリーミングシナリオにおすすめ)
|
|
179
181
|
|
|
180
182
|
```typescript
|
|
181
|
-
import { MarkdownCMD,
|
|
183
|
+
import { MarkdownCMD, MarkdownCMDRef } from 'ds-markdown';
|
|
182
184
|
|
|
183
|
-
interface
|
|
185
|
+
interface MarkdownCMDRef {
|
|
184
186
|
push: (content: string, answerType: AnswerType) => void;
|
|
185
187
|
clear: () => void;
|
|
186
188
|
triggerWholeEnd: () => void;
|
|
@@ -248,10 +250,10 @@ interface MarkdownRef {
|
|
|
248
250
|
|
|
249
251
|
````tsx
|
|
250
252
|
import { useRef } from 'react';
|
|
251
|
-
import { MarkdownCMD,
|
|
253
|
+
import { MarkdownCMD, MarkdownCMDRef } from 'ds-markdown';
|
|
252
254
|
|
|
253
255
|
function StreamingChat() {
|
|
254
|
-
const markdownRef = useRef<
|
|
256
|
+
const markdownRef = useRef<MarkdownCMDRef>(null);
|
|
255
257
|
|
|
256
258
|
// AI ストリーミング応答をシミュレート
|
|
257
259
|
const simulateAIResponse = async () => {
|
|
@@ -378,7 +380,7 @@ const handleStreamingMarkdown = () => {
|
|
|
378
380
|
|
|
379
381
|
```tsx
|
|
380
382
|
// ✅ 推奨:命令的 API
|
|
381
|
-
const ref = useRef<
|
|
383
|
+
const ref = useRef<MarkdownCMDRef>(null);
|
|
382
384
|
useEffect(() => {
|
|
383
385
|
ref.current?.push(newChunk, 'answer');
|
|
384
386
|
}, [newChunk]);
|
|
@@ -391,9 +393,9 @@ const [content, setContent] = useState('');
|
|
|
391
393
|
### 3. 型安全
|
|
392
394
|
|
|
393
395
|
```tsx
|
|
394
|
-
import {
|
|
396
|
+
import { MarkdownCMDRef } from 'ds-markdown';
|
|
395
397
|
|
|
396
|
-
const ref = useRef<
|
|
398
|
+
const ref = useRef<MarkdownCMDRef>(null);
|
|
397
399
|
// 完全な TypeScript 型ヒント
|
|
398
400
|
```
|
|
399
401
|
|
package/README.ko.md
CHANGED
|
@@ -12,7 +12,9 @@
|
|
|
12
12
|
[](https://react.dev)
|
|
13
13
|
[](https://www.typescriptlang.org/)
|
|
14
14
|
|
|
15
|
-
[📖
|
|
15
|
+
[📖 온라인 데모](https://onshinpei.github.io/ds-markdown/)
|
|
16
|
+
|
|
17
|
+
[DEMO:🔧 StackBlitz 체험](https://stackblitz.com/edit/vitejs-vite-ddfw8avb?file=src%2FApp.tsx)
|
|
16
18
|
|
|
17
19
|
---
|
|
18
20
|
|
|
@@ -178,9 +180,9 @@ React 19는 많은 흥미로운 새 기능을 제공합니다:
|
|
|
178
180
|
### 명령형 API (스트리밍 시나리오 추천)
|
|
179
181
|
|
|
180
182
|
```typescript
|
|
181
|
-
import { MarkdownCMD,
|
|
183
|
+
import { MarkdownCMD, MarkdownCMDRef } from 'ds-markdown';
|
|
182
184
|
|
|
183
|
-
interface
|
|
185
|
+
interface MarkdownCMDRef {
|
|
184
186
|
push: (content: string, answerType: AnswerType) => void;
|
|
185
187
|
clear: () => void;
|
|
186
188
|
triggerWholeEnd: () => void;
|
|
@@ -248,10 +250,10 @@ interface MarkdownRef {
|
|
|
248
250
|
|
|
249
251
|
````tsx
|
|
250
252
|
import { useRef } from 'react';
|
|
251
|
-
import { MarkdownCMD,
|
|
253
|
+
import { MarkdownCMD, MarkdownCMDRef } from 'ds-markdown';
|
|
252
254
|
|
|
253
255
|
function StreamingChat() {
|
|
254
|
-
const markdownRef = useRef<
|
|
256
|
+
const markdownRef = useRef<MarkdownCMDRef>(null);
|
|
255
257
|
|
|
256
258
|
// AI 스트리밍 응답 시뮬레이션
|
|
257
259
|
const simulateAIResponse = async () => {
|
|
@@ -378,7 +380,7 @@ const handleStreamingMarkdown = () => {
|
|
|
378
380
|
|
|
379
381
|
```tsx
|
|
380
382
|
// ✅ 추천: 명령형 API
|
|
381
|
-
const ref = useRef<
|
|
383
|
+
const ref = useRef<MarkdownCMDRef>(null);
|
|
382
384
|
useEffect(() => {
|
|
383
385
|
ref.current?.push(newChunk, 'answer');
|
|
384
386
|
}, [newChunk]);
|
|
@@ -391,9 +393,9 @@ const [content, setContent] = useState('');
|
|
|
391
393
|
### 3. 타입 안전성
|
|
392
394
|
|
|
393
395
|
```tsx
|
|
394
|
-
import {
|
|
396
|
+
import { MarkdownCMDRef } from 'ds-markdown';
|
|
395
397
|
|
|
396
|
-
const ref = useRef<
|
|
398
|
+
const ref = useRef<MarkdownCMDRef>(null);
|
|
397
399
|
// 완전한 TypeScript 타입 힌트
|
|
398
400
|
```
|
|
399
401
|
|
package/README.md
CHANGED
|
@@ -12,7 +12,9 @@
|
|
|
12
12
|
[](https://react.dev)
|
|
13
13
|
[](https://www.typescriptlang.org/)
|
|
14
14
|
|
|
15
|
-
[📖 在线演示](https://onshinpei.github.io/ds-markdown/)
|
|
15
|
+
[📖 在线演示](https://onshinpei.github.io/ds-markdown/)
|
|
16
|
+
|
|
17
|
+
[DEMO:🔧 StackBlitz 体验](https://stackblitz.com/edit/vitejs-vite-ddfw8avb?file=src%2FApp.tsx)
|
|
16
18
|
|
|
17
19
|
---
|
|
18
20
|
|
|
@@ -36,6 +38,7 @@
|
|
|
36
38
|
- 高频打字支持(`requestAnimationFrame`模式下打字间隔最低可接近于`0ms`)
|
|
37
39
|
- 帧同步渲染,与浏览器 60fps 完美配合
|
|
38
40
|
- 智能字符批量处理,视觉效果更自然
|
|
41
|
+
- 支持打字的中断 `stop` 和 继续`resume`
|
|
39
42
|
|
|
40
43
|
### 🔧 **灵活易用**
|
|
41
44
|
|
|
@@ -178,9 +181,9 @@ React 19 带来了许多激动人心的新特性:
|
|
|
178
181
|
### 命令式 API (推荐流式场景)
|
|
179
182
|
|
|
180
183
|
```typescript
|
|
181
|
-
import { MarkdownCMD,
|
|
184
|
+
import { MarkdownCMD, MarkdownCMDRef } from 'ds-markdown';
|
|
182
185
|
|
|
183
|
-
interface
|
|
186
|
+
interface MarkdownCMDRef {
|
|
184
187
|
push: (content: string, answerType: AnswerType) => void;
|
|
185
188
|
clear: () => void;
|
|
186
189
|
triggerWholeEnd: () => void;
|
|
@@ -246,12 +249,14 @@ interface MarkdownRef {
|
|
|
246
249
|
|
|
247
250
|
### 📝 AI 流式对话
|
|
248
251
|
|
|
252
|
+
[DEMO: 🔧 StackBlitz 体验](https://stackblitz.com/edit/vitejs-vite-2ri8kex3?file=src%2FApp.tsx)
|
|
253
|
+
|
|
249
254
|
````tsx
|
|
250
255
|
import { useRef } from 'react';
|
|
251
|
-
import { MarkdownCMD,
|
|
256
|
+
import { MarkdownCMD, MarkdownCMDRef } from 'ds-markdown';
|
|
252
257
|
|
|
253
258
|
function StreamingChat() {
|
|
254
|
-
const markdownRef = useRef<
|
|
259
|
+
const markdownRef = useRef<MarkdownCMDRef>(null);
|
|
255
260
|
|
|
256
261
|
// 模拟 AI 流式响应
|
|
257
262
|
const simulateAIResponse = async () => {
|
|
@@ -378,7 +383,7 @@ const handleStreamingMarkdown = () => {
|
|
|
378
383
|
|
|
379
384
|
```tsx
|
|
380
385
|
// ✅ 推荐:命令式 API
|
|
381
|
-
const ref = useRef<
|
|
386
|
+
const ref = useRef<MarkdownCMDRef>(null);
|
|
382
387
|
useEffect(() => {
|
|
383
388
|
ref.current?.push(newChunk, 'answer');
|
|
384
389
|
}, [newChunk]);
|
|
@@ -391,9 +396,9 @@ const [content, setContent] = useState('');
|
|
|
391
396
|
### 3. 类型安全
|
|
392
397
|
|
|
393
398
|
```tsx
|
|
394
|
-
import {
|
|
399
|
+
import { MarkdownCMDRef } from 'ds-markdown';
|
|
395
400
|
|
|
396
|
-
const ref = useRef<
|
|
401
|
+
const ref = useRef<MarkdownCMDRef>(null);
|
|
397
402
|
// 完整的 TypeScript 类型提示
|
|
398
403
|
```
|
|
399
404
|
|
|
@@ -5,5 +5,5 @@ interface MarkdownImplProps extends MarkdownProps {
|
|
|
5
5
|
answerType: AnswerType;
|
|
6
6
|
theme?: Theme;
|
|
7
7
|
}
|
|
8
|
-
declare const _default: React.NamedExoticComponent<MarkdownImplProps
|
|
8
|
+
declare const _default: React.NamedExoticComponent<MarkdownImplProps & React.RefAttributes<import("../defined.js").MarkdownBaseRef>>;
|
|
9
9
|
export default _default;
|
|
@@ -7,7 +7,7 @@ const jsx_runtime_1 = require("react/jsx-runtime");
|
|
|
7
7
|
const react_1 = require("react");
|
|
8
8
|
const constant_js_1 = require("../constant.js");
|
|
9
9
|
const index_js_1 = __importDefault(require("../MarkdownCMD/index.js"));
|
|
10
|
-
const MarkdownInner = ({ children: _children = '', answerType, ...rest }) => {
|
|
10
|
+
const MarkdownInner = ({ children: _children = '', answerType, markdownRef, ...rest }) => {
|
|
11
11
|
const cmdRef = (0, react_1.useRef)(null);
|
|
12
12
|
const prefixRef = (0, react_1.useRef)('');
|
|
13
13
|
const content = (0, react_1.useMemo)(() => {
|
|
@@ -38,9 +38,17 @@ const MarkdownInner = ({ children: _children = '', answerType, ...rest }) => {
|
|
|
38
38
|
prefixRef.current = content;
|
|
39
39
|
}
|
|
40
40
|
}, [answerType, content]);
|
|
41
|
+
(0, react_1.useImperativeHandle)(markdownRef, () => ({
|
|
42
|
+
stop: () => {
|
|
43
|
+
cmdRef.current.stop();
|
|
44
|
+
},
|
|
45
|
+
resume: () => {
|
|
46
|
+
cmdRef.current.resume();
|
|
47
|
+
},
|
|
48
|
+
}));
|
|
41
49
|
return (0, jsx_runtime_1.jsx)(index_js_1.default, { ref: cmdRef, ...rest });
|
|
42
50
|
};
|
|
43
|
-
const Markdown = (props) => {
|
|
51
|
+
const Markdown = (0, react_1.forwardRef)((props, ref) => {
|
|
44
52
|
const { children = '', answerType = 'answer' } = props;
|
|
45
53
|
if (constant_js_1.__DEV__) {
|
|
46
54
|
if (!['thinking', 'answer'].includes(answerType)) {
|
|
@@ -50,7 +58,7 @@ const Markdown = (props) => {
|
|
|
50
58
|
throw new Error('Markdown组件的子元素必须是一个字符串');
|
|
51
59
|
}
|
|
52
60
|
}
|
|
53
|
-
return (0, jsx_runtime_1.jsx)(MarkdownInner, { ...props, answerType: answerType });
|
|
54
|
-
};
|
|
61
|
+
return (0, jsx_runtime_1.jsx)(MarkdownInner, { ...props, answerType: answerType, markdownRef: ref });
|
|
62
|
+
});
|
|
55
63
|
exports.default = (0, react_1.memo)(Markdown);
|
|
56
64
|
//# sourceMappingURL=index.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../../src/Markdown/index.tsx"],"names":[],"mappings":";;;;;;AAAA,
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../../src/Markdown/index.tsx"],"names":[],"mappings":";;;;;;AAAA,iCAAiG;AACjG,gDAAyC;AAEzC,uEAAkD;AAYlD,MAAM,aAAa,GAAiC,CAAC,EAAE,QAAQ,EAAE,SAAS,GAAG,EAAE,EAAE,UAAU,EAAE,WAAW,EAAE,GAAG,IAAI,EAAE,EAAE,EAAE;IACrH,MAAM,MAAM,GAAG,IAAA,cAAM,EAAiB,IAAK,CAAC,CAAC;IAC7C,MAAM,SAAS,GAAG,IAAA,cAAM,EAAC,EAAE,CAAC,CAAC;IAC7B,MAAM,OAAO,GAAG,IAAA,eAAO,EAAC,GAAG,EAAE;QAC3B,IAAI,OAAO,SAAS,KAAK,QAAQ,EAAE,CAAC;YAClC,OAAO,SAAS,CAAC;QACnB,CAAC;QACD,IAAI,qBAAO,EAAE,CAAC;YACZ,OAAO,CAAC,KAAK,CAAC,wBAAwB,CAAC,CAAC;QAC1C,CAAC;QACD,OAAO,EAAE,CAAC;IACZ,CAAC,EAAE,CAAC,SAAS,CAAC,CAAC,CAAC;IAEhB,IAAA,iBAAS,EAAC,GAAG,EAAE;QACb,IAAI,SAAS,CAAC,OAAO,KAAK,OAAO,EAAE,CAAC;YAClC,IAAI,UAAU,GAAG,EAAE,CAAC;YACpB,IAAI,SAAS,CAAC,OAAO,KAAK,EAAE,EAAE,CAAC;gBAC7B,UAAU,GAAG,OAAO,CAAC;YACvB,CAAC;iBAAM,CAAC;gBACN,IAAI,OAAO,CAAC,UAAU,CAAC,SAAS,CAAC,OAAO,CAAC,EAAE,CAAC;oBAC1C,UAAU,GAAG,OAAO,CAAC,KAAK,CAAC,SAAS,CAAC,OAAO,CAAC,MAAM,CAAC,CAAC;gBACvD,CAAC;qBAAM,CAAC;oBACN,UAAU,GAAG,OAAO,CAAC;oBACrB,MAAM,CAAC,OAAO,CAAC,KAAK,EAAE,CAAC;gBACzB,CAAC;YACH,CAAC;YACD,MAAM,CAAC,OAAO,CAAC,IAAI,CAAC,UAAU,EAAE,UAAU,CAAC,CAAC;YAC5C,SAAS,CAAC,OAAO,GAAG,OAAO,CAAC;QAC9B,CAAC;IACH,CAAC,EAAE,CAAC,UAAU,EAAE,OAAO,CAAC,CAAC,CAAC;IAE1B,IAAA,2BAAmB,EAAC,WAAW,EAAE,GAAG,EAAE,CAAC,CAAC;QACtC,IAAI,EAAE,GAAG,EAAE;YACT,MAAM,CAAC,OAAO,CAAC,IAAI,EAAE,CAAC;QACxB,CAAC;QACD,MAAM,EAAE,GAAG,EAAE;YACX,MAAM,CAAC,OAAO,CAAC,MAAM,EAAE,CAAC;QAC1B,CAAC;KACF,CAAC,CAAC,CAAC;IAEJ,OAAO,uBAAC,kBAAW,IAAC,GAAG,EAAE,MAAM,KAAM,IAAI,GAAI,CAAC;AAChD,CAAC,CAAC;AAEF,MAAM,QAAQ,GAAG,IAAA,kBAAU,EAAiC,CAAC,KAAK,EAAE,GAAG,EAAE,EAAE;IACzE,MAAM,EAAE,QAAQ,GAAG,EAAE,EAAE,UAAU,GAAG,QAAQ,EAAE,GAAG,KAAK,CAAC;IAEvD,IAAI,qBAAO,EAAE,CAAC;QACZ,IAAI,CAAC,CAAC,UAAU,EAAE,QAAQ,CAAC,CAAC,QAAQ,CAAC,UAAU,CAAC,EAAE,CAAC;YACjD,MAAM,IAAI,KAAK,CAAC,yCAAyC,CAAC,CAAC;QAC7D,CAAC;QACD,IAAI,OAAO,QAAQ,KAAK,QAAQ,EAAE,CAAC;YACjC,MAAM,IAAI,KAAK,CAAC,wBAAwB,CAAC,CAAC;QAC5C,CAAC;IACH,CAAC;IAED,OAAO,uBAAC,aAAa,OAAK,KAAK,EAAE,UAAU,EAAE,UAAU,EAAE,WAAW,EAAE,GAAG,GAAI,CAAC;AAChF,CAAC,CAAC,CAAC;AAEH,kBAAe,IAAA,YAAI,EAAC,QAAQ,CAAC,CAAC"}
|
|
@@ -1,8 +1,3 @@
|
|
|
1
|
-
import {
|
|
2
|
-
|
|
3
|
-
push: (content: string, answerType: AnswerType) => void;
|
|
4
|
-
clear: () => void;
|
|
5
|
-
triggerWholeEnd: () => void;
|
|
6
|
-
}
|
|
7
|
-
declare const MarkdownCMD: import("react").ForwardRefExoticComponent<MarkdownProps & import("react").RefAttributes<MarkdownRef>>;
|
|
1
|
+
import { MarkdownProps, MarkdownCMDRef } from '../defined.js';
|
|
2
|
+
declare const MarkdownCMD: import("react").ForwardRefExoticComponent<MarkdownProps & import("react").RefAttributes<MarkdownCMDRef>>;
|
|
8
3
|
export default MarkdownCMD;
|
|
@@ -7,9 +7,7 @@ const jsx_runtime_1 = require("react/jsx-runtime");
|
|
|
7
7
|
const react_1 = require("react");
|
|
8
8
|
const index_js_1 = __importDefault(require("../components/HighReactMarkdown/index.js"));
|
|
9
9
|
const classnames_1 = __importDefault(require("classnames"));
|
|
10
|
-
const compiler_js_1 = require("../utils/compiler.js");
|
|
11
10
|
const constant_js_1 = require("../constant.js");
|
|
12
|
-
const deepClone_js_1 = __importDefault(require("../utils/methods/deepClone.js"));
|
|
13
11
|
const useTypingTask_js_1 = require("../hooks/useTypingTask.js");
|
|
14
12
|
const MarkdownCMD = (0, react_1.forwardRef)(({ interval = 30, onEnd, onStart, onTypedChar, timerType = 'setTimeout', theme = 'light' }, ref) => {
|
|
15
13
|
/** 当前需要打字的内容 */
|
|
@@ -19,83 +17,37 @@ const MarkdownCMD = (0, react_1.forwardRef)(({ interval = 30, onEnd, onStart, on
|
|
|
19
17
|
* 如果打字已经完全结束,则不会再触发打字效果
|
|
20
18
|
*/
|
|
21
19
|
const isWholeTypedEndRef = (0, react_1.useRef)(false);
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
20
|
+
const charIndexRef = (0, react_1.useRef)(0);
|
|
21
|
+
/** 整个内容引用 */
|
|
22
|
+
const wholeContentRef = (0, react_1.useRef)({
|
|
23
|
+
thinking: {
|
|
24
|
+
content: '',
|
|
25
|
+
length: 0,
|
|
26
|
+
},
|
|
27
|
+
answer: {
|
|
28
|
+
content: '',
|
|
29
|
+
length: 0,
|
|
30
|
+
},
|
|
31
|
+
allLength: 0,
|
|
32
|
+
});
|
|
33
|
+
const [, setUpdate] = (0, react_1.useState)(false);
|
|
34
|
+
const triggerUpdate = () => {
|
|
35
|
+
setUpdate((prev) => !prev);
|
|
36
|
+
};
|
|
31
37
|
/**
|
|
32
38
|
* 处理字符显示逻辑
|
|
33
39
|
*/
|
|
34
40
|
const processCharDisplay = (char) => {
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
if (char.contentType === 'space' || char.contentType === 'split_segment') {
|
|
39
|
-
if (currentSegment) {
|
|
40
|
-
setStableSegments((prev) => {
|
|
41
|
-
const newParagraphs = [...prev];
|
|
42
|
-
// 放入到稳定队列
|
|
43
|
-
if (currentSegment) {
|
|
44
|
-
newParagraphs.push({ ...currentSegment, isTyped: false });
|
|
45
|
-
}
|
|
46
|
-
return newParagraphs;
|
|
47
|
-
});
|
|
48
|
-
setCurrentSegment(() => undefined);
|
|
49
|
-
currentParagraphRef.current = undefined;
|
|
50
|
-
}
|
|
51
|
-
return;
|
|
52
|
-
}
|
|
53
|
-
// 处理当前段落
|
|
54
|
-
const newCurrentParagraph = {
|
|
55
|
-
content: '',
|
|
56
|
-
isTyped: false,
|
|
57
|
-
type: 'text',
|
|
58
|
-
answerType: char.answerType,
|
|
59
|
-
tokensReference: {},
|
|
60
|
-
};
|
|
61
|
-
let _currentParagraph = currentSegment;
|
|
62
|
-
if (!_currentParagraph) {
|
|
63
|
-
// 如果当前没有段落,则直接设置为新当前段落
|
|
64
|
-
_currentParagraph = newCurrentParagraph;
|
|
65
|
-
}
|
|
66
|
-
else if (currentSegment && currentSegment?.answerType !== char.answerType) {
|
|
67
|
-
// 如果当前段落和当前字符的回答类型不一致,则需要处理成两个段落
|
|
68
|
-
setStableSegments((prev) => {
|
|
69
|
-
const newParagraphs = [...prev];
|
|
70
|
-
newParagraphs.push({ ...currentSegment, isTyped: false });
|
|
71
|
-
return newParagraphs;
|
|
72
|
-
});
|
|
73
|
-
_currentParagraph = newCurrentParagraph;
|
|
74
|
-
}
|
|
75
|
-
const tokensReference = (0, deepClone_js_1.default)(_currentParagraph.tokensReference);
|
|
76
|
-
if (tokensReference[char.tokenId]) {
|
|
77
|
-
tokensReference[char.tokenId].raw += char.content;
|
|
78
|
-
tokensReference[char.tokenId].startIndex = currentSegment?.content?.length || 0;
|
|
41
|
+
if (char.answerType === 'thinking') {
|
|
42
|
+
wholeContentRef.current.thinking.content += char.content;
|
|
43
|
+
wholeContentRef.current.thinking.length += 1;
|
|
79
44
|
}
|
|
80
45
|
else {
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
raw: char.content,
|
|
84
|
-
};
|
|
46
|
+
wholeContentRef.current.answer.content += char.content;
|
|
47
|
+
wholeContentRef.current.answer.length += 1;
|
|
85
48
|
}
|
|
86
|
-
|
|
87
|
-
..._currentParagraph,
|
|
88
|
-
tokensReference,
|
|
89
|
-
content: (currentSegment?.content || '') + char.content,
|
|
90
|
-
isTyped: true,
|
|
91
|
-
};
|
|
92
|
-
currentParagraphRef.current = newCurrentSegment;
|
|
93
|
-
setCurrentSegment(() => newCurrentSegment);
|
|
49
|
+
triggerUpdate();
|
|
94
50
|
};
|
|
95
|
-
/** 思考段落 */
|
|
96
|
-
const thinkingParagraphs = (0, react_1.useMemo)(() => stableSegments.filter((paragraph) => paragraph.answerType === 'thinking'), [stableSegments]);
|
|
97
|
-
/** 回答段落 */
|
|
98
|
-
const answerParagraphs = (0, react_1.useMemo)(() => stableSegments.filter((paragraph) => paragraph.answerType === 'answer'), [stableSegments]);
|
|
99
51
|
// 使用新的打字任务 hook
|
|
100
52
|
const typingTask = (0, useTypingTask_js_1.useTypingTask)({
|
|
101
53
|
timerType,
|
|
@@ -105,20 +57,7 @@ const MarkdownCMD = (0, react_1.forwardRef)(({ interval = 30, onEnd, onStart, on
|
|
|
105
57
|
onStart,
|
|
106
58
|
onTypedChar,
|
|
107
59
|
processCharDisplay,
|
|
108
|
-
|
|
109
|
-
const lastSegmentRawRef = (0, react_1.useRef)({
|
|
110
|
-
thinkingReference: null,
|
|
111
|
-
answerReference: null,
|
|
112
|
-
});
|
|
113
|
-
const wholeContentRef = (0, react_1.useRef)({
|
|
114
|
-
thinking: {
|
|
115
|
-
content: '',
|
|
116
|
-
length: 0,
|
|
117
|
-
},
|
|
118
|
-
answer: {
|
|
119
|
-
content: '',
|
|
120
|
-
length: 0,
|
|
121
|
-
},
|
|
60
|
+
wholeContentRef,
|
|
122
61
|
});
|
|
123
62
|
/**
|
|
124
63
|
* 内部推送处理逻辑
|
|
@@ -127,108 +66,18 @@ const MarkdownCMD = (0, react_1.forwardRef)(({ interval = 30, onEnd, onStart, on
|
|
|
127
66
|
if (content.length === 0) {
|
|
128
67
|
return;
|
|
129
68
|
}
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
if (lastSegmentReference) {
|
|
143
|
-
lastSegmentRaw = lastSegmentReference.raw;
|
|
144
|
-
currentLastSegmentRaw = lastSegmentRaw + content;
|
|
145
|
-
}
|
|
146
|
-
else {
|
|
147
|
-
currentLastSegmentRaw = content;
|
|
148
|
-
}
|
|
149
|
-
const tokens = (0, compiler_js_1.compiler)(currentLastSegmentRaw);
|
|
150
|
-
// 如果最后一个token是space,则把lastSegmentRaw设置为空
|
|
151
|
-
if (tokens[tokens.length - 1].type === 'space') {
|
|
152
|
-
currentLastSegmentReference = null;
|
|
153
|
-
}
|
|
154
|
-
else {
|
|
155
|
-
// 如果上一个segment存在并且当前只有一个token,则说明是同一个segment
|
|
156
|
-
if (lastSegmentReference !== null && tokens.length === 1) {
|
|
157
|
-
const newCurrentLastSegmentReference = lastSegmentReference;
|
|
158
|
-
newCurrentLastSegmentReference.raw = newCurrentLastSegmentReference.raw + content;
|
|
159
|
-
currentLastSegmentReference = newCurrentLastSegmentReference;
|
|
160
|
-
}
|
|
161
|
-
else {
|
|
162
|
-
currentLastSegmentReference = tokens[tokens.length - 1];
|
|
163
|
-
}
|
|
164
|
-
}
|
|
165
|
-
const pushAndSplitSegment = (raw, tokenIndex, segmentTokenId) => {
|
|
166
|
-
const currentToken = tokens[tokenIndex];
|
|
167
|
-
if (tokenIndex > 0) {
|
|
168
|
-
const prevToken = tokens[tokenIndex - 1];
|
|
169
|
-
if (prevToken.type !== 'space' && currentToken.type !== 'space') {
|
|
170
|
-
charsRef.current.push({ content: '', answerType, contentType: 'split_segment', tokenId: currentToken.id, index: currentIndex++ });
|
|
171
|
-
}
|
|
172
|
-
}
|
|
173
|
-
charsRef.current.push(...raw.split('').map((char) => ({ content: char, answerType, contentType: 'segment', tokenId: segmentTokenId, index: currentIndex++ })));
|
|
174
|
-
};
|
|
175
|
-
if (!lastSegmentReference) {
|
|
176
|
-
tokens.forEach((token, i) => {
|
|
177
|
-
if (token.type === 'space') {
|
|
178
|
-
charsRef.current.push(...token.raw.split('').map((char) => ({ content: char, answerType, contentType: 'space', tokenId: token.id, index: currentIndex++ })));
|
|
179
|
-
}
|
|
180
|
-
else {
|
|
181
|
-
pushAndSplitSegment(token.raw, i, token.id);
|
|
182
|
-
}
|
|
183
|
-
});
|
|
184
|
-
}
|
|
185
|
-
else {
|
|
186
|
-
// debugger;
|
|
187
|
-
let str = '';
|
|
188
|
-
let firstSpaceIndex = -1;
|
|
189
|
-
let nextTokenIndex = lastSegmentRaw.length;
|
|
190
|
-
for (let i = 0; i < tokens.length; i++) {
|
|
191
|
-
const token = tokens[i];
|
|
192
|
-
if (token.type === 'space') {
|
|
193
|
-
if (firstSpaceIndex === -1) {
|
|
194
|
-
firstSpaceIndex = str.length;
|
|
195
|
-
}
|
|
196
|
-
str += token.raw;
|
|
197
|
-
if (lastSegmentRaw.length > firstSpaceIndex) {
|
|
198
|
-
// 如果lastSegmentRaw的长度大于firstSpaceIndex,则需要将当前设置为 segment
|
|
199
|
-
charsRef.current.push(...token.raw.split('').map((char) => ({ content: char, answerType, contentType: 'segment', tokenId: token.id, index: currentIndex++ })));
|
|
200
|
-
}
|
|
201
|
-
else {
|
|
202
|
-
charsRef.current.push(...token.raw.split('').map((char) => ({ content: char, answerType, contentType: 'space', tokenId: token.id, index: currentIndex++ })));
|
|
203
|
-
}
|
|
204
|
-
}
|
|
205
|
-
else {
|
|
206
|
-
str += token.raw;
|
|
207
|
-
if (str.length < nextTokenIndex && i == 0) {
|
|
208
|
-
/** 如果当前字符串长度小于下一个token的索引,则需要将当前段落更新, 以修正不完整的token */
|
|
209
|
-
const lastSegmentReferenceId = lastSegmentReference.id;
|
|
210
|
-
const currentSegment = currentParagraphRef.current;
|
|
211
|
-
const tokensReference = currentSegment?.tokensReference || {};
|
|
212
|
-
const lastTokenReference = tokensReference[lastSegmentReferenceId];
|
|
213
|
-
if (lastTokenReference) {
|
|
214
|
-
const newTokensReference = { ...tokensReference, [lastSegmentReferenceId]: { startIndex: lastTokenReference.startIndex, raw: token.raw } };
|
|
215
|
-
const newCurrentSegment = { ...currentSegment, tokensReference: newTokensReference, isTyped: false, type: 'text', answerType };
|
|
216
|
-
newCurrentSegment.content = Object.values(newTokensReference).reduce((acc, curr) => acc + curr.raw, '');
|
|
217
|
-
setCurrentSegment(newCurrentSegment);
|
|
218
|
-
currentParagraphRef.current = newCurrentSegment;
|
|
219
|
-
}
|
|
220
|
-
}
|
|
221
|
-
const realRaw = str.slice(nextTokenIndex);
|
|
222
|
-
if (realRaw.length > 0) {
|
|
223
|
-
pushAndSplitSegment(realRaw, i, lastSegmentReference.id);
|
|
224
|
-
}
|
|
225
|
-
}
|
|
226
|
-
nextTokenIndex = str.length;
|
|
227
|
-
}
|
|
228
|
-
}
|
|
229
|
-
lastSegmentRawRef.current[`${answerType}Reference`] = currentLastSegmentReference;
|
|
230
|
-
wholeContent.content = wholeContent.content + content;
|
|
231
|
-
wholeContent.length = wholeContent.content.length;
|
|
69
|
+
charsRef.current.push(...content.split('').map((chatStr) => {
|
|
70
|
+
const index = charIndexRef.current++;
|
|
71
|
+
const charObj = {
|
|
72
|
+
content: chatStr,
|
|
73
|
+
answerType,
|
|
74
|
+
contentType: 'segment',
|
|
75
|
+
tokenId: 0,
|
|
76
|
+
index,
|
|
77
|
+
};
|
|
78
|
+
return charObj;
|
|
79
|
+
}));
|
|
80
|
+
wholeContentRef.current.allLength += content.length;
|
|
232
81
|
if (!typingTask.isTyping()) {
|
|
233
82
|
typingTask.start();
|
|
234
83
|
}
|
|
@@ -247,20 +96,30 @@ const MarkdownCMD = (0, react_1.forwardRef)(({ interval = 30, onEnd, onStart, on
|
|
|
247
96
|
*/
|
|
248
97
|
clear: () => {
|
|
249
98
|
typingTask.stop();
|
|
99
|
+
typingTask.typedIsManualStopRef.current = false;
|
|
250
100
|
charsRef.current = [];
|
|
251
|
-
|
|
252
|
-
|
|
101
|
+
wholeContentRef.current = {
|
|
102
|
+
thinking: {
|
|
103
|
+
content: '',
|
|
104
|
+
length: 0,
|
|
105
|
+
},
|
|
106
|
+
answer: {
|
|
107
|
+
content: '',
|
|
108
|
+
length: 0,
|
|
109
|
+
},
|
|
110
|
+
allLength: 0,
|
|
111
|
+
};
|
|
253
112
|
isWholeTypedEndRef.current = false;
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
113
|
+
charIndexRef.current = 0;
|
|
114
|
+
triggerUpdate();
|
|
115
|
+
},
|
|
116
|
+
/** 停止打字任务 */
|
|
117
|
+
stop: () => {
|
|
118
|
+
typingTask.stop();
|
|
119
|
+
},
|
|
120
|
+
/** 重新开始打字任务 */
|
|
121
|
+
resume: () => {
|
|
122
|
+
typingTask.resume();
|
|
264
123
|
},
|
|
265
124
|
/**
|
|
266
125
|
* 主动触发打字结束
|
|
@@ -279,19 +138,17 @@ const MarkdownCMD = (0, react_1.forwardRef)(({ interval = 30, onEnd, onStart, on
|
|
|
279
138
|
* 刷新缓冲区 (新增方法)
|
|
280
139
|
*/
|
|
281
140
|
}));
|
|
282
|
-
const getParagraphs = (
|
|
283
|
-
return ((0, jsx_runtime_1.
|
|
284
|
-
if (paragraph.type === 'br') {
|
|
285
|
-
return null;
|
|
286
|
-
}
|
|
287
|
-
return ((0, jsx_runtime_1.jsx)(index_js_1.default, { theme: theme, children: paragraph.content || '' }, index));
|
|
288
|
-
}), currentSegment?.answerType === answerType && ((0, jsx_runtime_1.jsx)(index_js_1.default, { theme: theme, children: currentSegment.content || '' }, currentSegment.content))] }));
|
|
141
|
+
const getParagraphs = (answerType) => {
|
|
142
|
+
return ((0, jsx_runtime_1.jsx)("div", { className: `ds-markdown-paragraph ds-typed-${answerType}`, children: (0, jsx_runtime_1.jsx)(index_js_1.default, { theme: theme, children: wholeContentRef.current[answerType].content || '' }) }));
|
|
289
143
|
};
|
|
290
144
|
return ((0, jsx_runtime_1.jsxs)("div", { className: (0, classnames_1.default)({
|
|
291
145
|
'ds-markdown': true,
|
|
292
146
|
apple: true,
|
|
293
147
|
'ds-markdown-dark': theme === 'dark',
|
|
294
|
-
}), children: [(
|
|
148
|
+
}), children: [(0, jsx_runtime_1.jsx)("div", { className: "ds-markdown-thinking", children: getParagraphs('thinking') }), (0, jsx_runtime_1.jsx)("div", { className: "ds-markdown-answer", children: getParagraphs('answer') })] }));
|
|
295
149
|
});
|
|
150
|
+
if (constant_js_1.__DEV__) {
|
|
151
|
+
MarkdownCMD.displayName = 'MarkdownCMD';
|
|
152
|
+
}
|
|
296
153
|
exports.default = MarkdownCMD;
|
|
297
154
|
//# sourceMappingURL=index.js.map
|