prosemirror-suggestcat-plugin 2.2.1 → 2.2.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (151) hide show
  1. package/README.md +129 -299
  2. package/dist/index.es.js +1 -1
  3. package/dist/index.js +1 -1
  4. package/dist/playwright.config.d.ts +3 -0
  5. package/dist/playwright.config.d.ts.map +1 -0
  6. package/dist/src/api/config.d.ts.map +1 -0
  7. package/dist/src/api/index.d.ts.map +1 -0
  8. package/dist/src/api/request.d.ts.map +1 -0
  9. package/dist/src/api/streaming.d.ts.map +1 -0
  10. package/dist/src/api/types.d.ts.map +1 -0
  11. package/dist/src/autoComplete/actions.d.ts.map +1 -0
  12. package/dist/src/autoComplete/index.d.ts.map +1 -0
  13. package/dist/src/autoComplete/plugin.d.ts.map +1 -0
  14. package/dist/src/autoComplete/streaming.d.ts.map +1 -0
  15. package/dist/src/autoComplete/types.d.ts.map +1 -0
  16. package/dist/src/completeV2/actions.d.ts.map +1 -0
  17. package/dist/src/completeV2/index.d.ts.map +1 -0
  18. package/dist/src/completeV2/plugin.d.ts.map +1 -0
  19. package/dist/src/completeV2/streaming.d.ts.map +1 -0
  20. package/dist/src/completeV2/types.d.ts.map +1 -0
  21. package/dist/src/createUpdatePopup.d.ts.map +1 -0
  22. package/dist/src/defaults.d.ts.map +1 -0
  23. package/dist/{grammarSuggestV2 → src/grammarSuggestV2}/actions.d.ts +1 -1
  24. package/dist/src/grammarSuggestV2/actions.d.ts.map +1 -0
  25. package/dist/{grammarSuggestV2 → src/grammarSuggestV2}/decorations.d.ts +1 -1
  26. package/dist/src/grammarSuggestV2/decorations.d.ts.map +1 -0
  27. package/dist/src/grammarSuggestV2/index.d.ts.map +1 -0
  28. package/dist/src/grammarSuggestV2/modelState.d.ts.map +1 -0
  29. package/dist/{grammarSuggestV2 → src/grammarSuggestV2}/plugin.d.ts +1 -1
  30. package/dist/src/grammarSuggestV2/plugin.d.ts.map +1 -0
  31. package/dist/{grammarSuggestV2 → src/grammarSuggestV2}/processor.d.ts +1 -1
  32. package/dist/src/grammarSuggestV2/processor.d.ts.map +1 -0
  33. package/dist/src/grammarSuggestV2/types.d.ts.map +1 -0
  34. package/dist/{index.d.ts → src/index.d.ts} +0 -1
  35. package/dist/src/index.d.ts.map +1 -0
  36. package/dist/src/types.d.ts.map +1 -0
  37. package/dist/src/utils.d.ts.map +1 -0
  38. package/dist/{utils.test.d.ts.map → src/utils.test.d.ts.map} +1 -1
  39. package/package.json +11 -5
  40. package/dist/api/config.d.ts.map +0 -1
  41. package/dist/api/index.d.ts.map +0 -1
  42. package/dist/api/request.d.ts.map +0 -1
  43. package/dist/api/streaming.d.ts.map +0 -1
  44. package/dist/api/types.d.ts.map +0 -1
  45. package/dist/autoComplete/actions.d.ts.map +0 -1
  46. package/dist/autoComplete/index.d.ts.map +0 -1
  47. package/dist/autoComplete/plugin.d.ts.map +0 -1
  48. package/dist/autoComplete/streaming.d.ts.map +0 -1
  49. package/dist/autoComplete/types.d.ts.map +0 -1
  50. package/dist/blockRunner/defaults.d.ts +0 -4
  51. package/dist/blockRunner/defaults.d.ts.map +0 -1
  52. package/dist/blockRunner/examples/linkDetector/decorations.d.ts +0 -4
  53. package/dist/blockRunner/examples/linkDetector/decorations.d.ts.map +0 -1
  54. package/dist/blockRunner/examples/linkDetector/index.d.ts +0 -5
  55. package/dist/blockRunner/examples/linkDetector/index.d.ts.map +0 -1
  56. package/dist/blockRunner/examples/linkDetector/plugin.d.ts +0 -6
  57. package/dist/blockRunner/examples/linkDetector/plugin.d.ts.map +0 -1
  58. package/dist/blockRunner/examples/linkDetector/processor.d.ts +0 -5
  59. package/dist/blockRunner/examples/linkDetector/processor.d.ts.map +0 -1
  60. package/dist/blockRunner/examples/linkDetector/types.d.ts +0 -9
  61. package/dist/blockRunner/examples/linkDetector/types.d.ts.map +0 -1
  62. package/dist/blockRunner/examples/randomProcessor/decorations.d.ts +0 -5
  63. package/dist/blockRunner/examples/randomProcessor/decorations.d.ts.map +0 -1
  64. package/dist/blockRunner/examples/randomProcessor/index.d.ts +0 -5
  65. package/dist/blockRunner/examples/randomProcessor/index.d.ts.map +0 -1
  66. package/dist/blockRunner/examples/randomProcessor/plugin.d.ts +0 -6
  67. package/dist/blockRunner/examples/randomProcessor/plugin.d.ts.map +0 -1
  68. package/dist/blockRunner/examples/randomProcessor/processor.d.ts +0 -6
  69. package/dist/blockRunner/examples/randomProcessor/processor.d.ts.map +0 -1
  70. package/dist/blockRunner/examples/randomProcessor/types.d.ts +0 -15
  71. package/dist/blockRunner/examples/randomProcessor/types.d.ts.map +0 -1
  72. package/dist/blockRunner/examples/sentenceLength/decorations.d.ts +0 -4
  73. package/dist/blockRunner/examples/sentenceLength/decorations.d.ts.map +0 -1
  74. package/dist/blockRunner/examples/sentenceLength/index.d.ts +0 -5
  75. package/dist/blockRunner/examples/sentenceLength/index.d.ts.map +0 -1
  76. package/dist/blockRunner/examples/sentenceLength/plugin.d.ts +0 -6
  77. package/dist/blockRunner/examples/sentenceLength/plugin.d.ts.map +0 -1
  78. package/dist/blockRunner/examples/sentenceLength/processor.d.ts +0 -6
  79. package/dist/blockRunner/examples/sentenceLength/processor.d.ts.map +0 -1
  80. package/dist/blockRunner/examples/sentenceLength/types.d.ts +0 -13
  81. package/dist/blockRunner/examples/sentenceLength/types.d.ts.map +0 -1
  82. package/dist/blockRunner/examples/wordComplexity/decorations.d.ts +0 -4
  83. package/dist/blockRunner/examples/wordComplexity/decorations.d.ts.map +0 -1
  84. package/dist/blockRunner/examples/wordComplexity/index.d.ts +0 -5
  85. package/dist/blockRunner/examples/wordComplexity/index.d.ts.map +0 -1
  86. package/dist/blockRunner/examples/wordComplexity/plugin.d.ts +0 -6
  87. package/dist/blockRunner/examples/wordComplexity/plugin.d.ts.map +0 -1
  88. package/dist/blockRunner/examples/wordComplexity/processor.d.ts +0 -7
  89. package/dist/blockRunner/examples/wordComplexity/processor.d.ts.map +0 -1
  90. package/dist/blockRunner/examples/wordComplexity/types.d.ts +0 -14
  91. package/dist/blockRunner/examples/wordComplexity/types.d.ts.map +0 -1
  92. package/dist/blockRunner/executor.d.ts +0 -8
  93. package/dist/blockRunner/executor.d.ts.map +0 -1
  94. package/dist/blockRunner/executor.test.d.ts +0 -2
  95. package/dist/blockRunner/executor.test.d.ts.map +0 -1
  96. package/dist/blockRunner/index.d.ts +0 -13
  97. package/dist/blockRunner/index.d.ts.map +0 -1
  98. package/dist/blockRunner/plugin.d.ts +0 -15
  99. package/dist/blockRunner/plugin.d.ts.map +0 -1
  100. package/dist/blockRunner/state.d.ts +0 -5
  101. package/dist/blockRunner/state.d.ts.map +0 -1
  102. package/dist/blockRunner/state.test.d.ts +0 -2
  103. package/dist/blockRunner/state.test.d.ts.map +0 -1
  104. package/dist/blockRunner/testHelpers.d.ts +0 -18
  105. package/dist/blockRunner/testHelpers.d.ts.map +0 -1
  106. package/dist/blockRunner/types.d.ts +0 -163
  107. package/dist/blockRunner/types.d.ts.map +0 -1
  108. package/dist/blockRunner/utils.d.ts +0 -26
  109. package/dist/blockRunner/utils.d.ts.map +0 -1
  110. package/dist/blockRunner/utils.test.d.ts.map +0 -1
  111. package/dist/completeV2/actions.d.ts.map +0 -1
  112. package/dist/completeV2/index.d.ts.map +0 -1
  113. package/dist/completeV2/plugin.d.ts.map +0 -1
  114. package/dist/completeV2/streaming.d.ts.map +0 -1
  115. package/dist/completeV2/types.d.ts.map +0 -1
  116. package/dist/createUpdatePopup.d.ts.map +0 -1
  117. package/dist/defaults.d.ts.map +0 -1
  118. package/dist/grammarSuggestV2/actions.d.ts.map +0 -1
  119. package/dist/grammarSuggestV2/decorations.d.ts.map +0 -1
  120. package/dist/grammarSuggestV2/index.d.ts.map +0 -1
  121. package/dist/grammarSuggestV2/modelState.d.ts.map +0 -1
  122. package/dist/grammarSuggestV2/plugin.d.ts.map +0 -1
  123. package/dist/grammarSuggestV2/processor.d.ts.map +0 -1
  124. package/dist/grammarSuggestV2/types.d.ts.map +0 -1
  125. package/dist/index.d.ts.map +0 -1
  126. package/dist/types.d.ts.map +0 -1
  127. package/dist/utils.d.ts.map +0 -1
  128. package/dist/utils.test.d.ts +0 -2
  129. /package/dist/{api → src/api}/config.d.ts +0 -0
  130. /package/dist/{api → src/api}/index.d.ts +0 -0
  131. /package/dist/{api → src/api}/request.d.ts +0 -0
  132. /package/dist/{api → src/api}/streaming.d.ts +0 -0
  133. /package/dist/{api → src/api}/types.d.ts +0 -0
  134. /package/dist/{autoComplete → src/autoComplete}/actions.d.ts +0 -0
  135. /package/dist/{autoComplete → src/autoComplete}/index.d.ts +0 -0
  136. /package/dist/{autoComplete → src/autoComplete}/plugin.d.ts +0 -0
  137. /package/dist/{autoComplete → src/autoComplete}/streaming.d.ts +0 -0
  138. /package/dist/{autoComplete → src/autoComplete}/types.d.ts +0 -0
  139. /package/dist/{completeV2 → src/completeV2}/actions.d.ts +0 -0
  140. /package/dist/{completeV2 → src/completeV2}/index.d.ts +0 -0
  141. /package/dist/{completeV2 → src/completeV2}/plugin.d.ts +0 -0
  142. /package/dist/{completeV2 → src/completeV2}/streaming.d.ts +0 -0
  143. /package/dist/{completeV2 → src/completeV2}/types.d.ts +0 -0
  144. /package/dist/{createUpdatePopup.d.ts → src/createUpdatePopup.d.ts} +0 -0
  145. /package/dist/{defaults.d.ts → src/defaults.d.ts} +0 -0
  146. /package/dist/{grammarSuggestV2 → src/grammarSuggestV2}/index.d.ts +0 -0
  147. /package/dist/{grammarSuggestV2 → src/grammarSuggestV2}/modelState.d.ts +0 -0
  148. /package/dist/{grammarSuggestV2 → src/grammarSuggestV2}/types.d.ts +0 -0
  149. /package/dist/{types.d.ts → src/types.d.ts} +0 -0
  150. /package/dist/{utils.d.ts → src/utils.d.ts} +0 -0
  151. /package/dist/{blockRunner → src}/utils.test.d.ts +0 -0
package/README.md CHANGED
@@ -1,52 +1,53 @@
1
1
  # prosemirror-suggestcat-plugin
2
2
 
