@liwe3/webcomponents 1.0.0
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/LICENSE +21 -0
- package/README.md +383 -0
- package/dist/AITextEditor.js +477 -0
- package/dist/AITextEditor.js.map +1 -0
- package/dist/SmartSelect.js +452 -0
- package/dist/SmartSelect.js.map +1 -0
- package/dist/index.js +13 -0
- package/dist/index.js.map +1 -0
- package/package.json +60 -0
- package/src/AITextEditor.ts +708 -0
- package/src/SmartSelect.ts +775 -0
- package/src/index.ts +26 -0
|
@@ -0,0 +1,477 @@
|
|
|
1
|
+
const g = "ai-text-editor-api-key";
|
|
2
|
+
class l extends HTMLElement {
|
|
3
|
+
constructor() {
|
|
4
|
+
super(), this.typingTimer = null, this.fullSuggestion = null, this.suggestionParagraphs = [], this.currentParagraphIndex = 0, this.isShowingSuggestion = !1, this.apiKey = "", this.suggestionDelay = 1e3, this.systemPrompt = "You are a helpful writing assistant. Continue the user's text naturally and coherently. Provide 1-3 sentences that would logically follow their writing. Keep the same tone and style. Do not repeat what they've already written.", this.apiEndpoint = "https://api.openai.com/v1/chat/completions", this.modelName = "gpt-3.5-turbo", this.context = "", this.attachShadow({ mode: "open" }), this.render(), this.init();
|
|
5
|
+
}
|
|
6
|
+
/**
|
|
7
|
+
* Renders the component's HTML structure
|
|
8
|
+
*/
|
|
9
|
+
render() {
|
|
10
|
+
this.shadowRoot.innerHTML = `
|
|
11
|
+
<style>
|
|
12
|
+
:host {
|
|
13
|
+
display: block;
|
|
14
|
+
width: 100%;
|
|
15
|
+
height: 100%;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
.editor-container {
|
|
19
|
+
position: relative;
|
|
20
|
+
height: 100%;
|
|
21
|
+
display: flex;
|
|
22
|
+
flex-direction: column;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
.editor-status {
|
|
26
|
+
position: absolute;
|
|
27
|
+
top: 5px;
|
|
28
|
+
left: 5px;
|
|
29
|
+
width: 10px;
|
|
30
|
+
height: 10px;
|
|
31
|
+
border-radius: 100%;
|
|
32
|
+
background: #777;
|
|
33
|
+
z-index: 10;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
.editor-wrapper {
|
|
37
|
+
position: relative;
|
|
38
|
+
width: 100%;
|
|
39
|
+
flex: 1;
|
|
40
|
+
display: flex;
|
|
41
|
+
flex-direction: column;
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
.editor {
|
|
45
|
+
width: 100%;
|
|
46
|
+
height: 100%;
|
|
47
|
+
border: 2px solid #e1e5e9;
|
|
48
|
+
border-radius: 12px;
|
|
49
|
+
padding: 20px;
|
|
50
|
+
font-family: 'Monaco', 'Menlo', 'Ubuntu Mono', monospace;
|
|
51
|
+
font-size: 14px;
|
|
52
|
+
line-height: 1.6;
|
|
53
|
+
resize: none;
|
|
54
|
+
background: #fafbfc;
|
|
55
|
+
transition: all 0.3s ease;
|
|
56
|
+
position: relative;
|
|
57
|
+
z-index: 2;
|
|
58
|
+
background: transparent;
|
|
59
|
+
box-sizing: border-box;
|
|
60
|
+
min-height: auto;
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
.editor:focus {
|
|
64
|
+
outline: none;
|
|
65
|
+
border-color: #4facfe;
|
|
66
|
+
box-shadow: 0 0 0 3px rgba(79, 172, 254, 0.1);
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
.editor-background {
|
|
70
|
+
position: absolute;
|
|
71
|
+
top: 0;
|
|
72
|
+
left: 0;
|
|
73
|
+
width: 100%;
|
|
74
|
+
height: 100%;
|
|
75
|
+
border: 2px solid #e1e5e9;
|
|
76
|
+
border-radius: 12px;
|
|
77
|
+
padding: 20px;
|
|
78
|
+
font-family: 'Monaco', 'Menlo', 'Ubuntu Mono', monospace;
|
|
79
|
+
font-size: 14px;
|
|
80
|
+
line-height: 1.6;
|
|
81
|
+
background: #fafbfc;
|
|
82
|
+
z-index: 1;
|
|
83
|
+
pointer-events: none;
|
|
84
|
+
white-space: pre-wrap;
|
|
85
|
+
word-wrap: break-word;
|
|
86
|
+
overflow: hidden;
|
|
87
|
+
color: transparent;
|
|
88
|
+
box-sizing: border-box;
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
.editor-wrapper:focus-within .editor-background {
|
|
92
|
+
background: white;
|
|
93
|
+
border-color: #4facfe;
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
.suggestion-text {
|
|
97
|
+
color: #bbb;
|
|
98
|
+
position: relative;
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
.suggestion-text.accepted {
|
|
102
|
+
color: #ddd;
|
|
103
|
+
text-decoration: line-through;
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
.loading {
|
|
107
|
+
position: absolute;
|
|
108
|
+
top: 5px;
|
|
109
|
+
right: 10px;
|
|
110
|
+
z-index: 10;
|
|
111
|
+
display: none;
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
.loading.show {
|
|
115
|
+
display: block;
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
.spinner {
|
|
119
|
+
width: 10px;
|
|
120
|
+
height: 10px;
|
|
121
|
+
border: 2px solid #e1e5e9;
|
|
122
|
+
border-top: 2px solid #4facfe;
|
|
123
|
+
border-radius: 50%;
|
|
124
|
+
animation: spin 1s linear infinite;
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
@keyframes spin {
|
|
128
|
+
0% { transform: rotate(0deg); }
|
|
129
|
+
100% { transform: rotate(360deg); }
|
|
130
|
+
}
|
|
131
|
+
</style>
|
|
132
|
+
|
|
133
|
+
<div class="editor-container">
|
|
134
|
+
<div class="editor-status"></div>
|
|
135
|
+
<div class="loading" id="loading">
|
|
136
|
+
<div class="spinner"></div>
|
|
137
|
+
</div>
|
|
138
|
+
|
|
139
|
+
<div class="editor-wrapper">
|
|
140
|
+
<div class="editor-background" id="editorBackground"></div>
|
|
141
|
+
<textarea
|
|
142
|
+
class="editor"
|
|
143
|
+
id="editor"
|
|
144
|
+
placeholder="Start writing your markdown text here..."
|
|
145
|
+
></textarea>
|
|
146
|
+
</div>
|
|
147
|
+
</div>
|
|
148
|
+
`;
|
|
149
|
+
}
|
|
150
|
+
/**
|
|
151
|
+
* Initializes the component after rendering
|
|
152
|
+
*/
|
|
153
|
+
init() {
|
|
154
|
+
const t = this.shadowRoot.getElementById("editor"), e = this.shadowRoot.getElementById("editorBackground"), i = this.shadowRoot.getElementById("loading");
|
|
155
|
+
this.editorStatus = this.shadowRoot.querySelector(".editor-status"), this.editor = t, this.editorBackground = e, this.loading = i, this.editor.addEventListener("input", () => {
|
|
156
|
+
this.handleTextInput(), this.updateBackground(), this.dispatchEvent(new CustomEvent("change", { detail: { value: this.editor.value } }));
|
|
157
|
+
}), this.editor.addEventListener("keydown", (s) => {
|
|
158
|
+
this.handleKeyDown(s);
|
|
159
|
+
}), this.editor.addEventListener("keyup", (s) => {
|
|
160
|
+
this.isShowingSuggestion && ["ArrowLeft", "ArrowRight", "ArrowUp", "ArrowDown", "Home", "End"].includes(s.key) && this.hideSuggestion(), ["ArrowLeft", "ArrowRight", "ArrowUp", "ArrowDown", "Home", "End"].includes(s.key) && this.updateBackground();
|
|
161
|
+
}), this.editor.addEventListener("click", () => {
|
|
162
|
+
this.isShowingSuggestion && this.hideSuggestion(), setTimeout(() => this.updateBackground(), 0);
|
|
163
|
+
}), this.editor.addEventListener("scroll", () => {
|
|
164
|
+
this.syncScroll();
|
|
165
|
+
}), this._loadApiKey(), this._loadSettings(), this.updateBackground();
|
|
166
|
+
}
|
|
167
|
+
/**
|
|
168
|
+
* Updates the background layer with current text and suggestions
|
|
169
|
+
*/
|
|
170
|
+
updateBackground() {
|
|
171
|
+
const t = this.editor.value, e = this.editor.selectionStart;
|
|
172
|
+
let i = "";
|
|
173
|
+
if (this.isShowingSuggestion && this.fullSuggestion) {
|
|
174
|
+
const s = t.substring(0, e), o = t.substring(e), a = (Array.isArray(this.suggestionParagraphs) ? this.suggestionParagraphs.slice(this.currentParagraphIndex) : []).join(" ");
|
|
175
|
+
if (a.trim().length > 0) {
|
|
176
|
+
const n = s.endsWith(" ") || s === "" || a.startsWith(" ") || a === "" ? "" : " ", d = `<span class="suggestion-text">${this.escapeHtml(a)}</span>`;
|
|
177
|
+
i = this.escapeHtml(s).replace(/\n/g, "<br>") + n + d + this.escapeHtml(o).replace(/\n/g, "<br>");
|
|
178
|
+
} else
|
|
179
|
+
i = this.escapeHtml(t).replace(/\n/g, "<br>");
|
|
180
|
+
} else
|
|
181
|
+
i = this.escapeHtml(t).replace(/\n/g, "<br>");
|
|
182
|
+
this.editorBackground.innerHTML = i, this.syncScroll();
|
|
183
|
+
}
|
|
184
|
+
/**
|
|
185
|
+
* Synchronizes scroll position between editor and background
|
|
186
|
+
*/
|
|
187
|
+
syncScroll() {
|
|
188
|
+
this.editorBackground.scrollTop = this.editor.scrollTop, this.editorBackground.scrollLeft = this.editor.scrollLeft;
|
|
189
|
+
}
|
|
190
|
+
/**
|
|
191
|
+
* Handles text input events
|
|
192
|
+
*/
|
|
193
|
+
handleTextInput() {
|
|
194
|
+
this.hideSuggestion(), this.typingTimer && clearTimeout(this.typingTimer), this.apiKey && (this.typingTimer = window.setTimeout(() => {
|
|
195
|
+
this.requestSuggestion();
|
|
196
|
+
}, this.suggestionDelay));
|
|
197
|
+
}
|
|
198
|
+
/**
|
|
199
|
+
* Handles keyboard events
|
|
200
|
+
*/
|
|
201
|
+
handleKeyDown(t) {
|
|
202
|
+
this.isShowingSuggestion && (t.key === "Tab" ? (t.preventDefault(), this.acceptSuggestion()) : t.key === "Escape" && (t.preventDefault(), this.hideSuggestion()));
|
|
203
|
+
}
|
|
204
|
+
/**
|
|
205
|
+
* Requests an AI suggestion for the current text
|
|
206
|
+
*/
|
|
207
|
+
async requestSuggestion() {
|
|
208
|
+
if (!this.apiKey) return;
|
|
209
|
+
const t = this.editor.value;
|
|
210
|
+
if (!t.trim()) return;
|
|
211
|
+
const e = this.editor.selectionStart, i = t.substring(0, e);
|
|
212
|
+
if (this.dispatchEvent(new CustomEvent("beforeSuggestion", {
|
|
213
|
+
detail: {
|
|
214
|
+
text: i,
|
|
215
|
+
context: this.context,
|
|
216
|
+
apiEndpoint: this.apiEndpoint,
|
|
217
|
+
modelName: this.modelName,
|
|
218
|
+
systemPrompt: this.systemPrompt
|
|
219
|
+
},
|
|
220
|
+
cancelable: !0
|
|
221
|
+
})) !== !1) {
|
|
222
|
+
this.showLoading();
|
|
223
|
+
try {
|
|
224
|
+
const o = await this.callOpenAI(i);
|
|
225
|
+
this.hideLoading(), o && this.showSuggestion(o);
|
|
226
|
+
} catch (o) {
|
|
227
|
+
this.hideLoading(), this.showError("Failed to get AI suggestion: " + o.message);
|
|
228
|
+
}
|
|
229
|
+
}
|
|
230
|
+
}
|
|
231
|
+
/**
|
|
232
|
+
* Calls the OpenAI API for text completion
|
|
233
|
+
*/
|
|
234
|
+
async callOpenAI(t) {
|
|
235
|
+
const e = [];
|
|
236
|
+
this.context && this.context.trim() && e.push(`Context:
|
|
237
|
+
${this.context.trim()}`), e.push(`Please continue this text naturally:
|
|
238
|
+
|
|
239
|
+
${t}`);
|
|
240
|
+
const i = e.join(`
|
|
241
|
+
|
|
242
|
+
`), s = {
|
|
243
|
+
"Content-Type": "application/json"
|
|
244
|
+
};
|
|
245
|
+
this.apiKey && this.apiKey.trim() !== "" && (s.Authorization = `Bearer ${this.apiKey}`);
|
|
246
|
+
const o = {
|
|
247
|
+
model: this.modelName,
|
|
248
|
+
messages: [
|
|
249
|
+
{
|
|
250
|
+
role: "system",
|
|
251
|
+
content: this.systemPrompt
|
|
252
|
+
},
|
|
253
|
+
{
|
|
254
|
+
role: "user",
|
|
255
|
+
content: i
|
|
256
|
+
}
|
|
257
|
+
],
|
|
258
|
+
max_tokens: 150,
|
|
259
|
+
temperature: 0.7
|
|
260
|
+
}, r = await fetch(this.apiEndpoint, {
|
|
261
|
+
method: "POST",
|
|
262
|
+
headers: s,
|
|
263
|
+
body: JSON.stringify(o)
|
|
264
|
+
});
|
|
265
|
+
if (!r.ok) {
|
|
266
|
+
let n = "API request failed";
|
|
267
|
+
try {
|
|
268
|
+
n = (await r.json()).error?.message || n;
|
|
269
|
+
} catch (d) {
|
|
270
|
+
console.error("Failed to parse error response:", d), n = `HTTP ${r.status}: ${r.statusText}`;
|
|
271
|
+
}
|
|
272
|
+
throw new Error(n);
|
|
273
|
+
}
|
|
274
|
+
return (await r.json()).choices[0]?.message?.content?.trim();
|
|
275
|
+
}
|
|
276
|
+
/**
|
|
277
|
+
* Shows an AI suggestion
|
|
278
|
+
*/
|
|
279
|
+
showSuggestion(t) {
|
|
280
|
+
this.fullSuggestion = t, this.suggestionParagraphs = this.splitIntoParagraphs(t), this.currentParagraphIndex = 0, this.isShowingSuggestion = !0, this.updateBackground();
|
|
281
|
+
}
|
|
282
|
+
/**
|
|
283
|
+
* Splits text into paragraphs/sentences
|
|
284
|
+
*/
|
|
285
|
+
splitIntoParagraphs(t) {
|
|
286
|
+
return t.split(new RegExp("(?<=\\.)\\s+(?=[A-Z])|(?:\\n\\s*\\n)")).filter((i) => i.trim().length > 0);
|
|
287
|
+
}
|
|
288
|
+
/**
|
|
289
|
+
* Hides the current suggestion
|
|
290
|
+
*/
|
|
291
|
+
hideSuggestion() {
|
|
292
|
+
this.isShowingSuggestion = !1, this.fullSuggestion = null, this.suggestionParagraphs = [], this.currentParagraphIndex = 0, this.updateBackground();
|
|
293
|
+
}
|
|
294
|
+
/**
|
|
295
|
+
* Accepts the current suggestion paragraph
|
|
296
|
+
*/
|
|
297
|
+
acceptSuggestion() {
|
|
298
|
+
if (this.fullSuggestion && this.currentParagraphIndex < this.suggestionParagraphs.length) {
|
|
299
|
+
const t = this.editor.value, e = this.editor.selectionStart, i = this.suggestionParagraphs[this.currentParagraphIndex], s = t.substring(0, e), o = t.substring(e), r = s.endsWith(" ") || s === "" || i.startsWith(" ") || i === "" ? "" : " ", a = s + r + i + o;
|
|
300
|
+
this.editor.value = a;
|
|
301
|
+
const n = e + r.length + i.length;
|
|
302
|
+
this.editor.setSelectionRange(n, n), this.dispatchEvent(new CustomEvent("change", { detail: { value: this.editor.value } })), this.currentParagraphIndex++, this.currentParagraphIndex >= this.suggestionParagraphs.length ? this.hideSuggestion() : this.updateBackground();
|
|
303
|
+
}
|
|
304
|
+
}
|
|
305
|
+
/**
|
|
306
|
+
* Shows loading indicator
|
|
307
|
+
*/
|
|
308
|
+
showLoading() {
|
|
309
|
+
this.loading.classList.add("show");
|
|
310
|
+
}
|
|
311
|
+
/**
|
|
312
|
+
* Hides loading indicator
|
|
313
|
+
*/
|
|
314
|
+
hideLoading() {
|
|
315
|
+
this.loading.classList.remove("show");
|
|
316
|
+
}
|
|
317
|
+
/**
|
|
318
|
+
* Shows an error message
|
|
319
|
+
*/
|
|
320
|
+
showError(t) {
|
|
321
|
+
console.error("AI Text Editor Error:", t), this.dispatchEvent(new CustomEvent("error", {
|
|
322
|
+
detail: { message: t },
|
|
323
|
+
bubbles: !0,
|
|
324
|
+
composed: !0
|
|
325
|
+
}));
|
|
326
|
+
}
|
|
327
|
+
/**
|
|
328
|
+
* Escapes HTML special characters
|
|
329
|
+
*/
|
|
330
|
+
escapeHtml(t) {
|
|
331
|
+
if (typeof t != "string") {
|
|
332
|
+
if (t === null || typeof t > "u")
|
|
333
|
+
return "";
|
|
334
|
+
t = String(t);
|
|
335
|
+
}
|
|
336
|
+
return t.replace(/&/g, "&").replace(/</g, "<").replace(/>/g, ">").replace(/"/g, """).replace(/'/g, "'");
|
|
337
|
+
}
|
|
338
|
+
/**
|
|
339
|
+
* Sets the text content
|
|
340
|
+
*/
|
|
341
|
+
setText(t) {
|
|
342
|
+
this.editor.value = t, this.dispatchEvent(new CustomEvent("change", { detail: { value: this.editor.value } })), this.hideSuggestion(), this.updateBackground();
|
|
343
|
+
}
|
|
344
|
+
/**
|
|
345
|
+
* Gets the text content
|
|
346
|
+
*/
|
|
347
|
+
getText() {
|
|
348
|
+
return this.editor.value;
|
|
349
|
+
}
|
|
350
|
+
/**
|
|
351
|
+
* Sets the API key
|
|
352
|
+
*/
|
|
353
|
+
setApiKey(t) {
|
|
354
|
+
this.apiKey = t, this._saveApiKey(), this.editorStatus.style.backgroundColor = this.apiKey ? "#4caf50" : "#777";
|
|
355
|
+
}
|
|
356
|
+
/**
|
|
357
|
+
* Saves API key to localStorage
|
|
358
|
+
*/
|
|
359
|
+
_saveApiKey() {
|
|
360
|
+
if (!this.apiKey) {
|
|
361
|
+
localStorage.removeItem(g);
|
|
362
|
+
return;
|
|
363
|
+
}
|
|
364
|
+
const t = btoa(this.apiKey);
|
|
365
|
+
localStorage.setItem(g, t);
|
|
366
|
+
}
|
|
367
|
+
/**
|
|
368
|
+
* Loads API key from localStorage
|
|
369
|
+
*/
|
|
370
|
+
_loadApiKey() {
|
|
371
|
+
const t = localStorage.getItem(g);
|
|
372
|
+
if (!t) {
|
|
373
|
+
this.setApiKey("");
|
|
374
|
+
return;
|
|
375
|
+
}
|
|
376
|
+
const e = atob(t);
|
|
377
|
+
this.setApiKey(e);
|
|
378
|
+
}
|
|
379
|
+
/**
|
|
380
|
+
* Gets the API key
|
|
381
|
+
*/
|
|
382
|
+
getApiKey() {
|
|
383
|
+
return this.apiKey;
|
|
384
|
+
}
|
|
385
|
+
/**
|
|
386
|
+
* Sets the suggestion delay in seconds
|
|
387
|
+
*/
|
|
388
|
+
setSuggestionDelay(t) {
|
|
389
|
+
this.suggestionDelay = t * 1e3;
|
|
390
|
+
}
|
|
391
|
+
/**
|
|
392
|
+
* Gets the suggestion delay in seconds
|
|
393
|
+
*/
|
|
394
|
+
getSuggestionDelay() {
|
|
395
|
+
return this.suggestionDelay / 1e3;
|
|
396
|
+
}
|
|
397
|
+
/**
|
|
398
|
+
* Sets the system prompt
|
|
399
|
+
*/
|
|
400
|
+
setSystemPrompt(t) {
|
|
401
|
+
this.systemPrompt = t;
|
|
402
|
+
}
|
|
403
|
+
/**
|
|
404
|
+
* Gets the system prompt
|
|
405
|
+
*/
|
|
406
|
+
getSystemPrompt() {
|
|
407
|
+
return this.systemPrompt;
|
|
408
|
+
}
|
|
409
|
+
/**
|
|
410
|
+
* Sets the API endpoint
|
|
411
|
+
*/
|
|
412
|
+
setApiEndpoint(t) {
|
|
413
|
+
this.apiEndpoint = t, this._saveSettings();
|
|
414
|
+
}
|
|
415
|
+
/**
|
|
416
|
+
* Gets the API endpoint
|
|
417
|
+
*/
|
|
418
|
+
getApiEndpoint() {
|
|
419
|
+
return this.apiEndpoint;
|
|
420
|
+
}
|
|
421
|
+
/**
|
|
422
|
+
* Sets the model name
|
|
423
|
+
*/
|
|
424
|
+
setModelName(t) {
|
|
425
|
+
this.modelName = t, this._saveSettings();
|
|
426
|
+
}
|
|
427
|
+
/**
|
|
428
|
+
* Gets the model name
|
|
429
|
+
*/
|
|
430
|
+
getModelName() {
|
|
431
|
+
return this.modelName;
|
|
432
|
+
}
|
|
433
|
+
/**
|
|
434
|
+
* Sets the context
|
|
435
|
+
*/
|
|
436
|
+
setContext(t) {
|
|
437
|
+
this.context = typeof t == "string" ? t : "";
|
|
438
|
+
}
|
|
439
|
+
/**
|
|
440
|
+
* Gets the context
|
|
441
|
+
*/
|
|
442
|
+
getContext() {
|
|
443
|
+
return this.context;
|
|
444
|
+
}
|
|
445
|
+
/**
|
|
446
|
+
* Saves settings to localStorage
|
|
447
|
+
*/
|
|
448
|
+
_saveSettings() {
|
|
449
|
+
const t = {
|
|
450
|
+
apiEndpoint: this.apiEndpoint,
|
|
451
|
+
modelName: this.modelName
|
|
452
|
+
};
|
|
453
|
+
localStorage.setItem("ai-text-editor-settings", JSON.stringify(t));
|
|
454
|
+
}
|
|
455
|
+
/**
|
|
456
|
+
* Loads settings from localStorage
|
|
457
|
+
*/
|
|
458
|
+
_loadSettings() {
|
|
459
|
+
const t = localStorage.getItem("ai-text-editor-settings");
|
|
460
|
+
if (t)
|
|
461
|
+
try {
|
|
462
|
+
const e = JSON.parse(t);
|
|
463
|
+
e.apiEndpoint && (this.apiEndpoint = e.apiEndpoint), e.modelName && (this.modelName = e.modelName);
|
|
464
|
+
} catch (e) {
|
|
465
|
+
console.warn("Failed to load saved settings:", e);
|
|
466
|
+
}
|
|
467
|
+
}
|
|
468
|
+
}
|
|
469
|
+
const c = (h = "liwe3-ai-text-editor") => {
|
|
470
|
+
typeof window < "u" && !window.customElements.get(h) && customElements.define(h, l);
|
|
471
|
+
};
|
|
472
|
+
c();
|
|
473
|
+
export {
|
|
474
|
+
l as AITextEditorElement,
|
|
475
|
+
c as defineAITextEditor
|
|
476
|
+
};
|
|
477
|
+
//# sourceMappingURL=AITextEditor.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"AITextEditor.js","sources":["../src/AITextEditor.ts"],"sourcesContent":["/**\n * AITextEditor Web Component\n * A text editor with AI-powered text continuation suggestions\n */\n\nconst AI_TEXT_EDITOR_API_KEY = 'ai-text-editor-api-key';\n\nexport interface AITextEditorConfig {\n apiKey?: string;\n suggestionDelay?: number;\n systemPrompt?: string;\n apiEndpoint?: string;\n modelName?: string;\n context?: string;\n}\n\nexport class AITextEditorElement extends HTMLElement {\n declare shadowRoot: ShadowRoot;\n private editor!: HTMLTextAreaElement;\n private editorBackground!: HTMLElement;\n private loading!: HTMLElement;\n private editorStatus!: HTMLElement;\n\n private typingTimer: number | null = null;\n private fullSuggestion: string | null = null;\n private suggestionParagraphs: string[] = [];\n private currentParagraphIndex: number = 0;\n private isShowingSuggestion: boolean = false;\n\n private apiKey: string = '';\n private suggestionDelay: number = 1000;\n private systemPrompt: string = \"You are a helpful writing assistant. Continue the user's text naturally and coherently. Provide 1-3 sentences that would logically follow their writing. Keep the same tone and style. Do not repeat what they've already written.\";\n private apiEndpoint: string = 'https://api.openai.com/v1/chat/completions';\n private modelName: string = 'gpt-3.5-turbo';\n private context: string = '';\n\n constructor() {\n super();\n this.attachShadow({ mode: 'open' });\n this.render();\n this.init();\n }\n\n /**\n * Renders the component's HTML structure\n */\n private render(): void {\n this.shadowRoot.innerHTML = `\n <style>\n :host {\n display: block;\n width: 100%;\n height: 100%;\n }\n\n .editor-container {\n position: relative;\n height: 100%;\n display: flex;\n flex-direction: column;\n }\n\n .editor-status {\n position: absolute;\n top: 5px;\n left: 5px;\n width: 10px;\n height: 10px;\n border-radius: 100%;\n background: #777;\n z-index: 10;\n }\n\n .editor-wrapper {\n position: relative;\n width: 100%;\n flex: 1;\n display: flex;\n flex-direction: column;\n }\n\n .editor {\n width: 100%;\n height: 100%;\n border: 2px solid #e1e5e9;\n border-radius: 12px;\n padding: 20px;\n font-family: 'Monaco', 'Menlo', 'Ubuntu Mono', monospace;\n font-size: 14px;\n line-height: 1.6;\n resize: none;\n background: #fafbfc;\n transition: all 0.3s ease;\n position: relative;\n z-index: 2;\n background: transparent;\n box-sizing: border-box;\n min-height: auto;\n }\n\n .editor:focus {\n outline: none;\n border-color: #4facfe;\n box-shadow: 0 0 0 3px rgba(79, 172, 254, 0.1);\n }\n\n .editor-background {\n position: absolute;\n top: 0;\n left: 0;\n width: 100%;\n height: 100%;\n border: 2px solid #e1e5e9;\n border-radius: 12px;\n padding: 20px;\n font-family: 'Monaco', 'Menlo', 'Ubuntu Mono', monospace;\n font-size: 14px;\n line-height: 1.6;\n background: #fafbfc;\n z-index: 1;\n pointer-events: none;\n white-space: pre-wrap;\n word-wrap: break-word;\n overflow: hidden;\n color: transparent;\n box-sizing: border-box;\n }\n\n .editor-wrapper:focus-within .editor-background {\n background: white;\n border-color: #4facfe;\n }\n\n .suggestion-text {\n color: #bbb;\n position: relative;\n }\n\n .suggestion-text.accepted {\n color: #ddd;\n text-decoration: line-through;\n }\n\n .loading {\n position: absolute;\n top: 5px;\n right: 10px;\n z-index: 10;\n display: none;\n }\n\n .loading.show {\n display: block;\n }\n\n .spinner {\n width: 10px;\n height: 10px;\n border: 2px solid #e1e5e9;\n border-top: 2px solid #4facfe;\n border-radius: 50%;\n animation: spin 1s linear infinite;\n }\n\n @keyframes spin {\n 0% { transform: rotate(0deg); }\n 100% { transform: rotate(360deg); }\n }\n </style>\n\n <div class=\"editor-container\">\n <div class=\"editor-status\"></div>\n <div class=\"loading\" id=\"loading\">\n <div class=\"spinner\"></div>\n </div>\n\n <div class=\"editor-wrapper\">\n <div class=\"editor-background\" id=\"editorBackground\"></div>\n <textarea\n class=\"editor\"\n id=\"editor\"\n placeholder=\"Start writing your markdown text here...\"\n ></textarea>\n </div>\n </div>\n `;\n }\n\n /**\n * Initializes the component after rendering\n */\n private init(): void {\n const editor = this.shadowRoot.getElementById('editor') as HTMLTextAreaElement;\n const editorBackground = this.shadowRoot.getElementById('editorBackground') as HTMLElement;\n const loading = this.shadowRoot.getElementById('loading') as HTMLElement;\n\n this.editorStatus = this.shadowRoot.querySelector('.editor-status') as HTMLElement;\n this.editor = editor;\n this.editorBackground = editorBackground;\n this.loading = loading;\n\n this.editor.addEventListener('input', () => {\n this.handleTextInput();\n this.updateBackground();\n this.dispatchEvent(new CustomEvent('change', { detail: { value: this.editor.value } }));\n });\n\n this.editor.addEventListener('keydown', (e) => {\n this.handleKeyDown(e);\n });\n\n this.editor.addEventListener('keyup', (e) => {\n if (this.isShowingSuggestion && ['ArrowLeft', 'ArrowRight', 'ArrowUp', 'ArrowDown', 'Home', 'End'].includes(e.key)) {\n this.hideSuggestion();\n }\n // Update background on cursor movement, after potentially hiding suggestion\n if (['ArrowLeft', 'ArrowRight', 'ArrowUp', 'ArrowDown', 'Home', 'End'].includes(e.key)) {\n this.updateBackground();\n }\n });\n\n this.editor.addEventListener('click', () => {\n if (this.isShowingSuggestion) {\n this.hideSuggestion();\n }\n // Update background on mouse click (cursor position change)\n // setTimeout ensures cursor position is updated before background redraw\n setTimeout(() => this.updateBackground(), 0);\n });\n\n this.editor.addEventListener('scroll', () => {\n this.syncScroll();\n });\n\n this._loadApiKey();\n this._loadSettings();\n this.updateBackground();\n }\n\n /**\n * Updates the background layer with current text and suggestions\n */\n private updateBackground(): void {\n const currentText = this.editor.value;\n const cursorPosition = this.editor.selectionStart;\n\n let finalHtmlContent = '';\n\n if (this.isShowingSuggestion && this.fullSuggestion) {\n const beforeCursorText = currentText.substring(0, cursorPosition);\n const afterCursorText = currentText.substring(cursorPosition);\n\n // Determine the pending (not yet accepted) part of the suggestion\n const pendingParagraphs = Array.isArray(this.suggestionParagraphs)\n ? this.suggestionParagraphs.slice(this.currentParagraphIndex)\n : [];\n const pendingSuggestionText = pendingParagraphs.join(' ');\n\n if (pendingSuggestionText.trim().length > 0) {\n const mainSpacer = (beforeCursorText.endsWith(' ') || beforeCursorText === '' || pendingSuggestionText.startsWith(' ') || pendingSuggestionText === '') ? '' : ' ';\n const suggestionBlockHtml = `<span class=\"suggestion-text\">${this.escapeHtml(pendingSuggestionText)}</span>`;\n\n finalHtmlContent =\n this.escapeHtml(beforeCursorText).replace(/\\n/g, '<br>') +\n mainSpacer +\n suggestionBlockHtml +\n this.escapeHtml(afterCursorText).replace(/\\n/g, '<br>');\n } else {\n finalHtmlContent = this.escapeHtml(currentText).replace(/\\n/g, '<br>');\n }\n } else {\n finalHtmlContent = this.escapeHtml(currentText).replace(/\\n/g, '<br>');\n }\n\n this.editorBackground.innerHTML = finalHtmlContent;\n this.syncScroll();\n }\n\n /**\n * Synchronizes scroll position between editor and background\n */\n private syncScroll(): void {\n this.editorBackground.scrollTop = this.editor.scrollTop;\n this.editorBackground.scrollLeft = this.editor.scrollLeft;\n }\n\n /**\n * Handles text input events\n */\n private handleTextInput(): void {\n this.hideSuggestion();\n\n if (this.typingTimer) {\n clearTimeout(this.typingTimer);\n }\n\n if (!this.apiKey) return;\n\n this.typingTimer = window.setTimeout(() => {\n this.requestSuggestion();\n }, this.suggestionDelay);\n }\n\n /**\n * Handles keyboard events\n */\n private handleKeyDown(e: KeyboardEvent): void {\n if (this.isShowingSuggestion) {\n if (e.key === 'Tab') {\n e.preventDefault();\n this.acceptSuggestion();\n } else if (e.key === 'Escape') {\n e.preventDefault();\n this.hideSuggestion();\n }\n }\n }\n\n /**\n * Requests an AI suggestion for the current text\n */\n private async requestSuggestion(): Promise<void> {\n if (!this.apiKey) return;\n\n const currentText = this.editor.value;\n if (!currentText.trim()) return;\n\n // Get text up to cursor position for context\n const cursorPosition = this.editor.selectionStart;\n const textUpToCursor = currentText.substring(0, cursorPosition);\n\n // Dispatch an event before starting the AI request, allow listeners to cancel\n const proceed = this.dispatchEvent(new CustomEvent('beforeSuggestion', {\n detail: {\n text: textUpToCursor,\n context: this.context,\n apiEndpoint: this.apiEndpoint,\n modelName: this.modelName,\n systemPrompt: this.systemPrompt\n },\n cancelable: true\n }));\n\n if (proceed === false) {\n return; // aborted by listener via event.preventDefault()\n }\n\n this.showLoading();\n\n try {\n const suggestion = await this.callOpenAI(textUpToCursor);\n this.hideLoading();\n\n if (suggestion) {\n this.showSuggestion(suggestion);\n }\n } catch (error) {\n this.hideLoading();\n this.showError('Failed to get AI suggestion: ' + (error as Error).message);\n }\n }\n\n /**\n * Calls the OpenAI API for text completion\n */\n private async callOpenAI(text: string): Promise<string> {\n const parts: string[] = [];\n if (this.context && this.context.trim()) {\n parts.push(`Context:\\n${this.context.trim()}`);\n }\n parts.push(`Please continue this text naturally:\\n\\n${text}`);\n const userContent = parts.join('\\n\\n');\n\n // Prepare headers - only add Authorization if API key is provided\n const headers: Record<string, string> = {\n 'Content-Type': 'application/json'\n };\n\n if (this.apiKey && this.apiKey.trim() !== '') {\n headers['Authorization'] = `Bearer ${this.apiKey}`;\n }\n\n const requestBody = {\n model: this.modelName,\n messages: [\n {\n role: 'system',\n content: this.systemPrompt\n },\n {\n role: 'user',\n content: userContent\n }\n ],\n max_tokens: 150,\n temperature: 0.7\n };\n\n const response = await fetch(this.apiEndpoint, {\n method: 'POST',\n headers: headers,\n body: JSON.stringify(requestBody)\n });\n\n if (!response.ok) {\n let errorMessage = 'API request failed';\n try {\n const errorData = await response.json();\n errorMessage = errorData.error?.message || errorMessage;\n } catch (parseError) {\n console.error('Failed to parse error response:', parseError);\n errorMessage = `HTTP ${response.status}: ${response.statusText}`;\n }\n throw new Error(errorMessage);\n }\n\n const data = await response.json();\n return data.choices[0]?.message?.content?.trim();\n }\n\n /**\n * Shows an AI suggestion\n */\n private showSuggestion(suggestion: string): void {\n this.fullSuggestion = suggestion;\n this.suggestionParagraphs = this.splitIntoParagraphs(suggestion);\n this.currentParagraphIndex = 0;\n this.isShowingSuggestion = true;\n this.updateBackground();\n }\n\n /**\n * Splits text into paragraphs/sentences\n */\n private splitIntoParagraphs(text: string): string[] {\n // Split on periods followed by space and capital letter, or double newlines\n const sentences = text.split(/(?<=\\.)\\s+(?=[A-Z])|(?:\\n\\s*\\n)/);\n return sentences.filter(sentence => sentence.trim().length > 0);\n }\n\n /**\n * Hides the current suggestion\n */\n private hideSuggestion(): void {\n this.isShowingSuggestion = false;\n this.fullSuggestion = null;\n this.suggestionParagraphs = [];\n this.currentParagraphIndex = 0;\n this.updateBackground();\n }\n\n /**\n * Accepts the current suggestion paragraph\n */\n private acceptSuggestion(): void {\n if (this.fullSuggestion && this.currentParagraphIndex < this.suggestionParagraphs.length) {\n const currentText = this.editor.value;\n const cursorPosition = this.editor.selectionStart;\n const paragraphToAdd = this.suggestionParagraphs[this.currentParagraphIndex];\n\n // Insert at cursor position\n const beforeCursor = currentText.substring(0, cursorPosition);\n const afterCursor = currentText.substring(cursorPosition);\n\n // Refined spacer logic\n const spacer = (beforeCursor.endsWith(' ') || beforeCursor === '' || paragraphToAdd.startsWith(' ') || paragraphToAdd === '') ? '' : ' ';\n const newText = beforeCursor + spacer + paragraphToAdd + afterCursor;\n\n this.editor.value = newText;\n\n // Update cursor position to after the inserted text\n const newCursorPosition = cursorPosition + spacer.length + paragraphToAdd.length;\n this.editor.setSelectionRange(newCursorPosition, newCursorPosition);\n this.dispatchEvent(new CustomEvent('change', { detail: { value: this.editor.value } }));\n\n // Move to next paragraph or hide if no more paragraphs\n this.currentParagraphIndex++;\n if (this.currentParagraphIndex >= this.suggestionParagraphs.length) {\n this.hideSuggestion();\n } else {\n this.updateBackground();\n }\n }\n }\n\n /**\n * Shows loading indicator\n */\n private showLoading(): void {\n this.loading.classList.add('show');\n }\n\n /**\n * Hides loading indicator\n */\n private hideLoading(): void {\n this.loading.classList.remove('show');\n }\n\n /**\n * Shows an error message\n */\n private showError(message: string): void {\n console.error('AI Text Editor Error:', message);\n // Try to dispatch a custom error event that can be handled by parent\n this.dispatchEvent(new CustomEvent('error', {\n detail: { message },\n bubbles: true,\n composed: true\n }));\n }\n\n /**\n * Escapes HTML special characters\n */\n private escapeHtml(unsafe: any): string {\n if (typeof unsafe !== 'string') {\n if (unsafe === null || typeof unsafe === 'undefined') {\n return '';\n }\n unsafe = String(unsafe);\n }\n return unsafe\n .replace(/&/g, \"&\")\n .replace(/</g, \"<\")\n .replace(/>/g, \">\")\n .replace(/\"/g, \""\")\n .replace(/'/g, \"'\");\n }\n\n /**\n * Sets the text content\n */\n setText(text: string): void {\n this.editor.value = text;\n this.dispatchEvent(new CustomEvent('change', { detail: { value: this.editor.value } }));\n this.hideSuggestion();\n this.updateBackground();\n }\n\n /**\n * Gets the text content\n */\n getText(): string {\n return this.editor.value;\n }\n\n /**\n * Sets the API key\n */\n setApiKey(key: string): void {\n this.apiKey = key;\n this._saveApiKey();\n this.editorStatus.style.backgroundColor = this.apiKey ? '#4caf50' : '#777';\n }\n\n /**\n * Saves API key to localStorage\n */\n private _saveApiKey(): void {\n if (!this.apiKey) {\n localStorage.removeItem(AI_TEXT_EDITOR_API_KEY);\n return;\n }\n\n // Encrypt the API key in base64 format\n const encryptedKey = btoa(this.apiKey);\n localStorage.setItem(AI_TEXT_EDITOR_API_KEY, encryptedKey);\n }\n\n /**\n * Loads API key from localStorage\n */\n private _loadApiKey(): void {\n const savedKey = localStorage.getItem(AI_TEXT_EDITOR_API_KEY);\n if (!savedKey) {\n this.setApiKey('');\n return;\n }\n\n const decryptedKey = atob(savedKey);\n this.setApiKey(decryptedKey);\n }\n\n /**\n * Gets the API key\n */\n getApiKey(): string {\n return this.apiKey;\n }\n\n /**\n * Sets the suggestion delay in seconds\n */\n setSuggestionDelay(seconds: number): void {\n this.suggestionDelay = seconds * 1000;\n }\n\n /**\n * Gets the suggestion delay in seconds\n */\n getSuggestionDelay(): number {\n return this.suggestionDelay / 1000;\n }\n\n /**\n * Sets the system prompt\n */\n setSystemPrompt(prompt: string): void {\n this.systemPrompt = prompt;\n }\n\n /**\n * Gets the system prompt\n */\n getSystemPrompt(): string {\n return this.systemPrompt;\n }\n\n /**\n * Sets the API endpoint\n */\n setApiEndpoint(endpoint: string): void {\n this.apiEndpoint = endpoint;\n this._saveSettings();\n }\n\n /**\n * Gets the API endpoint\n */\n getApiEndpoint(): string {\n return this.apiEndpoint;\n }\n\n /**\n * Sets the model name\n */\n setModelName(modelName: string): void {\n this.modelName = modelName;\n this._saveSettings();\n }\n\n /**\n * Gets the model name\n */\n getModelName(): string {\n return this.modelName;\n }\n\n /**\n * Sets the context\n */\n setContext(context: string): void {\n this.context = typeof context === 'string' ? context : '';\n }\n\n /**\n * Gets the context\n */\n getContext(): string {\n return this.context;\n }\n\n /**\n * Saves settings to localStorage\n */\n private _saveSettings(): void {\n const settings = {\n apiEndpoint: this.apiEndpoint,\n modelName: this.modelName\n };\n localStorage.setItem('ai-text-editor-settings', JSON.stringify(settings));\n }\n\n /**\n * Loads settings from localStorage\n */\n private _loadSettings(): void {\n const savedSettings = localStorage.getItem('ai-text-editor-settings');\n if (savedSettings) {\n try {\n const settings = JSON.parse(savedSettings);\n if (settings.apiEndpoint) {\n this.apiEndpoint = settings.apiEndpoint;\n }\n if (settings.modelName) {\n this.modelName = settings.modelName;\n }\n } catch (error) {\n console.warn('Failed to load saved settings:', error);\n }\n }\n }\n}\n\n/**\n * Conditionally defines the custom element if in a browser environment.\n */\nconst defineAITextEditor = (tagName: string = 'liwe3-ai-text-editor'): void => {\n if (typeof window !== 'undefined' && !window.customElements.get(tagName)) {\n customElements.define(tagName, AITextEditorElement);\n }\n};\n\n// Auto-register with default tag name\ndefineAITextEditor();\n\nexport { defineAITextEditor };\n"],"names":["AI_TEXT_EDITOR_API_KEY","AITextEditorElement","editor","editorBackground","loading","e","currentText","cursorPosition","finalHtmlContent","beforeCursorText","afterCursorText","pendingSuggestionText","mainSpacer","suggestionBlockHtml","textUpToCursor","suggestion","error","text","parts","userContent","headers","requestBody","response","errorMessage","parseError","sentence","paragraphToAdd","beforeCursor","afterCursor","spacer","newText","newCursorPosition","message","unsafe","key","encryptedKey","savedKey","decryptedKey","seconds","prompt","endpoint","modelName","context","settings","savedSettings","defineAITextEditor","tagName"],"mappings":"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"}
|