ds-markdown 0.0.14-beta.0 → 0.0.14
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 +16 -7
- package/README.ja.md +16 -7
- package/README.ko.md +17 -7
- package/README.md +17 -15
- 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 +43 -186
- package/dist/cjs/MarkdownCMD/index.js.map +1 -1
- package/dist/cjs/defined.d.ts +13 -0
- package/dist/cjs/hooks/useTypingTask.d.ts +4 -0
- package/dist/cjs/hooks/useTypingTask.js +36 -9
- package/dist/cjs/hooks/useTypingTask.js.map +1 -1
- package/dist/cjs/index.d.ts +3 -2
- package/dist/cjs/index.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 +44 -187
- package/dist/esm/MarkdownCMD/index.js.map +1 -1
- package/dist/esm/defined.d.ts +13 -0
- package/dist/esm/hooks/useTypingTask.d.ts +4 -0
- package/dist/esm/hooks/useTypingTask.js +36 -9
- package/dist/esm/hooks/useTypingTask.js.map +1 -1
- package/dist/esm/index.d.ts +3 -2
- package/dist/esm/index.js.map +1 -1
- package/package.json +2 -2
package/README.en.md
CHANGED
|
@@ -180,9 +180,9 @@ Let's explore these new features together!`);
|
|
|
180
180
|
### Imperative API (Recommended for Streaming Scenarios)
|
|
181
181
|
|
|
182
182
|
```typescript
|
|
183
|
-
import { MarkdownCMD,
|
|
183
|
+
import { MarkdownCMD, MarkdownCMDRef } from 'ds-markdown';
|
|
184
184
|
|
|
185
|
-
interface
|
|
185
|
+
interface MarkdownCMDRef {
|
|
186
186
|
push: (content: string, answerType: AnswerType) => void;
|
|
187
187
|
clear: () => void;
|
|
188
188
|
triggerWholeEnd: () => void;
|
|
@@ -194,6 +194,15 @@ interface MarkdownRef {
|
|
|
194
194
|
| `push` | `(content: string, answerType: AnswerType)` | Add content and start typing |
|
|
195
195
|
| `clear` | - | Clear all content and state |
|
|
196
196
|
| `triggerWholeEnd` | - | Manually trigger end callback |
|
|
197
|
+
| `stop` | - | Pause typing animation |
|
|
198
|
+
| `resume` | - | Resume typing animation |
|
|
199
|
+
|
|
200
|
+
**Usage Example:**
|
|
201
|
+
|
|
202
|
+
```tsx
|
|
203
|
+
markdownRef.current?.stop(); // Pause animation
|
|
204
|
+
markdownRef.current?.resume(); // Resume animation
|
|
205
|
+
```
|
|
197
206
|
|
|
198
207
|
---
|
|
199
208
|
|
|
@@ -252,10 +261,10 @@ High-frequency recommends `requestAnimationFrame`, low-frequency recommends `set
|
|
|
252
261
|
|
|
253
262
|
````tsx
|
|
254
263
|
import { useRef } from 'react';
|
|
255
|
-
import { MarkdownCMD,
|
|
264
|
+
import { MarkdownCMD, MarkdownCMDRef } from 'ds-markdown';
|
|
256
265
|
|
|
257
266
|
function StreamingChat() {
|
|
258
|
-
const markdownRef = useRef<
|
|
267
|
+
const markdownRef = useRef<MarkdownCMDRef>(null);
|
|
259
268
|
|
|
260
269
|
// Simulate AI streaming response
|
|
261
270
|
const simulateAIResponse = async () => {
|
|
@@ -382,7 +391,7 @@ const handleStreamingMarkdown = () => {
|
|
|
382
391
|
|
|
383
392
|
```tsx
|
|
384
393
|
// ✅ Recommended: Imperative API
|
|
385
|
-
const ref = useRef<
|
|
394
|
+
const ref = useRef<MarkdownCMDRef>(null);
|
|
386
395
|
useEffect(() => {
|
|
387
396
|
ref.current?.push(newChunk, 'answer');
|
|
388
397
|
}, [newChunk]);
|
|
@@ -395,9 +404,9 @@ const [content, setContent] = useState('');
|
|
|
395
404
|
### 3. Type Safety
|
|
396
405
|
|
|
397
406
|
```tsx
|
|
398
|
-
import {
|
|
407
|
+
import { MarkdownCMDRef } from 'ds-markdown';
|
|
399
408
|
|
|
400
|
-
const ref = useRef<
|
|
409
|
+
const ref = useRef<MarkdownCMDRef>(null);
|
|
401
410
|
// Complete TypeScript type hints
|
|
402
411
|
```
|
|
403
412
|
|
package/README.ja.md
CHANGED
|
@@ -180,9 +180,9 @@ React 19 は多くのエキサイティングな新機能をもたらします
|
|
|
180
180
|
### 命令的 API(ストリーミングシナリオにおすすめ)
|
|
181
181
|
|
|
182
182
|
```typescript
|
|
183
|
-
import { MarkdownCMD,
|
|
183
|
+
import { MarkdownCMD, MarkdownCMDRef } from 'ds-markdown';
|
|
184
184
|
|
|
185
|
-
interface
|
|
185
|
+
interface MarkdownCMDRef {
|
|
186
186
|
push: (content: string, answerType: AnswerType) => void;
|
|
187
187
|
clear: () => void;
|
|
188
188
|
triggerWholeEnd: () => void;
|
|
@@ -194,6 +194,15 @@ interface MarkdownRef {
|
|
|
194
194
|
| `push` | `(content: string, answerType: AnswerType)` | コンテンツを追加してタイピングを開始 |
|
|
195
195
|
| `clear` | - | すべてのコンテンツと状態をクリア |
|
|
196
196
|
| `triggerWholeEnd` | - | 完了コールバックを手動でトリガー |
|
|
197
|
+
| `stop` | - | タイピングを一時停止 |
|
|
198
|
+
| `resume` | - | タイピングを再開 |
|
|
199
|
+
|
|
200
|
+
**使用例:**
|
|
201
|
+
|
|
202
|
+
```tsx
|
|
203
|
+
markdownRef.current?.stop(); // アニメーションを一時停止
|
|
204
|
+
markdownRef.current?.resume(); // アニメーションを再開
|
|
205
|
+
```
|
|
197
206
|
|
|
198
207
|
---
|
|
199
208
|
|
|
@@ -250,10 +259,10 @@ interface MarkdownRef {
|
|
|
250
259
|
|
|
251
260
|
````tsx
|
|
252
261
|
import { useRef } from 'react';
|
|
253
|
-
import { MarkdownCMD,
|
|
262
|
+
import { MarkdownCMD, MarkdownCMDRef } from 'ds-markdown';
|
|
254
263
|
|
|
255
264
|
function StreamingChat() {
|
|
256
|
-
const markdownRef = useRef<
|
|
265
|
+
const markdownRef = useRef<MarkdownCMDRef>(null);
|
|
257
266
|
|
|
258
267
|
// AI ストリーミング応答をシミュレート
|
|
259
268
|
const simulateAIResponse = async () => {
|
|
@@ -380,7 +389,7 @@ const handleStreamingMarkdown = () => {
|
|
|
380
389
|
|
|
381
390
|
```tsx
|
|
382
391
|
// ✅ 推奨:命令的 API
|
|
383
|
-
const ref = useRef<
|
|
392
|
+
const ref = useRef<MarkdownCMDRef>(null);
|
|
384
393
|
useEffect(() => {
|
|
385
394
|
ref.current?.push(newChunk, 'answer');
|
|
386
395
|
}, [newChunk]);
|
|
@@ -393,9 +402,9 @@ const [content, setContent] = useState('');
|
|
|
393
402
|
### 3. 型安全
|
|
394
403
|
|
|
395
404
|
```tsx
|
|
396
|
-
import {
|
|
405
|
+
import { MarkdownCMDRef } from 'ds-markdown';
|
|
397
406
|
|
|
398
|
-
const ref = useRef<
|
|
407
|
+
const ref = useRef<MarkdownCMDRef>(null);
|
|
399
408
|
// 完全な TypeScript 型ヒント
|
|
400
409
|
```
|
|
401
410
|
|
package/README.ko.md
CHANGED
|
@@ -180,9 +180,9 @@ React 19는 많은 흥미로운 새 기능을 제공합니다:
|
|
|
180
180
|
### 명령형 API (스트리밍 시나리오 추천)
|
|
181
181
|
|
|
182
182
|
```typescript
|
|
183
|
-
import { MarkdownCMD,
|
|
183
|
+
import { MarkdownCMD, MarkdownCMDRef } from 'ds-markdown';
|
|
184
184
|
|
|
185
|
-
interface
|
|
185
|
+
interface MarkdownCMDRef {
|
|
186
186
|
push: (content: string, answerType: AnswerType) => void;
|
|
187
187
|
clear: () => void;
|
|
188
188
|
triggerWholeEnd: () => void;
|
|
@@ -195,6 +195,16 @@ interface MarkdownRef {
|
|
|
195
195
|
| `clear` | - | 모든 콘텐츠와 상태 초기화 |
|
|
196
196
|
| `triggerWholeEnd` | - | 완료 콜백 수동 트리거 |
|
|
197
197
|
|
|
198
|
+
| `stop` | - | 타이핑 애니메이션 일시정지 |
|
|
199
|
+
| `resume` | - | 타이핑 애니메이션 재개 |
|
|
200
|
+
|
|
201
|
+
**사용 예시:**
|
|
202
|
+
|
|
203
|
+
```tsx
|
|
204
|
+
markdownRef.current?.stop(); // 애니메이션 일시정지
|
|
205
|
+
markdownRef.current?.resume(); // 애니메이션 재개
|
|
206
|
+
```
|
|
207
|
+
|
|
198
208
|
---
|
|
199
209
|
|
|
200
210
|
## 🎛️ 타이머 모드 상세 설명
|
|
@@ -250,10 +260,10 @@ interface MarkdownRef {
|
|
|
250
260
|
|
|
251
261
|
````tsx
|
|
252
262
|
import { useRef } from 'react';
|
|
253
|
-
import { MarkdownCMD,
|
|
263
|
+
import { MarkdownCMD, MarkdownCMDRef } from 'ds-markdown';
|
|
254
264
|
|
|
255
265
|
function StreamingChat() {
|
|
256
|
-
const markdownRef = useRef<
|
|
266
|
+
const markdownRef = useRef<MarkdownCMDRef>(null);
|
|
257
267
|
|
|
258
268
|
// AI 스트리밍 응답 시뮬레이션
|
|
259
269
|
const simulateAIResponse = async () => {
|
|
@@ -380,7 +390,7 @@ const handleStreamingMarkdown = () => {
|
|
|
380
390
|
|
|
381
391
|
```tsx
|
|
382
392
|
// ✅ 추천: 명령형 API
|
|
383
|
-
const ref = useRef<
|
|
393
|
+
const ref = useRef<MarkdownCMDRef>(null);
|
|
384
394
|
useEffect(() => {
|
|
385
395
|
ref.current?.push(newChunk, 'answer');
|
|
386
396
|
}, [newChunk]);
|
|
@@ -393,9 +403,9 @@ const [content, setContent] = useState('');
|
|
|
393
403
|
### 3. 타입 안전성
|
|
394
404
|
|
|
395
405
|
```tsx
|
|
396
|
-
import {
|
|
406
|
+
import { MarkdownCMDRef } from 'ds-markdown';
|
|
397
407
|
|
|
398
|
-
const ref = useRef<
|
|
408
|
+
const ref = useRef<MarkdownCMDRef>(null);
|
|
399
409
|
// 완전한 TypeScript 타입 힌트
|
|
400
410
|
```
|
|
401
411
|
|
package/README.md
CHANGED
|
@@ -38,6 +38,7 @@
|
|
|
38
38
|
- 高频打字支持(`requestAnimationFrame`模式下打字间隔最低可接近于`0ms`)
|
|
39
39
|
- 帧同步渲染,与浏览器 60fps 完美配合
|
|
40
40
|
- 智能字符批量处理,视觉效果更自然
|
|
41
|
+
- 支持打字的中断 `stop` 和 继续`resume`
|
|
41
42
|
|
|
42
43
|
### 🔧 **灵活易用**
|
|
43
44
|
|
|
@@ -104,6 +105,8 @@ pnpm add ds-markdown
|
|
|
104
105
|
|
|
105
106
|
### 基础用法
|
|
106
107
|
|
|
108
|
+
[DEMO](https://stackblitz.com/edit/vitejs-vite-z94syu8j?file=src%2FApp.tsx)
|
|
109
|
+
|
|
107
110
|
```tsx
|
|
108
111
|
import DsMarkdown from 'ds-markdown';
|
|
109
112
|
import 'ds-markdown/style.css';
|
|
@@ -179,21 +182,20 @@ React 19 带来了许多激动人心的新特性:
|
|
|
179
182
|
|
|
180
183
|
### 命令式 API (推荐流式场景)
|
|
181
184
|
|
|
182
|
-
```typescript
|
|
183
|
-
import { MarkdownCMD, MarkdownRef } from 'ds-markdown';
|
|
184
|
-
|
|
185
|
-
interface MarkdownRef {
|
|
186
|
-
push: (content: string, answerType: AnswerType) => void;
|
|
187
|
-
clear: () => void;
|
|
188
|
-
triggerWholeEnd: () => void;
|
|
189
|
-
}
|
|
190
|
-
```
|
|
191
|
-
|
|
192
185
|
| 方法 | 参数 | 说明 |
|
|
193
186
|
| ----------------- | ------------------------------------------- | ------------------ |
|
|
194
187
|
| `push` | `(content: string, answerType: AnswerType)` | 添加内容并开始打字 |
|
|
195
188
|
| `clear` | - | 清空所有内容和状态 |
|
|
196
189
|
| `triggerWholeEnd` | - | 手动触发完成回调 |
|
|
190
|
+
| `stop` | - | 暂停打字动画 |
|
|
191
|
+
| `resume` | - | 恢复打字动画 |
|
|
192
|
+
|
|
193
|
+
**用法示例:**
|
|
194
|
+
|
|
195
|
+
```tsx
|
|
196
|
+
markdownRef.current?.stop(); // 暂停动画
|
|
197
|
+
markdownRef.current?.resume(); // 恢复动画
|
|
198
|
+
```
|
|
197
199
|
|
|
198
200
|
---
|
|
199
201
|
|
|
@@ -252,10 +254,10 @@ interface MarkdownRef {
|
|
|
252
254
|
|
|
253
255
|
````tsx
|
|
254
256
|
import { useRef } from 'react';
|
|
255
|
-
import { MarkdownCMD,
|
|
257
|
+
import { MarkdownCMD, MarkdownCMDRef } from 'ds-markdown';
|
|
256
258
|
|
|
257
259
|
function StreamingChat() {
|
|
258
|
-
const markdownRef = useRef<
|
|
260
|
+
const markdownRef = useRef<MarkdownCMDRef>(null);
|
|
259
261
|
|
|
260
262
|
// 模拟 AI 流式响应
|
|
261
263
|
const simulateAIResponse = async () => {
|
|
@@ -382,7 +384,7 @@ const handleStreamingMarkdown = () => {
|
|
|
382
384
|
|
|
383
385
|
```tsx
|
|
384
386
|
// ✅ 推荐:命令式 API
|
|
385
|
-
const ref = useRef<
|
|
387
|
+
const ref = useRef<MarkdownCMDRef>(null);
|
|
386
388
|
useEffect(() => {
|
|
387
389
|
ref.current?.push(newChunk, 'answer');
|
|
388
390
|
}, [newChunk]);
|
|
@@ -395,9 +397,9 @@ const [content, setContent] = useState('');
|
|
|
395
397
|
### 3. 类型安全
|
|
396
398
|
|
|
397
399
|
```tsx
|
|
398
|
-
import {
|
|
400
|
+
import { MarkdownCMDRef } from 'ds-markdown';
|
|
399
401
|
|
|
400
|
-
const ref = useRef<
|
|
402
|
+
const ref = useRef<MarkdownCMDRef>(null);
|
|
401
403
|
// 完整的 TypeScript 类型提示
|
|
402
404
|
```
|
|
403
405
|
|
|
@@ -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,
|
|
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, onTypedChar, ...r
|
|
|
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,15 +17,7 @@ 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
|
-
const stableSegmentsRef = (0, react_1.useRef)([]);
|
|
27
|
-
const stableSegments = stableSegmentsRef.current;
|
|
28
|
-
/** 当前段落引用 */
|
|
29
|
-
const currentParagraphRef = (0, react_1.useRef)(undefined);
|
|
30
|
-
const currentSegment = currentParagraphRef.current;
|
|
20
|
+
const charIndexRef = (0, react_1.useRef)(0);
|
|
31
21
|
/** 整个内容引用 */
|
|
32
22
|
const wholeContentRef = (0, react_1.useRef)({
|
|
33
23
|
thinking: {
|
|
@@ -38,8 +28,8 @@ const MarkdownCMD = (0, react_1.forwardRef)(({ interval = 30, onEnd, onStart, on
|
|
|
38
28
|
content: '',
|
|
39
29
|
length: 0,
|
|
40
30
|
},
|
|
31
|
+
allLength: 0,
|
|
41
32
|
});
|
|
42
|
-
/** 触发更新 */
|
|
43
33
|
const [, setUpdate] = (0, react_1.useState)(false);
|
|
44
34
|
const triggerUpdate = () => {
|
|
45
35
|
setUpdate((prev) => !prev);
|
|
@@ -48,66 +38,16 @@ const MarkdownCMD = (0, react_1.forwardRef)(({ interval = 30, onEnd, onStart, on
|
|
|
48
38
|
* 处理字符显示逻辑
|
|
49
39
|
*/
|
|
50
40
|
const processCharDisplay = (char) => {
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
if (char.contentType === 'space' || char.contentType === 'split_segment') {
|
|
55
|
-
if (currentSegment) {
|
|
56
|
-
const newStableSegments = [...stableSegmentsRef.current];
|
|
57
|
-
// 放入到稳定队列
|
|
58
|
-
if (currentSegment) {
|
|
59
|
-
newStableSegments.push({ ...currentSegment, isTyped: false });
|
|
60
|
-
}
|
|
61
|
-
stableSegmentsRef.current = newStableSegments;
|
|
62
|
-
currentParagraphRef.current = undefined;
|
|
63
|
-
triggerUpdate();
|
|
64
|
-
}
|
|
65
|
-
return;
|
|
66
|
-
}
|
|
67
|
-
// 处理当前段落
|
|
68
|
-
const newCurrentParagraph = {
|
|
69
|
-
content: '',
|
|
70
|
-
isTyped: false,
|
|
71
|
-
type: 'text',
|
|
72
|
-
answerType: char.answerType,
|
|
73
|
-
tokensReference: {},
|
|
74
|
-
};
|
|
75
|
-
let _currentParagraph = currentSegment;
|
|
76
|
-
if (!_currentParagraph) {
|
|
77
|
-
// 如果当前没有段落,则直接设置为新当前段落
|
|
78
|
-
_currentParagraph = newCurrentParagraph;
|
|
79
|
-
}
|
|
80
|
-
else if (currentSegment && currentSegment?.answerType !== char.answerType) {
|
|
81
|
-
// 如果当前段落和当前字符的回答类型不一致,则需要处理成两个段落
|
|
82
|
-
const newStableSegments = [...stableSegmentsRef.current];
|
|
83
|
-
newStableSegments.push({ ...currentSegment, isTyped: false });
|
|
84
|
-
stableSegmentsRef.current = newStableSegments;
|
|
85
|
-
_currentParagraph = newCurrentParagraph;
|
|
86
|
-
}
|
|
87
|
-
const tokensReference = (0, deepClone_js_1.default)(_currentParagraph.tokensReference);
|
|
88
|
-
if (tokensReference[char.tokenId]) {
|
|
89
|
-
tokensReference[char.tokenId].raw += char.content;
|
|
90
|
-
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;
|
|
91
44
|
}
|
|
92
45
|
else {
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
raw: char.content,
|
|
96
|
-
};
|
|
46
|
+
wholeContentRef.current.answer.content += char.content;
|
|
47
|
+
wholeContentRef.current.answer.length += 1;
|
|
97
48
|
}
|
|
98
|
-
const newCurrentSegment = {
|
|
99
|
-
..._currentParagraph,
|
|
100
|
-
tokensReference,
|
|
101
|
-
content: (currentSegment?.content || '') + char.content,
|
|
102
|
-
isTyped: true,
|
|
103
|
-
};
|
|
104
|
-
currentParagraphRef.current = newCurrentSegment;
|
|
105
49
|
triggerUpdate();
|
|
106
50
|
};
|
|
107
|
-
/** 思考段落 */
|
|
108
|
-
const thinkingParagraphs = (0, react_1.useMemo)(() => stableSegments.filter((paragraph) => paragraph.answerType === 'thinking'), [stableSegments]);
|
|
109
|
-
/** 回答段落 */
|
|
110
|
-
const answerParagraphs = (0, react_1.useMemo)(() => stableSegments.filter((paragraph) => paragraph.answerType === 'answer'), [stableSegments]);
|
|
111
51
|
// 使用新的打字任务 hook
|
|
112
52
|
const typingTask = (0, useTypingTask_js_1.useTypingTask)({
|
|
113
53
|
timerType,
|
|
@@ -119,10 +59,6 @@ const MarkdownCMD = (0, react_1.forwardRef)(({ interval = 30, onEnd, onStart, on
|
|
|
119
59
|
processCharDisplay,
|
|
120
60
|
wholeContentRef,
|
|
121
61
|
});
|
|
122
|
-
const lastSegmentRawRef = (0, react_1.useRef)({
|
|
123
|
-
thinkingReference: null,
|
|
124
|
-
answerReference: null,
|
|
125
|
-
});
|
|
126
62
|
/**
|
|
127
63
|
* 内部推送处理逻辑
|
|
128
64
|
*/
|
|
@@ -130,103 +66,18 @@ const MarkdownCMD = (0, react_1.forwardRef)(({ interval = 30, onEnd, onStart, on
|
|
|
130
66
|
if (content.length === 0) {
|
|
131
67
|
return;
|
|
132
68
|
}
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
if (lastSegmentReference) {
|
|
146
|
-
lastSegmentRaw = lastSegmentReference.raw;
|
|
147
|
-
currentLastSegmentRaw = lastSegmentRaw + content;
|
|
148
|
-
}
|
|
149
|
-
else {
|
|
150
|
-
currentLastSegmentRaw = content;
|
|
151
|
-
}
|
|
152
|
-
const tokens = (0, compiler_js_1.compiler)(currentLastSegmentRaw);
|
|
153
|
-
// 如果最后一个token是space,则把lastSegmentRaw设置为空
|
|
154
|
-
if (tokens[tokens.length - 1].type === 'space') {
|
|
155
|
-
currentLastSegmentReference = null;
|
|
156
|
-
}
|
|
157
|
-
else {
|
|
158
|
-
// 如果上一个segment存在并且当前只有一个token,则说明是同一个segment
|
|
159
|
-
if (lastSegmentReference !== null && tokens.length === 1) {
|
|
160
|
-
const newCurrentLastSegmentReference = lastSegmentReference;
|
|
161
|
-
newCurrentLastSegmentReference.raw = newCurrentLastSegmentReference.raw + content;
|
|
162
|
-
currentLastSegmentReference = newCurrentLastSegmentReference;
|
|
163
|
-
}
|
|
164
|
-
else {
|
|
165
|
-
currentLastSegmentReference = tokens[tokens.length - 1];
|
|
166
|
-
}
|
|
167
|
-
}
|
|
168
|
-
const pushAndSplitSegment = (raw, tokenIndex, segmentTokenId) => {
|
|
169
|
-
const currentToken = tokens[tokenIndex];
|
|
170
|
-
if (tokenIndex > 0) {
|
|
171
|
-
const prevToken = tokens[tokenIndex - 1];
|
|
172
|
-
if (prevToken.type !== 'space' && currentToken.type !== 'space') {
|
|
173
|
-
charsRef.current.push({ content: '', answerType, contentType: 'split_segment', tokenId: currentToken.id, index: currentIndex++ });
|
|
174
|
-
}
|
|
175
|
-
}
|
|
176
|
-
charsRef.current.push(...raw.split('').map((char) => ({ content: char, answerType, contentType: 'segment', tokenId: segmentTokenId, index: currentIndex++ })));
|
|
177
|
-
};
|
|
178
|
-
if (!lastSegmentReference) {
|
|
179
|
-
tokens.forEach((token, i) => {
|
|
180
|
-
if (token.type === 'space') {
|
|
181
|
-
charsRef.current.push(...token.raw.split('').map((char) => ({ content: char, answerType, contentType: 'space', tokenId: token.id, index: currentIndex++ })));
|
|
182
|
-
}
|
|
183
|
-
else {
|
|
184
|
-
pushAndSplitSegment(token.raw, i, token.id);
|
|
185
|
-
}
|
|
186
|
-
});
|
|
187
|
-
}
|
|
188
|
-
else {
|
|
189
|
-
// debugger;
|
|
190
|
-
let str = '';
|
|
191
|
-
let nextTokenIndex = lastSegmentRaw.length;
|
|
192
|
-
for (let i = 0; i < tokens.length; i++) {
|
|
193
|
-
const token = tokens[i];
|
|
194
|
-
if (token.type === 'space') {
|
|
195
|
-
charsRef.current.push(...token.raw.split('').map((char) => ({ content: char, answerType, contentType: 'space', tokenId: token.id, index: currentIndex++ })));
|
|
196
|
-
str += token.raw;
|
|
197
|
-
}
|
|
198
|
-
else {
|
|
199
|
-
str += token.raw;
|
|
200
|
-
if (str.length < nextTokenIndex && i == 0) {
|
|
201
|
-
/** 如果当前字符串长度小于下一个token的索引,则需要将当前段落更新, 以修正不完整的token */
|
|
202
|
-
const lastSegmentReferenceId = lastSegmentReference.id;
|
|
203
|
-
const currentSegment = currentParagraphRef.current;
|
|
204
|
-
const tokensReference = currentSegment?.tokensReference || {};
|
|
205
|
-
const lastTokenReference = tokensReference[lastSegmentReferenceId];
|
|
206
|
-
if (lastTokenReference) {
|
|
207
|
-
const newTokensReference = { [lastSegmentReferenceId]: { startIndex: lastTokenReference.startIndex, raw: token.raw } };
|
|
208
|
-
// 去除charsRef中 tokenId = lastSegmentReferenceId 的字符
|
|
209
|
-
charsRef.current = charsRef.current.filter((char) => char.tokenId !== lastSegmentReferenceId);
|
|
210
|
-
const newCurrentSegment = { ...currentSegment, tokensReference: newTokensReference, isTyped: false, type: 'text', answerType };
|
|
211
|
-
newCurrentSegment.content = Object.values(newTokensReference).reduce((acc, curr) => acc + curr.raw, '');
|
|
212
|
-
currentParagraphRef.current = newCurrentSegment;
|
|
213
|
-
triggerUpdate();
|
|
214
|
-
}
|
|
215
|
-
else {
|
|
216
|
-
// TODO
|
|
217
|
-
}
|
|
218
|
-
}
|
|
219
|
-
const realRaw = str.slice(nextTokenIndex);
|
|
220
|
-
if (realRaw.length > 0) {
|
|
221
|
-
pushAndSplitSegment(realRaw, i, lastSegmentReference.id);
|
|
222
|
-
}
|
|
223
|
-
}
|
|
224
|
-
nextTokenIndex = str.length;
|
|
225
|
-
}
|
|
226
|
-
}
|
|
227
|
-
lastSegmentRawRef.current[`${answerType}Reference`] = currentLastSegmentReference;
|
|
228
|
-
wholeContent.content = wholeContent.content + content;
|
|
229
|
-
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;
|
|
230
81
|
if (!typingTask.isTyping()) {
|
|
231
82
|
typingTask.start();
|
|
232
83
|
}
|
|
@@ -245,20 +96,31 @@ const MarkdownCMD = (0, react_1.forwardRef)(({ interval = 30, onEnd, onStart, on
|
|
|
245
96
|
*/
|
|
246
97
|
clear: () => {
|
|
247
98
|
typingTask.stop();
|
|
99
|
+
typingTask.typedIsManualStopRef.current = false;
|
|
248
100
|
charsRef.current = [];
|
|
101
|
+
wholeContentRef.current = {
|
|
102
|
+
thinking: {
|
|
103
|
+
content: '',
|
|
104
|
+
length: 0,
|
|
105
|
+
},
|
|
106
|
+
answer: {
|
|
107
|
+
content: '',
|
|
108
|
+
length: 0,
|
|
109
|
+
},
|
|
110
|
+
allLength: 0,
|
|
111
|
+
};
|
|
249
112
|
isWholeTypedEndRef.current = false;
|
|
250
|
-
|
|
251
|
-
typingTask.clear();
|
|
252
|
-
// 清理缓冲区
|
|
253
|
-
const lastSegmentRef = lastSegmentRawRef.current;
|
|
254
|
-
lastSegmentRef.thinkingReference = null;
|
|
255
|
-
lastSegmentRef.answerReference = null;
|
|
256
|
-
wholeContentRef.current.thinking.content = '';
|
|
257
|
-
wholeContentRef.current.thinking.length = 0;
|
|
258
|
-
wholeContentRef.current.answer.content = '';
|
|
259
|
-
wholeContentRef.current.answer.length = 0;
|
|
113
|
+
charIndexRef.current = 0;
|
|
260
114
|
triggerUpdate();
|
|
261
115
|
},
|
|
116
|
+
/** 停止打字任务 */
|
|
117
|
+
stop: () => {
|
|
118
|
+
typingTask.stop();
|
|
119
|
+
},
|
|
120
|
+
/** 重新开始打字任务 */
|
|
121
|
+
resume: () => {
|
|
122
|
+
typingTask.resume();
|
|
123
|
+
},
|
|
262
124
|
/**
|
|
263
125
|
* 主动触发打字结束
|
|
264
126
|
*/
|
|
@@ -276,19 +138,14 @@ const MarkdownCMD = (0, react_1.forwardRef)(({ interval = 30, onEnd, onStart, on
|
|
|
276
138
|
* 刷新缓冲区 (新增方法)
|
|
277
139
|
*/
|
|
278
140
|
}));
|
|
279
|
-
const getParagraphs = (
|
|
280
|
-
return ((0, jsx_runtime_1.
|
|
281
|
-
if (paragraph.type === 'br') {
|
|
282
|
-
return null;
|
|
283
|
-
}
|
|
284
|
-
return ((0, jsx_runtime_1.jsx)(index_js_1.default, { theme: theme, children: paragraph.content || '' }, index));
|
|
285
|
-
}), 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 || '' }) }));
|
|
286
143
|
};
|
|
287
144
|
return ((0, jsx_runtime_1.jsxs)("div", { className: (0, classnames_1.default)({
|
|
288
145
|
'ds-markdown': true,
|
|
289
146
|
apple: true,
|
|
290
147
|
'ds-markdown-dark': theme === 'dark',
|
|
291
|
-
}), 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') })] }));
|
|
292
149
|
});
|
|
293
150
|
if (constant_js_1.__DEV__) {
|
|
294
151
|
MarkdownCMD.displayName = 'MarkdownCMD';
|