3
- [![made by Emergence Engineering](https://emergence-engineering.com/ee-logo.svg)](https://emergence-engineering.com)
3
+ [![logo](https://emergence-engineering.com/ee-logo.svg)](https://emergence-engineering.com)
4
4
 
5
- [**Made by Emergence-Engineering**](https://emergence-engineering.com/)
5
+ [**Made by Emergence Engineering**](https://emergence-engineering.com/)
6
6
 
7
- ## Features
7
+ > AI-powered grammar checking, text completion, and inline autocomplete for ProseMirror
8
8
 
9
- ![feature-gif](https://suggestcat.com/suggestcat.gif)
9
+ ## Features
10
10
 
11
- - AI-powered grammar and style corrections for ProseMirror
12
- - AI text completion, rewriting, translation, tone changes and more with streaming
11
+ - AI grammar and style corrections with paragraph-level processing and parallel execution
12
+ - AI text completion, rewriting, translation, tone changes, and more with streaming
13
13
  - Inline autocomplete with ghost text (like GitHub Copilot)
14
- - Paragraph-level processing with dirty state tracking and parallel execution
15
14
  - Multiple AI model support with automatic fallback
15
+ - Dirty-state tracking -- only edited paragraphs are re-checked
16
+ - Works with ProseMirror and TipTap
16
17
 
17
- ## Getting started
18
+ ## Installation
18
19
 
19
- 1. Create an account on [SuggestCat](https://www.suggestcat.com/) and generate an API key
20
- 2. Install the package
21
-
22
- ```sh
23
- npm i prosemirror-suggestcat-plugin
20
+ ```bash
21
+ npm install prosemirror-suggestcat-plugin
24
22
  ```
25
23
 
26
- 3. Add the plugins you need to your ProseMirror (or TipTap) editor using your API key
27
- 4. Import the styles or write your own CSS
28
- 5. Track your usage and manage API keys on your [admin dashboard](https://www.suggestcat.com/)
24
+ ### Peer dependencies
25
+
26
+ ```bash
27
+ npm install prosemirror-model prosemirror-state prosemirror-transform prosemirror-view
28
+ ```
29
29
 
30
- ## Grammar suggestion plugin
30
+ ## Quick Start
31
31
 
32
- Checks your text for grammar and style issues paragraph by paragraph. Only edited paragraphs are re-checked, and multiple paragraphs are processed in parallel.
32
+ Create an account on [SuggestCat](https://www.suggestcat.com/) and generate an API key, then register the plugins you need:
33
33
 
34
34
  ```typescript
35
+ import { EditorState } from "prosemirror-state";
36
+ import { EditorView } from "prosemirror-view";
35
37
  import {
36
38
  grammarSuggestPluginV2,
37
39
  grammarSuggestV2Key,
38
- ActionType,
40
+ completePluginV2,
41
+ autoCompletePlugin,
39
42
  } from "prosemirror-suggestcat-plugin";
43
+ import { ActionType } from "@emergence-engineering/prosemirror-block-runner";
40
44
 
41
45
  const view = new EditorView(document.querySelector("#editor"), {
42
46
  state: EditorState.create({
43
- doc: schema.nodeFromJSON(initialDoc),
44
47
  plugins: [
45
- ...exampleSetup({ schema }),
46
- grammarSuggestPluginV2("<YOUR_API_KEY>", {
47
- debounceMs: 1000,
48
- batchSize: 4,
49
- }),
48
+ grammarSuggestPluginV2("<YOUR_API_KEY>"),
49
+ completePluginV2("<YOUR_API_KEY>"),
50
+ autoCompletePlugin("<YOUR_API_KEY>"),
50
51
  ],
51
52
  }),
52
53
  });
@@ -60,153 +61,109 @@ view.dispatch(
60
61
  );
61
62
  ```
62
63
 
63
- ### Options
64
-
65
- ```typescript
66
- interface GrammarSuggestV2Options {
67
- apiKey: string;
68
- apiEndpoint?: string;
69
- model?: string | AIModel;
70
- fallback?: {
71
- fallbackModel: string | AIModel;
72
- failureThreshold?: number; // default: 3
73
- };
74
- batchSize?: number; // parallel workers, default: 4
75
- maxRetries?: number; // default: 3
76
- backoffBase?: number; // default: 2000ms
77
- debounceMs?: number; // default: 1000ms
78
- createPopup?: (
79
- view: EditorView,
80
- decoration: Decoration,
81
- pos: number,
82
- applySuggestion: () => void,
83
- discardSuggestion: () => void,
84
- requestHint: () => Promise<string>,
85
- ) => HTMLElement;
86
- }
87
- ```
88
-
89
- ### Styles
64
+ ## Options
65
+
66
+ ### GrammarSuggestV2Options
67
+
68
+ | Option | Type | Default | Description |
69
+ |---|---|---|---|
70
+ | `apiKey` | `string` | -- | SuggestCat API key (required, passed as first argument) |
71
+ | `apiEndpoint` | `string` | SuggestCat API | Custom endpoint URL |
72
+ | `model` | `string \| AIModel` | `"openai:gpt-4o-mini"` | AI model to use |
73
+ | `fallback` | `ModelFallbackConfig` | -- | Fallback model config (`{ fallbackModel, failureThreshold? }`) |
74
+ | `batchSize` | `number` | `2` | Number of parallel workers |
75
+ | `maxRetries` | `number` | `3` | Max retries per paragraph |
76
+ | `backoffBase` | `number` | `2000` | Base backoff delay in ms |
77
+ | `debounceMs` | `number` | `1000` | Debounce delay before re-checking |
78
+ | `createPopup` | `"react" \| function` | -- | `"react"` for React popups, or a factory function returning an HTMLElement |
79
+
80
+ ### CompleteV2Options
81
+
82
+ | Option | Type | Default | Description |
83
+ |---|---|---|---|
84
+ | `maxSelection` | `number` | `1000` | Max selected characters for a task |
85
+ | `apiEndpoint` | `string` | SuggestCat API | Custom endpoint URL |
86
+ | `model` | `string` | `"openai:gpt-4o-mini"` | AI model to use |
87
+
88
+ ### AutoCompleteOptions
89
+
90
+ | Option | Type | Default | Description |
91
+ |---|---|---|---|
92
+ | `debounceMs` | `number` | `500` | Debounce delay before requesting a suggestion |
93
+ | `maxContextLength` | `number` | `2000` | Max characters sent as context |
94
+ | `apiEndpoint` | `string` | SuggestCat API | Custom endpoint URL |
95
+ | `model` | `string` | `"openai:gpt-4o-mini"` | AI model to use |
96
+ | `ghostTextClass` | `string` | `"autoCompleteGhostText"` | CSS class for ghost text decoration |
97
+
98
+ ## API
99
+
100
+ ### Grammar plugin
101
+
102
+ | Export | Type | Description |
103
+ |---|---|---|
104
+ | `grammarSuggestPluginV2(apiKey, options?)` | Plugin factory | Creates the grammar checking plugin |
105
+ | `grammarSuggestV2Key` | `PluginKey` | Plugin key for dispatching metas |
106
+ | `acceptSuggestion(view, id)` | function | Accept a grammar suggestion |
107
+ | `discardSuggestion(view, id)` | function | Discard a grammar suggestion |
108
+ | `selectSuggestion(view, id)` | function | Select a suggestion (for popup) |
109
+ | `deselectSuggestion(view)` | function | Deselect the current suggestion |
110
+ | `requestHint(view, id)` | function | Request an AI explanation for a suggestion |
111
+ | `getSelectedDecoration(state)` | function | Get the currently selected decoration |
112
+ | `setGrammarSuggestEnabled(view, enabled)` | function | Enable/disable grammar checking |
113
+
114
+ ### Complete plugin
115
+
116
+ | Export | Type | Description |
117
+ |---|---|---|
118
+ | `completePluginV2(apiKey, options?)` | Plugin factory | Creates the completion plugin |
119
+ | `completeV2Key` | `PluginKey` | Plugin key |
120
+ | `startTask(view, taskType, params?)` | function | Start an AI task |
121
+ | `acceptResult(view)` | function | Accept the streamed result |
122
+ | `rejectResult(view)` | function | Reject the streamed result |
123
+ | `cancelTask(view)` | function | Cancel an in-progress task |
124
+ | `getCompleteState(state)` | function | Get the current plugin state |
125
+ | `setEnabled(view, enabled)` | function | Enable/disable the plugin |
126
+ | `AiPromptsWithoutParam` | enum | `Complete`, `SmallComplete`, `Improve`, `MakeLonger`, `MakeShorter`, `Simplify`, `Explain`, `ActionItems` |
127
+ | `AiPromptsWithParam` | enum | `ChangeTone`, `Translate`, `Hint`, `Custom` |
128
+ | `MoodParamType` | enum | `Casual`, `Confident`, `Straightforward`, `Friendly` |
129
+ | `TranslationTargetLanguage` | enum | `English`, `Spanish`, `French`, `German`, `Italian`, `Portuguese`, `Dutch`, `Russian`, `Chinese`, `Korean`, `Japanese` |
130
+
131
+ ### Autocomplete plugin
132
+
133
+ | Export | Type | Description |
134
+ |---|---|---|
135
+ | `autoCompletePlugin(apiKey, options?)` | Plugin factory | Creates the autocomplete plugin |
136
+ | `autoCompleteKey` | `PluginKey` | Plugin key |
137
+ | `setAutoCompleteEnabled(view, enabled)` | function | Enable/disable autocomplete |
138
+ | `acceptAutoCompletion(view)` | function | Accept the ghost text |
139
+ | `dismissAutoCompletion(view)` | function | Dismiss the ghost text |
140
+ | `isAutoCompleteEnabled(view)` | function | Check if autocomplete is enabled |
141
+ | `hasAutoCompletion(view)` | function | Check if a suggestion is showing |
142
+
143
+ ### Low-level API
144
+
145
+ | Export | Type | Description |
146
+ |---|---|---|
147
+ | `streamingRequest(options, callbacks)` | function | Streaming POST with `onChunk`, `onComplete`, `onError` |
148
+ | `grammarRequest(options)` | function | Non-streaming POST for grammar corrections |
149
+ | `createApiConfig(apiKey, endpoint?, model?)` | function | Build an API config object |
150
+ | `createGrammarApiConfig(apiKey, endpoint?, model?)` | function | Build a grammar-specific API config |
151
+ | `getDiff(oldText, newText)` | function | Re-exported from `@emergence-engineering/fast-diff-merge` |
152
+
153
+ ## Styles
90
154
 
91
155
  ```typescript
92
156
  import "prosemirror-suggestcat-plugin/dist/styles/styles.css";
93
157
  ```
94
158
 
95
- Or style the decorations yourself using these CSS classes:
96
-
97
- - `.grammarSuggestionV2` — inline decoration on suggestions
98
- - `.removalSuggestionV2` — when the suggestion is a deletion
99
- - `.grammarSuggestionV2-selected` — currently selected suggestion
100
- - `.grammarPopupV2` — popup container
159
+ CSS classes for grammar decorations:
101
160
 
102
- ## Complete plugin
161
+ - `.grammarSuggestionV2` -- inline decoration on suggestions
162
+ - `.removalSuggestionV2` -- when the suggestion is a deletion
163
+ - `.grammarSuggestionV2-selected` -- currently selected suggestion
164
+ - `.grammarPopupV2` -- popup container
103
165
 
104
- AI text completion and transformation with streaming support. Use it to complete, shorten, lengthen, simplify, explain, translate text and more.
105
-
106
- ```typescript
107
- import { completePluginV2 } from "prosemirror-suggestcat-plugin";
108
-
109
- const view = new EditorView(document.querySelector("#editor"), {
110
- state: EditorState.create({
111
- doc: schema.nodeFromJSON(initialDoc),
112
- plugins: [
113
- ...exampleSetup({ schema }),
114
- completePluginV2("<YOUR_API_KEY>", {
115
- maxSelection: 1000,
116
- }),
117
- ],
118
- }),
119
- });
120
- ```
121
-
122
- ### Triggering tasks
123
-
124
- Use action functions instead of dispatching metas directly:
125
-
126
- ```typescript
127
- import {
128
- startTask,
129
- acceptResult,
130
- rejectResult,
131
- cancelTask,
132
- getCompleteState,
133
- AiPromptsWithoutParam,
134
- AiPromptsWithParam,
135
- MoodParamType,
136
- TranslationTargetLanguage,
137
- } from "prosemirror-suggestcat-plugin";
138
-
139
- // Continue writing from cursor
140
- startTask(view, AiPromptsWithoutParam.Complete);
141
-
142
- // Transform selected text
143
- startTask(view, AiPromptsWithoutParam.MakeShorter);
144
- startTask(view, AiPromptsWithoutParam.MakeLonger);
145
- startTask(view, AiPromptsWithoutParam.Simplify);
146
- startTask(view, AiPromptsWithoutParam.Explain);
147
- startTask(view, AiPromptsWithoutParam.ActionItems);
148
- startTask(view, AiPromptsWithoutParam.Improve);
149
-
150
- // Tasks with parameters
151
- startTask(view, AiPromptsWithParam.ChangeTone, {
152
- mood: MoodParamType.Friendly,
153
- });
154
- startTask(view, AiPromptsWithParam.Translate, {
155
- targetLanguage: TranslationTargetLanguage.Spanish,
156
- });
157
-
158
- // Accept or reject the result once streaming finishes
159
- acceptResult(view);
160
- rejectResult(view);
161
-
162
- // Cancel an in-progress task
163
- cancelTask(view);
164
- ```
165
-
166
- ### State flow
167
-
168
- ```
169
- IDLE -> PENDING -> STREAMING -> PREVIEW -> APPLYING -> IDLE
170
- ```
171
-
172
- During `STREAMING` the result is built up incrementally. At `PREVIEW` you can accept or reject. Only one task runs at a time.
173
-
174
- ## Autocomplete plugin
175
-
176
- Inline ghost-text completions that appear after the cursor as you type. Press **Tab** to accept, **Escape** to dismiss.
177
-
178
- ```typescript
179
- import { autoCompletePlugin } from "prosemirror-suggestcat-plugin";
180
-
181
- const view = new EditorView(document.querySelector("#editor"), {
182
- state: EditorState.create({
183
- doc: schema.nodeFromJSON(initialDoc),
184
- plugins: [
185
- ...exampleSetup({ schema }),
186
- autoCompletePlugin("<YOUR_API_KEY>", {
187
- debounceMs: 500,
188
- maxContextLength: 2000,
189
- }),
190
- ],
191
- }),
192
- });
193
- ```
194
-
195
- ### Options
196
-
197
- ```typescript
198
- interface AutoCompleteOptions {
199
- debounceMs: number; // default: 500
200
- maxContextLength: number; // default: 2000
201
- apiEndpoint?: string;
202
- model?: string;
203
- ghostTextClass?: string; // default: "autoCompleteGhostText"
204
- }
205
- ```
206
-
207
- ### Styles
208
-
209
- Add CSS for the ghost text:
166
+ CSS for autocomplete ghost text (add your own):
210
167
 
211
168
  ```css
212
169
  .autoCompleteGhostText {
@@ -216,145 +173,14 @@ Add CSS for the ghost text:
216
173
  }
217
174
  ```
218
175
 
219
- ### Programmatic control
220
-
221
- ```typescript
222
- import {
223
- setAutoCompleteEnabled,
224
- acceptAutoCompletion,
225
- dismissAutoCompletion,
226
- isAutoCompleteEnabled,
227
- hasAutoCompletion,
228
- } from "prosemirror-suggestcat-plugin";
229
-
230
- setAutoCompleteEnabled(view, true);
231
- setAutoCompleteEnabled(view, false);
232
-
233
- if (hasAutoCompletion(view)) {
234
- acceptAutoCompletion(view);
235
- // or
236
- dismissAutoCompletion(view);
237
- }
238
- ```
239
-
240
- ## How it works
241
-
242
- ### API
243
-
244
- All plugins communicate with the SuggestCat backend through a shared API module. You can configure the endpoint and model per plugin, or use the defaults (`openai:gpt-4o-mini`).
245
-
246
- The module exposes two request functions if you need to use them directly:
247
-
248
- - `grammarRequest(options)` — a non-streaming POST that returns the corrected text and a list of modifications
249
- - `streamingRequest(options, callbacks)` — a streaming request with `onChunk`, `onComplete` and `onError` callbacks, used by the complete and autocomplete plugins. Supports cancellation via `AbortSignal`.
250
-
251
- Both accept an `ApiConfig` (`apiKey`, optional `endpoint`, optional `model`). You can use `createApiConfig` or `createGrammarApiConfig` to fill in defaults.
252
-
253
- ```typescript
254
- import {
255
- streamingRequest,
256
- grammarRequest,
257
- createApiConfig,
258
- createGrammarApiConfig,
259
- } from "prosemirror-suggestcat-plugin";
260
- ```
261
-
262
- ### Block runner
263
-
264
- The grammar suggestion plugin is built on top of a generic **block runner** framework. Instead of sending the entire document to the API, it splits the document into processing units (paragraphs by default) and processes them in parallel.
265
-
266
- Key properties:
267
-
268
- - **Paragraph-level processing** — each paragraph is an independent unit, so large documents don't result in oversized API calls
269
- - **Dirty state tracking** — when a paragraph is edited, only that unit is marked dirty and re-processed after a debounce delay. The plugin skips dirty marking for its own document changes (e.g. applying a suggestion)
270
- - **Parallel execution** — multiple units are processed concurrently, controlled by `batchSize`. A unit goes through `QUEUED → PROCESSING → DONE` (or `BACKOFF → retry` on failure)
271
- - **Retry with backoff** — failed units are retried up to `maxRetries` times with exponential backoff
272
-
273
- The block runner is also exported as a standalone building block. You can use it to build your own paragraph-level processing plugins. The package includes a few example plugins (link detector, word complexity, sentence length) that demonstrate how to wire up a custom processor.
274
-
275
- ```typescript
276
- import {
277
- blockRunnerPlugin,
278
- createBlockRunnerKey,
279
- ActionType,
280
- dispatchAction,
281
- } from "prosemirror-suggestcat-plugin";
282
- ```
283
-
284
- To create a block runner plugin, provide a `pluginKey`, a `unitProcessor` (async function that receives a processing unit and returns a result), a `decorationFactory` (turns results into ProseMirror decorations), and optionally a `widgetFactory` (shows per-unit status indicators) and a `decorationTransformer` (filters/modifies decorations based on context state).
285
-
286
- #### Helper functions
287
-
288
- The block runner exports utility functions you can use in your processor or elsewhere:
289
-
290
- - `extractTextWithMapping(doc, from, to)` — extracts text from a document range and builds a position mapping between text offsets and doc positions
291
- - `textToDocPos(textPos, mapping)` — converts a text-space position back to a document position using the mapping
292
- - `getUnitsInRange(doc, from, to, nodeTypes?)` — finds all matching nodes in a range
293
- - `createUnitsFromDocument(doc, from, to, metadataFactory, nodeTypes?)` — creates processing units from document nodes
294
- - `getProgress(state)` — returns `{ completed, total, decorations }` for progress tracking
295
- - `calculateBackoff(retryCount, baseMs)` — computes the backoff delay for retries
296
- - `allUnitsFinished(units)` — checks if all units are in a terminal state (DONE or ERROR)
297
-
298
- #### Manual transactions
299
-
300
- You can control the block runner by dispatching actions via `dispatchAction(view, pluginKey, action)`:
301
-
302
- ```typescript
303
- import { dispatchAction, ActionType } from "prosemirror-suggestcat-plugin";
304
-
305
- // Initialize — creates units from the document and starts processing
306
- dispatchAction(view, myPluginKey, {
307
- type: ActionType.INIT,
308
- metadata: {},
309
- });
310
-
311
- // Pause processing
312
- dispatchAction(view, myPluginKey, { type: ActionType.FINISH });
313
-
314
- // Resume paused processing
315
- dispatchAction(view, myPluginKey, { type: ActionType.RESUME });
316
-
317
- // Clear all state
318
- dispatchAction(view, myPluginKey, { type: ActionType.CLEAR });
319
-
320
- // Remove a specific decoration
321
- dispatchAction(view, myPluginKey, {
322
- type: ActionType.REMOVE_DECORATION,
323
- id: decorationId,
324
- });
325
-
326
- // Update context state (e.g. for filtering or selection)
327
- dispatchAction(view, myPluginKey, {
328
- type: ActionType.UPDATE_CONTEXT,
329
- contextState: { selectedSuggestionId: someId },
330
- });
331
- ```
332
-
333
- There are also convenience wrappers: `pauseRunner(view, pluginKey)`, `resumeRunner(view, pluginKey)`, and `canResume(state)`.
334
-
335
- ## Available models
336
-
337
- All plugins accept a `model` option:
338
-
339
- ```typescript
340
- type AIModel =
341
- | "openai:gpt-4o"
342
- | "openai:gpt-4o-mini" // default
343
- | "cerebras:llama-3.1-8b"
344
- | "cerebras:llama-3.3-70b"
345
- | "cerebras:qwen-3-32b";
346
- ```
347
-
348
176
  ## TipTap
349
177
 
350
- All plugins work with TipTap by wrapping them in an extension:
178
+ Wrap the plugins in a TipTap extension:
351
179
 
352
180
  ```typescript
353
181
  import { Extension } from "@tiptap/core";
354
182
  import {
355
183
  grammarSuggestPluginV2,
356
- grammarSuggestV2Key,
357
- ActionType,
358
184
  completePluginV2,
359
185
  autoCompletePlugin,
360
186
  } from "prosemirror-suggestcat-plugin";
@@ -371,6 +197,10 @@ const SuggestCatExtension = Extension.create({
371
197
  });
372
198
  ```
373
199
 
374
- ## React UI
200
+ ## Playground
201
+
202
+ See the [interactive demo](https://emergence-engineering.github.io/emergence-tools/#suggestcat) in the monorepo playground.
203
+
204
+ ## License
375
205
 
376
- For a ready-made React UI with a slash menu, suggestion overlay and "Ask AI" tooltip, see [prosemirror-suggestcat-plugin-react](https://github.com/emergence-engineering/prosemirror-suggestcat-plugin-react).
206
+ MIT
package/dist/index.es.js CHANGED
@@ -1 +1 @@
1
- import{Fragment as t,Slice as e}from"prosemirror-model";import{PluginKey as n,Plugin as s,TextSelection as r}from"prosemirror-state";import o from"fast-diff";import{getDiff as a,isIdentity as i}from"@emergence-engineering/fast-diff-merge";export{getDiff}from"@emergence-engineering/fast-diff-merge";import{StepMap as c,Mapping as u}from"prosemirror-transform";import{DecorationSet as l,Decoration as d}from"prosemirror-view";var p,m,g,E,f,T;!function(t){t.suggestionUpdate="suggestionUpdate",t.acceptSuggestion="acceptSuggestion",t.openSuggestion="openSuggestion",t.closeSuggestion="closeSuggestion",t.discardSuggestion="discardSuggestion",t.setEnabled="setEnabled"}(p||(p={})),function(t){t.grammarSuggestPopup="grammar-suggest-popup"}(m||(m={})),function(t){t.Casual="Casual",t.Confident="Confident",t.Straightforward="Straightforward",t.Friendly="Friendly"}(g||(g={})),function(t){t.English="English",t.Spanish="Spanish",t.French="French",t.German="German",t.Italian="Italian",t.Portuguese="Portuguese",t.Dutch="Dutch",t.Russian="Russian",t.Chinese="Chinese",t.Korean="Korean",t.Japanese="Japanese"}(E||(E={})),function(t){t.Complete="Complete",t.SmallComplete="SmallComplete",t.Improve="Improve",t.MakeLonger="MakeLonger",t.MakeShorter="MakeShorter",t.Simplify="Simplify",t.Explain="Explain",t.ActionItems="ActionItems"}(f||(f={})),function(t){t.ChangeTone="ChangeTone",t.Translate="Translate",t.Hint="Hint",t.Custom="Custom"}(T||(T={}));const S=f,y=T;var h,R,I;!function(t){t.idle="idle",t.new="new",t.streaming="streaming",t.finished="finished",t.accepted="accepted",t.cancelled="cancelled",t.rejected="rejected",t.done="done",t.error="error"}(h||(h={})),function(t){t.IDLE="idle",t.PENDING="pending",t.STREAMING="streaming",t.PREVIEW="preview",t.APPLYING="applying"}(R||(R={})),function(t){t.START_TASK="START_TASK",t.STREAM_UPDATE="STREAM_UPDATE",t.STREAM_COMPLETE="STREAM_COMPLETE",t.STREAM_ERROR="STREAM_ERROR",t.ACCEPT_RESULT="ACCEPT_RESULT",t.REJECT_RESULT="REJECT_RESULT",t.CANCEL_TASK="CANCEL_TASK",t.CLEAR_ERROR="CLEAR_ERROR",t.SET_ENABLED="SET_ENABLED"}(I||(I={}));const C={maxSelection:1e3},x="https://suggestion-gw5lxik4dq-uc.a.run.app",A="https://prosemirror-ai-plugin.web.app/api/suggestion",D="openai:gpt-4o-mini";function w(t){return{apiKey:t.apiKey,endpoint:t.endpoint??x,model:t.model??D}}function N(t){return{apiKey:t.apiKey,endpoint:t.endpoint??A,model:t.model??D}}async function M(t,e){const{apiKey:n,text:s,task:r,params:o,endpoint:a=x,model:i=D,signal:c}=t;let u="";const l={model:i,modelParams:{input:[s],task:r,params:o}};try{const t=await fetch(a,{method:"POST",cache:"no-cache",headers:{"Content-Type":"application/json",Authorization:`Bearer ${n}`},signal:c,body:JSON.stringify(l)});if(!t.ok)throw new Error(`HTTP error! status: ${t.status}`);const s=t.body?.getReader();if(!s)throw new Error("No response body");for(;;){const{done:t,value:n}=await s.read();if(t)break;if(c?.aborted)return;const r=(new TextDecoder).decode(n);u+=r,e.onChunk(r,u)}c?.aborted||e.onComplete(u)}catch(t){if(t instanceof Error&&"AbortError"===t.name)return;e.onError(t instanceof Error?t:new Error(String(t)))}}async function P(t){const{apiKey:e,text:n,endpoint:s=A,model:r=D}=t,o={model:r,modelParams:{input:[...n.split("\n")]}};try{const t=await fetch(s,{method:"POST",cache:"no-cache",headers:{"Content-Type":"application/json",Authorization:`Bearer ${e}`},body:JSON.stringify(o)});if(!t.ok){const e=await t.text();return console.error({status:t.status,text:e}),{fixed:!1,result:n,error:!0}}const r=await t.json();return r?.length?{result:r.join("\n"),fixed:!0}:{fixed:!1,result:n}}catch(t){return console.error("Grammar request error:",t),{fixed:!1,result:n,error:!0}}}const b=new WeakMap;function O(t){const e=b.get(t);e&&(e.abort(),b.delete(t))}function U(t){return b.has(t)}const v=new n("completePluginV2"),_={status:R.IDLE,streamedResult:"",enabled:!0};function L(n,o={}){const{maxSelection:a,apiEndpoint:i,model:c}={...C,...o};let u=!1;return new s({key:v,state:{init:()=>({..._}),apply(t,e){const n=t.getMeta(v);return n?function(t,e){switch(e.type){case I.SET_ENABLED:return{...t,enabled:e.enabled,...e.enabled?{}:_};case I.START_TASK:return t.status!==R.IDLE?t:{...t,status:R.PENDING,taskType:e.taskType,params:e.params,selection:e.selection,streamedResult:"",error:void 0};case I.STREAM_UPDATE:return t.status!==R.PENDING&&t.status!==R.STREAMING?t:{...t,status:R.STREAMING,streamedResult:e.result};case I.STREAM_COMPLETE:return t.status!==R.PENDING&&t.status!==R.STREAMING?t:{...t,status:R.PREVIEW,streamedResult:e.result};case I.STREAM_ERROR:return{...t,status:R.IDLE,error:e.error,streamedResult:"",taskType:void 0,params:void 0,selection:void 0};case I.ACCEPT_RESULT:return t.status!==R.PREVIEW?t:{...t,status:R.APPLYING};case I.REJECT_RESULT:return t.status!==R.PREVIEW?t:{..._,enabled:t.enabled};case I.CANCEL_TASK:return{..._,enabled:t.enabled};case I.CLEAR_ERROR:return{...t,error:void 0};default:return t}}(e,n):e.status===R.APPLYING?{..._,enabled:e.enabled}:e}},view:()=>({update(s){const o=v.getState(s.state);if(o&&o.enabled){if(o.status===R.PENDING&&!u){const{text:t,error:e}=function(t,e,n){const{taskType:s,selection:r}=e;if(s===f.Complete){const{doc:e}=t.state,n=[];return e.descendants(t=>{"paragraph"===t.type.name&&n.push(t.textContent)}),{text:n.length>=2?n.slice(-2).join(" "):n.join(" ")}}return r?t.state.doc.textBetween(r.from,r.to).length>n?{text:"",error:"Selection is too big"}:{text:t.state.doc.textBetween(r.from,r.to,"\n")}:{text:"",error:"No selection"}}(s,o,a);if(e)return void s.dispatch(s.state.tr.setMeta(v,{type:I.STREAM_ERROR,error:e}));if(!t)return void s.dispatch(s.state.tr.setMeta(v,{type:I.STREAM_ERROR,error:"No text to process"}));u=!0,async function({view:t,pluginKey:e,apiKey:n,text:s,task:r,params:o,apiEndpoint:a,model:i}){O(t);const c=new AbortController;b.set(t,c);try{await M({apiKey:n,text:s,task:r,params:o,endpoint:a,model:i,signal:c.signal},{onChunk:(n,s)=>{t.isDestroyed||t.dispatch(t.state.tr.setMeta(e,{type:I.STREAM_UPDATE,result:s}))},onComplete:n=>{t.isDestroyed||t.dispatch(t.state.tr.setMeta(e,{type:I.STREAM_COMPLETE,result:n}))},onError:n=>{console.error("Streaming request error:",n),t.isDestroyed||t.dispatch(t.state.tr.setMeta(e,{type:I.STREAM_ERROR,error:n.message}))}})}finally{b.delete(t)}}({view:s,pluginKey:v,apiKey:n,text:t,task:o.taskType,params:o.params,apiEndpoint:i,model:c}).finally(()=>{u=!1})}o.status===R.APPLYING&&function(n,s){const{taskType:o,selection:a,streamedResult:i}=s;if(!i)return;let{tr:c}=n.state;if(o===f.Complete)c=c.insertText(i,n.state.doc.nodeSize-2);else if(a){const s=i.split("\n\n").map(t=>n.state.schema.node("paragraph",null,t?n.state.schema.text(t):void 0)),o=t.fromArray(s);c=c.setSelection(r.create(n.state.doc,a.from,a.to)),c.selection.replace(c,new e(o,0,0))}n.dispatch(c),n.focus()}(s,o)}},destroy(){}})})}function k(t,e,n){const s=t.state.selection;t.dispatch(t.state.tr.setMeta(v,{type:I.START_TASK,taskType:e,params:n,selection:s.empty?void 0:s}))}function G(t){t.dispatch(t.state.tr.setMeta(v,{type:I.ACCEPT_RESULT}))}function K(t){t.dispatch(t.state.tr.setMeta(v,{type:I.REJECT_RESULT}))}function F(t){O(t),t.dispatch(t.state.tr.setMeta(v,{type:I.CANCEL_TASK}))}function V(t,e){e||O(t),t.dispatch(t.state.tr.setMeta(v,{type:I.SET_ENABLED,enabled:e}))}function B(t){t.dispatch(t.state.tr.setMeta(v,{type:I.CLEAR_ERROR}))}function H(t){return v.getState(t.state)}const W={debounceMs:2e3,createUpdatePopup:(t,e,n,s,r)=>{const o=document.createElement("div");o.className="grammar-suggest-tooltip";const{spec:a}=e,i=t.dom.getBoundingClientRect();o.id=m.grammarSuggestPopup;const c=t.coordsAtPos(n),u=t.dom.scrollTop||0,l=t.dom.scrollLeft||0;o.style.left=`${c.left-i.left+l}px`,o.style.top=`${c.bottom-i.top+u+5}px`;const d=document.createElement("div");d.className="grammar-suggest-tooltip-apply",d.innerText=a.text||a.originalText,a.text||(d.style.textDecoration="line-through",d.style.color="red"),d.onclick=()=>{s(t,e)},o.appendChild(d);const p=document.createElement("div");return p.innerHTML="<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' width='24' height='24'><path d='M12 22C6.47715 22 2 17.5228 2 12C2 6.47715 6.47715 2 12 2C17.5228 2 22 6.47715 22 12C22 17.5228 17.5228 22 12 22ZM12 20C16.4183 20 20 16.4183 20 12C20 7.58172 16.4183 4 12 4C7.58172 4 4 7.58172 4 12C4 16.4183 7.58172 20 12 20ZM12 10.5858L14.8284 7.75736L16.2426 9.17157L13.4142 12L16.2426 14.8284L14.8284 16.2426L12 13.4142L9.17157 16.2426L7.75736 14.8284L10.5858 12L7.75736 9.17157L9.17157 7.75736L12 10.5858Z'></path></svg>",p.className="grammar-suggest-tooltip-discard",p.onclick=()=>{r(t,e)},o.appendChild(p),o},withYjs:!1},$={maxSelection:1e3},q=new n("completePlugin"),Q=new n("grammarSuggestPlugin"),Y=(t,e)=>{t.dispatch(t.state.tr.setMeta(Q,{type:p.setEnabled,enabled:e}))},z=(t,e)=>{t.dispatch(t.state.tr.setMeta(q,{type:"setEnabled",enabled:e}))},j=(t,e)=>{if(t===e)return{start:t.length,end:t.length,oldStart:t.length,oldEnd:t.length,oldText:"",newText:""};const n=o(t,e),s=n[0],r=s[0]===o.EQUAL?s[1].length-1:0,a=((t,e)=>{if(e[e.length-1][0]!==o.EQUAL)return t.length;const n=e.slice(0,e.length-1);let s=0;for(const t of n){const[e,n]=t;e===o.EQUAL&&(s+=n.length),e===o.DELETE&&(s+=n.length)}return s})(t,n),i=t.lastIndexOf("\n",r),c=t.indexOf("\n",a),u=-1===i?0:i+1,l=-1===c?t.length:c,d=t.slice(u,l),p=e.slice(u,e.length-(t.length-l));return{start:u,end:u+p.length,oldStart:u,oldEnd:l,oldText:d,newText:p}};var J,X,Z;!function(t){t.IDLE="IDLE",t.ACTIVE="ACTIVE"}(J||(J={})),function(t){t.DIRTY="DIRTY",t.WAITING="WAITING",t.QUEUED="QUEUED",t.PROCESSING="PROCESSING",t.DONE="DONE",t.BACKOFF="BACKOFF",t.ERROR="ERROR"}(X||(X={})),function(t){t.INIT="INIT",t.FINISH="FINISH",t.CLEAR="CLEAR",t.RESUME="RESUME",t.UNIT_STARTED="UNIT_STARTED",t.UNIT_SUCCESS="UNIT_SUCCESS",t.UNIT_ERROR="UNIT_ERROR",t.UPDATE_CONTEXT="UPDATE_CONTEXT",t.REMOVE_DECORATION="REMOVE_DECORATION",t.SELECT_DECORATION="SELECT_DECORATION",t.DESELECT_DECORATION="DESELECT_DECORATION"}(Z||(Z={}));const tt={nodeTypes:"paragraph",priorityFilter:()=>!0,visibilityFilter:()=>!0,getGroupId:()=>"default",batchSize:4,maxRetries:3,backoffBase:1e3,dirtyHandling:{shouldRecalculate:!0,debounceDelay:2e3,skipDirtyOnSelfChange:!0},forceRerender:()=>{},onUpdate:void 0};function et(t){return{...tt,...t,dirtyHandling:{...tt.dirtyHandling,...t.dirtyHandling}}}function nt(t,e,n){const s=[];let r="",o=0;return t.nodesBetween(e,n,(t,a)=>{if(t.isText&&t.text){const i=Math.max(e,a),c=Math.min(n,a+t.nodeSize),u=t.text.slice(i-a,c-a);s.push({from:o,docPos:i}),r+=u,o+=u.length}else t.isBlock&&r.length>0&&(s.push({from:o,docPos:a}),r+="\n",o+=1)}),{text:r,mapping:s}}function st(t,e){for(let n=e.length-1;n>=0;n--)if(e[n].from<=t)return e[n].docPos+(t-e[n].from);return e[0]?.docPos??0}function rt(t,e,n,s="paragraph"){const r=Array.isArray(s)?s:[s],o=[];return t.nodesBetween(e,n,(e,n)=>{if(r.includes(e.type.name)){const s=n+e.nodeSize,r=nt(t,n,s);r.text.trim().length>0&&o.push({from:n,to:s,...r})}}),o}const ot=(t,e)=>t.map(t=>t?.map(e,0,0)).filter(t=>null!==t);function at(t,e,n,s,r=!1){const o=((t,e)=>{const n=t.doc.content.findDiffStart(e.doc.content),s=e.doc.content.findDiffEnd(t.doc.content);if(!s||!n)return;let r=new c([0,0,0]),o=n,a=s.a;return s&&n&&(s.a<n?(o=s.a,a=s.b,r=new c([s.a,0,s.b-s.a])):s.b<n?(o=s.b,a=s.a,r=new c([s.b,s.a-s.b,0])):r=new c([n,s.a-n,s.b-n])),{end:a,mapping:new u([r]),start:o}})(e,n);if(!o)return t;const{start:a,end:i,mapping:l}=o,d=Array.isArray(s.nodeTypes)?s.nodeTypes:[s.nodeTypes],p=Date.now()+s.dirtyHandling.debounceDelay,m=t.unitsInProgress?.slice().sort((t,e)=>t.from-e.from);if(m){const n=[],s=l.map(m[0].from),o=l.map(m[m.length-1].to),c=rt(e.doc,s,o,d);for(const t in m){const e=m[t],s=c[t];if(s){let{status:t}=e;!r&&s.from<=i&&s.to>=a&&(t=X.DIRTY),n.push({...e,status:t,waitUntil:r?e.waitUntil:p,...s})}else n.push({...e,status:X.DONE,from:o,to:o})}return{...t,unitsInProgress:n,decorations:ot(t.decorations,l)}}return{...t,decorations:ot(t.decorations,l)}}function it(t,e){return(e/1e3)**t*1e3}function ct(t){if(t.status!==J.ACTIVE)return{completed:0,total:0,decorations:t.decorations.length};const e=t.unitsInProgress??[];return{completed:e.filter(t=>t.status===X.DONE||t.status===X.ERROR).length,total:e.length,decorations:t.decorations.length}}function ut(t){return new Promise(e=>setTimeout(e,t))}function lt(t,e,n){t.dispatch(t.state.tr.setMeta(e,n))}function dt(t,e,n){if(e.status===J.ACTIVE)return e.unitsInProgress.find(t=>t.id===n)}function pt(t,e,n){const s=t.unitsInProgress.map(t=>t.id===e?{...t,...n}:t);return"waitUntil"in n&&s.sort((t,e)=>t.waitUntil-e.waitUntil),{...t,unitsInProgress:s}}function mt(t){return t.every(t=>t.status===X.DONE||t.status===X.ERROR)}function gt(t,e,n,s,r="paragraph"){return rt(t,e,n,r).map((t,e)=>({id:{},status:X.QUEUED,from:t.from,to:t.to,text:t.text,mapping:t.mapping,metadata:s(t,e),retryCount:0,waitUntil:0}))}function Et(t,e){lt(t,e,{type:Z.FINISH})}function ft(t,e){lt(t,e,{type:Z.RESUME})}function Tt(t){return t.status===J.IDLE&&!!t.unitsInProgress?.some(t=>t.status!==X.DONE&&t.status!==X.ERROR)}function St(t,e,n,s){switch(e.type){case Z.INIT:return function(t,e,n,s){const r=gt(n,e.onlySelection?s.from:0,e.onlySelection?s.to:n.content.size,(t,n)=>{if(void 0!==e.metadata.single)return e.metadata.single;if(void 0!==e.metadata.array)return e.metadata.array[n]??e.metadata.array[0];if(void 0!==e.metadata.factory){const n={id:{},status:X.QUEUED,from:t.from,to:t.to,text:t.text,mapping:[],metadata:{},retryCount:0,waitUntil:0};return e.metadata.factory(n)}return{}}).map(e=>({...e,status:t.options.priorityFilter(e,t.contextState)?X.QUEUED:X.WAITING}));return{...t,status:J.ACTIVE,unitsInProgress:r}}(t,e,s.doc,s.selection);case Z.UNIT_STARTED:return t.status!==J.ACTIVE?t:function(t,e){const n=t.unitsInProgress.find(t=>t.id===e);return n?pt(t,e,{status:X.PROCESSING,requestText:n.text}):t}(t,e.unitId);case Z.UNIT_SUCCESS:return t.status!==J.ACTIVE?t:function(t,e,n){const s=t.unitsInProgress.find(t=>t.id===e.unitId);if(!s)return t;const r=Date.now()+t.options.dirtyHandling.debounceDelay;if(void 0!==s.requestText&&s.requestText!==s.text)return pt(t,e.unitId,{status:X.DIRTY,requestText:void 0,waitUntil:r});const o=n(e.response,s),a=pt(t,e.unitId,{status:X.DONE,response:e.response,requestText:void 0});return{...a,decorations:[...a.decorations,...o]}}(t,e,n);case Z.UNIT_ERROR:return t.status!==J.ACTIVE?t:function(t,e){const n=t.unitsInProgress.find(t=>t.id===e.unitId);if(!n)return t;const s=Date.now()+t.options.dirtyHandling.debounceDelay;if(void 0!==n.requestText&&n.requestText!==n.text)return pt(t,e.unitId,{status:X.DIRTY,requestText:void 0,waitUntil:s});const r=n.retryCount+1;return r>=t.options.maxRetries?pt(t,e.unitId,{status:X.ERROR,retryCount:r,requestText:void 0}):pt(t,e.unitId,{status:X.BACKOFF,retryCount:r,waitUntil:Date.now()+it(r,t.options.backoffBase),requestText:void 0})}(t,e);case Z.FINISH:return{...t,status:J.IDLE};case Z.CLEAR:return{...t,decorations:[],unitsInProgress:void 0,status:J.IDLE,selected:void 0};case Z.RESUME:return t.status!==J.IDLE?t:t.unitsInProgress?.length?{...t,status:J.ACTIVE}:t;case Z.UPDATE_CONTEXT:return{...t,contextState:e.contextState};case Z.REMOVE_DECORATION:return{...t,decorations:t.decorations.filter(t=>t.spec.id!==e.id)};case Z.SELECT_DECORATION:return{...t,selected:e.id};case Z.DESELECT_DECORATION:return{...t,selected:void 0};default:return t}}function yt(t,e){return{status:J.IDLE,decorations:[],selected:void 0,contextState:t,options:e}}async function ht(t,e,n,s){const r=t.getState(e.state);if(!r||r.status!==J.ACTIVE)return!1;const o=function(t,e){if(t.status!==J.ACTIVE)return;const n=Date.now(),s=t.unitsInProgress??[],r=t=>[X.QUEUED,X.BACKOFF,X.DIRTY].includes(t.status)&&t.waitUntil<=n,o=s.find(n=>r(n)&&e.priorityFilter(n,t.contextState));if(o)return o;const a=s.find(r);if(a)return a;return s.find(t=>[X.QUEUED,X.BACKOFF,X.DIRTY].includes(t.status)&&t.waitUntil>n)||void 0}(r,s);if(!o)return!1;lt(e,t,{type:Z.UNIT_STARTED,unitId:o.id});const a=t.getState(e.state);if(!a)return!1;const i=dt(0,a,o.id);if(i&&i.waitUntil>Date.now()){await ut(i.waitUntil-Date.now());const n=t.getState(e.state);if(!n||n.status!==J.ACTIVE)return!1}try{const s=await n(e,o);s.error?lt(e,t,{type:Z.UNIT_ERROR,unitId:o.id,error:s.error}):void 0!==s.data&&lt(e,t,{type:Z.UNIT_SUCCESS,unitId:o.id,response:s.data})}catch(n){lt(e,t,{type:Z.UNIT_ERROR,unitId:o.id,error:n instanceof Error?n:new Error(String(n))})}return!0}async function Rt(t,e,n,s){return!!await ht(t,e,n,s)&&Rt(t,e,n,s)}async function It(t,e,n,s){const r=Array.from({length:s.batchSize},()=>Rt(t,e,n,s));await Promise.all(r)}function Ct(t){return t.status===J.ACTIVE&&t.unitsInProgress.some(t=>t.status===X.QUEUED||t.status===X.WAITING||t.status===X.BACKOFF||t.status===X.DIRTY||t.status===X.PROCESSING)}function xt(t){const{pluginKey:e,unitProcessor:n,decorationFactory:r,decorationTransformer:o,widgetFactory:a,initialContextState:i,options:c,handleKeyDown:u}=t,d=et(c??{});let p=!1;return new s({key:e,state:{init:()=>yt(i,d),apply(t,n,s,o){const a=t.getMeta(e),i=t.docChanged&&void 0!==a&&d.dirtyHandling.skipDirtyOnSelfChange;let c=n;if(t.docChanged&&(c=at(n,t,s,d,i)),a){const t=St(c,a,r,o);return d.onUpdate&&d.onUpdate(t),t}return c}},props:{decorations(t){const n=e.getState(t);if(!n)return l.empty;let s=n.decorations.filter(t=>d.visibilityFilter(t,n.contextState));o&&(s=o(s,n));const r=[];if(a&&n.unitsInProgress)for(const t of n.unitsInProgress)if(t.status!==X.DONE&&t.status!==X.ERROR){const e=a(t);e&&r.push(e)}return l.create(t.doc,[...s,...r])},handleKeyDown:u},view:()=>({update(t){const s=e.getState(t.state);s&&s.status===J.ACTIVE&&!p&&Ct(s)&&(p=!0,It(e,t,n,d).finally(()=>{p=!1;const n=e.getState(t.state);!n||n.status!==J.ACTIVE||Ct(n)||d.dirtyHandling.shouldRecalculate||lt(t,e,{type:Z.FINISH})}))}})})}function At(t){return new n(t)}const Dt=(t,e)=>t.map(n=>{const s=st(n.from,e.mapping),r=st(n.to,e.mapping);return d.inline(s,r,{class:"link-detected",style:"color: #0066cc; text-decoration: underline; cursor: pointer;"},{id:{},unitId:e.id,originalText:n.url,response:t,url:n.url})}),wt=/https?:\/\/[^\s<>"')\]]+/g;const Nt=async(t,e)=>({data:function(t){const e=[];let n=wt.exec(t);for(;null!==n;)e.push({url:n[0],from:n.index,to:n.index+n[0].length}),n=wt.exec(t);return e}(e.text)}),Mt=At("linkDetector");function Pt(){const t=xt({pluginKey:Mt,unitProcessor:Nt,decorationFactory:Dt,initialContextState:{}});return new s({key:Mt,state:t.spec.state,props:{...t.spec.props,handleDOMEvents:{click:(t,e)=>{const n=e.target;if(n.classList.contains("link-detected")){const s=t.posAtDOM(n,0),r=Mt.getState(t.state);if(r){const t=r.decorations.find(t=>t.from<=s&&t.to>=s&&t.spec.url);if(t?.spec.url)return window.open(t.spec.url,"_blank"),e.preventDefault(),!0}}return!1}}},view:t.spec.view})}const bt={moderate:{class:"word-complexity-moderate",style:"background-color: rgba(255, 193, 7, 0.3); border-radius: 2px;"},high:{class:"word-complexity-high",style:"background-color: rgba(244, 67, 54, 0.3); border-radius: 2px;"}},Ot=(t,e)=>t.map(n=>{const s=st(n.from,e.mapping),r=st(n.to,e.mapping),o=bt[n.complexity];return d.inline(s,r,{class:o.class,style:o.style},{id:{},unitId:e.id,originalText:n.word,response:t,syllables:n.syllables,complexity:n.complexity})});function Ut(t){let e=t.toLowerCase().replace(/[^a-z]/g,"");if(e.length<=3)return 1;e=e.replace(/(?:[^laeiouy]es|ed|[^laeiouy]e)$/,""),e=e.replace(/^y/,"");const n=e.match(/[aeiouy]{1,2}/g);return n?Math.max(1,n.length):1}const vt=/[a-zA-Z]+/g;function _t(t,e,n){const s=[];let r=vt.exec(t);for(;null!==r;){const o=r[0],a=Ut(o);a>=e&&s.push({word:o,from:r.index,to:r.index+o.length,syllables:a,complexity:a>=n?"high":"moderate"}),r=vt.exec(t)}return s}const Lt=t=>async(e,n)=>{const s=t(),r=s.moderateThreshold??3,o=s.highThreshold??4;return{data:_t(n.text,r,o)}},kt=async(t,e)=>({data:_t(e.text,3,4)}),Gt={moderateThreshold:3,highThreshold:4},Kt=At("wordComplexity");function Ft(t={}){return xt({pluginKey:Kt,unitProcessor:kt,decorationFactory:Ot,initialContextState:{...Gt,...t}})}const Vt={warning:{class:"sentence-too-long-warning",style:"background-color: rgba(255, 152, 0, 0.2); border-radius: 2px;"},error:{class:"sentence-too-long-error",style:"background-color: rgba(244, 67, 54, 0.2); border-radius: 2px;"}},Bt=(t,e)=>t.map(n=>{const s=st(n.from,e.mapping),r=st(n.to,e.mapping),o=Vt[n.severity];return d.inline(s,r,{class:o.class,style:o.style},{id:{},unitId:e.id,originalText:e.text.slice(n.from,n.to),response:t,wordCount:n.wordCount,severity:n.severity})}),Ht=/[^.!?]*[.!?]+/g,Wt=/\b\w+\b/g;function $t(t){const e=t.match(Wt);return e?e.length:0}function qt(t,e,n){const s=[];let r=Ht.exec(t);for(;null!==r;){const o=r[0],a=$t(o);a>=e&&s.push({from:r.index,to:r.index+o.length,wordCount:a,severity:a>=n?"error":"warning"}),r=Ht.exec(t)}return s}const Qt=t=>async(e,n)=>{const s=t(),r=s.warningThreshold??25,o=s.errorThreshold??40;return{data:qt(n.text,r,o)}},Yt=async(t,e)=>({data:qt(e.text,25,40)}),zt={warningThreshold:25,errorThreshold:40},jt=At("sentenceLength");function Jt(t={}){return xt({pluginKey:jt,unitProcessor:Yt,decorationFactory:Bt,initialContextState:{...zt,...t}})}const Xt=(t,e)=>[d.inline(e.from+1,e.to-1,{class:"random-processor-success",style:"background-color: rgba(76, 175, 80, 0.2); border-radius: 2px;"},{id:{},unitId:e.id,originalText:e.text,response:t,processingTime:t.processingTime})],Zt=t=>{let e,n;const s=Math.max(0,t.waitUntil-Date.now());switch(t.status){case X.QUEUED:e="⏳ Queued...",n="random-processor-widget queued";break;case X.PROCESSING:e="⚙️ Processing...",n="random-processor-widget processing";break;case X.BACKOFF:e=`🔄 Retry in ${Math.ceil(s/1e3)}s (attempt ${t.retryCount+1})`,n="random-processor-widget backoff";break;case X.ERROR:e=`❌ Failed after ${t.retryCount} retries`,n="random-processor-widget error";break;case X.DIRTY:e="⚠️ Changed",n="random-processor-widget dirty";break;case X.WAITING:e="⏸️ Waiting...",n="random-processor-widget waiting";break;default:return}let r,o;switch(t.status){case X.ERROR:r="#ffebee",o="#c62828";break;case X.DIRTY:r="#fff9c4",o="#f57f17";break;case X.BACKOFF:r="#fff3e0",o="#e65100";break;default:r="#e3f2fd",o="#1565c0"}const a=document.createElement("span");if(a.className=n,a.textContent=e,t.status===X.BACKOFF&&s>0){const e=setInterval(()=>{const n=Math.max(0,t.waitUntil-Date.now());a.textContent=`🔄 Retry in ${Math.ceil(n/1e3)}s (attempt ${t.retryCount+1})`,(n<=0||!a.isConnected)&&clearInterval(e)},1e3)}return a.style.cssText=`\n display: inline-block;\n padding: 2px 8px;\n margin-right: 8px;\n font-size: 12px;\n border-radius: 4px;\n background: ${r};\n color: ${o};\n `,d.widget(t.from+1,a,{side:-1})};function te(t,e){return Math.floor(Math.random()*(e-t+1))+t}function ee(t){return new Promise(e=>setTimeout(e,t))}const ne=t=>async(e,n)=>{const s=t(),r=s.minDelay??500,o=s.maxDelay??3e3,a=s.errorRate??.3,i=te(r,o);await ee(i);return Math.random()<a?{error:new Error(`Random error after ${i}ms (attempt ${n.metadata.attempt})`)}:{data:{processingTime:i,success:!0,message:`Processed "${n.text.slice(0,20)}..." in ${i}ms`}}},se=async(t,e)=>{const n=te(500,3e3);await ee(n);return Math.random()<.3?{error:new Error(`Random error after ${n}ms (attempt ${e.metadata.attempt})`)}:{data:{processingTime:n,success:!0,message:`Processed "${e.text.slice(0,20)}..." in ${n}ms`}}},re={minDelay:500,maxDelay:3e3,errorRate:.3},oe=At("randomProcessor");function ae(t={}){return xt({pluginKey:oe,unitProcessor:se,decorationFactory:Xt,widgetFactory:Zt,initialContextState:{...re,...t},options:{batchSize:2,maxRetries:3,backoffBase:2e3}})}const ie=(t,e)=>t.fixed&&0!==t.suggestions.length?t.suggestions.map(n=>{const s=st(n.from,e.mapping),r=st(n.to,e.mapping),o=""===n.replacement,a={id:{},unitId:e.id,originalText:n.original,replacement:n.replacement,response:t};return d.inline(s,r,{class:"grammarSuggestionV2 "+(o?"removalSuggestionV2":"")},a)}):[],ce=(t,e)=>{const n=e.contextState.selectedSuggestionId;return n?t.map(t=>{const e=t.spec;return e.id===n?d.inline(t.from,t.to,{class:"grammarSuggestionV2 grammarSuggestionV2-selected"},e):t}):t},ue=t=>{let e,n;const s=Math.max(0,t.waitUntil-Date.now());switch(t.status){case X.QUEUED:case X.WAITING:e="⏳",n="grammarWidgetV2 queued";break;case X.PROCESSING:e="🔍",n="grammarWidgetV2 processing";break;case X.BACKOFF:e=`🔄 ${Math.ceil(s/1e3)}s`,n="grammarWidgetV2 backoff";break;case X.ERROR:e="❌",n="grammarWidgetV2 error";break;case X.DIRTY:e="✏️",n="grammarWidgetV2 dirty";break;default:return}const r=document.createElement("span");if(r.className=n,r.textContent=e,t.status===X.BACKOFF&&s>0){const e=setInterval(()=>{const n=Math.max(0,t.waitUntil-Date.now());r.textContent=`🔄 ${Math.ceil(n/1e3)}s`,(n<=0||!r.isConnected)&&clearInterval(e)},1e3)}return d.widget(t.from+1,r,{side:-1})};const le=t=>{const{apiKey:e,apiEndpoint:n,model:s,modelStateManager:r}="string"==typeof t?{apiKey:t,apiEndpoint:void 0,model:void 0,modelStateManager:void 0}:t;return async(t,o)=>{try{const t=r?.getCurrentModel()??s,l=await P({apiKey:e,text:o.text,endpoint:n,model:t});if(l.error)return r?.handleFailure(),{error:new Error("Grammar API error")};if(r?.handleSuccess(),!l.fixed)return{data:{fixed:!1,originalText:o.text,fixedText:o.text,suggestions:[]}};const d=(c=o.text,u=l.result,a(c,u).filter(t=>!i(t)).filter(t=>t.original!==`${t.replacement}\n`).map(t=>({from:t.from,to:t.replacement.endsWith("\n")?t.to-1:t.to,original:t.original,replacement:t.replacement.endsWith("\n")?t.replacement.slice(0,-1):t.replacement})));return{data:{fixed:d.length>0,originalText:o.text,fixedText:l.result,suggestions:d}}}catch(t){return r?.handleFailure(),{error:t instanceof Error?t:new Error(String(t))}}var c,u}};function de(t,e,n){const s=e.getState(t.state);if(s)return s.decorations.find(t=>t.spec.id===n)}function pe(n,s,o){const a=de(n,s,o);if(!a)return;const i=a.spec,{replacement:c}=i,{from:u,to:l}=a,d=c.split("\n").map(t=>t?n.state.schema.node("paragraph",null,n.state.schema.text(t)):n.state.schema.node("paragraph")),p=t.fromArray(d);let{tr:m}=n.state;m=m.setSelection(r.create(n.state.doc,u,l)),m.selection.replace(m,new e(p,1,1)),m=m.setMeta(s,{type:Z.REMOVE_DECORATION,id:o}),n.dispatch(m)}function me(t,e,n){lt(t,e,{type:Z.REMOVE_DECORATION,id:n})}function ge(t,e,n){lt(t,e,{type:Z.UPDATE_CONTEXT,contextState:{selectedSuggestionId:n}})}function Ee(t,e){lt(t,e,{type:Z.UPDATE_CONTEXT,contextState:{selectedSuggestionId:void 0}})}function fe(t,e){const n=e.getState(t.state);if(n?.contextState.selectedSuggestionId)return de(t,e,n.contextState.selectedSuggestionId)}async function Te(t,e,n,s){const r=s?.endpoint??x,o=s?.modelStateManager,a=o?.getCurrentModel()??s?.model??D;return new Promise((s,i)=>{const c={previousPromptType:"Grammar",oldVersion:e,newVersion:n};M({apiKey:t,text:e,task:T.Hint,params:c,endpoint:r,model:a},{onChunk:()=>{},onComplete:t=>{o?.handleSuccess(),s(t)},onError:t=>{o?.handleFailure(),i(t)}})})}const Se={selectedSuggestionId:void 0},ye=At("grammarSuggestV2"),he=(t,e,n,s,r,o)=>{const a=e.spec,i=document.createElement("div");i.className="grammarPopupV2";const c=document.createElement("div");c.className="grammarPopupV2-mainRow";const u=document.createElement("span");u.className="grammarPopupV2-original",u.textContent=`"${a.originalText}"`;const l=document.createElement("span");l.className="grammarPopupV2-arrow",l.textContent=" → ";const d=document.createElement("span");d.className="grammarPopupV2-replacement",d.textContent=""===a.replacement?"(remove)":`"${a.replacement}"`;const p=document.createElement("button");p.className="grammarPopupV2-hint",p.textContent="?",p.title="Why this suggestion?";const m=document.createElement("div");m.className="grammarPopupV2-hintArea",m.style.display="none";let g=!1;p.onclick=async t=>{if(t.preventDefault(),t.stopPropagation(),"none"===m.style.display){if(m.style.display="block",!g){m.innerHTML='<span class="grammarPopupV2-loading">Loading...</span>';try{const t=await o();m.innerHTML="";const e=document.createElement("span");e.className="grammarPopupV2-hintText",e.textContent=t,m.appendChild(e),g=!0}catch{m.innerHTML='<span class="grammarPopupV2-hintError">Could not load hint</span>'}}}else m.style.display="none"};const E=document.createElement("button");E.className="grammarPopupV2-accept",E.textContent="✓",E.onclick=t=>{t.preventDefault(),t.stopPropagation(),s()};const f=document.createElement("button");return f.className="grammarPopupV2-discard",f.textContent="✕",f.onclick=t=>{t.preventDefault(),t.stopPropagation(),r()},c.appendChild(u),c.appendChild(l),c.appendChild(d),c.appendChild(p),c.appendChild(E),c.appendChild(f),i.appendChild(c),i.appendChild(m),i};function Re(t,e={}){const{apiEndpoint:n,model:r,fallback:o,batchSize:a=4,maxRetries:i=3,backoffBase:c=2e3,debounceMs:u=1e3,createPopup:p=he}=e,m=o?function(t){const{primaryModel:e,fallbackModel:n,failureThreshold:s}=t;let r=0;return{getCurrentModel:()=>n&&s>0&&r>=s?n:e,handleSuccess(){r=0},handleFailure(){n&&r++}}}({primaryModel:r,fallbackModel:o.fallbackModel,failureThreshold:o.failureThreshold??3}):void 0,g=le({apiKey:t,apiEndpoint:n,model:r,modelStateManager:m}),E=xt({pluginKey:ye,unitProcessor:g,decorationFactory:ie,decorationTransformer:ce,widgetFactory:ue,initialContextState:Se,options:{batchSize:a,maxRetries:i,backoffBase:c,dirtyHandling:{debounceDelay:u}}});return new s({key:ye,state:E.spec.state,props:{...E.spec.props,decorations(e){const s=E.spec.props?.decorations,o=s?s.call(E,e):l.empty;if("react"===p)return o;const a=ye.getState(e);if(!a?.contextState.selectedSuggestionId)return o;const i=a.decorations.find(t=>t.spec.id===a.contextState.selectedSuggestionId);if(!i)return o;const c=i.spec,u=d.widget(i.from,(e,s)=>{const o=s();return void 0===o?document.createElement("div"):p(e,i,o,()=>pe(e,ye,i.spec.id),()=>me(e,ye,i.spec.id),()=>Te(t,c.originalText,c.replacement,{endpoint:n,model:r,modelStateManager:m}))},{id:i.spec.id,side:-1,stopEvent:()=>!0});return o.add(e.doc,[u])},handleClick(t,e,n){const s=ye.getState(t.state);if(!s)return!1;const r=s.decorations.find(t=>t.from<=e&&e<=t.to);return r?(r.spec.id===s.contextState.selectedSuggestionId||ge(t,ye,r.spec.id),!1):(s.contextState.selectedSuggestionId&&Ee(t,ye),!1)}},view:E.spec.view})}var Ie,Ce;!function(t){t.IDLE="idle",t.DEBOUNCING="debouncing",t.PENDING="pending",t.STREAMING="streaming",t.SHOWING="showing"}(Ie||(Ie={})),function(t){t.SET_ENABLED="SET_ENABLED",t.START_DEBOUNCE="START_DEBOUNCE",t.START_REQUEST="START_REQUEST",t.STREAM_UPDATE="STREAM_UPDATE",t.STREAM_COMPLETE="STREAM_COMPLETE",t.STREAM_ERROR="STREAM_ERROR",t.DISMISS="DISMISS",t.ACCEPT="ACCEPT"}(Ce||(Ce={}));const xe={debounceMs:500,maxContextLength:2e3,ghostTextClass:"autoCompleteGhostText"},Ae=new WeakMap;function De(t){const e=Ae.get(t);e&&(e.abort(),Ae.delete(t))}function we(t){return Ae.has(t)}function Ne(t,e){if(!e)return e;const{doc:n,selection:s}=t.state,r=s.from;if(r<=0)return e;const o=n.textBetween(r-1,r,""),a=e.charAt(0),i=/[\s\-]/;return!i.test(o)&&!i.test(a)?` ${e}`:e}const Me=new n("autoCompletePlugin"),Pe={status:Ie.IDLE,enabled:!0,suggestion:"",cursorPos:0};function be(t,e){switch(e.type){case Ce.SET_ENABLED:return{...Pe,enabled:e.enabled};case Ce.START_DEBOUNCE:return{...t,status:Ie.DEBOUNCING,cursorPos:e.cursorPos,suggestion:"",error:void 0};case Ce.START_REQUEST:return t.status!==Ie.DEBOUNCING?t:{...t,status:Ie.PENDING};case Ce.STREAM_UPDATE:return t.status!==Ie.PENDING&&t.status!==Ie.STREAMING?t:{...t,status:Ie.STREAMING,suggestion:e.suggestion};case Ce.STREAM_COMPLETE:return t.status!==Ie.PENDING&&t.status!==Ie.STREAMING?t:{...t,status:Ie.SHOWING,suggestion:e.suggestion};case Ce.STREAM_ERROR:return{...t,status:Ie.IDLE,error:e.error,suggestion:""};case Ce.DISMISS:case Ce.ACCEPT:return{...t,status:Ie.IDLE,suggestion:""};default:return t}}function Oe(t,e={}){const n={...xe,...e},{debounceMs:r,maxContextLength:o,apiEndpoint:a,model:i,ghostTextClass:c}=n;let u=null,p=!1,m=null,g=null;return new s({key:Me,state:{init:()=>({...Pe}),apply(t,e,n,s){const r=t.getMeta(Me);if(r)return be(e,r);if(t.docChanged&&(e.status===Ie.SHOWING||e.status===Ie.STREAMING)){if(s.selection.from!==e.cursorPos)return be(e,{type:Ce.DISMISS})}return!t.selectionSet||t.docChanged||e.status!==Ie.SHOWING&&e.status!==Ie.STREAMING?e:be(e,{type:Ce.DISMISS})}},props:{decorations(t){const e=Me.getState(t);if(!e||!e.enabled)return l.empty;if((e.status===Ie.STREAMING||e.status===Ie.SHOWING)&&e.suggestion){const n=document.createElement("span");n.className=c,n.textContent=e.suggestion,n.setAttribute("contenteditable","false");const s=d.widget(e.cursorPos,n,{side:1});return l.create(t.doc,[s])}return l.empty},handleKeyDown(t,e){const n=Me.getState(t.state);return!(!n||!n.enabled)&&("Tab"===e.key&&!e.shiftKey&&n.status===Ie.SHOWING&&n.suggestion?(e.preventDefault(),m=n.suggestion,g=n.cursorPos,t.dispatch(t.state.tr.setMeta(Me,{type:Ce.ACCEPT})),!0):"Escape"===e.key&&(n.status===Ie.STREAMING||n.status===Ie.SHOWING)&&(e.preventDefault(),De(t),t.dispatch(t.state.tr.setMeta(Me,{type:Ce.DISMISS})),!0))}},view:()=>({update(e,n){const s=Me.getState(e.state),c=Me.getState(n);if(s&&s.enabled){if(null!==m&&null!==g){const t=m,n=g;m=null,g=null;const s=e.state.tr.insertText(t,n);return e.dispatch(s),void e.focus()}if(e.state,e.state.doc!==n.doc&&c?.status!==Ie.PENDING&&c?.status!==Ie.STREAMING){e.state.tr,u&&(clearTimeout(u),u=null),De(e);const t=e.state.selection.from;e.dispatch(e.state.tr.setMeta(Me,{type:Ce.START_DEBOUNCE,cursorPos:t})),u=setTimeout(()=>{u=null;const t=Me.getState(e.state);t?.status===Ie.DEBOUNCING&&e.dispatch(e.state.tr.setMeta(Me,{type:Ce.START_REQUEST}))},r)}if(s.status===Ie.PENDING&&!p){if(!function(t){const{doc:e,selection:n}=t.state,s=n.from,r=e.resolve(s),o=r.parent,a=r.start();if(s!==a+o.content.size)return!1;const i=o.textBetween(0,s-a,"");return!!i.trim()&&!/[.!?]\s*$/.test(i)}(e))return void e.dispatch(e.state.tr.setMeta(Me,{type:Ce.DISMISS}));const n=function(t,e){const{doc:n,selection:s}=t.state,r=s.from,o=n.textBetween(0,r,"\n");return o.length>e?o.slice(-e):o}(e,o);if(!n.trim())return void e.dispatch(e.state.tr.setMeta(Me,{type:Ce.DISMISS}));p=!0,async function({view:t,pluginKey:e,apiKey:n,context:s,apiEndpoint:r,model:o}){De(t);const a=new AbortController;Ae.set(t,a);try{await M({apiKey:n,text:s,task:f.SmallComplete,endpoint:r,model:o,signal:a.signal},{onChunk:(n,s)=>{t.isDestroyed||t.dispatch(t.state.tr.setMeta(e,{type:Ce.STREAM_UPDATE,suggestion:Ne(t,s)}))},onComplete:n=>{t.isDestroyed||t.dispatch(t.state.tr.setMeta(e,{type:Ce.STREAM_COMPLETE,suggestion:Ne(t,n)}))},onError:n=>{console.error("Auto-complete streaming error:",n),t.isDestroyed||t.dispatch(t.state.tr.setMeta(e,{type:Ce.STREAM_ERROR,error:n.message}))}})}finally{Ae.delete(t)}}({view:e,pluginKey:Me,apiKey:t,context:n,apiEndpoint:a,model:i}).finally(()=>{p=!1})}}else u&&(clearTimeout(u),u=null)},destroy(){u&&(clearTimeout(u),u=null)}})})}function Ue(t,e){e||De(t),t.dispatch(t.state.tr.setMeta(Me,{type:Ce.SET_ENABLED,enabled:e}))}function ve(t){const e=Me.getState(t.state);if(!e||!e.suggestion)return!1;const n=t.state.tr.insertText(e.suggestion,e.cursorPos);return t.dispatch(n),t.dispatch(t.state.tr.setMeta(Me,{type:Ce.DISMISS})),t.focus(),!0}function _e(t){De(t),t.dispatch(t.state.tr.setMeta(Me,{type:Ce.DISMISS}))}function Le(t){return Me.getState(t.state)}function ke(t){const e=Me.getState(t.state);return e?.enabled??!1}function Ge(t){const e=Me.getState(t.state);return!!e?.suggestion}const Ke=L;export{Z as ActionType,T as AiPromptsWithParam,f as AiPromptsWithoutParam,Ce as AutoCompleteActionType,Ie as AutoCompleteStatus,I as CompleteActionType,R as CompleteStatus,x as DEFAULT_COMPLETION_ENDPOINT,A as DEFAULT_GRAMMAR_ENDPOINT,D as DEFAULT_MODEL,m as GrammarSuggestElementClass,p as GrammarSuggestMetaType,g as MoodParamType,y as OpenAiPromptsWithParam,S as OpenAiPromptsWithoutParam,J as RunnerStatus,h as Status,E as TranslationTargetLanguage,X as UnitStatus,ve as acceptAutoCompletion,G as acceptResult,pe as acceptSuggestion,mt as allUnitsFinished,Me as autoCompleteKey,Oe as autoCompletePlugin,xt as blockRunnerPlugin,it as calculateBackoff,Tt as canResume,O as cancelActiveRequest,De as cancelAutoCompleteRequest,F as cancelTask,B as clearError,Ke as completePlugin,q as completePluginKey,L as completePluginV2,v as completeV2Key,Ut as countSyllables,w as createApiConfig,At as createBlockRunnerKey,N as createGrammarApiConfig,le as createGrammarProcessor,yt as createInitialState,Pt as createLinkDetectorPlugin,ne as createRandomProcessor,ae as createRandomProcessorPlugin,Jt as createSentenceLengthPlugin,Qt as createSentenceLengthProcessor,gt as createUnitsFromDocument,Ft as createWordComplexityPlugin,Lt as createWordComplexityProcessor,xe as defaultAutoCompleteOptions,$ as defaultCompleteOptions,C as defaultCompleteV2Options,W as defaultOptions,tt as defaultRunnerOptions,Ee as deselectSuggestion,me as discardSuggestion,_e as dismissAutoCompletion,lt as dispatchAction,It as executeParallel,nt as extractTextWithMapping,Le as getAutoCompleteState,j as getChangedRegions,H as getCompleteState,ct as getProgress,fe as getSelectedDecoration,dt as getUnitById,rt as getUnitsInRange,ie as grammarDecorationFactory,ce as grammarDecorationTransformer,P as grammarRequest,Q as grammarSuggestPluginKey,Re as grammarSuggestPluginV2,ye as grammarSuggestV2Key,ue as grammarWidgetFactory,St as handleAction,U as hasActiveRequest,we as hasAutoCompleteRequest,Ge as hasAutoCompletion,Ct as hasUnitsToProcess,ke as isAutoCompleteEnabled,Dt as linkDetectorDecorationFactory,Mt as linkDetectorKey,Nt as linkDetectorProcessor,et as mergeOptions,Et as pauseRunner,se as randomProcessor,Xt as randomProcessorDecorationFactory,oe as randomProcessorKey,Zt as randomProcessorWidgetFactory,K as rejectResult,at as remapPositions,Te as requestHint,ft as resumeRunner,ge as selectSuggestion,Bt as sentenceLengthDecorationFactory,jt as sentenceLengthKey,Yt as sentenceLengthProcessor,Ue as setAutoCompleteEnabled,z as setCompleteEnabled,V as setEnabled,Y as setGrammarSuggestEnabled,ut as sleep,k as startTask,M as streamingRequest,st as textToDocPos,pt as updateUnit,Ot as wordComplexityDecorationFactory,Kt as wordComplexityKey,kt as wordComplexityProcessor};
1
+ import{Fragment as e,Slice as t}from"prosemirror-model";import{PluginKey as n,Plugin as s,TextSelection as a}from"prosemirror-state";import r from"fast-diff";import{getDiff as o,isIdentity as i}from"@emergence-engineering/fast-diff-merge";export{getDiff}from"@emergence-engineering/fast-diff-merge";import{Decoration as c,DecorationSet as l}from"prosemirror-view";import{textPosToDocPos as d,UnitStatus as u,ActionType as p,dispatchAction as m,createBlockRunnerKey as g,blockRunnerPlugin as E}from"@emergence-engineering/prosemirror-block-runner";var S,f,T,h,R,y;!function(e){e.suggestionUpdate="suggestionUpdate",e.acceptSuggestion="acceptSuggestion",e.openSuggestion="openSuggestion",e.closeSuggestion="closeSuggestion",e.discardSuggestion="discardSuggestion",e.setEnabled="setEnabled"}(S||(S={})),function(e){e.grammarSuggestPopup="grammar-suggest-popup"}(f||(f={})),function(e){e.Casual="Casual",e.Confident="Confident",e.Straightforward="Straightforward",e.Friendly="Friendly"}(T||(T={})),function(e){e.English="English",e.Spanish="Spanish",e.French="French",e.German="German",e.Italian="Italian",e.Portuguese="Portuguese",e.Dutch="Dutch",e.Russian="Russian",e.Chinese="Chinese",e.Korean="Korean",e.Japanese="Japanese"}(h||(h={})),function(e){e.Complete="Complete",e.SmallComplete="SmallComplete",e.Improve="Improve",e.MakeLonger="MakeLonger",e.MakeShorter="MakeShorter",e.Simplify="Simplify",e.Explain="Explain",e.ActionItems="ActionItems"}(R||(R={})),function(e){e.ChangeTone="ChangeTone",e.Translate="Translate",e.Hint="Hint",e.Custom="Custom"}(y||(y={}));const C=R,A=y;var M,x,I;!function(e){e.idle="idle",e.new="new",e.streaming="streaming",e.finished="finished",e.accepted="accepted",e.cancelled="cancelled",e.rejected="rejected",e.done="done",e.error="error"}(M||(M={})),function(e){e.IDLE="idle",e.PENDING="pending",e.STREAMING="streaming",e.PREVIEW="preview",e.APPLYING="applying"}(x||(x={})),function(e){e.START_TASK="START_TASK",e.STREAM_UPDATE="STREAM_UPDATE",e.STREAM_COMPLETE="STREAM_COMPLETE",e.STREAM_ERROR="STREAM_ERROR",e.ACCEPT_RESULT="ACCEPT_RESULT",e.REJECT_RESULT="REJECT_RESULT",e.CANCEL_TASK="CANCEL_TASK",e.CLEAR_ERROR="CLEAR_ERROR",e.SET_ENABLED="SET_ENABLED"}(I||(I={}));const N={maxSelection:1e3},P="https://suggestion-gw5lxik4dq-uc.a.run.app",D="https://prosemirror-ai-plugin.web.app/api/suggestion",w="openai:gpt-4o-mini";function L(e){return{apiKey:e.apiKey,endpoint:e.endpoint??P,model:e.model??w}}function b(e){return{apiKey:e.apiKey,endpoint:e.endpoint??D,model:e.model??w}}async function _(e,t){const{apiKey:n,text:s,task:a,params:r,endpoint:o=P,model:i=w,signal:c}=e;let l="";const d={model:i,modelParams:{input:[s],task:a,params:r}};try{const e=await fetch(o,{method:"POST",cache:"no-cache",headers:{"Content-Type":"application/json",Authorization:`Bearer ${n}`},signal:c,body:JSON.stringify(d)});if(!e.ok)throw new Error(`HTTP error! status: ${e.status}`);const s=e.body?.getReader();if(!s)throw new Error("No response body");for(;;){const{done:e,value:n}=await s.read();if(e)break;if(c?.aborted)return;const a=(new TextDecoder).decode(n);l+=a,t.onChunk(a,l)}c?.aborted||t.onComplete(l)}catch(e){if(e instanceof Error&&"AbortError"===e.name)return;t.onError(e instanceof Error?e:new Error(String(e)))}}async function v(e){const{apiKey:t,text:n,endpoint:s=D,model:a=w}=e,r={model:a,modelParams:{input:[...n.split("\n")]}};try{const e=await fetch(s,{method:"POST",cache:"no-cache",headers:{"Content-Type":"application/json",Authorization:`Bearer ${t}`},body:JSON.stringify(r)});if(!e.ok){const t=await e.text();return console.error({status:e.status,text:t}),{fixed:!1,result:n,error:!0}}const a=await e.json();return a?.length?{result:a.join("\n"),fixed:!0}:{fixed:!1,result:n}}catch(e){return console.error("Grammar request error:",e),{fixed:!1,result:n,error:!0}}}const O=new WeakMap;function k(e){const t=O.get(e);t&&(t.abort(),O.delete(e))}function G(e){return O.has(e)}const U=new n("completePluginV2"),K={status:x.IDLE,streamedResult:"",enabled:!0};function V(n,r={}){const{maxSelection:o,apiEndpoint:i,model:c}={...N,...r};let l=!1;return new s({key:U,state:{init:()=>({...K}),apply(e,t){const n=e.getMeta(U);return n?function(e,t){switch(t.type){case I.SET_ENABLED:return{...e,enabled:t.enabled,...t.enabled?{}:K};case I.START_TASK:return e.status!==x.IDLE?e:{...e,status:x.PENDING,taskType:t.taskType,params:t.params,selection:t.selection,streamedResult:"",error:void 0};case I.STREAM_UPDATE:return e.status!==x.PENDING&&e.status!==x.STREAMING?e:{...e,status:x.STREAMING,streamedResult:t.result};case I.STREAM_COMPLETE:return e.status!==x.PENDING&&e.status!==x.STREAMING?e:{...e,status:x.PREVIEW,streamedResult:t.result};case I.STREAM_ERROR:return{...e,status:x.IDLE,error:t.error,streamedResult:"",taskType:void 0,params:void 0,selection:void 0};case I.ACCEPT_RESULT:return e.status!==x.PREVIEW?e:{...e,status:x.APPLYING};case I.REJECT_RESULT:return e.status!==x.PREVIEW?e:{...K,enabled:e.enabled};case I.CANCEL_TASK:return{...K,enabled:e.enabled};case I.CLEAR_ERROR:return{...e,error:void 0};default:return e}}(t,n):t.status===x.APPLYING?{...K,enabled:t.enabled}:t}},view:()=>({update(s){const r=U.getState(s.state);if(r&&r.enabled){if(r.status===x.PENDING&&!l){const{text:e,error:t}=function(e,t,n){const{taskType:s,selection:a}=t;if(s===R.Complete){const{doc:t}=e.state,n=[];return t.descendants(e=>{"paragraph"===e.type.name&&n.push(e.textContent)}),{text:n.length>=2?n.slice(-2).join(" "):n.join(" ")}}return a?e.state.doc.textBetween(a.from,a.to).length>n?{text:"",error:"Selection is too big"}:{text:e.state.doc.textBetween(a.from,a.to,"\n")}:{text:"",error:"No selection"}}(s,r,o);if(t)return void s.dispatch(s.state.tr.setMeta(U,{type:I.STREAM_ERROR,error:t}));if(!e)return void s.dispatch(s.state.tr.setMeta(U,{type:I.STREAM_ERROR,error:"No text to process"}));l=!0,async function({view:e,pluginKey:t,apiKey:n,text:s,task:a,params:r,apiEndpoint:o,model:i}){k(e);const c=new AbortController;O.set(e,c);try{await _({apiKey:n,text:s,task:a,params:r,endpoint:o,model:i,signal:c.signal},{onChunk:(n,s)=>{e.isDestroyed||e.dispatch(e.state.tr.setMeta(t,{type:I.STREAM_UPDATE,result:s}))},onComplete:n=>{e.isDestroyed||e.dispatch(e.state.tr.setMeta(t,{type:I.STREAM_COMPLETE,result:n}))},onError:n=>{console.error("Streaming request error:",n),e.isDestroyed||e.dispatch(e.state.tr.setMeta(t,{type:I.STREAM_ERROR,error:n.message}))}})}finally{O.delete(e)}}({view:s,pluginKey:U,apiKey:n,text:e,task:r.taskType,params:r.params,apiEndpoint:i,model:c}).finally(()=>{l=!1})}r.status===x.APPLYING&&function(n,s){const{taskType:r,selection:o,streamedResult:i}=s;if(!i)return;let{tr:c}=n.state;if(r===R.Complete)c=c.insertText(i,n.state.doc.nodeSize-2);else if(o){const s=i.split("\n\n").map(e=>n.state.schema.node("paragraph",null,e?n.state.schema.text(e):void 0)),r=e.fromArray(s);c=c.setSelection(a.create(n.state.doc,o.from,o.to)),c.selection.replace(c,new t(r,0,0))}n.dispatch(c),n.focus()}(s,r)}},destroy(){}})})}function B(e,t,n){const s=e.state.selection;e.dispatch(e.state.tr.setMeta(U,{type:I.START_TASK,taskType:t,params:n,selection:s.empty?void 0:s}))}function W(e){e.dispatch(e.state.tr.setMeta(U,{type:I.ACCEPT_RESULT}))}function H(e){e.dispatch(e.state.tr.setMeta(U,{type:I.REJECT_RESULT}))}function F(e){k(e),e.dispatch(e.state.tr.setMeta(U,{type:I.CANCEL_TASK}))}function $(e,t){t||k(e),e.dispatch(e.state.tr.setMeta(U,{type:I.SET_ENABLED,enabled:t}))}function j(e){e.dispatch(e.state.tr.setMeta(U,{type:I.CLEAR_ERROR}))}function q(e){return U.getState(e.state)}const J={debounceMs:2e3,createUpdatePopup:(e,t,n,s,a)=>{const r=document.createElement("div");r.className="grammar-suggest-tooltip";const{spec:o}=t,i=e.dom.getBoundingClientRect();r.id=f.grammarSuggestPopup;const c=e.coordsAtPos(n),l=e.dom.scrollTop||0,d=e.dom.scrollLeft||0;r.style.left=`${c.left-i.left+d}px`,r.style.top=`${c.bottom-i.top+l+5}px`;const u=document.createElement("div");u.className="grammar-suggest-tooltip-apply",u.innerText=o.text||o.originalText,o.text||(u.style.textDecoration="line-through",u.style.color="red"),u.onclick=()=>{s(e,t)},r.appendChild(u);const p=document.createElement("div");return p.innerHTML="<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' width='24' height='24'><path d='M12 22C6.47715 22 2 17.5228 2 12C2 6.47715 6.47715 2 12 2C17.5228 2 22 6.47715 22 12C22 17.5228 17.5228 22 12 22ZM12 20C16.4183 20 20 16.4183 20 12C20 7.58172 16.4183 4 12 4C7.58172 4 4 7.58172 4 12C4 16.4183 7.58172 20 12 20ZM12 10.5858L14.8284 7.75736L16.2426 9.17157L13.4142 12L16.2426 14.8284L14.8284 16.2426L12 13.4142L9.17157 16.2426L7.75736 14.8284L10.5858 12L7.75736 9.17157L9.17157 7.75736L12 10.5858Z'></path></svg>",p.className="grammar-suggest-tooltip-discard",p.onclick=()=>{a(e,t)},r.appendChild(p),r},withYjs:!1},Q={maxSelection:1e3},z=new n("completePlugin"),Y=new n("grammarSuggestPlugin"),Z=(e,t)=>{e.dispatch(e.state.tr.setMeta(Y,{type:S.setEnabled,enabled:t}))},X=(e,t)=>{e.dispatch(e.state.tr.setMeta(z,{type:"setEnabled",enabled:t}))},ee=(e,t)=>{if(e===t)return{start:e.length,end:e.length,oldStart:e.length,oldEnd:e.length,oldText:"",newText:""};const n=r(e,t),s=n[0],a=s[0]===r.EQUAL?s[1].length-1:0,o=((e,t)=>{if(t[t.length-1][0]!==r.EQUAL)return e.length;const n=t.slice(0,t.length-1);let s=0;for(const e of n){const[t,n]=e;t===r.EQUAL&&(s+=n.length),t===r.DELETE&&(s+=n.length)}return s})(e,n),i=e.lastIndexOf("\n",a),c=e.indexOf("\n",o),l=-1===i?0:i+1,d=-1===c?e.length:c,u=e.slice(l,d),p=t.slice(l,t.length-(e.length-d));return{start:l,end:l+p.length,oldStart:l,oldEnd:d,oldText:u,newText:p}},te=(e,t)=>e.fixed&&0!==e.suggestions.length?e.suggestions.map(n=>{const s=t.from+1+d(n.from,t.mapping),a=t.from+1+d(n.to,t.mapping),r=""===n.replacement,o={id:{},unitId:t.id,originalText:n.original,replacement:n.replacement,response:e};return c.inline(s,a,{class:"grammarSuggestionV2 "+(r?"removalSuggestionV2":"")},o)}):[],ne=(e,t)=>{const n=t.contextState.selectedSuggestionId;return n?e.map(e=>{const t=e.spec;return t.id===n?c.inline(e.from,e.to,{class:"grammarSuggestionV2 grammarSuggestionV2-selected"},t):e}):e},se=e=>{let t,n;const s=Math.max(0,e.waitUntil-Date.now());switch(e.status){case u.QUEUED:case u.WAITING:t="⏳",n="grammarWidgetV2 queued";break;case u.PROCESSING:t="🔍",n="grammarWidgetV2 processing";break;case u.BACKOFF:t=`🔄 ${Math.ceil(s/1e3)}s`,n="grammarWidgetV2 backoff";break;case u.ERROR:t="❌",n="grammarWidgetV2 error";break;case u.DIRTY:t="✏️",n="grammarWidgetV2 dirty";break;default:return}const a=document.createElement("span");if(a.className=n,a.textContent=t,e.status===u.BACKOFF&&s>0){const t=setInterval(()=>{const n=Math.max(0,e.waitUntil-Date.now());a.textContent=`🔄 ${Math.ceil(n/1e3)}s`,(n<=0||!a.isConnected)&&clearInterval(t)},1e3)}return c.widget(e.from+1,a,{side:-1})};const ae=e=>{const{apiKey:t,apiEndpoint:n,model:s,modelStateManager:a}="string"==typeof e?{apiKey:e,apiEndpoint:void 0,model:void 0,modelStateManager:void 0}:e;return async(e,r)=>{try{const e=a?.getCurrentModel()??s,d=await v({apiKey:t,text:r.text,endpoint:n,model:e});if(d.error)return a?.handleFailure(),{error:new Error("Grammar API error")};if(a?.handleSuccess(),!d.fixed)return{data:{fixed:!1,originalText:r.text,fixedText:r.text,suggestions:[]}};const u=(c=r.text,l=d.result,o(c,l).filter(e=>!i(e)).filter(e=>e.original!==`${e.replacement}\n`).map(e=>({from:e.from,to:e.replacement.endsWith("\n")?e.to-1:e.to,original:e.original,replacement:e.replacement.endsWith("\n")?e.replacement.slice(0,-1):e.replacement})));return{data:{fixed:u.length>0,originalText:r.text,fixedText:d.result,suggestions:u}}}catch(e){return a?.handleFailure(),{error:e instanceof Error?e:new Error(String(e))}}var c,l}};function re(e,t,n){const s=t.getState(e.state);if(s)return s.decorations.find(e=>e.spec.id===n)}function oe(n,s,r){const o=re(n,s,r);if(!o)return;const i=o.spec,{replacement:c}=i,{from:l,to:d}=o,u=c.split("\n").map(e=>e?n.state.schema.node("paragraph",null,n.state.schema.text(e)):n.state.schema.node("paragraph")),m=e.fromArray(u);let{tr:g}=n.state;g=g.setSelection(a.create(n.state.doc,l,d)),g.selection.replace(g,new t(m,1,1)),g=g.setMeta(s,{type:p.REMOVE_DECORATION,id:r}),n.dispatch(g)}function ie(e,t,n){m(e,t,{type:p.REMOVE_DECORATION,id:n})}function ce(e,t,n){m(e,t,{type:p.UPDATE_CONTEXT,contextState:{selectedSuggestionId:n}})}function le(e,t){m(e,t,{type:p.UPDATE_CONTEXT,contextState:{selectedSuggestionId:void 0}})}function de(e,t){const n=t.getState(e.state);if(n?.contextState.selectedSuggestionId)return re(e,t,n.contextState.selectedSuggestionId)}async function ue(e,t,n,s){const a=s?.endpoint??P,r=s?.modelStateManager,o=r?.getCurrentModel()??s?.model??w;return new Promise((s,i)=>{const c={previousPromptType:"Grammar",oldVersion:t,newVersion:n};_({apiKey:e,text:t,task:y.Hint,params:c,endpoint:a,model:o},{onChunk:()=>{},onComplete:e=>{r?.handleSuccess(),s(e)},onError:e=>{r?.handleFailure(),i(e)}})})}const pe={selectedSuggestionId:void 0},me=g("grammarSuggestV2"),ge=(e,t,n,s,a,r)=>{const o=t.spec,i=document.createElement("div");i.className="grammarPopupV2";const c=document.createElement("div");c.className="grammarPopupV2-mainRow";const l=document.createElement("span");l.className="grammarPopupV2-original",l.textContent=`"${o.originalText}"`;const d=document.createElement("span");d.className="grammarPopupV2-arrow",d.textContent=" → ";const u=document.createElement("span");u.className="grammarPopupV2-replacement",u.textContent=""===o.replacement?"(remove)":`"${o.replacement}"`;const p=document.createElement("button");p.className="grammarPopupV2-hint",p.textContent="?",p.title="Why this suggestion?";const m=document.createElement("div");m.className="grammarPopupV2-hintArea",m.style.display="none";let g=!1;p.onclick=async e=>{if(e.preventDefault(),e.stopPropagation(),"none"===m.style.display){if(m.style.display="block",!g){m.innerHTML='<span class="grammarPopupV2-loading">Loading...</span>';try{const e=await r();m.innerHTML="";const t=document.createElement("span");t.className="grammarPopupV2-hintText",t.textContent=e,m.appendChild(t),g=!0}catch{m.innerHTML='<span class="grammarPopupV2-hintError">Could not load hint</span>'}}}else m.style.display="none"};const E=document.createElement("button");E.className="grammarPopupV2-accept",E.textContent="✓",E.onclick=e=>{e.preventDefault(),e.stopPropagation(),s()};const S=document.createElement("button");return S.className="grammarPopupV2-discard",S.textContent="✕",S.onclick=e=>{e.preventDefault(),e.stopPropagation(),a()},c.appendChild(l),c.appendChild(d),c.appendChild(u),c.appendChild(p),c.appendChild(E),c.appendChild(S),i.appendChild(c),i.appendChild(m),i};function Ee(e,t={}){const{apiEndpoint:n,model:a,fallback:r,batchSize:o=4,maxRetries:i=3,backoffBase:d=2e3,debounceMs:u=1e3,createPopup:p=ge}=t,m=r?function(e){const{primaryModel:t,fallbackModel:n,failureThreshold:s}=e;let a=0;return{getCurrentModel:()=>n&&s>0&&a>=s?n:t,handleSuccess(){a=0},handleFailure(){n&&a++}}}({primaryModel:a,fallbackModel:r.fallbackModel,failureThreshold:r.failureThreshold??3}):void 0,g=ae({apiKey:e,apiEndpoint:n,model:a,modelStateManager:m}),S=E({pluginKey:me,unitProcessor:g,decorationFactory:te,decorationTransformer:ne,widgetFactory:se,initialContextState:pe,options:{batchSize:o,maxRetries:i,backoffBase:d,dirtyHandling:{debounceDelay:u}}});return new s({key:me,state:S.spec.state,props:{...S.spec.props,decorations(t){const s=S.spec.props?.decorations,r=s?s.call(S,t):l.empty;if("react"===p)return r;const o=me.getState(t);if(!o?.contextState.selectedSuggestionId)return r;const i=o.decorations.find(e=>e.spec.id===o.contextState.selectedSuggestionId);if(!i)return r;const d=i.spec,u=c.widget(i.from,(t,s)=>{const r=s();return void 0===r?document.createElement("div"):p(t,i,r,()=>oe(t,me,i.spec.id),()=>ie(t,me,i.spec.id),()=>ue(e,d.originalText,d.replacement,{endpoint:n,model:a,modelStateManager:m}))},{id:i.spec.id,side:-1,stopEvent:()=>!0});return r.add(t.doc,[u])},handleClick(e,t,n){const s=me.getState(e.state);if(!s)return!1;const a=s.decorations.find(e=>e.from<=t&&t<=e.to);return a?(a.spec.id===s.contextState.selectedSuggestionId||ce(e,me,a.spec.id),!1):(s.contextState.selectedSuggestionId&&le(e,me),!1)}},view:S.spec.view})}var Se,fe;!function(e){e.IDLE="idle",e.DEBOUNCING="debouncing",e.PENDING="pending",e.STREAMING="streaming",e.SHOWING="showing"}(Se||(Se={})),function(e){e.SET_ENABLED="SET_ENABLED",e.START_DEBOUNCE="START_DEBOUNCE",e.START_REQUEST="START_REQUEST",e.STREAM_UPDATE="STREAM_UPDATE",e.STREAM_COMPLETE="STREAM_COMPLETE",e.STREAM_ERROR="STREAM_ERROR",e.DISMISS="DISMISS",e.ACCEPT="ACCEPT"}(fe||(fe={}));const Te={debounceMs:500,maxContextLength:2e3,ghostTextClass:"autoCompleteGhostText"},he=new WeakMap;function Re(e){const t=he.get(e);t&&(t.abort(),he.delete(e))}function ye(e){return he.has(e)}function Ce(e,t){if(!t)return t;const{doc:n,selection:s}=e.state,a=s.from;if(a<=0)return t;const r=n.textBetween(a-1,a,""),o=t.charAt(0),i=/[\s\-]/;return!i.test(r)&&!i.test(o)?` ${t}`:t}const Ae=new n("autoCompletePlugin"),Me={status:Se.IDLE,enabled:!0,suggestion:"",cursorPos:0};function xe(e,t){switch(t.type){case fe.SET_ENABLED:return{...Me,enabled:t.enabled};case fe.START_DEBOUNCE:return{...e,status:Se.DEBOUNCING,cursorPos:t.cursorPos,suggestion:"",error:void 0};case fe.START_REQUEST:return e.status!==Se.DEBOUNCING?e:{...e,status:Se.PENDING};case fe.STREAM_UPDATE:return e.status!==Se.PENDING&&e.status!==Se.STREAMING?e:{...e,status:Se.STREAMING,suggestion:t.suggestion};case fe.STREAM_COMPLETE:return e.status!==Se.PENDING&&e.status!==Se.STREAMING?e:{...e,status:Se.SHOWING,suggestion:t.suggestion};case fe.STREAM_ERROR:return{...e,status:Se.IDLE,error:t.error,suggestion:""};case fe.DISMISS:case fe.ACCEPT:return{...e,status:Se.IDLE,suggestion:""};default:return e}}function Ie(e,t={}){const n={...Te,...t},{debounceMs:a,maxContextLength:r,apiEndpoint:o,model:i,ghostTextClass:d}=n;let u=null,p=!1,m=null,g=null;return new s({key:Ae,state:{init:()=>({...Me}),apply(e,t,n,s){const a=e.getMeta(Ae);if(a)return xe(t,a);if(e.docChanged&&(t.status===Se.SHOWING||t.status===Se.STREAMING)){if(s.selection.from!==t.cursorPos)return xe(t,{type:fe.DISMISS})}return!e.selectionSet||e.docChanged||t.status!==Se.SHOWING&&t.status!==Se.STREAMING?t:xe(t,{type:fe.DISMISS})}},props:{decorations(e){const t=Ae.getState(e);if(!t||!t.enabled)return l.empty;if((t.status===Se.STREAMING||t.status===Se.SHOWING)&&t.suggestion){const n=document.createElement("span");n.className=d,n.textContent=t.suggestion,n.setAttribute("contenteditable","false");const s=c.widget(t.cursorPos,n,{side:1});return l.create(e.doc,[s])}return l.empty},handleKeyDown(e,t){const n=Ae.getState(e.state);return!(!n||!n.enabled)&&("Tab"===t.key&&!t.shiftKey&&n.status===Se.SHOWING&&n.suggestion?(t.preventDefault(),m=n.suggestion,g=n.cursorPos,e.dispatch(e.state.tr.setMeta(Ae,{type:fe.ACCEPT})),!0):"Escape"===t.key&&(n.status===Se.STREAMING||n.status===Se.SHOWING)&&(t.preventDefault(),Re(e),e.dispatch(e.state.tr.setMeta(Ae,{type:fe.DISMISS})),!0))}},view:()=>({update(t,n){const s=Ae.getState(t.state),c=Ae.getState(n);if(s&&s.enabled){if(null!==m&&null!==g){const e=m,n=g;m=null,g=null;const s=t.state.tr.insertText(e,n);return t.dispatch(s),void t.focus()}if(t.state,t.state.doc!==n.doc&&c?.status!==Se.PENDING&&c?.status!==Se.STREAMING){t.state.tr,u&&(clearTimeout(u),u=null),Re(t);const e=t.state.selection.from;t.dispatch(t.state.tr.setMeta(Ae,{type:fe.START_DEBOUNCE,cursorPos:e})),u=setTimeout(()=>{u=null;const e=Ae.getState(t.state);e?.status===Se.DEBOUNCING&&t.dispatch(t.state.tr.setMeta(Ae,{type:fe.START_REQUEST}))},a)}if(s.status===Se.PENDING&&!p){if(!function(e){const{doc:t,selection:n}=e.state,s=n.from,a=t.resolve(s),r=a.parent,o=a.start();if(s!==o+r.content.size)return!1;const i=r.textBetween(0,s-o,"");return!!i.trim()&&!/[.!?]\s*$/.test(i)}(t))return void t.dispatch(t.state.tr.setMeta(Ae,{type:fe.DISMISS}));const n=function(e,t){const{doc:n,selection:s}=e.state,a=s.from,r=n.textBetween(0,a,"\n");return r.length>t?r.slice(-t):r}(t,r);if(!n.trim())return void t.dispatch(t.state.tr.setMeta(Ae,{type:fe.DISMISS}));p=!0,async function({view:e,pluginKey:t,apiKey:n,context:s,apiEndpoint:a,model:r}){Re(e);const o=new AbortController;he.set(e,o);try{await _({apiKey:n,text:s,task:R.SmallComplete,endpoint:a,model:r,signal:o.signal},{onChunk:(n,s)=>{e.isDestroyed||e.dispatch(e.state.tr.setMeta(t,{type:fe.STREAM_UPDATE,suggestion:Ce(e,s)}))},onComplete:n=>{e.isDestroyed||e.dispatch(e.state.tr.setMeta(t,{type:fe.STREAM_COMPLETE,suggestion:Ce(e,n)}))},onError:n=>{console.error("Auto-complete streaming error:",n),e.isDestroyed||e.dispatch(e.state.tr.setMeta(t,{type:fe.STREAM_ERROR,error:n.message}))}})}finally{he.delete(e)}}({view:t,pluginKey:Ae,apiKey:e,context:n,apiEndpoint:o,model:i}).finally(()=>{p=!1})}}else u&&(clearTimeout(u),u=null)},destroy(){u&&(clearTimeout(u),u=null)}})})}function Ne(e,t){t||Re(e),e.dispatch(e.state.tr.setMeta(Ae,{type:fe.SET_ENABLED,enabled:t}))}function Pe(e){const t=Ae.getState(e.state);if(!t||!t.suggestion)return!1;const n=e.state.tr.insertText(t.suggestion,t.cursorPos);return e.dispatch(n),e.dispatch(e.state.tr.setMeta(Ae,{type:fe.DISMISS})),e.focus(),!0}function De(e){Re(e),e.dispatch(e.state.tr.setMeta(Ae,{type:fe.DISMISS}))}function we(e){return Ae.getState(e.state)}function Le(e){const t=Ae.getState(e.state);return t?.enabled??!1}function be(e){const t=Ae.getState(e.state);return!!t?.suggestion}const _e=V;export{y as AiPromptsWithParam,R as AiPromptsWithoutParam,fe as AutoCompleteActionType,Se as AutoCompleteStatus,I as CompleteActionType,x as CompleteStatus,P as DEFAULT_COMPLETION_ENDPOINT,D as DEFAULT_GRAMMAR_ENDPOINT,w as DEFAULT_MODEL,f as GrammarSuggestElementClass,S as GrammarSuggestMetaType,T as MoodParamType,A as OpenAiPromptsWithParam,C as OpenAiPromptsWithoutParam,M as Status,h as TranslationTargetLanguage,Pe as acceptAutoCompletion,W as acceptResult,oe as acceptSuggestion,Ae as autoCompleteKey,Ie as autoCompletePlugin,k as cancelActiveRequest,Re as cancelAutoCompleteRequest,F as cancelTask,j as clearError,_e as completePlugin,z as completePluginKey,V as completePluginV2,U as completeV2Key,L as createApiConfig,b as createGrammarApiConfig,ae as createGrammarProcessor,Te as defaultAutoCompleteOptions,Q as defaultCompleteOptions,N as defaultCompleteV2Options,J as defaultOptions,le as deselectSuggestion,ie as discardSuggestion,De as dismissAutoCompletion,we as getAutoCompleteState,ee as getChangedRegions,q as getCompleteState,de as getSelectedDecoration,te as grammarDecorationFactory,ne as grammarDecorationTransformer,v as grammarRequest,Y as grammarSuggestPluginKey,Ee as grammarSuggestPluginV2,me as grammarSuggestV2Key,se as grammarWidgetFactory,G as hasActiveRequest,ye as hasAutoCompleteRequest,be as hasAutoCompletion,Le as isAutoCompleteEnabled,H as rejectResult,ue as requestHint,ce as selectSuggestion,Ne as setAutoCompleteEnabled,X as setCompleteEnabled,$ as setEnabled,Z as setGrammarSuggestEnabled,B as startTask,_ as streamingRequest};