@automattic/jetpack-ai-client 0.12.0 → 0.12.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/CHANGELOG.md +10 -0
- package/build/hooks/use-ai-suggestions/index.d.ts +5 -1
- package/build/hooks/use-ai-suggestions/index.js +44 -35
- package/build/hooks/use-transcription-post-processing/index.js +0 -1
- package/build/index.d.ts +2 -1
- package/build/index.js +5 -1
- package/build/libs/index.d.ts +1 -0
- package/build/libs/index.js +1 -0
- package/build/libs/markdown/html-to-markdown.d.ts +23 -0
- package/build/libs/markdown/html-to-markdown.js +31 -0
- package/build/libs/markdown/index.d.ts +17 -0
- package/build/libs/markdown/index.js +14 -0
- package/build/libs/markdown/markdown-to-html.d.ts +24 -0
- package/build/libs/markdown/markdown-to-html.js +33 -0
- package/build/suggestions-event-source/index.d.ts +0 -1
- package/build/suggestions-event-source/index.js +3 -8
- package/build/types.d.ts +10 -0
- package/package.json +9 -5
- package/src/hooks/use-ai-suggestions/index.ts +78 -36
- package/src/hooks/use-transcription-post-processing/index.ts +0 -1
- package/src/index.ts +6 -1
- package/src/libs/index.ts +6 -0
- package/src/libs/markdown/README.md +74 -0
- package/src/libs/markdown/html-to-markdown.ts +42 -0
- package/src/libs/markdown/index.ts +28 -0
- package/src/libs/markdown/markdown-to-html.ts +48 -0
- package/src/suggestions-event-source/index.ts +3 -11
- package/src/types.ts +11 -0
package/CHANGELOG.md
CHANGED
|
@@ -5,6 +5,14 @@ All notable changes to this project will be documented in this file.
|
|
|
5
5
|
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/)
|
|
6
6
|
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
|
7
7
|
|
|
8
|
+
## [0.12.2] - 2024-04-22
|
|
9
|
+
### Added
|
|
10
|
+
- AI Client: Add Markdown and HTML conversions. [#36906]
|
|
11
|
+
|
|
12
|
+
## [0.12.1] - 2024-04-15
|
|
13
|
+
### Added
|
|
14
|
+
- AI Client: Add callbacks, initial requesting state and change error handling. [#36869]
|
|
15
|
+
|
|
8
16
|
## [0.12.0] - 2024-04-08
|
|
9
17
|
### Added
|
|
10
18
|
- Add error rejection in image generation. [#36709]
|
|
@@ -282,6 +290,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|
|
282
290
|
- Updated package dependencies. [#31659]
|
|
283
291
|
- Updated package dependencies. [#31785]
|
|
284
292
|
|
|
293
|
+
[0.12.2]: https://github.com/Automattic/jetpack-ai-client/compare/v0.12.1...v0.12.2
|
|
294
|
+
[0.12.1]: https://github.com/Automattic/jetpack-ai-client/compare/v0.12.0...v0.12.1
|
|
285
295
|
[0.12.0]: https://github.com/Automattic/jetpack-ai-client/compare/v0.11.0...v0.12.0
|
|
286
296
|
[0.11.0]: https://github.com/Automattic/jetpack-ai-client/compare/v0.10.1...v0.11.0
|
|
287
297
|
[0.10.1]: https://github.com/Automattic/jetpack-ai-client/compare/v0.10.0...v0.10.1
|
|
@@ -17,9 +17,12 @@ type useAiSuggestionsOptions = {
|
|
|
17
17
|
* AskQuestion options.
|
|
18
18
|
*/
|
|
19
19
|
askQuestionOptions?: AskQuestionOptionsArgProps;
|
|
20
|
+
initialRequestingState?: RequestingStateProp;
|
|
20
21
|
onSuggestion?: (suggestion: string) => void;
|
|
21
22
|
onDone?: (content: string) => void;
|
|
23
|
+
onStop?: () => void;
|
|
22
24
|
onError?: (error: RequestingErrorProps) => void;
|
|
25
|
+
onAllErrors?: (error: RequestingErrorProps) => void;
|
|
23
26
|
};
|
|
24
27
|
type useAiSuggestionsProps = {
|
|
25
28
|
suggestion: string;
|
|
@@ -29,6 +32,7 @@ type useAiSuggestionsProps = {
|
|
|
29
32
|
request: (prompt: PromptProp, options?: AskQuestionOptionsArgProps) => Promise<void>;
|
|
30
33
|
reset: () => void;
|
|
31
34
|
stopSuggestion: () => void;
|
|
35
|
+
handleErrorQuotaExceededError: () => void;
|
|
32
36
|
};
|
|
33
37
|
/**
|
|
34
38
|
* Get the error data for a given error code.
|
|
@@ -44,5 +48,5 @@ export declare function getErrorData(errorCode: SuggestionErrorCode): Requesting
|
|
|
44
48
|
* @param {useAiSuggestionsOptions} options - The options for the hook.
|
|
45
49
|
* @returns {useAiSuggestionsProps} The props for the hook.
|
|
46
50
|
*/
|
|
47
|
-
export default function useAiSuggestions({ prompt, autoRequest, askQuestionOptions, onSuggestion, onDone, onError, }?: useAiSuggestionsOptions): useAiSuggestionsProps;
|
|
51
|
+
export default function useAiSuggestions({ prompt, autoRequest, askQuestionOptions, initialRequestingState, onSuggestion, onDone, onStop, onError, onAllErrors, }?: useAiSuggestionsOptions): useAiSuggestionsProps;
|
|
48
52
|
export {};
|
|
@@ -3,13 +3,11 @@
|
|
|
3
3
|
*/
|
|
4
4
|
import { useCallback, useEffect, useRef, useState } from '@wordpress/element';
|
|
5
5
|
import { __ } from '@wordpress/i18n';
|
|
6
|
-
import debugFactory from 'debug';
|
|
7
6
|
/**
|
|
8
7
|
* Internal dependencies
|
|
9
8
|
*/
|
|
10
9
|
import askQuestion from '../../ask-question/index.js';
|
|
11
|
-
import { ERROR_MODERATION, ERROR_NETWORK, ERROR_QUOTA_EXCEEDED, ERROR_SERVICE_UNAVAILABLE, ERROR_UNCLEAR_PROMPT, } from '../../types.js';
|
|
12
|
-
const debug = debugFactory('jetpack-ai-client:use-suggestion');
|
|
10
|
+
import { ERROR_CONTEXT_TOO_LARGE, ERROR_MODERATION, ERROR_NETWORK, ERROR_QUOTA_EXCEEDED, ERROR_SERVICE_UNAVAILABLE, ERROR_UNCLEAR_PROMPT, ERROR_RESPONSE, } from '../../types.js';
|
|
13
11
|
/**
|
|
14
12
|
* Get the error data for a given error code.
|
|
15
13
|
*
|
|
@@ -42,6 +40,12 @@ export function getErrorData(errorCode) {
|
|
|
42
40
|
message: __('This request has been flagged by our moderation system. Please try to rephrase it and try again.', 'jetpack-ai-client'),
|
|
43
41
|
severity: 'info',
|
|
44
42
|
};
|
|
43
|
+
case ERROR_CONTEXT_TOO_LARGE:
|
|
44
|
+
return {
|
|
45
|
+
code: ERROR_CONTEXT_TOO_LARGE,
|
|
46
|
+
message: __('The content is too large to be processed all at once. Please try to shorten it or divide it into smaller parts.', 'jetpack-ai-client'),
|
|
47
|
+
severity: 'info',
|
|
48
|
+
};
|
|
45
49
|
case ERROR_NETWORK:
|
|
46
50
|
default:
|
|
47
51
|
return {
|
|
@@ -58,8 +62,8 @@ export function getErrorData(errorCode) {
|
|
|
58
62
|
* @param {useAiSuggestionsOptions} options - The options for the hook.
|
|
59
63
|
* @returns {useAiSuggestionsProps} The props for the hook.
|
|
60
64
|
*/
|
|
61
|
-
export default function useAiSuggestions({ prompt, autoRequest = false, askQuestionOptions = {}, onSuggestion, onDone, onError, } = {}) {
|
|
62
|
-
const [requestingState, setRequestingState] = useState(
|
|
65
|
+
export default function useAiSuggestions({ prompt, autoRequest = false, askQuestionOptions = {}, initialRequestingState = 'init', onSuggestion, onDone, onStop, onError, onAllErrors, } = {}) {
|
|
66
|
+
const [requestingState, setRequestingState] = useState(initialRequestingState);
|
|
63
67
|
const [suggestion, setSuggestion] = useState('');
|
|
64
68
|
const [error, setError] = useState();
|
|
65
69
|
// Store the event source in a ref, so we can handle it if needed.
|
|
@@ -81,9 +85,13 @@ export default function useAiSuggestions({ prompt, autoRequest = false, askQuest
|
|
|
81
85
|
* @returns {void}
|
|
82
86
|
*/
|
|
83
87
|
const handleDone = useCallback((event) => {
|
|
88
|
+
closeEventSource();
|
|
84
89
|
onDone?.(event?.detail);
|
|
85
90
|
setRequestingState('done');
|
|
86
91
|
}, [onDone]);
|
|
92
|
+
const handleAnyError = useCallback((event) => {
|
|
93
|
+
onAllErrors?.(event?.detail);
|
|
94
|
+
}, [onAllErrors]);
|
|
87
95
|
const handleError = useCallback((errorCode) => {
|
|
88
96
|
eventSourceRef?.current?.close();
|
|
89
97
|
setRequestingState('error');
|
|
@@ -103,35 +111,26 @@ export default function useAiSuggestions({ prompt, autoRequest = false, askQuest
|
|
|
103
111
|
* @returns {Promise<void>} The promise.
|
|
104
112
|
*/
|
|
105
113
|
const request = useCallback(async (promptArg, options = { ...askQuestionOptions }) => {
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
}
|
|
109
|
-
else {
|
|
110
|
-
debug('%o', promptArg);
|
|
111
|
-
}
|
|
114
|
+
// Clear any error.
|
|
115
|
+
setError(undefined);
|
|
112
116
|
// Set the request status.
|
|
113
117
|
setRequestingState('requesting');
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
return;
|
|
118
|
-
}
|
|
119
|
-
// Alias
|
|
120
|
-
const eventSource = eventSourceRef.current;
|
|
121
|
-
// Set the request status.
|
|
122
|
-
setRequestingState('suggesting');
|
|
123
|
-
eventSource.addEventListener('suggestion', handleSuggestion);
|
|
124
|
-
eventSource.addEventListener(ERROR_QUOTA_EXCEEDED, handleErrorQuotaExceededError);
|
|
125
|
-
eventSource.addEventListener(ERROR_UNCLEAR_PROMPT, handleUnclearPromptError);
|
|
126
|
-
eventSource.addEventListener(ERROR_SERVICE_UNAVAILABLE, handleServiceUnavailableError);
|
|
127
|
-
eventSource.addEventListener(ERROR_MODERATION, handleModerationError);
|
|
128
|
-
eventSource.addEventListener(ERROR_NETWORK, handleNetworkError);
|
|
129
|
-
eventSource.addEventListener('done', handleDone);
|
|
130
|
-
}
|
|
131
|
-
catch (e) {
|
|
132
|
-
// eslint-disable-next-line no-console
|
|
133
|
-
console.error(e);
|
|
118
|
+
eventSourceRef.current = await askQuestion(promptArg, options);
|
|
119
|
+
if (!eventSourceRef?.current) {
|
|
120
|
+
return;
|
|
134
121
|
}
|
|
122
|
+
// Alias
|
|
123
|
+
const eventSource = eventSourceRef.current;
|
|
124
|
+
// Set the request status.
|
|
125
|
+
setRequestingState('suggesting');
|
|
126
|
+
eventSource.addEventListener('suggestion', handleSuggestion);
|
|
127
|
+
eventSource.addEventListener(ERROR_QUOTA_EXCEEDED, handleErrorQuotaExceededError);
|
|
128
|
+
eventSource.addEventListener(ERROR_UNCLEAR_PROMPT, handleUnclearPromptError);
|
|
129
|
+
eventSource.addEventListener(ERROR_SERVICE_UNAVAILABLE, handleServiceUnavailableError);
|
|
130
|
+
eventSource.addEventListener(ERROR_MODERATION, handleModerationError);
|
|
131
|
+
eventSource.addEventListener(ERROR_NETWORK, handleNetworkError);
|
|
132
|
+
eventSource.addEventListener(ERROR_RESPONSE, handleAnyError);
|
|
133
|
+
eventSource.addEventListener('done', handleDone);
|
|
135
134
|
}, [
|
|
136
135
|
handleDone,
|
|
137
136
|
handleErrorQuotaExceededError,
|
|
@@ -152,11 +151,11 @@ export default function useAiSuggestions({ prompt, autoRequest = false, askQuest
|
|
|
152
151
|
setError(undefined);
|
|
153
152
|
}, []);
|
|
154
153
|
/**
|
|
155
|
-
*
|
|
154
|
+
* Close the event source connection.
|
|
156
155
|
*
|
|
157
156
|
* @returns {void}
|
|
158
157
|
*/
|
|
159
|
-
const
|
|
158
|
+
const closeEventSource = useCallback(() => {
|
|
160
159
|
if (!eventSourceRef?.current) {
|
|
161
160
|
return;
|
|
162
161
|
}
|
|
@@ -172,8 +171,6 @@ export default function useAiSuggestions({ prompt, autoRequest = false, askQuest
|
|
|
172
171
|
eventSource.removeEventListener(ERROR_MODERATION, handleModerationError);
|
|
173
172
|
eventSource.removeEventListener(ERROR_NETWORK, handleNetworkError);
|
|
174
173
|
eventSource.removeEventListener('done', handleDone);
|
|
175
|
-
// Set requesting state to done since the suggestion stopped.
|
|
176
|
-
setRequestingState('done');
|
|
177
174
|
}, [
|
|
178
175
|
eventSourceRef,
|
|
179
176
|
handleSuggestion,
|
|
@@ -184,6 +181,16 @@ export default function useAiSuggestions({ prompt, autoRequest = false, askQuest
|
|
|
184
181
|
handleNetworkError,
|
|
185
182
|
handleDone,
|
|
186
183
|
]);
|
|
184
|
+
/**
|
|
185
|
+
* Stop suggestion handler.
|
|
186
|
+
*
|
|
187
|
+
* @returns {void}
|
|
188
|
+
*/
|
|
189
|
+
const stopSuggestion = useCallback(() => {
|
|
190
|
+
closeEventSource();
|
|
191
|
+
onStop?.();
|
|
192
|
+
setRequestingState('done');
|
|
193
|
+
}, [onStop]);
|
|
187
194
|
// Request suggestions automatically when ready.
|
|
188
195
|
useEffect(() => {
|
|
189
196
|
// Check if there is a prompt to request.
|
|
@@ -208,6 +215,8 @@ export default function useAiSuggestions({ prompt, autoRequest = false, askQuest
|
|
|
208
215
|
request,
|
|
209
216
|
stopSuggestion,
|
|
210
217
|
reset,
|
|
218
|
+
// Error handlers
|
|
219
|
+
handleErrorQuotaExceededError,
|
|
211
220
|
// SuggestionsEventSource
|
|
212
221
|
eventSource: eventSourceRef.current,
|
|
213
222
|
};
|
|
@@ -39,7 +39,6 @@ export default function useTranscriptionPostProcessing({ feature, onReady, onErr
|
|
|
39
39
|
onError?.(errorData.message);
|
|
40
40
|
}, [setPostProcessingError, onError]);
|
|
41
41
|
const { request, stopSuggestion } = useAiSuggestions({
|
|
42
|
-
autoRequest: false,
|
|
43
42
|
onSuggestion: handleOnSuggestion,
|
|
44
43
|
onDone: handleOnDone,
|
|
45
44
|
onError: handleOnError,
|
package/build/index.d.ts
CHANGED
|
@@ -2,7 +2,7 @@ export { default as requestJwt } from './jwt/index.js';
|
|
|
2
2
|
export { default as SuggestionsEventSource } from './suggestions-event-source/index.js';
|
|
3
3
|
export { default as askQuestion } from './ask-question/index.js';
|
|
4
4
|
export { default as transcribeAudio } from './audio-transcription/index.js';
|
|
5
|
-
export { default as useAiSuggestions } from './hooks/use-ai-suggestions/index.js';
|
|
5
|
+
export { default as useAiSuggestions, getErrorData } from './hooks/use-ai-suggestions/index.js';
|
|
6
6
|
export { default as useMediaRecording } from './hooks/use-media-recording/index.js';
|
|
7
7
|
export { default as useAudioTranscription } from './hooks/use-audio-transcription/index.js';
|
|
8
8
|
export { default as useTranscriptionPostProcessing } from './hooks/use-transcription-post-processing/index.js';
|
|
@@ -12,3 +12,4 @@ export * from './icons/index.js';
|
|
|
12
12
|
export * from './components/index.js';
|
|
13
13
|
export * from './data-flow/index.js';
|
|
14
14
|
export * from './types.js';
|
|
15
|
+
export * from './libs/index.js';
|
package/build/index.js
CHANGED
|
@@ -8,7 +8,7 @@ export { default as transcribeAudio } from './audio-transcription/index.js';
|
|
|
8
8
|
/*
|
|
9
9
|
* Hooks
|
|
10
10
|
*/
|
|
11
|
-
export { default as useAiSuggestions } from './hooks/use-ai-suggestions/index.js';
|
|
11
|
+
export { default as useAiSuggestions, getErrorData } from './hooks/use-ai-suggestions/index.js';
|
|
12
12
|
export { default as useMediaRecording } from './hooks/use-media-recording/index.js';
|
|
13
13
|
export { default as useAudioTranscription } from './hooks/use-audio-transcription/index.js';
|
|
14
14
|
export { default as useTranscriptionPostProcessing } from './hooks/use-transcription-post-processing/index.js';
|
|
@@ -30,3 +30,7 @@ export * from './data-flow/index.js';
|
|
|
30
30
|
* Types
|
|
31
31
|
*/
|
|
32
32
|
export * from './types.js';
|
|
33
|
+
/*
|
|
34
|
+
* Libs
|
|
35
|
+
*/
|
|
36
|
+
export * from './libs/index.js';
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export { MarkdownToHTML, HTMLToMarkdown, renderHTMLFromMarkdown, renderMarkdownFromHTML, } from './markdown/index.js';
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export { MarkdownToHTML, HTMLToMarkdown, renderHTMLFromMarkdown, renderMarkdownFromHTML, } from './markdown/index.js';
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* External dependencies
|
|
3
|
+
*/
|
|
4
|
+
import TurndownService from 'turndown';
|
|
5
|
+
/**
|
|
6
|
+
* Types
|
|
7
|
+
*/
|
|
8
|
+
import type { Options, Rule } from 'turndown';
|
|
9
|
+
export default class HTMLToMarkdown {
|
|
10
|
+
turndownService: TurndownService;
|
|
11
|
+
constructor(options?: Options, rules?: {
|
|
12
|
+
[key: string]: Rule;
|
|
13
|
+
});
|
|
14
|
+
/**
|
|
15
|
+
* Renders HTML from Markdown content with specified processing rules.
|
|
16
|
+
* @param {object} options - The options to use when rendering the Markdown content
|
|
17
|
+
* @param {string} options.content - The HTML content to render
|
|
18
|
+
* @returns {string} The rendered Markdown content
|
|
19
|
+
*/
|
|
20
|
+
render({ content }: {
|
|
21
|
+
content: string;
|
|
22
|
+
}): string;
|
|
23
|
+
}
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* External dependencies
|
|
3
|
+
*/
|
|
4
|
+
import TurndownService from 'turndown';
|
|
5
|
+
const defaultTurndownOptions = { emDelimiter: '_', headingStyle: 'atx' };
|
|
6
|
+
const defaultTurndownRules = {
|
|
7
|
+
strikethrough: {
|
|
8
|
+
filter: ['del', 's'],
|
|
9
|
+
replacement: function (content) {
|
|
10
|
+
return '~~' + content + '~~';
|
|
11
|
+
},
|
|
12
|
+
},
|
|
13
|
+
};
|
|
14
|
+
export default class HTMLToMarkdown {
|
|
15
|
+
turndownService;
|
|
16
|
+
constructor(options = defaultTurndownOptions, rules = defaultTurndownRules) {
|
|
17
|
+
this.turndownService = new TurndownService(options);
|
|
18
|
+
for (const rule in rules) {
|
|
19
|
+
this.turndownService.addRule(rule, rules[rule]);
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
/**
|
|
23
|
+
* Renders HTML from Markdown content with specified processing rules.
|
|
24
|
+
* @param {object} options - The options to use when rendering the Markdown content
|
|
25
|
+
* @param {string} options.content - The HTML content to render
|
|
26
|
+
* @returns {string} The rendered Markdown content
|
|
27
|
+
*/
|
|
28
|
+
render({ content }) {
|
|
29
|
+
return this.turndownService.turndown(content);
|
|
30
|
+
}
|
|
31
|
+
}
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Internal dependencies
|
|
3
|
+
*/
|
|
4
|
+
import HTMLToMarkdown from './html-to-markdown.js';
|
|
5
|
+
import MarkdownToHTML from './markdown-to-html.js';
|
|
6
|
+
/**
|
|
7
|
+
* Types
|
|
8
|
+
*/
|
|
9
|
+
import type { Fix as HTMLFix } from './markdown-to-html.js';
|
|
10
|
+
declare const renderHTMLFromMarkdown: ({ content, rules, }: {
|
|
11
|
+
content: string;
|
|
12
|
+
rules?: Array<HTMLFix> | 'all';
|
|
13
|
+
}) => string;
|
|
14
|
+
declare const renderMarkdownFromHTML: ({ content }: {
|
|
15
|
+
content: string;
|
|
16
|
+
}) => string;
|
|
17
|
+
export { MarkdownToHTML, HTMLToMarkdown, renderHTMLFromMarkdown, renderMarkdownFromHTML };
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Internal dependencies
|
|
3
|
+
*/
|
|
4
|
+
import HTMLToMarkdown from './html-to-markdown.js';
|
|
5
|
+
import MarkdownToHTML from './markdown-to-html.js';
|
|
6
|
+
const defaultMarkdownConverter = new MarkdownToHTML();
|
|
7
|
+
const defaultHTMLConverter = new HTMLToMarkdown();
|
|
8
|
+
const renderHTMLFromMarkdown = ({ content, rules = 'all', }) => {
|
|
9
|
+
return defaultMarkdownConverter.render({ content, rules });
|
|
10
|
+
};
|
|
11
|
+
const renderMarkdownFromHTML = ({ content }) => {
|
|
12
|
+
return defaultHTMLConverter.render({ content });
|
|
13
|
+
};
|
|
14
|
+
export { MarkdownToHTML, HTMLToMarkdown, renderHTMLFromMarkdown, renderMarkdownFromHTML };
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* External dependencies
|
|
3
|
+
*/
|
|
4
|
+
import MarkdownIt from 'markdown-it';
|
|
5
|
+
/**
|
|
6
|
+
* Types
|
|
7
|
+
*/
|
|
8
|
+
import type { Options } from 'markdown-it';
|
|
9
|
+
export type Fix = 'list';
|
|
10
|
+
export default class MarkdownToHTML {
|
|
11
|
+
markdownConverter: MarkdownIt;
|
|
12
|
+
constructor(options?: Options);
|
|
13
|
+
/**
|
|
14
|
+
* Renders HTML from Markdown content with specified processing rules.
|
|
15
|
+
* @param {object} options - The options to use when rendering the HTML content
|
|
16
|
+
* @param {string} options.content - The Markdown content to render
|
|
17
|
+
* @param {string} options.rules - The rules to apply to the rendered content
|
|
18
|
+
* @returns {string} The rendered HTML content
|
|
19
|
+
*/
|
|
20
|
+
render({ content, rules }: {
|
|
21
|
+
content: string;
|
|
22
|
+
rules: Array<Fix> | 'all';
|
|
23
|
+
}): string;
|
|
24
|
+
}
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* External dependencies
|
|
3
|
+
*/
|
|
4
|
+
import MarkdownIt from 'markdown-it';
|
|
5
|
+
const fixes = {
|
|
6
|
+
list: (content) => {
|
|
7
|
+
// Fix list indentation
|
|
8
|
+
return content.replace(/<li>\s+<p>/g, '<li>').replace(/<\/p>\s+<\/li>/g, '</li>');
|
|
9
|
+
},
|
|
10
|
+
};
|
|
11
|
+
const defaultMarkdownItOptions = {
|
|
12
|
+
breaks: true,
|
|
13
|
+
};
|
|
14
|
+
export default class MarkdownToHTML {
|
|
15
|
+
markdownConverter;
|
|
16
|
+
constructor(options = defaultMarkdownItOptions) {
|
|
17
|
+
this.markdownConverter = new MarkdownIt(options);
|
|
18
|
+
}
|
|
19
|
+
/**
|
|
20
|
+
* Renders HTML from Markdown content with specified processing rules.
|
|
21
|
+
* @param {object} options - The options to use when rendering the HTML content
|
|
22
|
+
* @param {string} options.content - The Markdown content to render
|
|
23
|
+
* @param {string} options.rules - The rules to apply to the rendered content
|
|
24
|
+
* @returns {string} The rendered HTML content
|
|
25
|
+
*/
|
|
26
|
+
render({ content, rules = 'all' }) {
|
|
27
|
+
const rendered = this.markdownConverter.render(content);
|
|
28
|
+
const rulesToApply = rules === 'all' ? Object.keys(fixes) : rules;
|
|
29
|
+
return rulesToApply.reduce((renderedContent, rule) => {
|
|
30
|
+
return fixes[rule](renderedContent);
|
|
31
|
+
}, rendered);
|
|
32
|
+
}
|
|
33
|
+
}
|
|
@@ -49,7 +49,6 @@ export default class SuggestionsEventSource extends EventTarget {
|
|
|
49
49
|
checkForUnclearPrompt(): void;
|
|
50
50
|
close(): void;
|
|
51
51
|
processEvent(e: EventSourceMessage): void;
|
|
52
|
-
processConnectionError(response: any): void;
|
|
53
52
|
processErrorEvent(e: any): void;
|
|
54
53
|
}
|
|
55
54
|
export {};
|
|
@@ -128,7 +128,9 @@ export default class SuggestionsEventSource extends EventTarget {
|
|
|
128
128
|
if (response.status >= 400 &&
|
|
129
129
|
response.status <= 500 &&
|
|
130
130
|
![413, 422, 429].includes(response.status)) {
|
|
131
|
-
|
|
131
|
+
debug('Connection error: %o', response);
|
|
132
|
+
errorCode = ERROR_NETWORK;
|
|
133
|
+
this.dispatchEvent(new CustomEvent(ERROR_NETWORK, { detail: response }));
|
|
132
134
|
}
|
|
133
135
|
/*
|
|
134
136
|
* error code 503
|
|
@@ -264,13 +266,6 @@ export default class SuggestionsEventSource extends EventTarget {
|
|
|
264
266
|
this.dispatchEvent(new CustomEvent('functionCallChunk', { detail: this.fullFunctionCall }));
|
|
265
267
|
}
|
|
266
268
|
}
|
|
267
|
-
processConnectionError(response) {
|
|
268
|
-
debug('Connection error: %o', response);
|
|
269
|
-
this.dispatchEvent(new CustomEvent(ERROR_NETWORK, { detail: response }));
|
|
270
|
-
this.dispatchEvent(new CustomEvent(ERROR_RESPONSE, {
|
|
271
|
-
detail: getErrorData(ERROR_NETWORK),
|
|
272
|
-
}));
|
|
273
|
-
}
|
|
274
269
|
processErrorEvent(e) {
|
|
275
270
|
debug('onerror: %o', e);
|
|
276
271
|
// Dispatch a generic network error event
|
package/build/types.d.ts
CHANGED
|
@@ -28,4 +28,14 @@ export type { RecordingState } from './hooks/use-media-recording/index.js';
|
|
|
28
28
|
export type CancelablePromise<T = void> = Promise<T> & {
|
|
29
29
|
canceled?: boolean;
|
|
30
30
|
};
|
|
31
|
+
export type Block = {
|
|
32
|
+
attributes?: {
|
|
33
|
+
[key: string]: unknown;
|
|
34
|
+
};
|
|
35
|
+
clientId?: string;
|
|
36
|
+
innerBlocks?: Block[];
|
|
37
|
+
isValid?: boolean;
|
|
38
|
+
name?: string;
|
|
39
|
+
originalContent?: string;
|
|
40
|
+
};
|
|
31
41
|
export type TranscriptionState = RecordingState | 'validating' | 'processing' | 'error';
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"private": false,
|
|
3
3
|
"name": "@automattic/jetpack-ai-client",
|
|
4
|
-
"version": "0.12.
|
|
4
|
+
"version": "0.12.2",
|
|
5
5
|
"description": "A JS client for consuming Jetpack AI services",
|
|
6
6
|
"homepage": "https://github.com/Automattic/jetpack/tree/HEAD/projects/js-packages/ai-client/#readme",
|
|
7
7
|
"bugs": {
|
|
@@ -26,6 +26,8 @@
|
|
|
26
26
|
"@storybook/addon-actions": "8.0.6",
|
|
27
27
|
"@storybook/blocks": "8.0.6",
|
|
28
28
|
"@storybook/react": "8.0.6",
|
|
29
|
+
"@types/markdown-it": "14.0.0",
|
|
30
|
+
"@types/turndown": "5.0.4",
|
|
29
31
|
"jest": "^29.6.2",
|
|
30
32
|
"jest-environment-jsdom": "29.7.0",
|
|
31
33
|
"typescript": "5.0.4"
|
|
@@ -39,9 +41,9 @@
|
|
|
39
41
|
"main": "./build/index.js",
|
|
40
42
|
"types": "./build/index.d.ts",
|
|
41
43
|
"dependencies": {
|
|
42
|
-
"@automattic/jetpack-base-styles": "^0.6.
|
|
43
|
-
"@automattic/jetpack-connection": "^0.33.
|
|
44
|
-
"@automattic/jetpack-shared-extension-utils": "^0.14.
|
|
44
|
+
"@automattic/jetpack-base-styles": "^0.6.22",
|
|
45
|
+
"@automattic/jetpack-connection": "^0.33.8",
|
|
46
|
+
"@automattic/jetpack-shared-extension-utils": "^0.14.10",
|
|
45
47
|
"@microsoft/fetch-event-source": "2.0.1",
|
|
46
48
|
"@types/react": "18.2.74",
|
|
47
49
|
"@wordpress/api-fetch": "6.52.0",
|
|
@@ -54,7 +56,9 @@
|
|
|
54
56
|
"@wordpress/icons": "9.46.0",
|
|
55
57
|
"classnames": "2.3.2",
|
|
56
58
|
"debug": "4.3.4",
|
|
59
|
+
"markdown-it": "14.0.0",
|
|
57
60
|
"react": "18.2.0",
|
|
58
|
-
"react-dom": "18.2.0"
|
|
61
|
+
"react-dom": "18.2.0",
|
|
62
|
+
"turndown": "7.1.2"
|
|
59
63
|
}
|
|
60
64
|
}
|
|
@@ -3,17 +3,18 @@
|
|
|
3
3
|
*/
|
|
4
4
|
import { useCallback, useEffect, useRef, useState } from '@wordpress/element';
|
|
5
5
|
import { __ } from '@wordpress/i18n';
|
|
6
|
-
import debugFactory from 'debug';
|
|
7
6
|
/**
|
|
8
7
|
* Internal dependencies
|
|
9
8
|
*/
|
|
10
9
|
import askQuestion from '../../ask-question/index.js';
|
|
11
10
|
import {
|
|
11
|
+
ERROR_CONTEXT_TOO_LARGE,
|
|
12
12
|
ERROR_MODERATION,
|
|
13
13
|
ERROR_NETWORK,
|
|
14
14
|
ERROR_QUOTA_EXCEEDED,
|
|
15
15
|
ERROR_SERVICE_UNAVAILABLE,
|
|
16
16
|
ERROR_UNCLEAR_PROMPT,
|
|
17
|
+
ERROR_RESPONSE,
|
|
17
18
|
} from '../../types.js';
|
|
18
19
|
/**
|
|
19
20
|
* Types & constants
|
|
@@ -56,6 +57,11 @@ type useAiSuggestionsOptions = {
|
|
|
56
57
|
*/
|
|
57
58
|
askQuestionOptions?: AskQuestionOptionsArgProps;
|
|
58
59
|
|
|
60
|
+
/*
|
|
61
|
+
* Initial requesting state.
|
|
62
|
+
*/
|
|
63
|
+
initialRequestingState?: RequestingStateProp;
|
|
64
|
+
|
|
59
65
|
/*
|
|
60
66
|
* onSuggestion callback.
|
|
61
67
|
*/
|
|
@@ -66,10 +72,20 @@ type useAiSuggestionsOptions = {
|
|
|
66
72
|
*/
|
|
67
73
|
onDone?: ( content: string ) => void;
|
|
68
74
|
|
|
75
|
+
/*
|
|
76
|
+
* onStop callback.
|
|
77
|
+
*/
|
|
78
|
+
onStop?: () => void;
|
|
79
|
+
|
|
69
80
|
/*
|
|
70
81
|
* onError callback.
|
|
71
82
|
*/
|
|
72
83
|
onError?: ( error: RequestingErrorProps ) => void;
|
|
84
|
+
|
|
85
|
+
/*
|
|
86
|
+
* Error callback common for all errors.
|
|
87
|
+
*/
|
|
88
|
+
onAllErrors?: ( error: RequestingErrorProps ) => void;
|
|
73
89
|
};
|
|
74
90
|
|
|
75
91
|
type useAiSuggestionsProps = {
|
|
@@ -107,9 +123,12 @@ type useAiSuggestionsProps = {
|
|
|
107
123
|
* The handler to stop a suggestion.
|
|
108
124
|
*/
|
|
109
125
|
stopSuggestion: () => void;
|
|
110
|
-
};
|
|
111
126
|
|
|
112
|
-
|
|
127
|
+
/*
|
|
128
|
+
* The handler to handle the quota exceeded error.
|
|
129
|
+
*/
|
|
130
|
+
handleErrorQuotaExceededError: () => void;
|
|
131
|
+
};
|
|
113
132
|
|
|
114
133
|
/**
|
|
115
134
|
* Get the error data for a given error code.
|
|
@@ -149,6 +168,15 @@ export function getErrorData( errorCode: SuggestionErrorCode ): RequestingErrorP
|
|
|
149
168
|
),
|
|
150
169
|
severity: 'info',
|
|
151
170
|
};
|
|
171
|
+
case ERROR_CONTEXT_TOO_LARGE:
|
|
172
|
+
return {
|
|
173
|
+
code: ERROR_CONTEXT_TOO_LARGE,
|
|
174
|
+
message: __(
|
|
175
|
+
'The content is too large to be processed all at once. Please try to shorten it or divide it into smaller parts.',
|
|
176
|
+
'jetpack-ai-client'
|
|
177
|
+
),
|
|
178
|
+
severity: 'info',
|
|
179
|
+
};
|
|
152
180
|
case ERROR_NETWORK:
|
|
153
181
|
default:
|
|
154
182
|
return {
|
|
@@ -173,11 +201,15 @@ export default function useAiSuggestions( {
|
|
|
173
201
|
prompt,
|
|
174
202
|
autoRequest = false,
|
|
175
203
|
askQuestionOptions = {},
|
|
204
|
+
initialRequestingState = 'init',
|
|
176
205
|
onSuggestion,
|
|
177
206
|
onDone,
|
|
207
|
+
onStop,
|
|
178
208
|
onError,
|
|
209
|
+
onAllErrors,
|
|
179
210
|
}: useAiSuggestionsOptions = {} ): useAiSuggestionsProps {
|
|
180
|
-
const [ requestingState, setRequestingState ] =
|
|
211
|
+
const [ requestingState, setRequestingState ] =
|
|
212
|
+
useState< RequestingStateProp >( initialRequestingState );
|
|
181
213
|
const [ suggestion, setSuggestion ] = useState< string >( '' );
|
|
182
214
|
const [ error, setError ] = useState< RequestingErrorProps >();
|
|
183
215
|
|
|
@@ -206,12 +238,20 @@ export default function useAiSuggestions( {
|
|
|
206
238
|
*/
|
|
207
239
|
const handleDone = useCallback(
|
|
208
240
|
( event: CustomEvent ) => {
|
|
241
|
+
closeEventSource();
|
|
209
242
|
onDone?.( event?.detail );
|
|
210
243
|
setRequestingState( 'done' );
|
|
211
244
|
},
|
|
212
245
|
[ onDone ]
|
|
213
246
|
);
|
|
214
247
|
|
|
248
|
+
const handleAnyError = useCallback(
|
|
249
|
+
( event: CustomEvent ) => {
|
|
250
|
+
onAllErrors?.( event?.detail );
|
|
251
|
+
},
|
|
252
|
+
[ onAllErrors ]
|
|
253
|
+
);
|
|
254
|
+
|
|
215
255
|
const handleError = useCallback(
|
|
216
256
|
( errorCode: SuggestionErrorCode ) => {
|
|
217
257
|
eventSourceRef?.current?.close();
|
|
@@ -250,43 +290,34 @@ export default function useAiSuggestions( {
|
|
|
250
290
|
promptArg: PromptProp,
|
|
251
291
|
options: AskQuestionOptionsArgProps = { ...askQuestionOptions }
|
|
252
292
|
) => {
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
debug( '(%s/%s) %o\n%s', i + 1, promptArg.length, `[${ role }]`, promptContent )
|
|
256
|
-
);
|
|
257
|
-
} else {
|
|
258
|
-
debug( '%o', promptArg );
|
|
259
|
-
}
|
|
293
|
+
// Clear any error.
|
|
294
|
+
setError( undefined );
|
|
260
295
|
|
|
261
296
|
// Set the request status.
|
|
262
297
|
setRequestingState( 'requesting' );
|
|
263
298
|
|
|
264
|
-
|
|
265
|
-
eventSourceRef.current = await askQuestion( promptArg, options );
|
|
299
|
+
eventSourceRef.current = await askQuestion( promptArg, options );
|
|
266
300
|
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
301
|
+
if ( ! eventSourceRef?.current ) {
|
|
302
|
+
return;
|
|
303
|
+
}
|
|
270
304
|
|
|
271
|
-
|
|
272
|
-
|
|
305
|
+
// Alias
|
|
306
|
+
const eventSource = eventSourceRef.current;
|
|
273
307
|
|
|
274
|
-
|
|
275
|
-
|
|
308
|
+
// Set the request status.
|
|
309
|
+
setRequestingState( 'suggesting' );
|
|
276
310
|
|
|
277
|
-
|
|
311
|
+
eventSource.addEventListener( 'suggestion', handleSuggestion );
|
|
278
312
|
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
313
|
+
eventSource.addEventListener( ERROR_QUOTA_EXCEEDED, handleErrorQuotaExceededError );
|
|
314
|
+
eventSource.addEventListener( ERROR_UNCLEAR_PROMPT, handleUnclearPromptError );
|
|
315
|
+
eventSource.addEventListener( ERROR_SERVICE_UNAVAILABLE, handleServiceUnavailableError );
|
|
316
|
+
eventSource.addEventListener( ERROR_MODERATION, handleModerationError );
|
|
317
|
+
eventSource.addEventListener( ERROR_NETWORK, handleNetworkError );
|
|
318
|
+
eventSource.addEventListener( ERROR_RESPONSE, handleAnyError );
|
|
284
319
|
|
|
285
|
-
|
|
286
|
-
} catch ( e ) {
|
|
287
|
-
// eslint-disable-next-line no-console
|
|
288
|
-
console.error( e );
|
|
289
|
-
}
|
|
320
|
+
eventSource.addEventListener( 'done', handleDone );
|
|
290
321
|
},
|
|
291
322
|
[
|
|
292
323
|
handleDone,
|
|
@@ -311,11 +342,11 @@ export default function useAiSuggestions( {
|
|
|
311
342
|
}, [] );
|
|
312
343
|
|
|
313
344
|
/**
|
|
314
|
-
*
|
|
345
|
+
* Close the event source connection.
|
|
315
346
|
*
|
|
316
347
|
* @returns {void}
|
|
317
348
|
*/
|
|
318
|
-
const
|
|
349
|
+
const closeEventSource = useCallback( () => {
|
|
319
350
|
if ( ! eventSourceRef?.current ) {
|
|
320
351
|
return;
|
|
321
352
|
}
|
|
@@ -336,9 +367,6 @@ export default function useAiSuggestions( {
|
|
|
336
367
|
eventSource.removeEventListener( ERROR_NETWORK, handleNetworkError );
|
|
337
368
|
|
|
338
369
|
eventSource.removeEventListener( 'done', handleDone );
|
|
339
|
-
|
|
340
|
-
// Set requesting state to done since the suggestion stopped.
|
|
341
|
-
setRequestingState( 'done' );
|
|
342
370
|
}, [
|
|
343
371
|
eventSourceRef,
|
|
344
372
|
handleSuggestion,
|
|
@@ -350,6 +378,17 @@ export default function useAiSuggestions( {
|
|
|
350
378
|
handleDone,
|
|
351
379
|
] );
|
|
352
380
|
|
|
381
|
+
/**
|
|
382
|
+
* Stop suggestion handler.
|
|
383
|
+
*
|
|
384
|
+
* @returns {void}
|
|
385
|
+
*/
|
|
386
|
+
const stopSuggestion = useCallback( () => {
|
|
387
|
+
closeEventSource();
|
|
388
|
+
onStop?.();
|
|
389
|
+
setRequestingState( 'done' );
|
|
390
|
+
}, [ onStop ] );
|
|
391
|
+
|
|
353
392
|
// Request suggestions automatically when ready.
|
|
354
393
|
useEffect( () => {
|
|
355
394
|
// Check if there is a prompt to request.
|
|
@@ -379,6 +418,9 @@ export default function useAiSuggestions( {
|
|
|
379
418
|
stopSuggestion,
|
|
380
419
|
reset,
|
|
381
420
|
|
|
421
|
+
// Error handlers
|
|
422
|
+
handleErrorQuotaExceededError,
|
|
423
|
+
|
|
382
424
|
// SuggestionsEventSource
|
|
383
425
|
eventSource: eventSourceRef.current,
|
|
384
426
|
};
|
package/src/index.ts
CHANGED
|
@@ -9,7 +9,7 @@ export { default as transcribeAudio } from './audio-transcription/index.js';
|
|
|
9
9
|
/*
|
|
10
10
|
* Hooks
|
|
11
11
|
*/
|
|
12
|
-
export { default as useAiSuggestions } from './hooks/use-ai-suggestions/index.js';
|
|
12
|
+
export { default as useAiSuggestions, getErrorData } from './hooks/use-ai-suggestions/index.js';
|
|
13
13
|
export { default as useMediaRecording } from './hooks/use-media-recording/index.js';
|
|
14
14
|
export { default as useAudioTranscription } from './hooks/use-audio-transcription/index.js';
|
|
15
15
|
export { default as useTranscriptionPostProcessing } from './hooks/use-transcription-post-processing/index.js';
|
|
@@ -35,3 +35,8 @@ export * from './data-flow/index.js';
|
|
|
35
35
|
* Types
|
|
36
36
|
*/
|
|
37
37
|
export * from './types.js';
|
|
38
|
+
|
|
39
|
+
/*
|
|
40
|
+
* Libs
|
|
41
|
+
*/
|
|
42
|
+
export * from './libs/index.js';
|
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
# Markdown converters
|
|
2
|
+
|
|
3
|
+
Typescript functions and classes to convert Markdown to and from HTML.
|
|
4
|
+
|
|
5
|
+
## HTML to Markdown
|
|
6
|
+
|
|
7
|
+
The HTML to Markdown conversion uses the [Turndown](https://github.com/mixmark-io/turndown) library and supports Turndown's options and rules.
|
|
8
|
+
|
|
9
|
+
Example:
|
|
10
|
+
```typescript
|
|
11
|
+
/**
|
|
12
|
+
* External dependencies
|
|
13
|
+
*/
|
|
14
|
+
import { renderMarkdownFromHTML } from '@automattic/jetpack-ai-client';
|
|
15
|
+
|
|
16
|
+
const htmlContent = '<strong>Hello world</strong>';
|
|
17
|
+
const markdownContent = renderMarkdownFromHTML( { content: htmlContent } );
|
|
18
|
+
// **Hello world**
|
|
19
|
+
```
|
|
20
|
+
|
|
21
|
+
To use custom options and rules:
|
|
22
|
+
```typescript
|
|
23
|
+
/**
|
|
24
|
+
* External dependencies
|
|
25
|
+
*/
|
|
26
|
+
import { HTMLToMarkdown } from '@automattic/jetpack-ai-client';
|
|
27
|
+
|
|
28
|
+
const htmlContent = '<strong>Hello world</strong>';
|
|
29
|
+
const options = { headingStyle: 'setext' };
|
|
30
|
+
const rules = {
|
|
31
|
+
customStrong: {
|
|
32
|
+
filter: [ 'strong' ],
|
|
33
|
+
replacement: function( content: string ) {
|
|
34
|
+
return '***' + content + '***';
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
};
|
|
38
|
+
const renderer = new HTMLToMarkdown( options, rules );
|
|
39
|
+
const markdownContent = renderer.render( { content: htmlContent } );
|
|
40
|
+
// ***Hello world***
|
|
41
|
+
```
|
|
42
|
+
|
|
43
|
+
## Markdown to HTML
|
|
44
|
+
|
|
45
|
+
The Markdown to HTML conversion uses the [markdown-it](https://github.com/markdown-it/markdown-it) library and supports markdown-it's options. It also adds access to common fixes.
|
|
46
|
+
|
|
47
|
+
Example:
|
|
48
|
+
```typescript
|
|
49
|
+
/**
|
|
50
|
+
* External dependencies
|
|
51
|
+
*/
|
|
52
|
+
import { renderHTMLFromMarkdown } from '@automattic/jetpack-ai-client';
|
|
53
|
+
|
|
54
|
+
const markdownContent = '**Hello world**';
|
|
55
|
+
const htmlContent = renderHTMLFromMarkdown( { content: markdownContent, rules: 'all' } ); // 'all' is a default value
|
|
56
|
+
// <p><strong>Hello world</strong></p>\n
|
|
57
|
+
```
|
|
58
|
+
|
|
59
|
+
To use custom options and fixes:
|
|
60
|
+
```typescript
|
|
61
|
+
/**
|
|
62
|
+
* External dependencies
|
|
63
|
+
*/
|
|
64
|
+
import { MarkdownToHTML } from '@automattic/jetpack-ai-client';
|
|
65
|
+
|
|
66
|
+
const markdownContent = '**Hello world**';
|
|
67
|
+
const options = { breaks: 'false' };
|
|
68
|
+
const rules = [ 'list' ];
|
|
69
|
+
const renderer = new MarkdownToHTML( options );
|
|
70
|
+
const htmlContent = renderer.render( { content: markdownContent, rules } );
|
|
71
|
+
// <p><strong>Hello world</strong></p>\n
|
|
72
|
+
```
|
|
73
|
+
|
|
74
|
+
Currently `rules` only supports `'all'` and `['list']`. Further specific fixes can be added when necessary.
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* External dependencies
|
|
3
|
+
*/
|
|
4
|
+
import TurndownService from 'turndown';
|
|
5
|
+
/**
|
|
6
|
+
* Types
|
|
7
|
+
*/
|
|
8
|
+
import type { Options, Rule } from 'turndown';
|
|
9
|
+
|
|
10
|
+
const defaultTurndownOptions: Options = { emDelimiter: '_', headingStyle: 'atx' };
|
|
11
|
+
const defaultTurndownRules: { [ key: string ]: Rule } = {
|
|
12
|
+
strikethrough: {
|
|
13
|
+
filter: [ 'del', 's' ],
|
|
14
|
+
replacement: function ( content: string ) {
|
|
15
|
+
return '~~' + content + '~~';
|
|
16
|
+
},
|
|
17
|
+
},
|
|
18
|
+
};
|
|
19
|
+
|
|
20
|
+
export default class HTMLToMarkdown {
|
|
21
|
+
turndownService: TurndownService;
|
|
22
|
+
|
|
23
|
+
constructor(
|
|
24
|
+
options: Options = defaultTurndownOptions,
|
|
25
|
+
rules: { [ key: string ]: Rule } = defaultTurndownRules
|
|
26
|
+
) {
|
|
27
|
+
this.turndownService = new TurndownService( options );
|
|
28
|
+
for ( const rule in rules ) {
|
|
29
|
+
this.turndownService.addRule( rule, rules[ rule ] );
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
/**
|
|
34
|
+
* Renders HTML from Markdown content with specified processing rules.
|
|
35
|
+
* @param {object} options - The options to use when rendering the Markdown content
|
|
36
|
+
* @param {string} options.content - The HTML content to render
|
|
37
|
+
* @returns {string} The rendered Markdown content
|
|
38
|
+
*/
|
|
39
|
+
render( { content }: { content: string } ): string {
|
|
40
|
+
return this.turndownService.turndown( content );
|
|
41
|
+
}
|
|
42
|
+
}
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Internal dependencies
|
|
3
|
+
*/
|
|
4
|
+
import HTMLToMarkdown from './html-to-markdown.js';
|
|
5
|
+
import MarkdownToHTML from './markdown-to-html.js';
|
|
6
|
+
/**
|
|
7
|
+
* Types
|
|
8
|
+
*/
|
|
9
|
+
import type { Fix as HTMLFix } from './markdown-to-html.js';
|
|
10
|
+
|
|
11
|
+
const defaultMarkdownConverter = new MarkdownToHTML();
|
|
12
|
+
const defaultHTMLConverter = new HTMLToMarkdown();
|
|
13
|
+
|
|
14
|
+
const renderHTMLFromMarkdown = ( {
|
|
15
|
+
content,
|
|
16
|
+
rules = 'all',
|
|
17
|
+
}: {
|
|
18
|
+
content: string;
|
|
19
|
+
rules?: Array< HTMLFix > | 'all';
|
|
20
|
+
} ) => {
|
|
21
|
+
return defaultMarkdownConverter.render( { content, rules } );
|
|
22
|
+
};
|
|
23
|
+
|
|
24
|
+
const renderMarkdownFromHTML = ( { content }: { content: string } ) => {
|
|
25
|
+
return defaultHTMLConverter.render( { content } );
|
|
26
|
+
};
|
|
27
|
+
|
|
28
|
+
export { MarkdownToHTML, HTMLToMarkdown, renderHTMLFromMarkdown, renderMarkdownFromHTML };
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* External dependencies
|
|
3
|
+
*/
|
|
4
|
+
import MarkdownIt from 'markdown-it';
|
|
5
|
+
/**
|
|
6
|
+
* Types
|
|
7
|
+
*/
|
|
8
|
+
import type { Options } from 'markdown-it';
|
|
9
|
+
|
|
10
|
+
export type Fix = 'list';
|
|
11
|
+
type Fixes = {
|
|
12
|
+
[ key in Fix ]: ( content: string ) => string;
|
|
13
|
+
};
|
|
14
|
+
|
|
15
|
+
const fixes: Fixes = {
|
|
16
|
+
list: ( content: string ) => {
|
|
17
|
+
// Fix list indentation
|
|
18
|
+
return content.replace( /<li>\s+<p>/g, '<li>' ).replace( /<\/p>\s+<\/li>/g, '</li>' );
|
|
19
|
+
},
|
|
20
|
+
};
|
|
21
|
+
|
|
22
|
+
const defaultMarkdownItOptions: Options = {
|
|
23
|
+
breaks: true,
|
|
24
|
+
};
|
|
25
|
+
|
|
26
|
+
export default class MarkdownToHTML {
|
|
27
|
+
markdownConverter: MarkdownIt;
|
|
28
|
+
|
|
29
|
+
constructor( options: Options = defaultMarkdownItOptions ) {
|
|
30
|
+
this.markdownConverter = new MarkdownIt( options );
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
/**
|
|
34
|
+
* Renders HTML from Markdown content with specified processing rules.
|
|
35
|
+
* @param {object} options - The options to use when rendering the HTML content
|
|
36
|
+
* @param {string} options.content - The Markdown content to render
|
|
37
|
+
* @param {string} options.rules - The rules to apply to the rendered content
|
|
38
|
+
* @returns {string} The rendered HTML content
|
|
39
|
+
*/
|
|
40
|
+
render( { content, rules = 'all' }: { content: string; rules: Array< Fix > | 'all' } ): string {
|
|
41
|
+
const rendered = this.markdownConverter.render( content );
|
|
42
|
+
const rulesToApply = rules === 'all' ? Object.keys( fixes ) : rules;
|
|
43
|
+
|
|
44
|
+
return rulesToApply.reduce( ( renderedContent, rule ) => {
|
|
45
|
+
return fixes[ rule ]( renderedContent );
|
|
46
|
+
}, rendered );
|
|
47
|
+
}
|
|
48
|
+
}
|
|
@@ -197,7 +197,9 @@ export default class SuggestionsEventSource extends EventTarget {
|
|
|
197
197
|
response.status <= 500 &&
|
|
198
198
|
! [ 413, 422, 429 ].includes( response.status )
|
|
199
199
|
) {
|
|
200
|
-
|
|
200
|
+
debug( 'Connection error: %o', response );
|
|
201
|
+
errorCode = ERROR_NETWORK;
|
|
202
|
+
this.dispatchEvent( new CustomEvent( ERROR_NETWORK, { detail: response } ) );
|
|
201
203
|
}
|
|
202
204
|
|
|
203
205
|
/*
|
|
@@ -358,16 +360,6 @@ export default class SuggestionsEventSource extends EventTarget {
|
|
|
358
360
|
}
|
|
359
361
|
}
|
|
360
362
|
|
|
361
|
-
processConnectionError( response ) {
|
|
362
|
-
debug( 'Connection error: %o', response );
|
|
363
|
-
this.dispatchEvent( new CustomEvent( ERROR_NETWORK, { detail: response } ) );
|
|
364
|
-
this.dispatchEvent(
|
|
365
|
-
new CustomEvent( ERROR_RESPONSE, {
|
|
366
|
-
detail: getErrorData( ERROR_NETWORK ),
|
|
367
|
-
} )
|
|
368
|
-
);
|
|
369
|
-
}
|
|
370
|
-
|
|
371
363
|
processErrorEvent( e ) {
|
|
372
364
|
debug( 'onerror: %o', e );
|
|
373
365
|
|
package/src/types.ts
CHANGED
|
@@ -93,6 +93,17 @@ export type { RecordingState } from './hooks/use-media-recording/index.js';
|
|
|
93
93
|
*/
|
|
94
94
|
export type CancelablePromise< T = void > = Promise< T > & { canceled?: boolean };
|
|
95
95
|
|
|
96
|
+
export type Block = {
|
|
97
|
+
attributes?: {
|
|
98
|
+
[ key: string ]: unknown;
|
|
99
|
+
};
|
|
100
|
+
clientId?: string;
|
|
101
|
+
innerBlocks?: Block[];
|
|
102
|
+
isValid?: boolean;
|
|
103
|
+
name?: string;
|
|
104
|
+
originalContent?: string;
|
|
105
|
+
};
|
|
106
|
+
|
|
96
107
|
/*
|
|
97
108
|
* Transcription types
|
|
98
109
|
*/
|