AI разширение
AI разширението добавя бутон в лентата, който отваря диалог за промпт, изпраща заявката към избран от вас доставчик, показва поточно получения резултат и го вмъква в редактора чрез бутона за действие според режима (Вмъкване / Замяна на избраното / Добавяне в края).
<Tiptapify
:content="content"
:items="['ai', '|', 'bold', 'italic']"
:ai="{
aiProvider,
model: 'gpt-4.1-mini',
stream: true,
}"
/>Пропъртито ai приема true (опции със стойности по подразбиране) или обект с опции (TiptapifyAiOptions). Елементът ai на лентата винаги е регистриран, но бутонът е деактивиран, освен ако aiProvider не е зададен.
Вижте AI примера за работеща демонстрация и Пропъртита за пълния справочник на опциите.
Архитектура
Tiptapify.vue Toolbar item `ai` Dialog.vue
resolves the `ai` -> Button.vue -> builds the request
prop into a resolved -> (disabled without -> calls aiProvider(request, context, stream)
options object and -> aiProvider) -> streams chunks into result/reasoning fields
provides it (inject -> opens the dialog -> the action button (Insert/Replace/Append) places the result into the editor
'tiptapifyAi')Tiptapifyнормализира пропъртитоai:ai: trueстава празен обект с опции,promptExamplesсе връща към локализираните стойности по подразбиране, а резултатът е достъпен за наследниците чрезinject('tiptapifyAi')(TiptapifyAiResolvedOptionsилиfalse).- Елементът на лентата (
src/components/Toolbar/ai.ts) рендираButton.vue. Бутонът проверява наличността наaiProviderи отваряDialog.vue. Dialog.vueпритежава целия жизнен цикъл на заявката: изграждане на заявката, извикване на доставчика, поточно предаване и вмъкване на резултата.
Договорът на доставчика
type TiptapifyAiProvider = (
request: TiptapifyAiRequest,
context: TiptapifyAiEditorContext,
stream?: TiptapifyAiStream,
) => Promise<TiptapifyAiResponse | TiptapifyAiOpenAiResponse | string>Доставчикът е обикновена async функция, която вие имплементирате. tiptapify сам не извиква никакво API — работи всичко, което може да приеме OpenAI-съвместим обект request (fetch, SDK, backend адаптер). Изключение е backend режимът aiEndpoint (вижте Backend генериране), при който tiptapify изпраща заявката към вашата крайна точка.
Заявката (TiptapifyAiRequest) се разпростира от вашите chatCompletionOptions и после се допълва от плъгина:
| Поле | Източник |
|---|---|
messages | createMessages(context), ако е зададена, иначе стандартната двойка: системно съобщение (вашият systemPrompt или вграден по подразбиране) и потребителско съобщение, което включва промпта плюс избрания текст на редактора с текста около него и правилото да се отговори само за селекцията (или за целия текст, когато нищо не е избрано) |
model | опцията model, когато е зададена |
temperature | опцията temperature, когато е зададена |
stream | опцията stream или chatCompletionOptions.stream |
enable_thinking / reasoning_effort | превключвателят за мислене / падащото меню за ниво — вижте по-долу |
Контекстът (TiptapifyAiEditorContext) дава на доставчика пълното състояние на редактора: prompt, selectedText, textBefore / textAfter (текстът на абзаца на селекцията около нея, до 500 символа от всяка страна), text, html, json и mode (insert / replace / append).
Селекция, която започва или свършва в средата на дума, обхваща цялата дума, а съобщенията по подразбиране изпращат текста около селекцията заедно с правилото да се отговори само за нея: когато получи само фрагмент, моделът го взема за целия текст и го допълва, а заместването би повторило онова, което следва след селекцията.
Отговорът: доставчикът може да върне съдържанието като обикновен string, като { content } (TiptapifyAiResponse) или като OpenAI-съвместим payload с choices (TiptapifyAiOpenAiResponse). Диалогът извлича съдържанието в този ред и премахва водещите интервали. При отхвърляне съобщението за грешка се показва в диалога (или ai.error, когато грешката не е екземпляр на Error) и диалогът остава отворен.
Backend генериране (aiEndpoint)
Вместо да имплементирате aiProvider, насочете AI функцията към backend LLM крайна точка:
// example
const ai = {
aiEndpoint: '/api/ai/generate',
model: 'gpt-4.1-mini',
stream: true,
}Тогава tiptapify сам изгражда заявката и я изпраща към крайната точка. Изрично зададен aiProvider винаги има предимство, когато са зададени и двете.
Заявка — минимален JSON payload (TiptapifyAiBackendRequest):
| Поле | Изпраща се |
|---|---|
prompt | винаги — заявката, въведена в диалога |
instruction | винаги — вашият systemPrompt, системното съобщение на собствен createMessages или вграденият по подразбиране |
stream | само при stream: true |
thinking | само когато превключвателят Мислене в диалога е включен |
reasoning_effort | само когато потребителят е избрал ниво (различно от default) |
model | само когато опцията model е зададена |
Отговор — приема се свободно: обикновен низ, обект { content } или OpenAI-съвместим payload с choices.
Поточно предаване — когато заявката има stream: true, тялото на отговора се чете като SSE поток (редове data:, завършващи с data: [DONE]). Приемани формати на частите:
- В стил OpenAI —
data: {"choices":[{"delta":{"content":"…", "reasoning_content":"…"}}]} - Опростен —
data: {"type":"content"|"reasoning","data":"…"} - Плосък —
data: {"content":"…","reasoning_content":"…"} - Обикновен текст —
data: some text(редdata, който не е JSON, е част със съдържание)
Частите content обновяват полето с резултата чрез onChunk, а частите reasoning_content захранват панела Мислене чрез onReasoning. Ако backend игнорира stream и отговаря с обикновено тяло, използва се то. Щракване върху Спри или затваряне на диалога прекъсва заявката; частично получено съдържание се запазва в полето с резултата.
Хедъри — aiHeaders (Record<string, string>) се сливат върху Content-Type: application/json. Хедърът Authorization в aiHeaders се запазва както е; иначе токен от tokenProvider се изпраща като Authorization: Bearer <token>.
Грешки — отговор, който не е OK, показва в диалога съобщението за грешка на backend (JSON { error } / { message } или суровото тяло).
Поточно предаване
Когато stream: true (или chatCompletionOptions.stream е true), диалогът подава обект TiptapifyAiStream като трети аргумент:
type TiptapifyAiStream = {
signal: AbortSignal,
onChunk: (chunk: string) => void,
onReasoning: (chunk: string) => void,
}signal— подайте го наfetch(или на извикването на вашия SDK). Той прекъсва, когато потребителят щракне Спри или затвори диалога, така че заявките в процес не продължават да работят.onChunk— добавяйте всяка част със съдържание към полето с резултата. Докато се генерира, полето с резултата е само за четене и частично полученото съдържание се запазва (можете да спрете, после да го вмъкнете или отхвърлите).onReasoning— препращайте изхода от разсъжденията/мисленето на модела (напр. делтатаreasoning_contentна Qwen/DeepSeek/o-series) към сгъваем панел Мислене над резултата. Панелът се разгъва и превърта автоматично по време на поточното предаване. Изходът от разсъжденията е само за показване и никога не се вмъква в редактора.
Доставчици, които не предават поточно, игнорират аргумента stream — той е незадължителен.
Мислене и ниво на разсъждение
Два потребителски контрола в диалога съответстват на полета на заявката. И двата се включват чрез опции и се притежават от плъгина — вие не задавате (и не бива да задавате) тези полета сами в chatCompletionOptions.
thinking: trueпоказва превключвателя Мислене (включен по подразбиране, нулира се при всяко отваряне на диалога). Плъгинът записва стойността на превключвателя в заявката катоenable_thinking: true | false.- липсващ
thinkingилиthinking: falseскрива превключвателя и плъгинът изпращаenable_thinking: false, така че моделите, които мислят по подразбиране, не мислят. reasoningEffort: { options, default? }показва падащото меню Труд на разсъждения.optionsизброява собствените нива, които искате да предложите ('low' | 'medium' | 'high' | 'xhigh' | 'max'); разширението винаги само добавя елементdefault(базовото ниво на режима на мислене), така че никога не изброяватеdefaultвъв вашата конфигурация. Полетоdefaultпредизбира едно от вашите собствени нива при отваряне на диалога — когато е пропуснато, предизбран еdefault. Стойността се нулира при всяко отваряне. Падащото меню се рендира само когатоoptionsе непразен списък,thinkingе включено и превключвателят Мислене е включен.showReasoning(по подразбиранеfalse) управлява сгъваемия панел Мислене, който предава поточно процеса на разсъждение на модела (делтатаreasoning_content). ЗадайтеshowReasoning: true, за да покажете този панел, като мисленето остава включено — моделът пак разсъждава, резултатът се прилага по същия начин, само стъпка по стъпка разсъжденията се показват допълнително.
Тъй като доставчици (особено vLLM/LM Studio) отхвърлят enable_thinking и reasoning_effort в една и съща заявка, плъгинът изпраща точно едно от двете:
| Състояние | Поле на заявката |
|---|---|
Мислене включено, ниво = default (или няма конфигурирано ниво) | enable_thinking (от превключвателя) |
Мислене включено, ниво = low / medium / high / xhigh / max | reasoning_effort (от падащото меню); превключвателят е деактивиран |
Мислене изключено (превключвателят е изключен или thinking не е включено в конфигурацията) | enable_thinking: false; reasoning_effort не се изпраща |
Грешки
Диалогът обяснява неуспешната заявка, вместо да показва суровата грешка, и добавя след обяснението собственото съобщение на услугата:
| Какво се е случило | Обяснение |
|---|---|
Браузърът не е получил отговор (Failed to fetch, NetworkError…, Load failed) | Услугата не работи, недостъпна е или CORS е блокирал отговора; конзолата на браузъра показва кое |
401 / 403 | Проверете API ключа или правата за достъп |
404 | Проверете пътя на крайната точка |
408 / 504 / таймаут | Услугата не е отговорила навреме |
429 | Твърде много заявки |
5xx | Услугата е недостъпна (зарежда модел, заета е) |
При aiEndpoint статусът идва от отговора. Собствен aiProvider получава същите обяснения, като хвърли грешка със status:
if (!response.ok) {
const body = await response.json().catch(() => null)
throw Object.assign(new Error(body?.error?.message ?? ''), { status: response.status })
}Всяка друга грешка се показва със собственото си съобщение.
Вмъкване на резултата
С бутона за действие — Вмъкване, Замяна на избраното или Добавяне в края, според режима — диалогът поставя result в редактора:
- Режим — бутонът за действие в диалога е split бутон. Основната му част прилага резултата в предизбрания режим (Вмъкване / Замяна на избраното / Добавяне в края): опцията
modeго предизбира, или Замяна на избраното, когато при отварянето на диалога е имало избран текст, и Вмъкване в противен случай. Малка секция „още“ (⋮) вдясно от бутона отваря меню с останалите режими — щракване върху един от тях прилага резултата веднага в този режим. Вмъкване поставя резултата на курсора, Замяна на избраното заменя избрания текст (запомнен при щракването на Генериране), а Добавяне в края го добавя в края на документа. - Ограничение на символите — когато редакторът е конфигуриран с
limit(разширениетоCharacterCountотхвърля всяка транзакция, която не е поставяне и би увеличила документа над лимита), под полето с резултата се показва брояч на символитеgenerated / limit(на живо по време на поточното предаване, в мек червен цвят, когато лимитът би бил надвишен), а диалогът показва предупреждениетоai.limit_exceededведнага след генерирането, когато резултатът би надвишил лимита. Тогава бутонът Вмъкване и менюто му с алтернативни режими са деактивирани: резултатът се прилага изцяло или никак, така че трябва да го съкратите, преди да го приложите.