tiptapify 0.2.4 → 0.2.6
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +1 -1
- package/dist/tiptapify.css +1 -1
- package/dist/tiptapify.mjs +74210 -54575
- package/dist/tiptapify.umd.js +87 -84
- package/index.d.ts +42 -2
- package/package.json +45 -45
- package/src/components/Tiptapify.vue +108 -9
- package/src/components/Toolbar/Index.vue +2 -2
- package/src/components/Toolbar/Items.vue +9 -2
- package/src/components/UI/TiptapifyDialog.vue +7 -1
- package/src/extensions/components/ai/Button.vue +1 -1
- package/src/extensions/components/ai/Dialog.vue +338 -27
- package/src/extensions/components/ai/backend.test.ts +314 -0
- package/src/extensions/components/ai/backend.ts +303 -0
- package/src/extensions/components/ai/charLimit.test.ts +55 -0
- package/src/extensions/components/ai/charLimit.ts +30 -0
- package/src/extensions/components/ai/characterCount.test.ts +27 -0
- package/src/extensions/components/media/iframe/Button.vue +1 -2
- package/src/extensions/components/media/iframe/IframeDialog.vue +36 -31
- package/src/extensions/components/media/image/Button.vue +1 -1
- package/src/extensions/components/media/link/Button.vue +1 -1
- package/src/extensions/components/media/link/LinkDialog.vue +19 -36
- package/src/extensions/components/media/video/Button.vue +1 -1
- package/src/extensions/components/media/video/VideoDialog.vue +17 -17
- package/src/extensions/components/misc/source/Button.vue +1 -1
- package/src/i18n/locales/ar.json +165 -159
- package/src/i18n/locales/bg.json +212 -0
- package/src/i18n/locales/ch.json +165 -159
- package/src/i18n/locales/cz.json +165 -159
- package/src/i18n/locales/de.json +165 -159
- package/src/i18n/locales/el.json +212 -0
- package/src/i18n/locales/en.json +166 -159
- package/src/i18n/locales/es.json +165 -159
- package/src/i18n/locales/et.json +212 -0
- package/src/i18n/locales/fi.json +165 -159
- package/src/i18n/locales/fr.json +165 -159
- package/src/i18n/locales/he.json +212 -0
- package/src/i18n/locales/hi.json +212 -0
- package/src/i18n/locales/hr.json +212 -0
- package/src/i18n/locales/hu.json +165 -159
- package/src/i18n/locales/id.json +212 -0
- package/src/i18n/locales/it.json +165 -159
- package/src/i18n/locales/ja.json +165 -159
- package/src/i18n/locales/ko.json +165 -159
- package/src/i18n/locales/la.json +165 -159
- package/src/i18n/locales/lt.json +165 -159
- package/src/i18n/locales/nl.json +165 -159
- package/src/i18n/locales/no.json +212 -0
- package/src/i18n/locales/pl.json +165 -159
- package/src/i18n/locales/pt-BR.json +212 -0
- package/src/i18n/locales/pt.json +165 -159
- package/src/i18n/locales/ro.json +212 -0
- package/src/i18n/locales/ru.json +165 -159
- package/src/i18n/locales/se.json +165 -159
- package/src/i18n/locales/sk.json +212 -0
- package/src/i18n/locales/sl.json +212 -0
- package/src/i18n/locales/sr.json +212 -0
- package/src/i18n/locales/th.json +165 -159
- package/src/i18n/locales/tr.json +165 -159
- package/src/i18n/locales/uk.json +165 -159
- package/src/i18n/locales/vi.json +165 -159
- package/src/index.ts +7 -0
- package/src/types/editor.ts +40 -2
- package/src/utils/validateHref.test.ts +68 -0
- package/src/utils/validateHref.ts +21 -0
|
@@ -6,15 +6,20 @@ import {
|
|
|
6
6
|
TiptapifyAiEditorContext,
|
|
7
7
|
TiptapifyAiMode,
|
|
8
8
|
TiptapifyAiOpenAiResponse,
|
|
9
|
+
TiptapifyAiReasoningEffort,
|
|
9
10
|
TiptapifyAiRequest,
|
|
10
11
|
TiptapifyAiResponse,
|
|
11
12
|
TiptapifyAiResolvedOptions,
|
|
13
|
+
TiptapifyAiStream,
|
|
12
14
|
TiptapifyEditor,
|
|
13
15
|
variantBtnTypes,
|
|
14
16
|
variantFieldTypes
|
|
15
17
|
} from '@tiptapify/types/editor'
|
|
16
|
-
import { computed, ComputedRef, inject, PropType, Ref, ref, useTemplateRef } from 'vue'
|
|
18
|
+
import { computed, ComputedRef, inject, nextTick, PropType, Ref, ref, useTemplateRef, watch } from 'vue'
|
|
19
|
+
import * as mdi from '@mdi/js'
|
|
17
20
|
import { ComposerTranslation } from 'vue-i18n'
|
|
21
|
+
import { exceedsLimit } from './charLimit'
|
|
22
|
+
import { DEFAULT_AI_INSTRUCTION } from './backend'
|
|
18
23
|
|
|
19
24
|
defineProps({
|
|
20
25
|
variantBtn: { type: String as PropType<variantBtnTypes>, default() { return defaults.variantBtn } },
|
|
@@ -28,31 +33,128 @@ const { t } = inject('tiptapifyI18n') as { t: ComposerTranslation }
|
|
|
28
33
|
const dialog = useTemplateRef('dialog')
|
|
29
34
|
const prompt = ref('')
|
|
30
35
|
const result = ref('')
|
|
36
|
+
const reasoning = ref('')
|
|
37
|
+
const reasoningPanel = ref<number[]>([])
|
|
38
|
+
const reasoningTextEl = ref<HTMLElement | null>(null)
|
|
31
39
|
const error = ref('')
|
|
32
40
|
const loading = ref(false)
|
|
41
|
+
const requestController = ref<AbortController | null>(null)
|
|
33
42
|
const requestRange = ref<{ from: number, to: number } | null>(null)
|
|
34
43
|
const requestMode = ref<TiptapifyAiMode>('insert')
|
|
44
|
+
const thinkingEnabled = ref(false)
|
|
45
|
+
const reasoningEffort = ref<'default' | TiptapifyAiReasoningEffort | null>(null)
|
|
46
|
+
|
|
47
|
+
watch(loading, (value) => {
|
|
48
|
+
if (value) {
|
|
49
|
+
reasoningPanel.value = [0]
|
|
50
|
+
}
|
|
51
|
+
})
|
|
52
|
+
|
|
53
|
+
watch(reasoning, () => {
|
|
54
|
+
nextTick(() => {
|
|
55
|
+
const element = reasoningTextEl.value
|
|
56
|
+
if (element) {
|
|
57
|
+
element.scrollTop = element.scrollHeight
|
|
58
|
+
}
|
|
59
|
+
})
|
|
60
|
+
})
|
|
35
61
|
|
|
36
62
|
const aiProvider = computed(() => ai?.value ? ai.value.aiProvider : undefined)
|
|
37
63
|
const promptExamples = computed(() => ai?.value ? ai.value.promptExamples : [])
|
|
64
|
+
const reasoningEffortOptions = computed<TiptapifyAiReasoningEffort[]>(() => {
|
|
65
|
+
const configured = ai?.value?.reasoningEffort
|
|
66
|
+
if (!configured || !Array.isArray(configured.options)) {
|
|
67
|
+
return []
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
return configured.options.filter(option => option !== 'default')
|
|
71
|
+
})
|
|
72
|
+
const reasoningEffortItems = computed<Array<'default' | TiptapifyAiReasoningEffort>>(() => {
|
|
73
|
+
return ['default', ...reasoningEffortOptions.value]
|
|
74
|
+
})
|
|
75
|
+
const reasoningEffortSupported = computed(() => reasoningEffortOptions.value.length > 0)
|
|
76
|
+
const thinkingSupported = computed(() => ai?.value?.thinking === true)
|
|
77
|
+
const showReasoning = computed(() => ai?.value?.showReasoning === true)
|
|
78
|
+
const showReasoningEffort = computed(() => thinkingSupported.value && reasoningEffortSupported.value && thinkingEnabled.value)
|
|
79
|
+
const useReasoningEffort = computed(() => {
|
|
80
|
+
return thinkingSupported.value
|
|
81
|
+
&& thinkingEnabled.value
|
|
82
|
+
&& reasoningEffortSupported.value
|
|
83
|
+
&& reasoningEffort.value !== null
|
|
84
|
+
&& reasoningEffort.value !== 'default'
|
|
85
|
+
})
|
|
38
86
|
const isGenerateDisabled = computed(() => !aiProvider.value || !prompt.value.trim() || loading.value)
|
|
39
|
-
const
|
|
87
|
+
const characterCountLimit = computed(() => {
|
|
88
|
+
const limit = getCharacterCountExtension()?.options?.limit
|
|
89
|
+
return typeof limit === 'number' ? limit : null
|
|
90
|
+
})
|
|
91
|
+
const limitExceeded = computed(() => {
|
|
92
|
+
const limit = characterCountLimit.value
|
|
93
|
+
if (limit === null || !result.value) {
|
|
94
|
+
return false
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
return exceedsLimit(result.value, {
|
|
98
|
+
doc: editor.value.state.doc,
|
|
99
|
+
limit,
|
|
100
|
+
mode: requestMode.value,
|
|
101
|
+
range: requestRange.value,
|
|
102
|
+
})
|
|
103
|
+
})
|
|
104
|
+
// A result that exceeds the character limit cannot be inserted (the
|
|
105
|
+
// CharacterCount extension rejects it), so the apply action (and its
|
|
106
|
+
// alternative modes) is disabled until the result is shortened.
|
|
107
|
+
const isApplyDisabled = computed(() => !result.value.trim() || loading.value || limitExceeded.value)
|
|
108
|
+
const actionLabel = computed(() => t(`ai.${requestMode.value}`))
|
|
109
|
+
const modeOptions: TiptapifyAiMode[] = ['insert', 'replace', 'append']
|
|
110
|
+
const alternativeModes = computed(() => modeOptions.filter(mode => mode !== requestMode.value))
|
|
111
|
+
|
|
112
|
+
function applyInMode(mode: TiptapifyAiMode) {
|
|
113
|
+
if (isApplyDisabled.value) {
|
|
114
|
+
return
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
requestMode.value = mode
|
|
118
|
+
apply()
|
|
119
|
+
}
|
|
40
120
|
|
|
41
121
|
defineExpose({ showDialog })
|
|
42
122
|
|
|
123
|
+
function resetReasoningEffort() {
|
|
124
|
+
const configured = ai?.value?.reasoningEffort
|
|
125
|
+
const options = reasoningEffortOptions.value
|
|
126
|
+
|
|
127
|
+
if (options.length === 0) {
|
|
128
|
+
reasoningEffort.value = null
|
|
129
|
+
return
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
reasoningEffort.value = configured?.default && options.includes(configured.default)
|
|
133
|
+
? configured.default
|
|
134
|
+
: 'default'
|
|
135
|
+
}
|
|
136
|
+
|
|
43
137
|
function showDialog() {
|
|
44
138
|
prompt.value = ai?.value && ai.value.defaultPrompt ? ai.value.defaultPrompt : ''
|
|
45
139
|
result.value = ''
|
|
140
|
+
reasoning.value = ''
|
|
46
141
|
error.value = ''
|
|
47
142
|
requestRange.value = null
|
|
48
|
-
|
|
143
|
+
const { from, to } = editor.value.state.selection
|
|
144
|
+
requestMode.value = ai.value?.mode ?? (getSelectedText(from, to) ? 'replace' : 'insert')
|
|
145
|
+
thinkingEnabled.value = true
|
|
146
|
+
resetReasoningEffort()
|
|
49
147
|
|
|
50
148
|
dialog.value?.open()
|
|
51
149
|
}
|
|
52
150
|
|
|
53
151
|
function close() {
|
|
152
|
+
requestController.value?.abort()
|
|
153
|
+
requestController.value = null
|
|
154
|
+
|
|
54
155
|
prompt.value = ''
|
|
55
156
|
result.value = ''
|
|
157
|
+
reasoning.value = ''
|
|
56
158
|
error.value = ''
|
|
57
159
|
requestRange.value = null
|
|
58
160
|
requestMode.value = 'insert'
|
|
@@ -60,6 +162,10 @@ function close() {
|
|
|
60
162
|
dialog.value?.close()
|
|
61
163
|
}
|
|
62
164
|
|
|
165
|
+
function stop() {
|
|
166
|
+
requestController.value?.abort()
|
|
167
|
+
}
|
|
168
|
+
|
|
63
169
|
function applyExample(examplePrompt: string) {
|
|
64
170
|
prompt.value = examplePrompt
|
|
65
171
|
}
|
|
@@ -75,7 +181,7 @@ function getSelectedText(from: number, to: number) {
|
|
|
75
181
|
function buildDefaultMessages(context: TiptapifyAiEditorContext) {
|
|
76
182
|
const systemPrompt = ai.value && ai.value.systemPrompt
|
|
77
183
|
? ai.value.systemPrompt
|
|
78
|
-
:
|
|
184
|
+
: DEFAULT_AI_INSTRUCTION
|
|
79
185
|
const contextText = context.selectedText || context.text
|
|
80
186
|
const contextLabel = context.selectedText ? 'Selected text' : 'Editor text'
|
|
81
187
|
|
|
@@ -95,10 +201,12 @@ function buildDefaultMessages(context: TiptapifyAiEditorContext) {
|
|
|
95
201
|
|
|
96
202
|
function buildOpenAiRequest(context: TiptapifyAiEditorContext): TiptapifyAiRequest {
|
|
97
203
|
const options = ai.value || {}
|
|
204
|
+
const chatCompletionOptions = options.chatCompletionOptions ?? {}
|
|
205
|
+
const defaultStream = typeof chatCompletionOptions.stream === 'boolean' ? chatCompletionOptions.stream : false
|
|
98
206
|
const request: TiptapifyAiRequest = {
|
|
99
|
-
...
|
|
207
|
+
...chatCompletionOptions,
|
|
100
208
|
messages: options.createMessages ? options.createMessages(context) : buildDefaultMessages(context),
|
|
101
|
-
stream:
|
|
209
|
+
stream: options.stream ?? defaultStream,
|
|
102
210
|
}
|
|
103
211
|
|
|
104
212
|
if (options.model) {
|
|
@@ -109,6 +217,15 @@ function buildOpenAiRequest(context: TiptapifyAiEditorContext): TiptapifyAiReque
|
|
|
109
217
|
request.temperature = options.temperature
|
|
110
218
|
}
|
|
111
219
|
|
|
220
|
+
if (useReasoningEffort.value) {
|
|
221
|
+
delete request.enable_thinking
|
|
222
|
+
request.reasoning_effort = reasoningEffort.value
|
|
223
|
+
} else {
|
|
224
|
+
delete request.reasoning_effort
|
|
225
|
+
|
|
226
|
+
request.enable_thinking = thinkingSupported.value && thinkingEnabled.value
|
|
227
|
+
}
|
|
228
|
+
|
|
112
229
|
return request
|
|
113
230
|
}
|
|
114
231
|
|
|
@@ -141,12 +258,15 @@ async function generate() {
|
|
|
141
258
|
|
|
142
259
|
error.value = ''
|
|
143
260
|
result.value = ''
|
|
261
|
+
reasoning.value = ''
|
|
144
262
|
loading.value = true
|
|
145
263
|
|
|
264
|
+
const controller = new AbortController()
|
|
265
|
+
requestController.value = controller
|
|
266
|
+
|
|
146
267
|
const { from, to } = editor.value.state.selection
|
|
147
268
|
const selectedText = getSelectedText(from, to)
|
|
148
269
|
requestRange.value = selectedText ? { from, to } : null
|
|
149
|
-
requestMode.value = ai.value ? ai.value.mode ?? (selectedText ? 'replace' : 'insert') : 'insert'
|
|
150
270
|
|
|
151
271
|
const context: TiptapifyAiEditorContext = {
|
|
152
272
|
prompt: prompt.value.trim(),
|
|
@@ -158,27 +278,51 @@ async function generate() {
|
|
|
158
278
|
}
|
|
159
279
|
const request = buildOpenAiRequest(context)
|
|
160
280
|
|
|
281
|
+
const stream: TiptapifyAiStream = {
|
|
282
|
+
signal: controller.signal,
|
|
283
|
+
onChunk: (chunk: string) => {
|
|
284
|
+
if (chunk && !controller.signal.aborted) {
|
|
285
|
+
result.value += chunk
|
|
286
|
+
}
|
|
287
|
+
},
|
|
288
|
+
onReasoning: (chunk: string) => {
|
|
289
|
+
if (chunk && !controller.signal.aborted) {
|
|
290
|
+
reasoning.value += chunk
|
|
291
|
+
}
|
|
292
|
+
},
|
|
293
|
+
}
|
|
294
|
+
|
|
161
295
|
try {
|
|
162
|
-
const response = await aiProvider.value(request, context)
|
|
163
|
-
|
|
296
|
+
const response = await aiProvider.value(request, context, stream)
|
|
297
|
+
const content = getResponseContent(response)
|
|
298
|
+
|
|
299
|
+
if (!controller.signal.aborted && content) {
|
|
300
|
+
result.value = content
|
|
301
|
+
}
|
|
164
302
|
} catch (err) {
|
|
165
|
-
|
|
303
|
+
if (!controller.signal.aborted) {
|
|
304
|
+
error.value = err instanceof Error ? err.message : t('ai.error')
|
|
305
|
+
}
|
|
166
306
|
} finally {
|
|
167
307
|
loading.value = false
|
|
168
308
|
}
|
|
169
309
|
}
|
|
170
310
|
|
|
311
|
+
function getCharacterCountExtension() {
|
|
312
|
+
return editor.value.options.extensions?.find(item => item.name === 'characterCount')
|
|
313
|
+
}
|
|
314
|
+
|
|
171
315
|
function apply() {
|
|
172
316
|
if (isApplyDisabled.value) {
|
|
173
317
|
return
|
|
174
318
|
}
|
|
175
319
|
|
|
176
320
|
const chain = editor.value.chain().focus()
|
|
177
|
-
if (requestRange.value) {
|
|
321
|
+
if (requestMode.value === 'replace' && requestRange.value) {
|
|
178
322
|
chain.deleteRange(requestRange.value)
|
|
179
323
|
}
|
|
180
324
|
|
|
181
|
-
if (
|
|
325
|
+
if (requestMode.value === 'append') {
|
|
182
326
|
chain.insertContentAt(editor.value.state.doc.content.size, result.value).run()
|
|
183
327
|
} else {
|
|
184
328
|
chain.insertContent(result.value).run()
|
|
@@ -197,19 +341,9 @@ function apply() {
|
|
|
197
341
|
{{ t('ai.unavailable') }}
|
|
198
342
|
</VAlert>
|
|
199
343
|
|
|
200
|
-
<VTextarea
|
|
201
|
-
v-model="prompt"
|
|
202
|
-
:label="t('ai.prompt')"
|
|
203
|
-
:variant="variantField"
|
|
204
|
-
:disabled="loading || !aiProvider"
|
|
205
|
-
rows="4"
|
|
206
|
-
auto-grow
|
|
207
|
-
autofocus
|
|
208
|
-
/>
|
|
209
|
-
|
|
210
344
|
<div v-if="promptExamples.length" class="mb-4">
|
|
211
345
|
<VLabel class="mb-2 d-block">
|
|
212
|
-
{{ t('ai.
|
|
346
|
+
{{ t('ai.quick_prompts') }}
|
|
213
347
|
</VLabel>
|
|
214
348
|
<div class="d-flex flex-wrap ga-2">
|
|
215
349
|
<VChip
|
|
@@ -224,16 +358,83 @@ function apply() {
|
|
|
224
358
|
</div>
|
|
225
359
|
</div>
|
|
226
360
|
|
|
361
|
+
<VTextarea
|
|
362
|
+
v-model="prompt"
|
|
363
|
+
:label="t('ai.prompt')"
|
|
364
|
+
:variant="variantField"
|
|
365
|
+
:disabled="loading || !aiProvider"
|
|
366
|
+
rows="4"
|
|
367
|
+
auto-grow
|
|
368
|
+
autofocus
|
|
369
|
+
/>
|
|
370
|
+
|
|
371
|
+
<div v-if="thinkingSupported" class="ai-thinking-toggle d-flex align-center mb-4">
|
|
372
|
+
<VIcon size="x-small" class="mr-2" :icon="`mdiSvg:${mdi.mdiBrain}`" />
|
|
373
|
+
<VLabel>{{ t('ai.thinking') }}</VLabel>
|
|
374
|
+
<VSwitch
|
|
375
|
+
v-model="thinkingEnabled"
|
|
376
|
+
:disabled="loading || useReasoningEffort"
|
|
377
|
+
density="compact"
|
|
378
|
+
hide-details
|
|
379
|
+
color="primary"
|
|
380
|
+
class="ai-thinking-toggle__switch"
|
|
381
|
+
/>
|
|
382
|
+
</div>
|
|
383
|
+
|
|
384
|
+
<div v-if="showReasoningEffort" class="ai-reasoning-effort d-flex align-center mb-4">
|
|
385
|
+
<VIcon size="x-small" class="mr-2" :icon="`mdiSvg:${mdi.mdiGauge}`" />
|
|
386
|
+
<VLabel>{{ t('ai.reasoning_effort') }}</VLabel>
|
|
387
|
+
<VSelect
|
|
388
|
+
v-model="reasoningEffort"
|
|
389
|
+
:items="reasoningEffortItems"
|
|
390
|
+
:disabled="loading"
|
|
391
|
+
:variant="variantField"
|
|
392
|
+
density="compact"
|
|
393
|
+
hide-details
|
|
394
|
+
class="ai-reasoning-effort__select"
|
|
395
|
+
/>
|
|
396
|
+
</div>
|
|
397
|
+
|
|
398
|
+
<VExpansionPanels v-if="showReasoning && reasoning" v-model="reasoningPanel" variant="default" class="ai-reasoning mb-4">
|
|
399
|
+
<VExpansionPanel :value="0">
|
|
400
|
+
<template #title>
|
|
401
|
+
<VIcon size="x-small" :icon="`mdiSvg:${mdi.mdiBrain}`" />
|
|
402
|
+
<span class="ml-2">{{ t('ai.thinking') }}</span>
|
|
403
|
+
</template>
|
|
404
|
+
<template #text>
|
|
405
|
+
<pre ref="reasoningTextEl" class="ai-reasoning__text">{{ reasoning }}</pre>
|
|
406
|
+
</template>
|
|
407
|
+
</VExpansionPanel>
|
|
408
|
+
</VExpansionPanels>
|
|
409
|
+
|
|
227
410
|
<VTextarea
|
|
228
411
|
v-model="result"
|
|
229
412
|
:label="t('ai.result')"
|
|
230
413
|
:variant="variantField"
|
|
231
|
-
:loading="loading"
|
|
232
|
-
:
|
|
414
|
+
:loading="loading && !result"
|
|
415
|
+
:readonly="loading"
|
|
233
416
|
rows="5"
|
|
234
417
|
auto-grow
|
|
235
418
|
/>
|
|
236
419
|
|
|
420
|
+
<VLabel
|
|
421
|
+
v-if="characterCountLimit !== null && result"
|
|
422
|
+
class="ai-char-count d-block"
|
|
423
|
+
:class="{ 'ai-char-count--exceeded': limitExceeded }"
|
|
424
|
+
>
|
|
425
|
+
{{ result.length }} / {{ characterCountLimit }}
|
|
426
|
+
</VLabel>
|
|
427
|
+
|
|
428
|
+
<VAlert
|
|
429
|
+
v-if="limitExceeded"
|
|
430
|
+
type="error"
|
|
431
|
+
variant="tonal"
|
|
432
|
+
density="compact"
|
|
433
|
+
class="ai-limit-alert mt-4"
|
|
434
|
+
>
|
|
435
|
+
{{ t('ai.limit_exceeded') }}
|
|
436
|
+
</VAlert>
|
|
437
|
+
|
|
237
438
|
<VAlert v-if="loading" type="info" variant="tonal" density="compact" class="mt-4">
|
|
238
439
|
{{ t('ai.loading') }}
|
|
239
440
|
</VAlert>
|
|
@@ -253,10 +454,120 @@ function apply() {
|
|
|
253
454
|
<VBtn color="primary" :variant="variantBtn" :disabled="isGenerateDisabled" :loading="loading" @click="generate">
|
|
254
455
|
{{ t('ai.generate') }}
|
|
255
456
|
</VBtn>
|
|
256
|
-
<VBtn
|
|
257
|
-
{{ t('ai.
|
|
457
|
+
<VBtn v-if="loading" color="warning" :variant="variantBtn" @click="stop">
|
|
458
|
+
{{ t('ai.stop') }}
|
|
258
459
|
</VBtn>
|
|
460
|
+
|
|
461
|
+
<VBtnGroup
|
|
462
|
+
:class="{ 'tiptapify-btn-group--elevated': variantBtn === 'elevated' && !isApplyDisabled }"
|
|
463
|
+
density="compact"
|
|
464
|
+
:variant="variantBtn"
|
|
465
|
+
divided
|
|
466
|
+
>
|
|
467
|
+
<VBtn
|
|
468
|
+
color="primary"
|
|
469
|
+
:variant="variantBtn"
|
|
470
|
+
:disabled="isApplyDisabled"
|
|
471
|
+
@click="apply"
|
|
472
|
+
>
|
|
473
|
+
{{ t('ai.insert') }}
|
|
474
|
+
</VBtn>
|
|
475
|
+
|
|
476
|
+
<VMenu>
|
|
477
|
+
<template #activator="{ props }">
|
|
478
|
+
<VBtn
|
|
479
|
+
v-bind="{ ...props, minWidth: 30 }"
|
|
480
|
+
color="primary"
|
|
481
|
+
:disabled="isApplyDisabled"
|
|
482
|
+
:variant="variantBtn"
|
|
483
|
+
>
|
|
484
|
+
<VIcon :icon="`mdiSvg:${mdi.mdiDotsVertical}`" />
|
|
485
|
+
</VBtn>
|
|
486
|
+
</template>
|
|
487
|
+
<VList>
|
|
488
|
+
<VListItem
|
|
489
|
+
v-for="mode in alternativeModes"
|
|
490
|
+
:key="mode"
|
|
491
|
+
@click="applyInMode(mode)"
|
|
492
|
+
>
|
|
493
|
+
<VListItemTitle>{{ t(`ai.${mode}`) }}</VListItemTitle>
|
|
494
|
+
</VListItem>
|
|
495
|
+
</VList>
|
|
496
|
+
</VMenu>
|
|
497
|
+
</VBtnGroup>
|
|
259
498
|
</VCardActions>
|
|
260
499
|
</template>
|
|
261
500
|
</TiptapifyDialog>
|
|
262
501
|
</template>
|
|
502
|
+
|
|
503
|
+
<style lang="scss" scoped>
|
|
504
|
+
.tiptapify-btn-group--elevated {
|
|
505
|
+
box-shadow:
|
|
506
|
+
0 3px 1px -2px var(--v-shadow-key-umbra-opacity, rgba(0, 0, 0, 0.2)),
|
|
507
|
+
0 2px 2px 0 var(--v-shadow-key-penumbra-opacity, rgba(0, 0, 0, 0.14)),
|
|
508
|
+
0 1px 5px 0 var(--v-shadow-key-ambient-opacity, rgba(0, 0, 0, 0.12));
|
|
509
|
+
}
|
|
510
|
+
|
|
511
|
+
.ai-thinking-toggle {
|
|
512
|
+
&__switch {
|
|
513
|
+
flex: 0 0 auto;
|
|
514
|
+
margin-left: auto;
|
|
515
|
+
|
|
516
|
+
--v-switch-scale: 1;
|
|
517
|
+
--v-switch-track-height: 14px;
|
|
518
|
+
--v-switch-thumb-height: 20px;
|
|
519
|
+
--v-switch-thumb-width: 20px;
|
|
520
|
+
}
|
|
521
|
+
}
|
|
522
|
+
|
|
523
|
+
.ai-reasoning-effort {
|
|
524
|
+
&__select {
|
|
525
|
+
flex: 0 0 auto;
|
|
526
|
+
margin-left: auto;
|
|
527
|
+
|
|
528
|
+
:deep(.v-input) {
|
|
529
|
+
width: 140px;
|
|
530
|
+
}
|
|
531
|
+
}
|
|
532
|
+
}
|
|
533
|
+
|
|
534
|
+
.ai-reasoning {
|
|
535
|
+
:deep(.v-expansion-panel-title) {
|
|
536
|
+
color: rgba(var(--v-theme-on-surface), var(--v-medium-emphasis-opacity));
|
|
537
|
+
font-size: .8125rem;
|
|
538
|
+
font-weight: 500;
|
|
539
|
+
padding: .625rem .75rem;
|
|
540
|
+
}
|
|
541
|
+
|
|
542
|
+
:deep(.v-expansion-panel-title__icon) {
|
|
543
|
+
color: rgba(var(--v-theme-on-surface), var(--v-disabled-opacity));
|
|
544
|
+
}
|
|
545
|
+
}
|
|
546
|
+
|
|
547
|
+
.ai-reasoning__text {
|
|
548
|
+
color: rgba(var(--v-theme-on-surface), var(--v-medium-emphasis-opacity));
|
|
549
|
+
font-family: 'JetBrainsMono', monospace;
|
|
550
|
+
font-size: .75rem;
|
|
551
|
+
line-height: 1.4;
|
|
552
|
+
margin: 0;
|
|
553
|
+
max-height: 200px;
|
|
554
|
+
overflow: auto;
|
|
555
|
+
padding: .25rem .75rem .625rem;
|
|
556
|
+
white-space: pre-wrap;
|
|
557
|
+
word-break: break-word;
|
|
558
|
+
}
|
|
559
|
+
|
|
560
|
+
.ai-char-count {
|
|
561
|
+
font-size: .75rem;
|
|
562
|
+
margin-top: .25rem;
|
|
563
|
+
text-align: right;
|
|
564
|
+
|
|
565
|
+
&--exceeded {
|
|
566
|
+
color: var(--red-soft);
|
|
567
|
+
}
|
|
568
|
+
}
|
|
569
|
+
|
|
570
|
+
.ai-limit-alert {
|
|
571
|
+
color: var(--red-soft, #B24A40) !important;
|
|
572
|
+
}
|
|
573
|
+
</style>
|