AI ekstenzija
AI ekstenzija dodaje dugme na traci sa alatima koje otvara dijalog za unos upita, šalje zahtev provajderu po vašem izboru, prikazuje strimovani rezultat i umeće ga u uređivač preko dugmeta radnje zavisnog od režima (Umetni / Zameni / Dodaj na kraj).
<Tiptapify
:content="content"
:items="['ai', '|', 'bold', 'italic']"
:ai="{
aiProvider,
model: 'gpt-4.1-mini',
stream: true,
}"
/>Prop ai prihvata true (opcije sa podrazumevanim vrednostima) ili objekat opcija (TiptapifyAiOptions). Stavka ai na traci sa alatima je uvek registrovana, ali je dugme onemogućeno osim ako je podešen aiProvider.
Pokretljivu demonstraciju potražite u AI primeru, a kompletnu referencu opcija u Props.
Arhitektura
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')Tiptapifynormalizuje propai:ai: truepostaje prazan objekat opcija,promptExamplesse vraća na lokalizovane podrazumevane vrednosti, a rezultat se izlaže potomcima prekoinject('tiptapifyAi')(TiptapifyAiResolvedOptionsilifalse).- Stavka trake sa alatima (
src/components/Toolbar/ai.ts) renderujeButton.vue. Dugme proverava dostupnostaiProvideri otvaraDialog.vue. Dialog.vueupravlja celim životnim ciklusom zahteva: pravljenjem zahteva, pozivom provajdera, strimovanjem i umetanjem rezultata.
Ugovor provajdera
type TiptapifyAiProvider = (
request: TiptapifyAiRequest,
context: TiptapifyAiEditorContext,
stream?: TiptapifyAiStream,
) => Promise<TiptapifyAiResponse | TiptapifyAiOpenAiResponse | string>Provajder je obična async funkcija koju sami implementirate. tiptapify sam ne poziva nijedan API — radi sve što može da primi OpenAI-kompatibilan objekat request (fetch, SDK, backend adapter). Izuzetak je backend režim aiEndpoint (pogledajte Backend generisanje), gde tiptapify šalje zahtev na vaš endpoint.
Zahtev (TiptapifyAiRequest) se razvija iz vaših chatCompletionOptions, a zatim ga plugin dopunjuje:
| Polje | Izvor |
|---|---|
messages | createMessages(context) ako je zadat, inače podrazumevani par: sistemska poruka (vaš systemPrompt ili ugrađena podrazumevana) i korisnička poruka koja uključuje upit plus izabrani tekst uređivača sa tekstom oko njega i pravilom da se odgovori samo za izbor (ili za ceo tekst kada ništa nije izabrano) |
model | opcija model, kada je zadata |
temperature | opcija temperature, kada je zadata |
stream | opcija stream ili chatCompletionOptions.stream |
enable_thinking / reasoning_effort | prekidač razmišljanja / padajući meni nivoa — pogledajte ispod |
Kontekst (TiptapifyAiEditorContext) daje provajderu celo stanje uređivača: prompt, selectedText, textBefore / textAfter (tekst pasusa izbora oko njega, do 500 znakova sa svake strane), text, html, json i mode (insert / replace / append).
Izbor koji počinje ili se završava unutar reči obuhvata celu reč, a podrazumevane poruke šalju tekst oko izbora sa pravilom da se odgovori samo za izbor: kada dobije samo fragment, model ga smatra celim tekstom i dopunjava ga, pa bi zamena ponovila ono što sledi iza izbora.
Odgovor: provajder može vratiti sadržaj kao običan string, kao { content } (TiptapifyAiResponse) ili kao OpenAI-kompatibilan payload choices (TiptapifyAiOpenAiResponse). Dijalog izdvaja sadržaj tim redosledom i uklanja vodeće razmake. Pri odbijanju poruka o grešci se prikazuje u dijalogu (ili ai.error kada greška nije instanca Error), a dijalog ostaje otvoren.
Backend generisanje (aiEndpoint)
Umesto implementacije aiProvider, usmerite AI funkciju na backend LLM endpoint:
// example
const ai = {
aiEndpoint: '/api/ai/generate',
model: 'gpt-4.1-mini',
stream: true,
}tiptapify tada sam pravi zahtev i šalje ga na endpoint. Eksplicitan aiProvider uvek ima prednost kada su oba zadata.
Zahtev — minimalan JSON payload (TiptapifyAiBackendRequest):
| Polje | Šalje se |
|---|---|
prompt | uvek — zahtev ukucan u dijalogu |
instruction | uvek — vaš systemPrompt, sistemska poruka prilagođenog createMessages ili ugrađena podrazumevana |
stream | samo kada je stream: true |
thinking | samo kada je prekidač Razmišljanje u dijalogu uključen |
reasoning_effort | samo kada je korisnik izabrao nivo (ne default) |
model | samo kada je zadata opcija model |
Odgovor — prihvata se tolerantno: običan string, objekat { content } ili OpenAI-kompatibilan payload choices.
Strimovanje — kada zahtev ima stream: true, telo odgovora se čita kao SSE tok (redovi data:, završeni sa data: [DONE]). Prihvaćeni formati delova:
- U OpenAI stilu —
data: {"choices":[{"delta":{"content":"…", "reasoning_content":"…"}}]} - Jednostavan —
data: {"type":"content"|"reasoning","data":"…"} - Ravan —
data: {"content":"…","reasoning_content":"…"} - Običan tekst —
data: some text(reddatakoji nije JSON je deo sadržaja)
Delovi content ažuriraju polje rezultata preko onChunk, a delovi reasoning_content hrane panel Razmišljanje preko onReasoning. Ako backend ignoriše stream i odgovori običnim telom, koristi se to telo. Klik na Zaustavi ili zatvaranje dijaloga prekida zahtev; delimično strimovani sadržaj ostaje u polju rezultata.
Zaglavlja — aiHeaders (Record<string, string>) se spajaju preko Content-Type: application/json. Zaglavlje Authorization u aiHeaders ostaje kakvo jeste; u suprotnom se token iz tokenProvider šalje kao Authorization: Bearer <token>.
Greške — odgovor koji nije OK prikazuje u dijalogu poruku o grešci backenda (JSON { error } / { message } ili sirovo telo).
Strimovanje
Kada je stream: true (ili je chatCompletionOptions.stream true), dijalog prosleđuje objekat TiptapifyAiStream kao treći argument:
type TiptapifyAiStream = {
signal: AbortSignal,
onChunk: (chunk: string) => void,
onReasoning: (chunk: string) => void,
}signal— prosledite ga ufetch(ili poziv vašeg SDK-a). Prekida se kada korisnik klikne Zaustavi ili zatvori dijalog, pa zahtevi u toku ne nastavljaju da rade.onChunk— dodajte svaki deo sadržaja u polje rezultata. Tokom generisanja polje rezultata je samo za čitanje, a delimično strimovani sadržaj se zadržava (možete zaustaviti, pa umetnuti ili odbaciti).onReasoning— prosledite izlaz modela o rezonovanju/razmišljanju (npr. deltareasoning_contentkod Qwen/DeepSeek/o-serije) u sklopivi panel Razmišljanje iznad rezultata. Panel se automatski širi i skroluje tokom strimovanja. Izlaz rezonovanja služi samo za prikaz i nikada se ne umeće u uređivač.
Provajderi koji ne strimuju ignorišu argument stream — on je opcion.
Razmišljanje i nivo rezonovanja
Dve korisničke kontrole u dijalogu mapiraju se na polja zahteva. Obe se uključuju opcijama i pripadaju plugin-u — ova polja ne postavljate (i ne treba da ih postavljate) sami u chatCompletionOptions.
thinking: trueprikazuje prekidač Razmišljanje (podrazumevano uključen, resetuje se pri svakom otvaranju dijaloga). Plugin upisuje vrednost prekidača u zahtev kaoenable_thinking: true | false.thinkingkoji nedostaje ilithinking: falsesakriva prekidač, a plugin šaljeenable_thinking: false, pa modeli koji podrazumevano razmišljaju ne razmišljaju.reasoningEffort: { options, default? }prikazuje padajući meni Napori razmišljanja.optionsnavodi prilagođene nivoe koje želite da ponudite ('low' | 'medium' | 'high' | 'xhigh' | 'max'); ekstenzija sama uvek dodaje stavkudefault(osnovu režima razmišljanja), padefaultnikada ne navodite u konfiguraciji. Poljedefaultunapred bira jedan od vaših prilagođenih nivoa pri otvaranju dijaloga — kada je izostavljeno, unapred je izabrandefault. Vrednost se resetuje pri svakom otvaranju. Padajući meni se renderuje samo kada jeoptionsneprazna lista,thinkingje omogućen i prekidač Razmišljanje je uključen.showReasoning(podrazumevanofalse) kontroliše sklopivi panel Razmišljanje koji strimuje proces rezonovanja modela (deltareasoning_content). PostaviteshowReasoning: trueda prikažete taj panel uz uključeno razmišljanje — model i dalje rezonuje, rezultat se primenjuje na isti način, samo se dodatno prikazuje rezonovanje korak po korak.
Pošto provajderi (naročito vLLM/LM Studio) odbijaju enable_thinking i reasoning_effort u jednom zahtevu, plugin šalje tačno jedno od ta dva:
| Stanje | Polje zahteva |
|---|---|
Razmišljanje uključeno, nivo = default (ili nivo nije podešen) | enable_thinking (iz prekidača) |
Razmišljanje uključeno, nivo = low / medium / high / xhigh / max | reasoning_effort (iz padajućeg menija); prekidač je onemogućen |
Razmišljanje isključeno (prekidač isključen ili thinking nije omogućen u konfiguraciji) | enable_thinking: false; reasoning_effort se ne šalje |
Greške
Dijalog objašnjava neuspeli zahtev umesto da prikaže sirovu grešku, a iza objašnjenja dodaje sopstvenu poruku servisa:
| Šta se desilo | Objašnjenje |
|---|---|
Pregledač nije dobio odgovor (Failed to fetch, NetworkError…, Load failed) | Servis je van mreže, nedostupan, ili je CORS blokirao odgovor; konzola pregledača kaže šta je u pitanju |
401 / 403 | Proverite API ključ ili prava pristupa |
404 | Proverite putanju endpointa |
408 / 504 / timeout | Servis nije odgovorio na vreme |
429 | Previše zahteva |
5xx | Servis nije dostupan (učitava model, zauzet je) |
Sa aiEndpoint status dolazi iz odgovora. Prilagođeni aiProvider dobija ista objašnjenja bacanjem greške sa status:
if (!response.ok) {
const body = await response.json().catch(() => null)
throw Object.assign(new Error(body?.error?.message ?? ''), { status: response.status })
}Svaka druga greška prikazuje se sa sopstvenom porukom.
Umetanje rezultata
Na dugmetu radnje — Umetni, Zameni ili Dodaj na kraj, zavisno od režima — dijalog smešta result u uređivač:
- Režim — dugme radnje u dijalogu je podeljeno dugme. Glavni deo primenjuje rezultat u unapred izabranom režimu (Umetni / Zameni / Dodaj na kraj): opcija
modega unapred bira, ili Zameni kada je tekst bio izabran pri otvaranju dijaloga, a inače Umetni. Mali odeljak „više“ (⋮) desno od dugmeta otvara meni sa preostalim režimima — klik na jedan odmah primenjuje rezultat u tom režimu. Umetni smešta rezultat na kursor, Zameni menja izabrani tekst (zabeležen kada je kliknuto Generiši), a Dodaj na kraj ga dodaje na kraj dokumenta. - Ograničenje broja znakova — kada je uređivač podešen sa
limit(ekstenzijaCharacterCountodbija svaku transakciju koja nije lepljenje a prekoračila bi ograničenje), ispod polja rezultata prikazuje se brojač znakovagenerated / limit(uživo tokom strimovanja, blago crven kada bi ograničenje bilo prekoračeno), a dijalog odmah nakon generisanja prikazuje upozorenjeai.limit_exceededkada bi rezultat prekoračio ograničenje. Dugme Umetni i njegov meni alternativnih režima tada su onemogućeni: rezultat se primenjuje u celosti ili nikako, pa ga morate skratiti pre primene.