Plugin Directory

Changeset 3437262


Ignore:
Timestamp:
01/11/2026 10:22:51 PM (3 months ago)
Author:
primetimejas
Message:

Update to version v0.2.8 from GitHub

Location:
kaigen
Files:
12 added
14 edited
1 copied

Legend:

Unmodified
Added
Removed
  • kaigen/tags/v0.2.8/build/index.asset.php

    r3428748 r3437262  
    1 <?php return array('dependencies' => array('react', 'wp-api-fetch', 'wp-block-editor', 'wp-components', 'wp-data', 'wp-element', 'wp-hooks', 'wp-rich-text'), 'version' => 'ac234a5ba569d8e13376');
     1<?php return array('dependencies' => array('react', 'wp-api-fetch', 'wp-block-editor', 'wp-components', 'wp-data', 'wp-element', 'wp-hooks', 'wp-rich-text'), 'version' => '75d7006d3a754a269677');
  • kaigen/tags/v0.2.8/build/index.js

    r3428748 r3437262  
    1 (()=>{"use strict";var e={n:t=>{var a=t&&t.__esModule?()=>t.default:()=>t;return e.d(a,{a}),a},d:(t,a)=>{for(var n in a)e.o(a,n)&&!e.o(t,n)&&Object.defineProperty(t,n,{enumerable:!0,get:a[n]})},o:(e,t)=>Object.prototype.hasOwnProperty.call(e,t)};const t=window.React,a=window.wp.element,n=window.wp.components,r=async(e,t,a={})=>{try{const n=wp.data.select("core/editor")?.getEditorSettings()?.kaigen_provider;if(!n)throw new Error("No provider configured. Please check your plugin settings.");const r=wp.data.select("core/editor")?.getEditorSettings()?.kaigen_has_api_key;if(!r)throw new Error("No API key configured for the selected provider. Please add one in the KaiGen settings.");const l={prompt:e,provider:n};a.sourceImageUrls&&Array.isArray(a.sourceImageUrls)?l.source_image_urls=a.sourceImageUrls:a.sourceImageUrl&&(l.source_image_url=a.sourceImageUrl),a.sourceImageIds&&Array.isArray(a.sourceImageIds)&&(l.source_image_ids=a.sourceImageIds),a.additionalImageUrls&&Array.isArray(a.additionalImageUrls)&&(l.additional_image_urls=a.additionalImageUrls),a.maskUrl&&(l.mask_url=a.maskUrl),a.moderation&&["auto","low"].includes(a.moderation)&&(l.moderation=a.moderation),a.style&&["natural","vivid"].includes(a.style)&&(l.style=a.style),a.aspectRatio&&["1:1","16:9","9:16","4:3","3:4"].includes(a.aspectRatio)&&(l.aspect_ratio=a.aspectRatio),a.quality&&["low","medium","high"].includes(a.quality)&&(l.quality=a.quality);const i=wp.apiFetch({path:"/kaigen/v1/estimated-generation-time",method:"POST",data:l});"function"==typeof a.onEstimatedTime&&i.then(e=>{e&&"number"==typeof e.estimated_time_seconds&&a.onEstimatedTime(e.estimated_time_seconds)}).catch(()=>{});const o=await wp.apiFetch({path:"/kaigen/v1/generate-image",method:"POST",data:l});if(o.code&&o.message){if("content_moderation"===o.code)throw new Error(o.message);if("replicate_error"===o.code)throw new Error("Image generation failed: "+o.message);throw new Error(o.message)}if(!o||!o.url)throw new Error("Invalid response from server: "+JSON.stringify(o));o.id&&"number"==typeof o.id&&o.id>0?t({url:o.url,alt:e,id:o.id,caption:""}):t({url:o.url,alt:e,caption:""})}catch(e){t({error:e.message||"An unknown error occurred while generating the image"})}},l=(e,t)=>{const[n,r]=(0,a.useState)(0),l=(0,a.useRef)(3e4),i=(0,a.useRef)(null),o=(0,a.useRef)(0);return(0,a.useEffect)(()=>{l.current="number"==typeof t&&t>0?t:3e4},[t]),(0,a.useEffect)(()=>{if(!e)return r(0),i.current=null,void(o.current=0);i.current||(i.current=Date.now(),r(0),o.current=0);const t=setInterval(()=>{const e=Date.now()-i.current,t=Math.min(Math.floor(e/l.current*100),99),a=Math.max(t,o.current);o.current=a,r(a)},200);return()=>clearInterval(t)},[e]),n},i=window.kaiGen?.logoUrl,o=({isOpen:e,onClose:o,onSelect:c,initialReferenceImage:s})=>{const[m,d]=(0,a.useState)(""),[u,g]=(0,a.useState)(!1),[p,k]=(0,a.useState)(null),[E,b]=(0,a.useState)([]),[h,f]=(0,a.useState)([]),[_,w]=(0,a.useState)("1:1"),[y,v]=(0,a.useState)("medium"),[N,I]=(0,a.useState)(null),S=wp.data.select("core/editor")?.getEditorSettings()||{},G=S.kaigen_provider||"replicate",C=S.kaigen_quality||"medium",T="replicate"===G?10:16,x=l(u,N);(0,a.useEffect)(()=>{e&&((async()=>{try{const e=await wp.apiFetch({path:"/kaigen/v1/reference-images",method:"GET"});return Array.isArray(e)?e:[]}catch(e){return[]}})().then(b),v(C),s&&s.url?f([s]):f([]))},[e,s,C]);const A=()=>{if(!m.trim())return void k("Please enter a prompt for image generation.");g(!0),I(null),k(null);const e={};h.length>0&&(e.sourceImageUrls=h.map(e=>e.url),e.sourceImageIds=h.map(e=>e.id).filter(e=>Number.isInteger(e)&&e>0)),_&&(e.aspectRatio=_),y&&(e.quality=y),e.onEstimatedTime=e=>{"number"==typeof e&&I(1e3*e)},r(m.trim(),e=>{e.error?(k(e.error),g(!1)):(c(e),g(!1),B())},e)},B=()=>{d(""),k(null),f([]),v(C),I(null),o()};return e?(0,t.createElement)(n.Modal,{className:"kaigen-modal",title:(0,t.createElement)("div",{className:"kaigen-modal__logo-container"},(0,t.createElement)("img",{src:i,alt:"KaiGen logo",className:"kaigen-modal__logo"})),"aria-label":"KaiGen",onRequestClose:B},p&&(0,t.createElement)("p",{className:"kaigen-error-text"},p),(0,t.createElement)("div",{className:"kaigen-modal__input-container"},(0,t.createElement)(n.Dropdown,{popoverProps:{placement:"bottom-start",focusOnMount:!0},renderToggle:({isOpen:e,onToggle:a})=>(0,t.createElement)(n.Button,{className:"kaigen-modal__ref-button "+(h.length>0?"kaigen-ref-button-selected":""),onClick:a,"aria-expanded":e,"aria-label":"Reference Images"},(0,t.createElement)(n.Dashicon,{icon:"format-image",className:h.length>0?"kaigen-ref-button-icon-selected":""})),renderContent:()=>{const e=s?[s,...E.filter(e=>e.id!==s.id)]:E;return(0,t.createElement)("div",{className:"kaigen-modal-dropdown-content-container"},(0,t.createElement)("h4",{className:"kaigen-modal-dropdown-content-title"},"Reference Images (up to ",T,")"),e.length>0?(0,t.createElement)("div",{className:"kaigen-modal-reference-images-container"},e.map((e,a)=>(0,t.createElement)("button",{type:"button",key:e.id||`initial-${a}`,onClick:()=>{f(t=>t.some(t=>t.url===e.url)?t.filter(t=>t.url!==e.url):t.length<T?[...t,e]:t)},className:"kaigen-modal-reference-image "+(h.some(t=>t.url===e.url)?"kaigen-modal-reference-image-selected":""),"aria-label":e.alt||"Select reference image"},(0,t.createElement)("img",{src:e.thumbnail_url||e.url,alt:e.alt||""})))):(0,t.createElement)("p",{className:"kaigen-modal-no-references"},"No reference images. Mark images in the Media Library to use them here."))}}),(0,t.createElement)("div",{className:"kaigen-modal__textarea-container"},(0,t.createElement)(n.TextareaControl,{className:"kaigen-modal__textarea",placeholder:"Image prompt...",value:m,onChange:d,onKeyDown:e=>{"Enter"!==e.key||e.shiftKey||(e.preventDefault(),A())},rows:2})),m.trim()&&(0,t.createElement)(n.Button,{className:"kaigen-modal__submit-button",variant:"primary",onClick:A,disabled:u||!m.trim(),"aria-label":"Generate Image"},(0,t.createElement)(n.Dashicon,{icon:"admin-appearance"})),(0,t.createElement)(n.Dropdown,{popoverProps:{placement:"bottom-end",focusOnMount:!0},renderToggle:({isOpen:e,onToggle:a})=>(0,t.createElement)(n.Button,{className:"kaigen-modal__settings-button",onClick:a,"aria-expanded":e,"aria-label":"Settings"},(0,t.createElement)(n.Dashicon,{icon:"admin-generic"})),renderContent:()=>(0,t.createElement)("div",{className:"kaigen-modal-dropdown-content-container"},(0,t.createElement)("h4",{className:"kaigen-modal-dropdown-content-title"},"Aspect Ratio"),(0,t.createElement)("div",{className:"kaigen-modal-aspect-ratio-container"},[{value:"1:1",label:"1:1",title:"Square"},{value:"16:9",label:"16:9",title:"Landscape"},{value:"9:16",label:"9:16",title:"Portrait"}].map(e=>(0,t.createElement)("button",{type:"button",key:e.value,onClick:()=>w(t=>t===e.value?null:e.value),"aria-pressed":_===e.value,"aria-label":`${e.title} (${e.label})`,className:"kaigen-modal__aspect-ratio-button "+(_===e.value?"kaigen-modal__aspect-ratio-button-selected":"")},(0,t.createElement)("div",{className:"kaigen-modal-aspect-ratio-icon-container"},(0,t.createElement)("div",{className:`kaigen-modal-aspect-ratio-icon ${_===e.value?"kaigen-modal-aspect-ratio-icon-selected":""} kaigen-aspect-ratio-${e.value.replace(":","-")}`})),(0,t.createElement)("span",{className:"kaigen-modal-aspect-ratio-label"},e.label)))),(0,t.createElement)("h4",{className:"kaigen-modal-dropdown-content-title"},"Quality"),(0,t.createElement)("div",{className:"kaigen-modal-quality-container"},[{value:"low",label:"Low"},{value:"medium",label:"Medium"},{value:"high",label:"High"}].map(e=>(0,t.createElement)("button",{type:"button",key:e.value,onClick:()=>v(e.value),"aria-pressed":y===e.value,className:"kaigen-modal__quality-button "+(y===e.value?"kaigen-modal__quality-button-selected":"")},e.label))))})),u&&(0,t.createElement)("div",{className:"kaigen-modal__progress"},(0,t.createElement)("div",{className:"kaigen-modal__progress-label"},"Generating... ",x,"%"),(0,t.createElement)("div",{className:"kaigen-modal__progress-track",role:"progressbar","aria-valuenow":x,"aria-valuemin":0,"aria-valuemax":100,"aria-label":"Image generation progress"},(0,t.createElement)("div",{className:"kaigen-modal__progress-fill",style:{width:`${x}%`}})))):null},c=window.kaiGen?.logoUrl,s=({onSelect:e,shouldDisplay:r})=>{const[l,i]=(0,a.useState)(!1);return r?(0,t.createElement)(t.Fragment,null,(0,t.createElement)(n.Button,{onClick:()=>i(!0),className:"kaigen-placeholder-button","aria-label":"KaiGen"},(0,t.createElement)("img",{src:c,alt:"KaiGen",style:{width:"48px",height:"48px"}})),(0,t.createElement)("button",{type:"button",role:"menuitem",onClick:()=>i(!0),className:"components-button components-menu-item__button is-next-40px-default-size kaigen-menu-item-button"},(0,t.createElement)("span",{className:"components-menu-item__item"},"KaiGen"),(0,t.createElement)("img",{src:c,alt:"","aria-hidden":"true",className:"components-menu-items__item-icon has-icon-right",style:{width:"24px",height:"24px"}})),(0,t.createElement)(o,{isOpen:l,onClose:()=>i(!1),onSelect:e})):null},m=window.kaiGen?.logoUrl,d=({isGenerating:e,onGenerateImage:r,isRegenerating:i,onImageGenerated:c,isImageBlock:s,isTextSelected:d,currentImage:u,estimatedDurationMs:g})=>{const[p,k]=(0,a.useState)(!1),E=l(e||i,g),b=(0,t.createElement)("span",{className:"kaigen-progress-icon","aria-hidden":"true"},(0,t.createElement)("span",{className:"kaigen-progress-icon__track"},(0,t.createElement)("span",{className:"kaigen-progress-icon__fill",style:{width:`${E}%`}})));return s?(0,t.createElement)(t.Fragment,null,(0,t.createElement)(n.ToolbarGroup,null,(0,t.createElement)(n.ToolbarButton,{icon:i?b:(0,t.createElement)("img",{src:m,alt:"KaiGen logo",className:"kaigen-toolbar-icon"}),label:i?`KaiGen is generating... ${E}%`:"KaiGen",onClick:()=>k(!0),disabled:i})),(0,t.createElement)(o,{isOpen:p,onClose:()=>k(!1),onSelect:c,initialReferenceImage:u})):d?(0,t.createElement)(n.ToolbarGroup,null,(0,t.createElement)(n.ToolbarButton,{icon:e?b:"format-image",label:e?`KaiGen is generating... ${E}%`:"KaiGen",onClick:r,disabled:e})):null},u=window.wp.blockEditor,g=window.wp.data,p=window.wp.richText,k=({value:e})=>{const[n,l]=(0,a.useState)(!1),[i,o]=(0,a.useState)(null),c=(0,g.useSelect)(e=>e("core/block-editor").getSelectedBlock(),[]),{replaceBlocks:s}=(0,g.useDispatch)("core/block-editor"),m=(0,a.useCallback)(()=>{if(c&&"core/paragraph"===c.name){const t=e.text.slice(e.start,e.end).trim();if(!t)return void wp.data.dispatch("core/notices").createErrorNotice("Please select some text to use as the image generation prompt.",{type:"snackbar"});const a=wp.data.select("core/editor")?.getEditorSettings()?.kaigen_provider;if(!a)return void wp.data.dispatch("core/notices").createErrorNotice("No AI provider configured. Please set one in the plugin settings.",{type:"snackbar"});const n=wp.blocks.createBlock("core/heading",{content:"Generating AI image...",level:2,className:"kaigen-text-center"});s(c.clientId,[n,c]),l(!0),o(null),r(t,e=>{if(l(!1),o(null),e.error)wp.data.dispatch("core/notices").createErrorNotice("Failed to generate image: "+e.error,{type:"snackbar"}),s(n.clientId,[]);else{const t={url:e.url,alt:e.alt,caption:""};e.id&&"number"==typeof e.id&&e.id>0&&(t.id=e.id);const a=wp.blocks.createBlock("core/image",t);s(n.clientId,[a])}},{onEstimatedTime:e=>{"number"==typeof e&&o(1e3*e)}})}},[c,e.text,e.start,e.end,s]),p=""!==e.text.slice(e.start,e.end).trim();return(0,t.createElement)(u.BlockControls,null,(0,t.createElement)(d,{isGenerating:n,onGenerateImage:m,isTextSelected:p,estimatedDurationMs:i}))};(0,p.registerFormatType)("kaigen/custom-format",{title:"AI Image Gen",tagName:"span",className:"kaigen-format",edit:({value:e})=>(0,t.createElement)(k,{value:e})});const E=window.wp.hooks;(0,E.addFilter)("editor.MediaUpload","kaigen/add-ai-tab",e=>a=>{const n=a.allowedTypes&&a.allowedTypes.includes("image")&&!a.multiple,r=wp.data.select("core/block-editor").getSelectedBlock(),l=r&&"core/image"===r.name,i=wp.data.select("core/editor")?.getEditorSettings()?.kaigen_has_api_key,o=n&&l&&!(r&&r.attributes&&r.attributes.url)&&i;return(0,t.createElement)(e,{...a,render:e=>(0,t.createElement)(t.Fragment,null,a.render(e),(0,t.createElement)(s,{onSelect:a.onSelect,shouldDisplay:o}))})});const b=window.wp.apiFetch;var h=e.n(b);(0,E.addFilter)("editor.BlockEdit","kaigen/add-regenerate-button",e=>r=>{if("core/image"!==r.name)return(0,t.createElement)(e,{...r});const l=r.attributes.id&&"number"==typeof r.attributes.id&&r.attributes.id>0,[i,o]=(0,a.useState)(!1),[c,s]=(0,a.useState)(null),[m,g]=(0,a.useState)(!1),[p,k]=(0,a.useState)([]),[E,b]=(0,a.useState)(!1),[f,_]=(0,a.useState)(null),{attributes:{id:w,kaigen_reference_image:y},setAttributes:v}=r;(0,a.useEffect)(()=>{l&&!i&&(null==y?h()({path:`/wp/v2/media/${w}`}).then(e=>{if(e&&e.meta&&void 0!==e.meta.kaigen_reference_image){const t=!0===e.meta.kaigen_reference_image||1===e.meta.kaigen_reference_image;v({kaigen_reference_image:t})}o(!0)}).catch(()=>{o(!0)}):o(!0))},[l,w,y,i,v]),(0,a.useEffect)(()=>{w&&(s(null),k([]),_(null))},[w]),(0,a.useEffect)(()=>{E&&w&&f!==w&&(g(!0),h()({path:`/kaigen/v1/generation-meta?attachment_id=${w}`}).then(e=>{s(e&&Object.keys(e).length?e:null)}).catch(()=>{s(null)}).finally(()=>{g(!1),_(w)}))},[E,w,f]),(0,a.useEffect)(()=>{if(!(E&&c&&Array.isArray(c.reference_image_ids)&&c.reference_image_ids.length))return void k([]);const e=c.reference_image_ids.join(",");h()({path:`/wp/v2/media?include=${e}&per_page=${c.reference_image_ids.length}`}).then(e=>{const t=Array.isArray(e)?e.map(e=>({id:e.id,url:e.media_details?.sizes?.thumbnail?.source_url||e.source_url})).filter(e=>e.url):[];k(t)}).catch(()=>{k([])})},[E,c]);const N=r.attributes.url?{url:r.attributes.url,id:r.attributes.id,alt:r.attributes.alt||""}:null;return(0,t.createElement)(t.Fragment,null,(0,t.createElement)(e,{...r}),r.attributes.url&&(0,t.createElement)(u.BlockControls,null,(0,t.createElement)(d,{onImageGenerated:e=>{e.id&&"number"==typeof e.id&&e.id>0?r.setAttributes({url:e.url,id:e.id}):r.setAttributes({url:e.url,id:void 0}),wp.data.dispatch("core/notices").createSuccessNotice("Image generated successfully!",{type:"snackbar"})},isImageBlock:!0,currentImage:N})),l&&(0,t.createElement)(u.InspectorControls,null,(0,t.createElement)(n.PanelBody,{title:"KaiGen Settings",initialOpen:!1,onToggle:e=>{b(e)}},(0,t.createElement)(n.CheckboxControl,{label:"Reference image",checked:!0===r.attributes.kaigen_reference_image,onChange:async e=>{const t=!0===e;r.setAttributes({kaigen_reference_image:t}),o(!0);try{await h()({path:`/wp/v2/media/${r.attributes.id}`,method:"POST",data:{meta:{kaigen_reference_image:t?1:0}}})}catch(e){wp.data.dispatch("core/notices").createErrorNotice("Failed to update reference image meta",{type:"snackbar"})}},help:"Add to the list of reference images."}),m&&(0,t.createElement)("p",{className:"kaigen-generation-meta-loading"},"Loading generation details..."),!m&&c&&(0,t.createElement)("table",{className:"kaigen-generation-meta-table"},(0,t.createElement)("tbody",null,(0,t.createElement)("tr",null,(0,t.createElement)("th",null,"Prompt"),(0,t.createElement)("td",null,c.prompt)),(0,t.createElement)("tr",null,(0,t.createElement)("th",null,"Provider"),(0,t.createElement)("td",null,c.provider)),(0,t.createElement)("tr",null,(0,t.createElement)("th",null,"Quality"),(0,t.createElement)("td",null,c.quality)),(0,t.createElement)("tr",null,(0,t.createElement)("th",null,"Model"),(0,t.createElement)("td",null,c.model)),p.length>0&&(0,t.createElement)("tr",null,(0,t.createElement)("th",null,"References"),(0,t.createElement)("td",null,(0,t.createElement)("div",{className:"kaigen-generation-meta-images"},p.map(e=>(0,t.createElement)("img",{key:e.id,src:e.url,alt:"",className:"kaigen-generation-meta-image"}))))))))))}),(0,E.addFilter)("blocks.registerBlockType","kaigen/add-reference-image-attribute",(e,t)=>"core/image"!==t?e:{...e,attributes:{...e.attributes,kaigen_reference_image:{type:"boolean",default:!1}}})})();
     1(()=>{"use strict";var e={n:t=>{var a=t&&t.__esModule?()=>t.default:()=>t;return e.d(a,{a}),a},d:(t,a)=>{for(var r in a)e.o(a,r)&&!e.o(t,r)&&Object.defineProperty(t,r,{enumerable:!0,get:a[r]})},o:(e,t)=>Object.prototype.hasOwnProperty.call(e,t)};const t=window.React,a=window.wp.element,r=window.wp.components,n=async(e,t,a={})=>{try{const r=wp.data.select("core/editor")?.getEditorSettings()?.kaigen_provider;if(!r)throw new Error("No provider configured. Please check your plugin settings.");const n=wp.data.select("core/editor")?.getEditorSettings()?.kaigen_has_api_key;if(!n)throw new Error("No API key configured for the selected provider. Please add one in the KaiGen settings.");const l={prompt:e,provider:r};a.sourceImageUrls&&Array.isArray(a.sourceImageUrls)?l.source_image_urls=a.sourceImageUrls:a.sourceImageUrl&&(l.source_image_url=a.sourceImageUrl),a.sourceImageIds&&Array.isArray(a.sourceImageIds)&&(l.source_image_ids=a.sourceImageIds),a.additionalImageUrls&&Array.isArray(a.additionalImageUrls)&&(l.additional_image_urls=a.additionalImageUrls),a.maskUrl&&(l.mask_url=a.maskUrl),a.moderation&&["auto","low"].includes(a.moderation)&&(l.moderation=a.moderation),a.style&&["natural","vivid"].includes(a.style)&&(l.style=a.style),a.aspectRatio&&["1:1","16:9","9:16","4:3","3:4"].includes(a.aspectRatio)&&(l.aspect_ratio=a.aspectRatio),a.quality&&["low","medium","high"].includes(a.quality)&&(l.quality=a.quality);const i=wp.apiFetch({path:"/kaigen/v1/estimated-generation-time",method:"POST",data:l});"function"==typeof a.onEstimatedTime&&i.then(e=>{e&&"number"==typeof e.estimated_time_seconds&&a.onEstimatedTime(e.estimated_time_seconds)}).catch(()=>{});const o=await wp.apiFetch({path:"/kaigen/v1/generate-image",method:"POST",data:l});if(o.code&&o.message){if("content_moderation"===o.code)throw new Error(o.message);if("replicate_error"===o.code)throw new Error("Image generation failed: "+o.message);throw new Error(o.message)}if(!o||!o.url)throw new Error("Invalid response from server: "+JSON.stringify(o));o.id&&"number"==typeof o.id&&o.id>0?t({url:o.url,alt:e,id:o.id,caption:""}):t({url:o.url,alt:e,caption:""})}catch(e){t({error:e.message||"An unknown error occurred while generating the image"})}},l=(e,t)=>{const[r,n]=(0,a.useState)(0),l=(0,a.useRef)(3e4),i=(0,a.useRef)(null),o=(0,a.useRef)(0);return(0,a.useEffect)(()=>{l.current="number"==typeof t&&t>0?t:3e4},[t]),(0,a.useEffect)(()=>{if(!e)return n(0),i.current=null,void(o.current=0);i.current||(i.current=Date.now(),n(0),o.current=0);const t=setInterval(()=>{const e=Date.now()-i.current,t=Math.min(Math.floor(e/l.current*100),99),a=Math.max(t,o.current);o.current=a,n(a)},200);return()=>clearInterval(t)},[e]),r},i=window.kaiGen?.logoUrl,o=({isOpen:e,onClose:o,onSelect:c,initialReferenceImage:s})=>{const[m,d]=(0,a.useState)(""),[u,g]=(0,a.useState)(!1),[p,k]=(0,a.useState)(null),[h,E]=(0,a.useState)([]),[b,f]=(0,a.useState)([]),[_,w]=(0,a.useState)("1:1"),[y,v]=(0,a.useState)("medium"),[N,I]=(0,a.useState)(null),S=wp.data.select("core/editor")?.getEditorSettings()||{},x=S.kaigen_provider||"replicate",G=S.kaigen_quality||"medium",T="replicate"===x?10:16,A=l(u,N);(0,a.useEffect)(()=>{e&&((async()=>{try{const e=await wp.apiFetch({path:"/kaigen/v1/reference-images",method:"GET"});return Array.isArray(e)?e:[]}catch(e){return[]}})().then(E),v(G),s&&s.url?f([s]):f([]))},[e,s,G]);const C=()=>{if(!m.trim())return void k("Please enter a prompt for image generation.");g(!0),I(null),k(null);const e={};b.length>0&&(e.sourceImageUrls=b.map(e=>e.url),e.sourceImageIds=b.map(e=>e.id).filter(e=>Number.isInteger(e)&&e>0)),_&&(e.aspectRatio=_),y&&(e.quality=y),e.onEstimatedTime=e=>{"number"==typeof e&&I(1e3*e)},n(m.trim(),e=>{e.error?(k(e.error),g(!1)):(c(e),g(!1),B())},e)},B=()=>{d(""),k(null),f([]),v(G),I(null),o()};return e?(0,t.createElement)(r.Modal,{className:"kaigen-modal",title:(0,t.createElement)("div",{className:"kaigen-modal__logo-container"},(0,t.createElement)("img",{src:i,alt:"KaiGen logo",className:"kaigen-modal__logo"})),"aria-label":"KaiGen",onRequestClose:B},p&&(0,t.createElement)("p",{className:"kaigen-error-text"},p),(0,t.createElement)("div",{className:"kaigen-modal__input-container"},(0,t.createElement)(r.Dropdown,{popoverProps:{placement:"bottom-start",focusOnMount:!0},renderToggle:({isOpen:e,onToggle:a})=>(0,t.createElement)(r.Button,{className:"kaigen-modal__ref-button "+(b.length>0?"kaigen-ref-button-selected":""),onClick:a,"aria-expanded":e,"aria-label":"Reference Images"},(0,t.createElement)(r.Dashicon,{icon:"format-image",className:b.length>0?"kaigen-ref-button-icon-selected":""})),renderContent:()=>{const e=s?[s,...h.filter(e=>e.id!==s.id)]:h;return(0,t.createElement)("div",{className:"kaigen-modal-dropdown-content-container"},(0,t.createElement)("h4",{className:"kaigen-modal-dropdown-content-title"},"Reference Images (up to ",T,")"),e.length>0?(0,t.createElement)("div",{className:"kaigen-modal-reference-images-container"},e.map((e,a)=>(0,t.createElement)("button",{type:"button",key:e.id||`initial-${a}`,onClick:()=>{f(t=>t.some(t=>t.url===e.url)?t.filter(t=>t.url!==e.url):t.length<T?[...t,e]:t)},className:"kaigen-modal-reference-image "+(b.some(t=>t.url===e.url)?"kaigen-modal-reference-image-selected":""),"aria-label":e.alt||"Select reference image"},(0,t.createElement)("img",{src:e.thumbnail_url||e.url,alt:e.alt||""})))):(0,t.createElement)("p",{className:"kaigen-modal-no-references"},"No reference images. Mark images in the Media Library to use them here."))}}),(0,t.createElement)("div",{className:"kaigen-modal__textarea-container"},(0,t.createElement)(r.TextareaControl,{className:"kaigen-modal__textarea",placeholder:"Image prompt...",value:m,onChange:d,onKeyDown:e=>{"Enter"!==e.key||e.shiftKey||(e.preventDefault(),C())},rows:2})),m.trim()&&(0,t.createElement)(r.Button,{className:"kaigen-modal__submit-button",variant:"primary",onClick:C,disabled:u||!m.trim(),"aria-label":"Generate Image"},(0,t.createElement)(r.Dashicon,{icon:"admin-appearance"})),(0,t.createElement)(r.Dropdown,{popoverProps:{placement:"bottom-end",focusOnMount:!0},renderToggle:({isOpen:e,onToggle:a})=>(0,t.createElement)(r.Button,{className:"kaigen-modal__settings-button",onClick:a,"aria-expanded":e,"aria-label":"Settings"},(0,t.createElement)(r.Dashicon,{icon:"admin-generic"})),renderContent:()=>(0,t.createElement)("div",{className:"kaigen-modal-dropdown-content-container"},(0,t.createElement)("h4",{className:"kaigen-modal-dropdown-content-title"},"Aspect Ratio"),(0,t.createElement)("div",{className:"kaigen-modal-aspect-ratio-container"},[{value:"1:1",label:"1:1",title:"Square"},{value:"16:9",label:"16:9",title:"Landscape"},{value:"9:16",label:"9:16",title:"Portrait"}].map(e=>(0,t.createElement)("button",{type:"button",key:e.value,onClick:()=>w(t=>t===e.value?null:e.value),"aria-pressed":_===e.value,"aria-label":`${e.title} (${e.label})`,className:"kaigen-modal__aspect-ratio-button "+(_===e.value?"kaigen-modal__aspect-ratio-button-selected":"")},(0,t.createElement)("div",{className:"kaigen-modal-aspect-ratio-icon-container"},(0,t.createElement)("div",{className:`kaigen-modal-aspect-ratio-icon ${_===e.value?"kaigen-modal-aspect-ratio-icon-selected":""} kaigen-aspect-ratio-${e.value.replace(":","-")}`})),(0,t.createElement)("span",{className:"kaigen-modal-aspect-ratio-label"},e.label)))),(0,t.createElement)("h4",{className:"kaigen-modal-dropdown-content-title"},"Quality"),(0,t.createElement)("div",{className:"kaigen-modal-quality-container"},[{value:"low",label:"Low"},{value:"medium",label:"Medium"},{value:"high",label:"High"}].map(e=>(0,t.createElement)("button",{type:"button",key:e.value,onClick:()=>v(e.value),"aria-pressed":y===e.value,className:"kaigen-modal__quality-button "+(y===e.value?"kaigen-modal__quality-button-selected":"")},e.label))))})),u&&(0,t.createElement)("div",{className:"kaigen-modal__progress"},(0,t.createElement)("div",{className:"kaigen-modal__progress-label"},"Generating... ",A,"%"),(0,t.createElement)("div",{className:"kaigen-modal__progress-track",role:"progressbar","aria-valuenow":A,"aria-valuemin":0,"aria-valuemax":100,"aria-label":"Image generation progress"},(0,t.createElement)("div",{className:"kaigen-modal__progress-fill",style:{width:`${A}%`}})))):null},c=window.kaiGen?.logoUrl,s=({onSelect:e,shouldDisplay:n})=>{const[l,i]=(0,a.useState)(!1);return n?(0,t.createElement)(t.Fragment,null,(0,t.createElement)(r.Button,{onClick:()=>i(!0),className:"kaigen-placeholder-button","aria-label":"KaiGen"},(0,t.createElement)("img",{src:c,alt:"KaiGen",style:{width:"48px",height:"48px"}})),(0,t.createElement)("button",{type:"button",role:"menuitem",onClick:()=>i(!0),className:"components-button components-menu-item__button is-next-40px-default-size kaigen-menu-item-button"},(0,t.createElement)("span",{className:"components-menu-item__item"},"KaiGen"),(0,t.createElement)("img",{src:c,alt:"","aria-hidden":"true",className:"components-menu-items__item-icon has-icon-right",style:{width:"24px",height:"24px"}})),(0,t.createElement)(o,{isOpen:l,onClose:()=>i(!1),onSelect:e})):null},m=window.kaiGen?.logoUrl,d=({isGenerating:e,onGenerateImage:n,isRegenerating:i,onImageGenerated:c,isImageBlock:s,isTextSelected:d,currentImage:u,estimatedDurationMs:g})=>{const[p,k]=(0,a.useState)(!1),h=l(e||i,g),E=(0,t.createElement)("span",{className:"kaigen-progress-icon","aria-hidden":"true"},(0,t.createElement)("span",{className:"kaigen-progress-icon__track"},(0,t.createElement)("span",{className:"kaigen-progress-icon__fill",style:{width:`${h}%`}})));return s?(0,t.createElement)(t.Fragment,null,(0,t.createElement)(r.ToolbarGroup,null,(0,t.createElement)(r.ToolbarButton,{icon:i?E:(0,t.createElement)("img",{src:m,alt:"KaiGen logo",className:"kaigen-toolbar-icon"}),label:i?`KaiGen is generating... ${h}%`:"KaiGen",onClick:()=>k(!0),disabled:i})),(0,t.createElement)(o,{isOpen:p,onClose:()=>k(!1),onSelect:c,initialReferenceImage:u})):d?(0,t.createElement)(r.ToolbarGroup,null,(0,t.createElement)(r.ToolbarButton,{icon:e?E:"format-image",label:e?`KaiGen is generating... ${h}%`:"KaiGen",onClick:n,disabled:e})):null},u=window.wp.blockEditor,g=window.wp.data,p=window.wp.richText,k=({value:e})=>{const[r,l]=(0,a.useState)(!1),[i,o]=(0,a.useState)(null),c=(0,g.useSelect)(e=>e("core/block-editor").getSelectedBlock(),[]),{replaceBlocks:s}=(0,g.useDispatch)("core/block-editor"),m=(0,a.useCallback)(()=>{if(c&&"core/paragraph"===c.name){const t=e.text.slice(e.start,e.end).trim();if(!t)return void wp.data.dispatch("core/notices").createErrorNotice("Please select some text to use as the image generation prompt.",{type:"snackbar"});const a=wp.data.select("core/editor")?.getEditorSettings()?.kaigen_provider;if(!a)return void wp.data.dispatch("core/notices").createErrorNotice("No AI provider configured. Please set one in the plugin settings.",{type:"snackbar"});const r=wp.blocks.createBlock("core/heading",{content:"Generating AI image...",level:2,className:"kaigen-text-center"});s(c.clientId,[r,c]),l(!0),o(null),n(t,e=>{if(l(!1),o(null),e.error)wp.data.dispatch("core/notices").createErrorNotice("Failed to generate image: "+e.error,{type:"snackbar"}),s(r.clientId,[]);else{const t={url:e.url,alt:e.alt,caption:""};e.id&&"number"==typeof e.id&&e.id>0&&(t.id=e.id);const a=wp.blocks.createBlock("core/image",t);s(r.clientId,[a])}},{onEstimatedTime:e=>{"number"==typeof e&&o(1e3*e)}})}},[c,e.text,e.start,e.end,s]),p=""!==e.text.slice(e.start,e.end).trim();return(0,t.createElement)(u.BlockControls,null,(0,t.createElement)(d,{isGenerating:r,onGenerateImage:m,isTextSelected:p,estimatedDurationMs:i}))};(0,p.registerFormatType)("kaigen/custom-format",{title:"AI Image Gen",tagName:"span",className:"kaigen-format",edit:({value:e})=>(0,t.createElement)(k,{value:e})});const h=window.wp.hooks;(0,h.addFilter)("editor.MediaUpload","kaigen/add-ai-tab",e=>a=>{const r=a.allowedTypes&&a.allowedTypes.includes("image")&&!a.multiple,n=wp.data.select("core/block-editor").getSelectedBlock(),l=n&&"core/image"===n.name,i=wp.data.select("core/editor")?.getEditorSettings()?.kaigen_has_api_key,o=r&&l&&!(n&&n.attributes&&n.attributes.url)&&i;return(0,t.createElement)(e,{...a,render:e=>(0,t.createElement)(t.Fragment,null,a.render(e),(0,t.createElement)(s,{onSelect:a.onSelect,shouldDisplay:o}))})});const E=window.wp.apiFetch;var b=e.n(E);(0,h.addFilter)("editor.BlockEdit","kaigen/add-regenerate-button",e=>n=>{if("core/image"!==n.name)return(0,t.createElement)(e,{...n});const l=Number(n.attributes.id),i=Number.isInteger(l)&&l>0,[o,c]=(0,a.useState)(!1),[s,m]=(0,a.useState)(null),[g,p]=(0,a.useState)(!1),[k,h]=(0,a.useState)([]),[E,f]=(0,a.useState)(!1),[_,w]=(0,a.useState)(!1),[y,v]=(0,a.useState)(null),{attributes:{kaigen_reference_image:N},setAttributes:I}=n;(0,a.useEffect)(()=>{i&&!o&&(null==N?b()({path:`/wp/v2/media/${l}`}).then(e=>{if(e&&e.meta&&void 0!==e.meta.kaigen_reference_image){const t=!0===e.meta.kaigen_reference_image||1===e.meta.kaigen_reference_image;I({kaigen_reference_image:t})}c(!0)}).catch(()=>{c(!0)}):c(!0))},[i,l,N,o,I]),(0,a.useEffect)(()=>{l&&(n.attributes.id!==l&&I({id:l}),m(null),h([]),v(null))},[l,n.attributes.id,I]),(0,a.useEffect)(()=>{E&&l&&y!==l&&(p(!0),b()({path:`/kaigen/v1/generation-meta?attachment_id=${l}`}).then(e=>{m(e&&Object.keys(e).length?e:null)}).catch(()=>{m(null)}).finally(()=>{p(!1),v(l)}))},[E,l,y]),(0,a.useEffect)(()=>{if(!(E&&s&&Array.isArray(s.reference_image_ids)&&s.reference_image_ids.length))return void h([]);const e=s.reference_image_ids.join(",");b()({path:`/wp/v2/media?include=${e}&per_page=${s.reference_image_ids.length}`}).then(e=>{const t=Array.isArray(e)?e.map(e=>({id:e.id,url:e.media_details?.sizes?.thumbnail?.source_url||e.source_url})).filter(e=>e.url):[];h(t)}).catch(()=>{h([])})},[E,s]);const S=n.attributes.url?{url:n.attributes.url,id:n.attributes.id,alt:n.attributes.alt||""}:null;return(0,t.createElement)(t.Fragment,null,(0,t.createElement)(e,{...n}),n.attributes.url&&(0,t.createElement)(u.BlockControls,null,(0,t.createElement)(d,{onImageGenerated:e=>{e.id&&"number"==typeof e.id&&e.id>0?n.setAttributes({url:e.url,id:e.id}):n.setAttributes({url:e.url,id:void 0}),wp.data.dispatch("core/notices").createSuccessNotice("Image generated successfully!",{type:"snackbar"})},isImageBlock:!0,currentImage:S})),i&&(0,t.createElement)(u.InspectorControls,null,(0,t.createElement)(r.PanelBody,{title:"KaiGen",initialOpen:!1,onToggle:e=>{f(e)}},(0,t.createElement)(r.CheckboxControl,{label:"Reference image",checked:!0===n.attributes.kaigen_reference_image,onChange:async e=>{const t=!0===e;n.setAttributes({kaigen_reference_image:t}),c(!0);try{await b()({path:`/wp/v2/media/${l}`,method:"POST",data:{meta:{kaigen_reference_image:t?1:0}}})}catch(e){wp.data.dispatch("core/notices").createErrorNotice("Failed to update reference image meta",{type:"snackbar"})}},help:"Add to the list of reference images."}),(0,t.createElement)(r.Button,{variant:"secondary",isBusy:_,disabled:_,style:{marginTop:"8px"},onClick:async()=>{const e=wp.data.select("core/editor")?.getEditorSettings()?.kaigen_provider||"";if(!e)return void wp.data.dispatch("core/notices").createErrorNotice("No AI provider configured. Please set one in the plugin settings.",{type:"snackbar"});const t=s?.prompt_text?s.prompt_text:"string"==typeof s?.prompt?s.prompt:n.attributes.alt||"";if(!t.trim()&&!l)return void wp.data.dispatch("core/notices").createErrorNotice("No prompt or image available to generate alt text.",{type:"snackbar"});let a=null;if(s&&"string"==typeof s.prompt)try{const e=JSON.parse(s.prompt);e&&"object"==typeof e&&(a=e)}catch(e){a=null}w(!0);try{const r=await(async(e,t,a=null,r=null)=>{try{const n={prompt:e,provider:t};a&&(n.prompt_structured=a),r&&(n.attachment_id=r);const l=await wp.apiFetch({path:"/kaigen/v1/generate-alt-text",method:"POST",data:n});if(l&&l.code&&l.message)throw new Error(l.message);return l}catch(e){throw new Error(e.message||"An unknown error occurred while generating alt text")}})(t.trim(),e,a,l),n=r?.alt_text||"";if(!n)throw new Error("Alt text response was empty.");I({alt:n}),await b()({path:`/wp/v2/media/${l}`,method:"POST",data:{alt_text:n}}),wp.data.dispatch("core/notices").createSuccessNotice("Alt text generated.",{type:"snackbar"})}catch(e){wp.data.dispatch("core/notices").createErrorNotice("Failed to generate alt text.",{type:"snackbar"})}finally{w(!1)}}},"Generate Alt Text"),(0,t.createElement)("p",{className:"components-base-control__help"},"Generate a descriptive alt text suggestion for this image."),g&&(0,t.createElement)("p",{className:"kaigen-generation-meta-loading"},"Loading generation details..."),!g&&s&&(0,t.createElement)("table",{className:"kaigen-generation-meta-table"},(0,t.createElement)("tbody",null,(0,t.createElement)("tr",null,(0,t.createElement)("th",null,"Prompt"),(0,t.createElement)("td",null,s.prompt)),(0,t.createElement)("tr",null,(0,t.createElement)("th",null,"Provider"),(0,t.createElement)("td",null,s.provider)),(0,t.createElement)("tr",null,(0,t.createElement)("th",null,"Quality"),(0,t.createElement)("td",null,s.quality)),(0,t.createElement)("tr",null,(0,t.createElement)("th",null,"Model"),(0,t.createElement)("td",null,s.model)),k.length>0&&(0,t.createElement)("tr",null,(0,t.createElement)("th",null,"References"),(0,t.createElement)("td",null,(0,t.createElement)("div",{className:"kaigen-generation-meta-images"},k.map(e=>(0,t.createElement)("img",{key:e.id,src:e.url,alt:"",className:"kaigen-generation-meta-image"}))))))))))}),(0,h.addFilter)("blocks.registerBlockType","kaigen/add-reference-image-attribute",(e,t)=>"core/image"!==t?e:{...e,attributes:{...e.attributes,kaigen_reference_image:{type:"boolean",default:!1}}})})();
  • kaigen/tags/v0.2.8/inc/class-admin.php

    r3428748 r3437262  
    7373            'block_editor_settings_all',
    7474            function ( $settings ) {
    75                 $provider = get_option( 'kaigen_provider', '' );
     75                $provider = kaigen_provider_manager()->get_active_provider_id();
    7676                $api_keys = get_option( 'kaigen_provider_api_keys', [] );
    77 
    78                 // If no provider is set but we have active providers, use OpenAI if available, otherwise use the first active provider.
    79                 if ( empty( $provider ) ) {
    80                     $active_providers = $this->get_active_providers(); // Get fresh list.
    81                     if ( ! empty( $active_providers ) ) {
    82 
    83                         if ( isset( $api_keys['openai'] ) && ! empty( $api_keys['openai'] ) ) {
    84                             $provider = 'openai';
    85                         } else {
    86                             $provider = $active_providers[0];
    87                         }
    88                     }
    89                 }
    9077
    9178                // Ensure settings is an array.
     
    475462        if ( in_array( $hook, [ 'post.php', 'post-new.php' ], true ) ) {
    476463            // Get the provider setting.
    477             $provider = get_option( 'kaigen_provider', '' );
    478 
    479             // If no provider is set but we have active providers, use OpenAI if available, otherwise use the first active provider.
    480             if ( empty( $provider ) ) {
    481                 $active_providers = $this->get_active_providers(); // Get fresh list instead of cached.
    482                 if ( ! empty( $active_providers ) ) {
    483                     $api_keys = get_option( 'kaigen_provider_api_keys', [] );
    484                     if ( isset( $api_keys['openai'] ) && ! empty( $api_keys['openai'] ) ) {
    485                         $provider = 'openai';
    486                     } else {
    487                         $provider = $active_providers[0];
    488                     }
    489                 }
    490             }
     464            $provider = kaigen_provider_manager()->get_active_provider_id();
    491465
    492466            // Enqueue admin CSS for block editor.
  • kaigen/tags/v0.2.8/inc/class-image-handler.php

    r3424397 r3437262  
    5656        $prompt_slug = substr( $prompt_slug, 0, 50 ); // Limit length.
    5757
     58        $mime_type  = '';
     59        $image_info = getimagesizefromstring( $image_data );
     60        if ( ! empty( $image_info['mime'] ) ) {
     61            $mime_type = $image_info['mime'];
     62        }
     63
     64        // Match the filename extension to the actual image data for reliable attachments (e.g., E2E base64 PNGs).
     65        $extension_map = [
     66            'image/jpeg' => 'jpg',
     67            'image/png'  => 'png',
     68            'image/webp' => 'webp',
     69        ];
     70        $extension     = $extension_map[ $mime_type ] ?? 'webp';
     71
    5872        // Generate a filename with prompt and unique ID.
    59         $filename = 'ai-' . $prompt_slug . '-' . uniqid() . '.webp';
     73        $filename = 'ai-' . $prompt_slug . '-' . uniqid() . '.' . $extension;
    6074
    6175        $upload = wp_upload_bits( $filename, null, $image_data );
  • kaigen/tags/v0.2.8/inc/class-provider-manager.php

    r3424397 r3437262  
    3737     */
    3838    private static $providers_loaded = false;
     39
     40    /**
     41     * Cached alt text generator class name.
     42     *
     43     * @var string|null
     44     */
     45    private static $alt_text_generator_class = null;
    3946
    4047    /**
     
    132139
    133140    /**
     141     * Gets the active provider ID based on settings and stored keys.
     142     *
     143     * @return string Active provider ID or empty string if none configured.
     144     */
     145    public function get_active_provider_id() {
     146        $provider = get_option( 'kaigen_provider', '' );
     147        if ( '' !== $provider ) {
     148            return strtolower( trim( (string) $provider ) );
     149        }
     150
     151        $api_keys = get_option( 'kaigen_provider_api_keys', [] );
     152        if ( ! empty( $api_keys['openai'] ) ) {
     153            return 'openai';
     154        }
     155
     156        foreach ( $api_keys as $provider_id => $api_key ) {
     157            if ( ! empty( $api_key ) ) {
     158                return strtolower( trim( (string) $provider_id ) );
     159            }
     160        }
     161
     162        return '';
     163    }
     164
     165    /**
     166     * Gets the loaded alt text generator class for the active provider.
     167     *
     168     * @return string Loaded generator class name or empty string.
     169     */
     170    public function get_active_alt_text_generator_class() {
     171        $this->load_active_alt_text_generator();
     172
     173        $active = $this->get_active_provider_id();
     174        if ( '' === $active ) {
     175            return '';
     176        }
     177
     178        if ( null !== self::$alt_text_generator_class ) {
     179            return self::$alt_text_generator_class;
     180        }
     181
     182        foreach ( get_declared_classes() as $class ) {
     183            if ( 0 !== strpos( $class, __NAMESPACE__ . '\\' ) ) {
     184                continue;
     185            }
     186
     187            $implements = class_implements( $class );
     188            if ( empty( $implements ) ) {
     189                continue;
     190            }
     191
     192            if ( in_array( Alt_Text_Generator_Core::class, $implements, true ) ) {
     193                self::$alt_text_generator_class = $class;
     194                return $class;
     195            }
     196        }
     197
     198        self::$alt_text_generator_class = '';
     199        return '';
     200    }
     201
     202    /**
     203     * Loads the active alt text generator class file when available.
     204     *
     205     * @return void
     206     */
     207    private function load_active_alt_text_generator() {
     208        $provider = $this->get_active_provider_id();
     209        if ( '' === $provider ) {
     210            return;
     211        }
     212
     213        $provider = sanitize_key( $provider );
     214        $base_dir = plugin_dir_path( __DIR__ ) . 'inc/alt-text/';
     215
     216        require_once $base_dir . 'trait-alt-text-generator-helpers.php';
     217
     218        $class_file = $base_dir . 'class-alt-text-generator-' . $provider . '.php';
     219        if ( file_exists( $class_file ) ) {
     220            require_once $class_file;
     221        }
     222    }
     223
     224    /**
    134225     * Formats a provider name from filename format to class name format.
    135226     *
  • kaigen/tags/v0.2.8/inc/class-rest-api.php

    r3428748 r3437262  
    123123            ]
    124124        );
     125
     126        // Register the alt text generation endpoint.
     127        register_rest_route(
     128            self::API_NAMESPACE,
     129            '/generate-alt-text',
     130            [
     131                'methods'             => 'POST',
     132                'callback'            => [ $this, 'handle_generate_alt_text_request' ],
     133                'permission_callback' => [ $this, 'check_permission' ],
     134            ]
     135        );
    125136    }
    126137
     
    231242
    232243        return new \WP_REST_Response( $meta, 200 );
     244    }
     245
     246    /**
     247     * Generates alt text for an image based on a prompt.
     248     *
     249     * @param WP_REST_Request $request The request object.
     250     * @return \WP_REST_Response|WP_Error The response or error.
     251     */
     252    public function handle_generate_alt_text_request( $request ) {
     253        $prompt = sanitize_textarea_field( (string) $request->get_param( 'prompt' ) );
     254
     255        $provider = sanitize_text_field( (string) $request->get_param( 'provider' ) );
     256        if ( '' === $provider ) {
     257            return new WP_Error( 'invalid_provider', 'Provider is required.', [ 'status' => 400 ] );
     258        }
     259
     260        $attachment_id = absint( $request->get_param( 'attachment_id' ) );
     261        if ( ! $attachment_id ) {
     262            return new WP_Error( 'missing_image', 'Image is required for alt text generation.', [ 'status' => 400 ] );
     263        }
     264
     265        if ( ! current_user_can( 'edit_post', $attachment_id ) ) {
     266            return new WP_Error( 'forbidden', 'You do not have permission to access this attachment.', [ 'status' => 403 ] );
     267        }
     268
     269        $image_data = $this->get_attachment_data_url( $attachment_id );
     270        if ( is_wp_error( $image_data ) ) {
     271            return $image_data;
     272        }
     273
     274        $structured = $this->sanitize_prompt_structured( $request->get_param( 'prompt_structured' ) );
     275        $alt_prompt = $prompt;
     276        if ( ! empty( $structured ) ) {
     277            $alt_prompt = $prompt . "\nStructured details: " . wp_json_encode( $structured );
     278        }
     279
     280        $generator = $this->get_alt_text_generator( $provider );
     281        if ( is_wp_error( $generator ) ) {
     282            return $generator;
     283        }
     284
     285        $result = $generator->generate( $alt_prompt, $image_data );
     286        if ( is_wp_error( $result ) ) {
     287            return $result;
     288        }
     289
     290        return new \WP_REST_Response(
     291            [
     292                'alt_text' => $result,
     293            ],
     294            200
     295        );
    233296    }
    234297
     
    274337
    275338        update_post_meta( $attachment_id, 'kaigen_generation_meta', $meta );
     339    }
     340
     341    /**
     342     * Resolves an alt text generator for the selected provider.
     343     *
     344     * @param string $provider The provider key.
     345     * @return Alt_Text_Generator_Core|WP_Error Generator instance or error.
     346     */
     347    private function get_alt_text_generator( $provider ) {
     348        $provider = strtolower( trim( (string) $provider ) );
     349        $active   = kaigen_provider_manager()->get_active_provider_id();
     350
     351        if ( '' === $active || $provider !== $active ) {
     352            return new WP_Error( 'invalid_provider', 'Alt text generator is not available for the selected provider.', [ 'status' => 400 ] );
     353        }
     354
     355        $class = kaigen_provider_manager()->get_active_alt_text_generator_class();
     356        if ( '' === $class ) {
     357            return new WP_Error( 'invalid_provider', 'Alt text generator class not found.', [ 'status' => 400 ] );
     358        }
     359
     360        if ( ! class_exists( $class ) ) {
     361            return new WP_Error( 'invalid_provider', 'Alt text generator class not found.', [ 'status' => 400 ] );
     362        }
     363
     364        if ( ! in_array( Alt_Text_Generator_Core::class, class_implements( $class ), true ) ) {
     365            return new WP_Error( 'invalid_provider', 'Alt text generator class is invalid.', [ 'status' => 400 ] );
     366        }
     367
     368        return new $class();
     369    }
     370
     371    /**
     372     * Sanitizes a structured prompt into a safe array.
     373     *
     374     * @param mixed $structured The structured prompt data.
     375     * @return array Sanitized structured prompt.
     376     */
     377    private function sanitize_prompt_structured( $structured ) {
     378        if ( is_string( $structured ) ) {
     379            $decoded = json_decode( $structured, true );
     380            if ( is_array( $decoded ) ) {
     381                $structured = $decoded;
     382            }
     383        }
     384
     385        if ( ! is_array( $structured ) ) {
     386            return [];
     387        }
     388
     389        $clean = [];
     390        foreach ( $structured as $key => $phrases ) {
     391            if ( ! is_array( $phrases ) ) {
     392                continue;
     393            }
     394            $key               = sanitize_key( $key );
     395            $sanitized_phrases = [];
     396            foreach ( $phrases as $phrase ) {
     397                if ( ! is_string( $phrase ) ) {
     398                    continue;
     399                }
     400                $phrase = sanitize_text_field( $phrase );
     401                if ( '' !== $phrase ) {
     402                    $sanitized_phrases[] = $phrase;
     403                }
     404            }
     405            if ( ! empty( $sanitized_phrases ) ) {
     406                $clean[ $key ] = $sanitized_phrases;
     407            }
     408        }
     409
     410        return $clean;
     411    }
     412
     413    /**
     414     * Builds a base64 data URL for an attachment image.
     415     *
     416     * @param int $attachment_id Attachment ID.
     417     * @return string|WP_Error Base64 data URL or error.
     418     */
     419    private function get_attachment_data_url( $attachment_id ) {
     420        $file_path = get_attached_file( $attachment_id );
     421        if ( empty( $file_path ) || ! file_exists( $file_path ) ) {
     422            $attachment_url = wp_get_attachment_url( $attachment_id );
     423            if ( empty( $attachment_url ) ) {
     424                return new WP_Error( 'missing_file', 'Attachment file not found.', [ 'status' => 404 ] );
     425            }
     426
     427            $response = wp_remote_get(
     428                $attachment_url,
     429                [
     430                    'timeout' => 10,
     431                ]
     432            );
     433
     434            if ( is_wp_error( $response ) ) {
     435                return new WP_Error( 'file_read_error', 'Unable to fetch attachment data.', [ 'status' => 500 ] );
     436            }
     437
     438            $body         = wp_remote_retrieve_body( $response );
     439            $content_type = wp_remote_retrieve_header( $response, 'content-type' );
     440            $content_len  = wp_remote_retrieve_header( $response, 'content-length' );
     441            if ( $content_len && (int) $content_len > 10 * 1024 * 1024 ) {
     442                return new WP_Error( 'file_too_large', 'Attachment is too large for alt text generation.', [ 'status' => 400 ] );
     443            }
     444
     445            if ( '' === $body ) {
     446                return new WP_Error( 'file_read_error', 'Unable to read attachment data.', [ 'status' => 500 ] );
     447            }
     448
     449            $mime = $content_type ? $content_type : 'image/jpeg';
     450            // phpcs:ignore WordPress.PHP.DiscouragedPHPFunctions.obfuscation_base64_encode -- Needed for image data URL.
     451            return 'data:' . $mime . ';base64,' . base64_encode( $body );
     452        }
     453
     454        $max_size  = 10 * 1024 * 1024;
     455        $file_size = filesize( $file_path );
     456        if ( $file_size && $file_size > $max_size ) {
     457            return new WP_Error( 'file_too_large', 'Attachment is too large for alt text generation.', [ 'status' => 400 ] );
     458        }
     459
     460        // phpcs:ignore WordPress.WP.AlternativeFunctions.file_get_contents_file_get_contents -- Local file read for attachment data.
     461        $contents = file_get_contents( $file_path );
     462        if ( false === $contents ) {
     463            return new WP_Error( 'file_read_error', 'Unable to read attachment data.', [ 'status' => 500 ] );
     464        }
     465
     466        $filetype = wp_check_filetype( $file_path );
     467        $mime     = $filetype['type'] ?? 'image/jpeg';
     468
     469        // phpcs:ignore WordPress.PHP.DiscouragedPHPFunctions.obfuscation_base64_encode -- Needed for image data URL.
     470        return 'data:' . $mime . ';base64,' . base64_encode( $contents );
    276471    }
    277472
  • kaigen/tags/v0.2.8/kaigen.php

    r3428748 r3437262  
    55 * Requires at least: 6.1
    66 * Requires PHP:      7.0
    7  * Version:           0.2.7
     7 * Version:           0.2.8
    88 * Author:            Jacob Schweitzer
    99 * License:           GPL-2.0-or-later
     
    3636require_once __DIR__ . '/inc/class-provider-manager.php';
    3737require_once __DIR__ . '/inc/class-admin.php';
     38require_once __DIR__ . '/inc/alt-text/class-alt-text-generator-core.php';
    3839
    3940// Load REST API functionality.
  • kaigen/trunk/build/index.asset.php

    r3428748 r3437262  
    1 <?php return array('dependencies' => array('react', 'wp-api-fetch', 'wp-block-editor', 'wp-components', 'wp-data', 'wp-element', 'wp-hooks', 'wp-rich-text'), 'version' => 'ac234a5ba569d8e13376');
     1<?php return array('dependencies' => array('react', 'wp-api-fetch', 'wp-block-editor', 'wp-components', 'wp-data', 'wp-element', 'wp-hooks', 'wp-rich-text'), 'version' => '75d7006d3a754a269677');
  • kaigen/trunk/build/index.js

    r3428748 r3437262  
    1 (()=>{"use strict";var e={n:t=>{var a=t&&t.__esModule?()=>t.default:()=>t;return e.d(a,{a}),a},d:(t,a)=>{for(var n in a)e.o(a,n)&&!e.o(t,n)&&Object.defineProperty(t,n,{enumerable:!0,get:a[n]})},o:(e,t)=>Object.prototype.hasOwnProperty.call(e,t)};const t=window.React,a=window.wp.element,n=window.wp.components,r=async(e,t,a={})=>{try{const n=wp.data.select("core/editor")?.getEditorSettings()?.kaigen_provider;if(!n)throw new Error("No provider configured. Please check your plugin settings.");const r=wp.data.select("core/editor")?.getEditorSettings()?.kaigen_has_api_key;if(!r)throw new Error("No API key configured for the selected provider. Please add one in the KaiGen settings.");const l={prompt:e,provider:n};a.sourceImageUrls&&Array.isArray(a.sourceImageUrls)?l.source_image_urls=a.sourceImageUrls:a.sourceImageUrl&&(l.source_image_url=a.sourceImageUrl),a.sourceImageIds&&Array.isArray(a.sourceImageIds)&&(l.source_image_ids=a.sourceImageIds),a.additionalImageUrls&&Array.isArray(a.additionalImageUrls)&&(l.additional_image_urls=a.additionalImageUrls),a.maskUrl&&(l.mask_url=a.maskUrl),a.moderation&&["auto","low"].includes(a.moderation)&&(l.moderation=a.moderation),a.style&&["natural","vivid"].includes(a.style)&&(l.style=a.style),a.aspectRatio&&["1:1","16:9","9:16","4:3","3:4"].includes(a.aspectRatio)&&(l.aspect_ratio=a.aspectRatio),a.quality&&["low","medium","high"].includes(a.quality)&&(l.quality=a.quality);const i=wp.apiFetch({path:"/kaigen/v1/estimated-generation-time",method:"POST",data:l});"function"==typeof a.onEstimatedTime&&i.then(e=>{e&&"number"==typeof e.estimated_time_seconds&&a.onEstimatedTime(e.estimated_time_seconds)}).catch(()=>{});const o=await wp.apiFetch({path:"/kaigen/v1/generate-image",method:"POST",data:l});if(o.code&&o.message){if("content_moderation"===o.code)throw new Error(o.message);if("replicate_error"===o.code)throw new Error("Image generation failed: "+o.message);throw new Error(o.message)}if(!o||!o.url)throw new Error("Invalid response from server: "+JSON.stringify(o));o.id&&"number"==typeof o.id&&o.id>0?t({url:o.url,alt:e,id:o.id,caption:""}):t({url:o.url,alt:e,caption:""})}catch(e){t({error:e.message||"An unknown error occurred while generating the image"})}},l=(e,t)=>{const[n,r]=(0,a.useState)(0),l=(0,a.useRef)(3e4),i=(0,a.useRef)(null),o=(0,a.useRef)(0);return(0,a.useEffect)(()=>{l.current="number"==typeof t&&t>0?t:3e4},[t]),(0,a.useEffect)(()=>{if(!e)return r(0),i.current=null,void(o.current=0);i.current||(i.current=Date.now(),r(0),o.current=0);const t=setInterval(()=>{const e=Date.now()-i.current,t=Math.min(Math.floor(e/l.current*100),99),a=Math.max(t,o.current);o.current=a,r(a)},200);return()=>clearInterval(t)},[e]),n},i=window.kaiGen?.logoUrl,o=({isOpen:e,onClose:o,onSelect:c,initialReferenceImage:s})=>{const[m,d]=(0,a.useState)(""),[u,g]=(0,a.useState)(!1),[p,k]=(0,a.useState)(null),[E,b]=(0,a.useState)([]),[h,f]=(0,a.useState)([]),[_,w]=(0,a.useState)("1:1"),[y,v]=(0,a.useState)("medium"),[N,I]=(0,a.useState)(null),S=wp.data.select("core/editor")?.getEditorSettings()||{},G=S.kaigen_provider||"replicate",C=S.kaigen_quality||"medium",T="replicate"===G?10:16,x=l(u,N);(0,a.useEffect)(()=>{e&&((async()=>{try{const e=await wp.apiFetch({path:"/kaigen/v1/reference-images",method:"GET"});return Array.isArray(e)?e:[]}catch(e){return[]}})().then(b),v(C),s&&s.url?f([s]):f([]))},[e,s,C]);const A=()=>{if(!m.trim())return void k("Please enter a prompt for image generation.");g(!0),I(null),k(null);const e={};h.length>0&&(e.sourceImageUrls=h.map(e=>e.url),e.sourceImageIds=h.map(e=>e.id).filter(e=>Number.isInteger(e)&&e>0)),_&&(e.aspectRatio=_),y&&(e.quality=y),e.onEstimatedTime=e=>{"number"==typeof e&&I(1e3*e)},r(m.trim(),e=>{e.error?(k(e.error),g(!1)):(c(e),g(!1),B())},e)},B=()=>{d(""),k(null),f([]),v(C),I(null),o()};return e?(0,t.createElement)(n.Modal,{className:"kaigen-modal",title:(0,t.createElement)("div",{className:"kaigen-modal__logo-container"},(0,t.createElement)("img",{src:i,alt:"KaiGen logo",className:"kaigen-modal__logo"})),"aria-label":"KaiGen",onRequestClose:B},p&&(0,t.createElement)("p",{className:"kaigen-error-text"},p),(0,t.createElement)("div",{className:"kaigen-modal__input-container"},(0,t.createElement)(n.Dropdown,{popoverProps:{placement:"bottom-start",focusOnMount:!0},renderToggle:({isOpen:e,onToggle:a})=>(0,t.createElement)(n.Button,{className:"kaigen-modal__ref-button "+(h.length>0?"kaigen-ref-button-selected":""),onClick:a,"aria-expanded":e,"aria-label":"Reference Images"},(0,t.createElement)(n.Dashicon,{icon:"format-image",className:h.length>0?"kaigen-ref-button-icon-selected":""})),renderContent:()=>{const e=s?[s,...E.filter(e=>e.id!==s.id)]:E;return(0,t.createElement)("div",{className:"kaigen-modal-dropdown-content-container"},(0,t.createElement)("h4",{className:"kaigen-modal-dropdown-content-title"},"Reference Images (up to ",T,")"),e.length>0?(0,t.createElement)("div",{className:"kaigen-modal-reference-images-container"},e.map((e,a)=>(0,t.createElement)("button",{type:"button",key:e.id||`initial-${a}`,onClick:()=>{f(t=>t.some(t=>t.url===e.url)?t.filter(t=>t.url!==e.url):t.length<T?[...t,e]:t)},className:"kaigen-modal-reference-image "+(h.some(t=>t.url===e.url)?"kaigen-modal-reference-image-selected":""),"aria-label":e.alt||"Select reference image"},(0,t.createElement)("img",{src:e.thumbnail_url||e.url,alt:e.alt||""})))):(0,t.createElement)("p",{className:"kaigen-modal-no-references"},"No reference images. Mark images in the Media Library to use them here."))}}),(0,t.createElement)("div",{className:"kaigen-modal__textarea-container"},(0,t.createElement)(n.TextareaControl,{className:"kaigen-modal__textarea",placeholder:"Image prompt...",value:m,onChange:d,onKeyDown:e=>{"Enter"!==e.key||e.shiftKey||(e.preventDefault(),A())},rows:2})),m.trim()&&(0,t.createElement)(n.Button,{className:"kaigen-modal__submit-button",variant:"primary",onClick:A,disabled:u||!m.trim(),"aria-label":"Generate Image"},(0,t.createElement)(n.Dashicon,{icon:"admin-appearance"})),(0,t.createElement)(n.Dropdown,{popoverProps:{placement:"bottom-end",focusOnMount:!0},renderToggle:({isOpen:e,onToggle:a})=>(0,t.createElement)(n.Button,{className:"kaigen-modal__settings-button",onClick:a,"aria-expanded":e,"aria-label":"Settings"},(0,t.createElement)(n.Dashicon,{icon:"admin-generic"})),renderContent:()=>(0,t.createElement)("div",{className:"kaigen-modal-dropdown-content-container"},(0,t.createElement)("h4",{className:"kaigen-modal-dropdown-content-title"},"Aspect Ratio"),(0,t.createElement)("div",{className:"kaigen-modal-aspect-ratio-container"},[{value:"1:1",label:"1:1",title:"Square"},{value:"16:9",label:"16:9",title:"Landscape"},{value:"9:16",label:"9:16",title:"Portrait"}].map(e=>(0,t.createElement)("button",{type:"button",key:e.value,onClick:()=>w(t=>t===e.value?null:e.value),"aria-pressed":_===e.value,"aria-label":`${e.title} (${e.label})`,className:"kaigen-modal__aspect-ratio-button "+(_===e.value?"kaigen-modal__aspect-ratio-button-selected":"")},(0,t.createElement)("div",{className:"kaigen-modal-aspect-ratio-icon-container"},(0,t.createElement)("div",{className:`kaigen-modal-aspect-ratio-icon ${_===e.value?"kaigen-modal-aspect-ratio-icon-selected":""} kaigen-aspect-ratio-${e.value.replace(":","-")}`})),(0,t.createElement)("span",{className:"kaigen-modal-aspect-ratio-label"},e.label)))),(0,t.createElement)("h4",{className:"kaigen-modal-dropdown-content-title"},"Quality"),(0,t.createElement)("div",{className:"kaigen-modal-quality-container"},[{value:"low",label:"Low"},{value:"medium",label:"Medium"},{value:"high",label:"High"}].map(e=>(0,t.createElement)("button",{type:"button",key:e.value,onClick:()=>v(e.value),"aria-pressed":y===e.value,className:"kaigen-modal__quality-button "+(y===e.value?"kaigen-modal__quality-button-selected":"")},e.label))))})),u&&(0,t.createElement)("div",{className:"kaigen-modal__progress"},(0,t.createElement)("div",{className:"kaigen-modal__progress-label"},"Generating... ",x,"%"),(0,t.createElement)("div",{className:"kaigen-modal__progress-track",role:"progressbar","aria-valuenow":x,"aria-valuemin":0,"aria-valuemax":100,"aria-label":"Image generation progress"},(0,t.createElement)("div",{className:"kaigen-modal__progress-fill",style:{width:`${x}%`}})))):null},c=window.kaiGen?.logoUrl,s=({onSelect:e,shouldDisplay:r})=>{const[l,i]=(0,a.useState)(!1);return r?(0,t.createElement)(t.Fragment,null,(0,t.createElement)(n.Button,{onClick:()=>i(!0),className:"kaigen-placeholder-button","aria-label":"KaiGen"},(0,t.createElement)("img",{src:c,alt:"KaiGen",style:{width:"48px",height:"48px"}})),(0,t.createElement)("button",{type:"button",role:"menuitem",onClick:()=>i(!0),className:"components-button components-menu-item__button is-next-40px-default-size kaigen-menu-item-button"},(0,t.createElement)("span",{className:"components-menu-item__item"},"KaiGen"),(0,t.createElement)("img",{src:c,alt:"","aria-hidden":"true",className:"components-menu-items__item-icon has-icon-right",style:{width:"24px",height:"24px"}})),(0,t.createElement)(o,{isOpen:l,onClose:()=>i(!1),onSelect:e})):null},m=window.kaiGen?.logoUrl,d=({isGenerating:e,onGenerateImage:r,isRegenerating:i,onImageGenerated:c,isImageBlock:s,isTextSelected:d,currentImage:u,estimatedDurationMs:g})=>{const[p,k]=(0,a.useState)(!1),E=l(e||i,g),b=(0,t.createElement)("span",{className:"kaigen-progress-icon","aria-hidden":"true"},(0,t.createElement)("span",{className:"kaigen-progress-icon__track"},(0,t.createElement)("span",{className:"kaigen-progress-icon__fill",style:{width:`${E}%`}})));return s?(0,t.createElement)(t.Fragment,null,(0,t.createElement)(n.ToolbarGroup,null,(0,t.createElement)(n.ToolbarButton,{icon:i?b:(0,t.createElement)("img",{src:m,alt:"KaiGen logo",className:"kaigen-toolbar-icon"}),label:i?`KaiGen is generating... ${E}%`:"KaiGen",onClick:()=>k(!0),disabled:i})),(0,t.createElement)(o,{isOpen:p,onClose:()=>k(!1),onSelect:c,initialReferenceImage:u})):d?(0,t.createElement)(n.ToolbarGroup,null,(0,t.createElement)(n.ToolbarButton,{icon:e?b:"format-image",label:e?`KaiGen is generating... ${E}%`:"KaiGen",onClick:r,disabled:e})):null},u=window.wp.blockEditor,g=window.wp.data,p=window.wp.richText,k=({value:e})=>{const[n,l]=(0,a.useState)(!1),[i,o]=(0,a.useState)(null),c=(0,g.useSelect)(e=>e("core/block-editor").getSelectedBlock(),[]),{replaceBlocks:s}=(0,g.useDispatch)("core/block-editor"),m=(0,a.useCallback)(()=>{if(c&&"core/paragraph"===c.name){const t=e.text.slice(e.start,e.end).trim();if(!t)return void wp.data.dispatch("core/notices").createErrorNotice("Please select some text to use as the image generation prompt.",{type:"snackbar"});const a=wp.data.select("core/editor")?.getEditorSettings()?.kaigen_provider;if(!a)return void wp.data.dispatch("core/notices").createErrorNotice("No AI provider configured. Please set one in the plugin settings.",{type:"snackbar"});const n=wp.blocks.createBlock("core/heading",{content:"Generating AI image...",level:2,className:"kaigen-text-center"});s(c.clientId,[n,c]),l(!0),o(null),r(t,e=>{if(l(!1),o(null),e.error)wp.data.dispatch("core/notices").createErrorNotice("Failed to generate image: "+e.error,{type:"snackbar"}),s(n.clientId,[]);else{const t={url:e.url,alt:e.alt,caption:""};e.id&&"number"==typeof e.id&&e.id>0&&(t.id=e.id);const a=wp.blocks.createBlock("core/image",t);s(n.clientId,[a])}},{onEstimatedTime:e=>{"number"==typeof e&&o(1e3*e)}})}},[c,e.text,e.start,e.end,s]),p=""!==e.text.slice(e.start,e.end).trim();return(0,t.createElement)(u.BlockControls,null,(0,t.createElement)(d,{isGenerating:n,onGenerateImage:m,isTextSelected:p,estimatedDurationMs:i}))};(0,p.registerFormatType)("kaigen/custom-format",{title:"AI Image Gen",tagName:"span",className:"kaigen-format",edit:({value:e})=>(0,t.createElement)(k,{value:e})});const E=window.wp.hooks;(0,E.addFilter)("editor.MediaUpload","kaigen/add-ai-tab",e=>a=>{const n=a.allowedTypes&&a.allowedTypes.includes("image")&&!a.multiple,r=wp.data.select("core/block-editor").getSelectedBlock(),l=r&&"core/image"===r.name,i=wp.data.select("core/editor")?.getEditorSettings()?.kaigen_has_api_key,o=n&&l&&!(r&&r.attributes&&r.attributes.url)&&i;return(0,t.createElement)(e,{...a,render:e=>(0,t.createElement)(t.Fragment,null,a.render(e),(0,t.createElement)(s,{onSelect:a.onSelect,shouldDisplay:o}))})});const b=window.wp.apiFetch;var h=e.n(b);(0,E.addFilter)("editor.BlockEdit","kaigen/add-regenerate-button",e=>r=>{if("core/image"!==r.name)return(0,t.createElement)(e,{...r});const l=r.attributes.id&&"number"==typeof r.attributes.id&&r.attributes.id>0,[i,o]=(0,a.useState)(!1),[c,s]=(0,a.useState)(null),[m,g]=(0,a.useState)(!1),[p,k]=(0,a.useState)([]),[E,b]=(0,a.useState)(!1),[f,_]=(0,a.useState)(null),{attributes:{id:w,kaigen_reference_image:y},setAttributes:v}=r;(0,a.useEffect)(()=>{l&&!i&&(null==y?h()({path:`/wp/v2/media/${w}`}).then(e=>{if(e&&e.meta&&void 0!==e.meta.kaigen_reference_image){const t=!0===e.meta.kaigen_reference_image||1===e.meta.kaigen_reference_image;v({kaigen_reference_image:t})}o(!0)}).catch(()=>{o(!0)}):o(!0))},[l,w,y,i,v]),(0,a.useEffect)(()=>{w&&(s(null),k([]),_(null))},[w]),(0,a.useEffect)(()=>{E&&w&&f!==w&&(g(!0),h()({path:`/kaigen/v1/generation-meta?attachment_id=${w}`}).then(e=>{s(e&&Object.keys(e).length?e:null)}).catch(()=>{s(null)}).finally(()=>{g(!1),_(w)}))},[E,w,f]),(0,a.useEffect)(()=>{if(!(E&&c&&Array.isArray(c.reference_image_ids)&&c.reference_image_ids.length))return void k([]);const e=c.reference_image_ids.join(",");h()({path:`/wp/v2/media?include=${e}&per_page=${c.reference_image_ids.length}`}).then(e=>{const t=Array.isArray(e)?e.map(e=>({id:e.id,url:e.media_details?.sizes?.thumbnail?.source_url||e.source_url})).filter(e=>e.url):[];k(t)}).catch(()=>{k([])})},[E,c]);const N=r.attributes.url?{url:r.attributes.url,id:r.attributes.id,alt:r.attributes.alt||""}:null;return(0,t.createElement)(t.Fragment,null,(0,t.createElement)(e,{...r}),r.attributes.url&&(0,t.createElement)(u.BlockControls,null,(0,t.createElement)(d,{onImageGenerated:e=>{e.id&&"number"==typeof e.id&&e.id>0?r.setAttributes({url:e.url,id:e.id}):r.setAttributes({url:e.url,id:void 0}),wp.data.dispatch("core/notices").createSuccessNotice("Image generated successfully!",{type:"snackbar"})},isImageBlock:!0,currentImage:N})),l&&(0,t.createElement)(u.InspectorControls,null,(0,t.createElement)(n.PanelBody,{title:"KaiGen Settings",initialOpen:!1,onToggle:e=>{b(e)}},(0,t.createElement)(n.CheckboxControl,{label:"Reference image",checked:!0===r.attributes.kaigen_reference_image,onChange:async e=>{const t=!0===e;r.setAttributes({kaigen_reference_image:t}),o(!0);try{await h()({path:`/wp/v2/media/${r.attributes.id}`,method:"POST",data:{meta:{kaigen_reference_image:t?1:0}}})}catch(e){wp.data.dispatch("core/notices").createErrorNotice("Failed to update reference image meta",{type:"snackbar"})}},help:"Add to the list of reference images."}),m&&(0,t.createElement)("p",{className:"kaigen-generation-meta-loading"},"Loading generation details..."),!m&&c&&(0,t.createElement)("table",{className:"kaigen-generation-meta-table"},(0,t.createElement)("tbody",null,(0,t.createElement)("tr",null,(0,t.createElement)("th",null,"Prompt"),(0,t.createElement)("td",null,c.prompt)),(0,t.createElement)("tr",null,(0,t.createElement)("th",null,"Provider"),(0,t.createElement)("td",null,c.provider)),(0,t.createElement)("tr",null,(0,t.createElement)("th",null,"Quality"),(0,t.createElement)("td",null,c.quality)),(0,t.createElement)("tr",null,(0,t.createElement)("th",null,"Model"),(0,t.createElement)("td",null,c.model)),p.length>0&&(0,t.createElement)("tr",null,(0,t.createElement)("th",null,"References"),(0,t.createElement)("td",null,(0,t.createElement)("div",{className:"kaigen-generation-meta-images"},p.map(e=>(0,t.createElement)("img",{key:e.id,src:e.url,alt:"",className:"kaigen-generation-meta-image"}))))))))))}),(0,E.addFilter)("blocks.registerBlockType","kaigen/add-reference-image-attribute",(e,t)=>"core/image"!==t?e:{...e,attributes:{...e.attributes,kaigen_reference_image:{type:"boolean",default:!1}}})})();
     1(()=>{"use strict";var e={n:t=>{var a=t&&t.__esModule?()=>t.default:()=>t;return e.d(a,{a}),a},d:(t,a)=>{for(var r in a)e.o(a,r)&&!e.o(t,r)&&Object.defineProperty(t,r,{enumerable:!0,get:a[r]})},o:(e,t)=>Object.prototype.hasOwnProperty.call(e,t)};const t=window.React,a=window.wp.element,r=window.wp.components,n=async(e,t,a={})=>{try{const r=wp.data.select("core/editor")?.getEditorSettings()?.kaigen_provider;if(!r)throw new Error("No provider configured. Please check your plugin settings.");const n=wp.data.select("core/editor")?.getEditorSettings()?.kaigen_has_api_key;if(!n)throw new Error("No API key configured for the selected provider. Please add one in the KaiGen settings.");const l={prompt:e,provider:r};a.sourceImageUrls&&Array.isArray(a.sourceImageUrls)?l.source_image_urls=a.sourceImageUrls:a.sourceImageUrl&&(l.source_image_url=a.sourceImageUrl),a.sourceImageIds&&Array.isArray(a.sourceImageIds)&&(l.source_image_ids=a.sourceImageIds),a.additionalImageUrls&&Array.isArray(a.additionalImageUrls)&&(l.additional_image_urls=a.additionalImageUrls),a.maskUrl&&(l.mask_url=a.maskUrl),a.moderation&&["auto","low"].includes(a.moderation)&&(l.moderation=a.moderation),a.style&&["natural","vivid"].includes(a.style)&&(l.style=a.style),a.aspectRatio&&["1:1","16:9","9:16","4:3","3:4"].includes(a.aspectRatio)&&(l.aspect_ratio=a.aspectRatio),a.quality&&["low","medium","high"].includes(a.quality)&&(l.quality=a.quality);const i=wp.apiFetch({path:"/kaigen/v1/estimated-generation-time",method:"POST",data:l});"function"==typeof a.onEstimatedTime&&i.then(e=>{e&&"number"==typeof e.estimated_time_seconds&&a.onEstimatedTime(e.estimated_time_seconds)}).catch(()=>{});const o=await wp.apiFetch({path:"/kaigen/v1/generate-image",method:"POST",data:l});if(o.code&&o.message){if("content_moderation"===o.code)throw new Error(o.message);if("replicate_error"===o.code)throw new Error("Image generation failed: "+o.message);throw new Error(o.message)}if(!o||!o.url)throw new Error("Invalid response from server: "+JSON.stringify(o));o.id&&"number"==typeof o.id&&o.id>0?t({url:o.url,alt:e,id:o.id,caption:""}):t({url:o.url,alt:e,caption:""})}catch(e){t({error:e.message||"An unknown error occurred while generating the image"})}},l=(e,t)=>{const[r,n]=(0,a.useState)(0),l=(0,a.useRef)(3e4),i=(0,a.useRef)(null),o=(0,a.useRef)(0);return(0,a.useEffect)(()=>{l.current="number"==typeof t&&t>0?t:3e4},[t]),(0,a.useEffect)(()=>{if(!e)return n(0),i.current=null,void(o.current=0);i.current||(i.current=Date.now(),n(0),o.current=0);const t=setInterval(()=>{const e=Date.now()-i.current,t=Math.min(Math.floor(e/l.current*100),99),a=Math.max(t,o.current);o.current=a,n(a)},200);return()=>clearInterval(t)},[e]),r},i=window.kaiGen?.logoUrl,o=({isOpen:e,onClose:o,onSelect:c,initialReferenceImage:s})=>{const[m,d]=(0,a.useState)(""),[u,g]=(0,a.useState)(!1),[p,k]=(0,a.useState)(null),[h,E]=(0,a.useState)([]),[b,f]=(0,a.useState)([]),[_,w]=(0,a.useState)("1:1"),[y,v]=(0,a.useState)("medium"),[N,I]=(0,a.useState)(null),S=wp.data.select("core/editor")?.getEditorSettings()||{},x=S.kaigen_provider||"replicate",G=S.kaigen_quality||"medium",T="replicate"===x?10:16,A=l(u,N);(0,a.useEffect)(()=>{e&&((async()=>{try{const e=await wp.apiFetch({path:"/kaigen/v1/reference-images",method:"GET"});return Array.isArray(e)?e:[]}catch(e){return[]}})().then(E),v(G),s&&s.url?f([s]):f([]))},[e,s,G]);const C=()=>{if(!m.trim())return void k("Please enter a prompt for image generation.");g(!0),I(null),k(null);const e={};b.length>0&&(e.sourceImageUrls=b.map(e=>e.url),e.sourceImageIds=b.map(e=>e.id).filter(e=>Number.isInteger(e)&&e>0)),_&&(e.aspectRatio=_),y&&(e.quality=y),e.onEstimatedTime=e=>{"number"==typeof e&&I(1e3*e)},n(m.trim(),e=>{e.error?(k(e.error),g(!1)):(c(e),g(!1),B())},e)},B=()=>{d(""),k(null),f([]),v(G),I(null),o()};return e?(0,t.createElement)(r.Modal,{className:"kaigen-modal",title:(0,t.createElement)("div",{className:"kaigen-modal__logo-container"},(0,t.createElement)("img",{src:i,alt:"KaiGen logo",className:"kaigen-modal__logo"})),"aria-label":"KaiGen",onRequestClose:B},p&&(0,t.createElement)("p",{className:"kaigen-error-text"},p),(0,t.createElement)("div",{className:"kaigen-modal__input-container"},(0,t.createElement)(r.Dropdown,{popoverProps:{placement:"bottom-start",focusOnMount:!0},renderToggle:({isOpen:e,onToggle:a})=>(0,t.createElement)(r.Button,{className:"kaigen-modal__ref-button "+(b.length>0?"kaigen-ref-button-selected":""),onClick:a,"aria-expanded":e,"aria-label":"Reference Images"},(0,t.createElement)(r.Dashicon,{icon:"format-image",className:b.length>0?"kaigen-ref-button-icon-selected":""})),renderContent:()=>{const e=s?[s,...h.filter(e=>e.id!==s.id)]:h;return(0,t.createElement)("div",{className:"kaigen-modal-dropdown-content-container"},(0,t.createElement)("h4",{className:"kaigen-modal-dropdown-content-title"},"Reference Images (up to ",T,")"),e.length>0?(0,t.createElement)("div",{className:"kaigen-modal-reference-images-container"},e.map((e,a)=>(0,t.createElement)("button",{type:"button",key:e.id||`initial-${a}`,onClick:()=>{f(t=>t.some(t=>t.url===e.url)?t.filter(t=>t.url!==e.url):t.length<T?[...t,e]:t)},className:"kaigen-modal-reference-image "+(b.some(t=>t.url===e.url)?"kaigen-modal-reference-image-selected":""),"aria-label":e.alt||"Select reference image"},(0,t.createElement)("img",{src:e.thumbnail_url||e.url,alt:e.alt||""})))):(0,t.createElement)("p",{className:"kaigen-modal-no-references"},"No reference images. Mark images in the Media Library to use them here."))}}),(0,t.createElement)("div",{className:"kaigen-modal__textarea-container"},(0,t.createElement)(r.TextareaControl,{className:"kaigen-modal__textarea",placeholder:"Image prompt...",value:m,onChange:d,onKeyDown:e=>{"Enter"!==e.key||e.shiftKey||(e.preventDefault(),C())},rows:2})),m.trim()&&(0,t.createElement)(r.Button,{className:"kaigen-modal__submit-button",variant:"primary",onClick:C,disabled:u||!m.trim(),"aria-label":"Generate Image"},(0,t.createElement)(r.Dashicon,{icon:"admin-appearance"})),(0,t.createElement)(r.Dropdown,{popoverProps:{placement:"bottom-end",focusOnMount:!0},renderToggle:({isOpen:e,onToggle:a})=>(0,t.createElement)(r.Button,{className:"kaigen-modal__settings-button",onClick:a,"aria-expanded":e,"aria-label":"Settings"},(0,t.createElement)(r.Dashicon,{icon:"admin-generic"})),renderContent:()=>(0,t.createElement)("div",{className:"kaigen-modal-dropdown-content-container"},(0,t.createElement)("h4",{className:"kaigen-modal-dropdown-content-title"},"Aspect Ratio"),(0,t.createElement)("div",{className:"kaigen-modal-aspect-ratio-container"},[{value:"1:1",label:"1:1",title:"Square"},{value:"16:9",label:"16:9",title:"Landscape"},{value:"9:16",label:"9:16",title:"Portrait"}].map(e=>(0,t.createElement)("button",{type:"button",key:e.value,onClick:()=>w(t=>t===e.value?null:e.value),"aria-pressed":_===e.value,"aria-label":`${e.title} (${e.label})`,className:"kaigen-modal__aspect-ratio-button "+(_===e.value?"kaigen-modal__aspect-ratio-button-selected":"")},(0,t.createElement)("div",{className:"kaigen-modal-aspect-ratio-icon-container"},(0,t.createElement)("div",{className:`kaigen-modal-aspect-ratio-icon ${_===e.value?"kaigen-modal-aspect-ratio-icon-selected":""} kaigen-aspect-ratio-${e.value.replace(":","-")}`})),(0,t.createElement)("span",{className:"kaigen-modal-aspect-ratio-label"},e.label)))),(0,t.createElement)("h4",{className:"kaigen-modal-dropdown-content-title"},"Quality"),(0,t.createElement)("div",{className:"kaigen-modal-quality-container"},[{value:"low",label:"Low"},{value:"medium",label:"Medium"},{value:"high",label:"High"}].map(e=>(0,t.createElement)("button",{type:"button",key:e.value,onClick:()=>v(e.value),"aria-pressed":y===e.value,className:"kaigen-modal__quality-button "+(y===e.value?"kaigen-modal__quality-button-selected":"")},e.label))))})),u&&(0,t.createElement)("div",{className:"kaigen-modal__progress"},(0,t.createElement)("div",{className:"kaigen-modal__progress-label"},"Generating... ",A,"%"),(0,t.createElement)("div",{className:"kaigen-modal__progress-track",role:"progressbar","aria-valuenow":A,"aria-valuemin":0,"aria-valuemax":100,"aria-label":"Image generation progress"},(0,t.createElement)("div",{className:"kaigen-modal__progress-fill",style:{width:`${A}%`}})))):null},c=window.kaiGen?.logoUrl,s=({onSelect:e,shouldDisplay:n})=>{const[l,i]=(0,a.useState)(!1);return n?(0,t.createElement)(t.Fragment,null,(0,t.createElement)(r.Button,{onClick:()=>i(!0),className:"kaigen-placeholder-button","aria-label":"KaiGen"},(0,t.createElement)("img",{src:c,alt:"KaiGen",style:{width:"48px",height:"48px"}})),(0,t.createElement)("button",{type:"button",role:"menuitem",onClick:()=>i(!0),className:"components-button components-menu-item__button is-next-40px-default-size kaigen-menu-item-button"},(0,t.createElement)("span",{className:"components-menu-item__item"},"KaiGen"),(0,t.createElement)("img",{src:c,alt:"","aria-hidden":"true",className:"components-menu-items__item-icon has-icon-right",style:{width:"24px",height:"24px"}})),(0,t.createElement)(o,{isOpen:l,onClose:()=>i(!1),onSelect:e})):null},m=window.kaiGen?.logoUrl,d=({isGenerating:e,onGenerateImage:n,isRegenerating:i,onImageGenerated:c,isImageBlock:s,isTextSelected:d,currentImage:u,estimatedDurationMs:g})=>{const[p,k]=(0,a.useState)(!1),h=l(e||i,g),E=(0,t.createElement)("span",{className:"kaigen-progress-icon","aria-hidden":"true"},(0,t.createElement)("span",{className:"kaigen-progress-icon__track"},(0,t.createElement)("span",{className:"kaigen-progress-icon__fill",style:{width:`${h}%`}})));return s?(0,t.createElement)(t.Fragment,null,(0,t.createElement)(r.ToolbarGroup,null,(0,t.createElement)(r.ToolbarButton,{icon:i?E:(0,t.createElement)("img",{src:m,alt:"KaiGen logo",className:"kaigen-toolbar-icon"}),label:i?`KaiGen is generating... ${h}%`:"KaiGen",onClick:()=>k(!0),disabled:i})),(0,t.createElement)(o,{isOpen:p,onClose:()=>k(!1),onSelect:c,initialReferenceImage:u})):d?(0,t.createElement)(r.ToolbarGroup,null,(0,t.createElement)(r.ToolbarButton,{icon:e?E:"format-image",label:e?`KaiGen is generating... ${h}%`:"KaiGen",onClick:n,disabled:e})):null},u=window.wp.blockEditor,g=window.wp.data,p=window.wp.richText,k=({value:e})=>{const[r,l]=(0,a.useState)(!1),[i,o]=(0,a.useState)(null),c=(0,g.useSelect)(e=>e("core/block-editor").getSelectedBlock(),[]),{replaceBlocks:s}=(0,g.useDispatch)("core/block-editor"),m=(0,a.useCallback)(()=>{if(c&&"core/paragraph"===c.name){const t=e.text.slice(e.start,e.end).trim();if(!t)return void wp.data.dispatch("core/notices").createErrorNotice("Please select some text to use as the image generation prompt.",{type:"snackbar"});const a=wp.data.select("core/editor")?.getEditorSettings()?.kaigen_provider;if(!a)return void wp.data.dispatch("core/notices").createErrorNotice("No AI provider configured. Please set one in the plugin settings.",{type:"snackbar"});const r=wp.blocks.createBlock("core/heading",{content:"Generating AI image...",level:2,className:"kaigen-text-center"});s(c.clientId,[r,c]),l(!0),o(null),n(t,e=>{if(l(!1),o(null),e.error)wp.data.dispatch("core/notices").createErrorNotice("Failed to generate image: "+e.error,{type:"snackbar"}),s(r.clientId,[]);else{const t={url:e.url,alt:e.alt,caption:""};e.id&&"number"==typeof e.id&&e.id>0&&(t.id=e.id);const a=wp.blocks.createBlock("core/image",t);s(r.clientId,[a])}},{onEstimatedTime:e=>{"number"==typeof e&&o(1e3*e)}})}},[c,e.text,e.start,e.end,s]),p=""!==e.text.slice(e.start,e.end).trim();return(0,t.createElement)(u.BlockControls,null,(0,t.createElement)(d,{isGenerating:r,onGenerateImage:m,isTextSelected:p,estimatedDurationMs:i}))};(0,p.registerFormatType)("kaigen/custom-format",{title:"AI Image Gen",tagName:"span",className:"kaigen-format",edit:({value:e})=>(0,t.createElement)(k,{value:e})});const h=window.wp.hooks;(0,h.addFilter)("editor.MediaUpload","kaigen/add-ai-tab",e=>a=>{const r=a.allowedTypes&&a.allowedTypes.includes("image")&&!a.multiple,n=wp.data.select("core/block-editor").getSelectedBlock(),l=n&&"core/image"===n.name,i=wp.data.select("core/editor")?.getEditorSettings()?.kaigen_has_api_key,o=r&&l&&!(n&&n.attributes&&n.attributes.url)&&i;return(0,t.createElement)(e,{...a,render:e=>(0,t.createElement)(t.Fragment,null,a.render(e),(0,t.createElement)(s,{onSelect:a.onSelect,shouldDisplay:o}))})});const E=window.wp.apiFetch;var b=e.n(E);(0,h.addFilter)("editor.BlockEdit","kaigen/add-regenerate-button",e=>n=>{if("core/image"!==n.name)return(0,t.createElement)(e,{...n});const l=Number(n.attributes.id),i=Number.isInteger(l)&&l>0,[o,c]=(0,a.useState)(!1),[s,m]=(0,a.useState)(null),[g,p]=(0,a.useState)(!1),[k,h]=(0,a.useState)([]),[E,f]=(0,a.useState)(!1),[_,w]=(0,a.useState)(!1),[y,v]=(0,a.useState)(null),{attributes:{kaigen_reference_image:N},setAttributes:I}=n;(0,a.useEffect)(()=>{i&&!o&&(null==N?b()({path:`/wp/v2/media/${l}`}).then(e=>{if(e&&e.meta&&void 0!==e.meta.kaigen_reference_image){const t=!0===e.meta.kaigen_reference_image||1===e.meta.kaigen_reference_image;I({kaigen_reference_image:t})}c(!0)}).catch(()=>{c(!0)}):c(!0))},[i,l,N,o,I]),(0,a.useEffect)(()=>{l&&(n.attributes.id!==l&&I({id:l}),m(null),h([]),v(null))},[l,n.attributes.id,I]),(0,a.useEffect)(()=>{E&&l&&y!==l&&(p(!0),b()({path:`/kaigen/v1/generation-meta?attachment_id=${l}`}).then(e=>{m(e&&Object.keys(e).length?e:null)}).catch(()=>{m(null)}).finally(()=>{p(!1),v(l)}))},[E,l,y]),(0,a.useEffect)(()=>{if(!(E&&s&&Array.isArray(s.reference_image_ids)&&s.reference_image_ids.length))return void h([]);const e=s.reference_image_ids.join(",");b()({path:`/wp/v2/media?include=${e}&per_page=${s.reference_image_ids.length}`}).then(e=>{const t=Array.isArray(e)?e.map(e=>({id:e.id,url:e.media_details?.sizes?.thumbnail?.source_url||e.source_url})).filter(e=>e.url):[];h(t)}).catch(()=>{h([])})},[E,s]);const S=n.attributes.url?{url:n.attributes.url,id:n.attributes.id,alt:n.attributes.alt||""}:null;return(0,t.createElement)(t.Fragment,null,(0,t.createElement)(e,{...n}),n.attributes.url&&(0,t.createElement)(u.BlockControls,null,(0,t.createElement)(d,{onImageGenerated:e=>{e.id&&"number"==typeof e.id&&e.id>0?n.setAttributes({url:e.url,id:e.id}):n.setAttributes({url:e.url,id:void 0}),wp.data.dispatch("core/notices").createSuccessNotice("Image generated successfully!",{type:"snackbar"})},isImageBlock:!0,currentImage:S})),i&&(0,t.createElement)(u.InspectorControls,null,(0,t.createElement)(r.PanelBody,{title:"KaiGen",initialOpen:!1,onToggle:e=>{f(e)}},(0,t.createElement)(r.CheckboxControl,{label:"Reference image",checked:!0===n.attributes.kaigen_reference_image,onChange:async e=>{const t=!0===e;n.setAttributes({kaigen_reference_image:t}),c(!0);try{await b()({path:`/wp/v2/media/${l}`,method:"POST",data:{meta:{kaigen_reference_image:t?1:0}}})}catch(e){wp.data.dispatch("core/notices").createErrorNotice("Failed to update reference image meta",{type:"snackbar"})}},help:"Add to the list of reference images."}),(0,t.createElement)(r.Button,{variant:"secondary",isBusy:_,disabled:_,style:{marginTop:"8px"},onClick:async()=>{const e=wp.data.select("core/editor")?.getEditorSettings()?.kaigen_provider||"";if(!e)return void wp.data.dispatch("core/notices").createErrorNotice("No AI provider configured. Please set one in the plugin settings.",{type:"snackbar"});const t=s?.prompt_text?s.prompt_text:"string"==typeof s?.prompt?s.prompt:n.attributes.alt||"";if(!t.trim()&&!l)return void wp.data.dispatch("core/notices").createErrorNotice("No prompt or image available to generate alt text.",{type:"snackbar"});let a=null;if(s&&"string"==typeof s.prompt)try{const e=JSON.parse(s.prompt);e&&"object"==typeof e&&(a=e)}catch(e){a=null}w(!0);try{const r=await(async(e,t,a=null,r=null)=>{try{const n={prompt:e,provider:t};a&&(n.prompt_structured=a),r&&(n.attachment_id=r);const l=await wp.apiFetch({path:"/kaigen/v1/generate-alt-text",method:"POST",data:n});if(l&&l.code&&l.message)throw new Error(l.message);return l}catch(e){throw new Error(e.message||"An unknown error occurred while generating alt text")}})(t.trim(),e,a,l),n=r?.alt_text||"";if(!n)throw new Error("Alt text response was empty.");I({alt:n}),await b()({path:`/wp/v2/media/${l}`,method:"POST",data:{alt_text:n}}),wp.data.dispatch("core/notices").createSuccessNotice("Alt text generated.",{type:"snackbar"})}catch(e){wp.data.dispatch("core/notices").createErrorNotice("Failed to generate alt text.",{type:"snackbar"})}finally{w(!1)}}},"Generate Alt Text"),(0,t.createElement)("p",{className:"components-base-control__help"},"Generate a descriptive alt text suggestion for this image."),g&&(0,t.createElement)("p",{className:"kaigen-generation-meta-loading"},"Loading generation details..."),!g&&s&&(0,t.createElement)("table",{className:"kaigen-generation-meta-table"},(0,t.createElement)("tbody",null,(0,t.createElement)("tr",null,(0,t.createElement)("th",null,"Prompt"),(0,t.createElement)("td",null,s.prompt)),(0,t.createElement)("tr",null,(0,t.createElement)("th",null,"Provider"),(0,t.createElement)("td",null,s.provider)),(0,t.createElement)("tr",null,(0,t.createElement)("th",null,"Quality"),(0,t.createElement)("td",null,s.quality)),(0,t.createElement)("tr",null,(0,t.createElement)("th",null,"Model"),(0,t.createElement)("td",null,s.model)),k.length>0&&(0,t.createElement)("tr",null,(0,t.createElement)("th",null,"References"),(0,t.createElement)("td",null,(0,t.createElement)("div",{className:"kaigen-generation-meta-images"},k.map(e=>(0,t.createElement)("img",{key:e.id,src:e.url,alt:"",className:"kaigen-generation-meta-image"}))))))))))}),(0,h.addFilter)("blocks.registerBlockType","kaigen/add-reference-image-attribute",(e,t)=>"core/image"!==t?e:{...e,attributes:{...e.attributes,kaigen_reference_image:{type:"boolean",default:!1}}})})();
  • kaigen/trunk/inc/class-admin.php

    r3428748 r3437262  
    7373            'block_editor_settings_all',
    7474            function ( $settings ) {
    75                 $provider = get_option( 'kaigen_provider', '' );
     75                $provider = kaigen_provider_manager()->get_active_provider_id();
    7676                $api_keys = get_option( 'kaigen_provider_api_keys', [] );
    77 
    78                 // If no provider is set but we have active providers, use OpenAI if available, otherwise use the first active provider.
    79                 if ( empty( $provider ) ) {
    80                     $active_providers = $this->get_active_providers(); // Get fresh list.
    81                     if ( ! empty( $active_providers ) ) {
    82 
    83                         if ( isset( $api_keys['openai'] ) && ! empty( $api_keys['openai'] ) ) {
    84                             $provider = 'openai';
    85                         } else {
    86                             $provider = $active_providers[0];
    87                         }
    88                     }
    89                 }
    9077
    9178                // Ensure settings is an array.
     
    475462        if ( in_array( $hook, [ 'post.php', 'post-new.php' ], true ) ) {
    476463            // Get the provider setting.
    477             $provider = get_option( 'kaigen_provider', '' );
    478 
    479             // If no provider is set but we have active providers, use OpenAI if available, otherwise use the first active provider.
    480             if ( empty( $provider ) ) {
    481                 $active_providers = $this->get_active_providers(); // Get fresh list instead of cached.
    482                 if ( ! empty( $active_providers ) ) {
    483                     $api_keys = get_option( 'kaigen_provider_api_keys', [] );
    484                     if ( isset( $api_keys['openai'] ) && ! empty( $api_keys['openai'] ) ) {
    485                         $provider = 'openai';
    486                     } else {
    487                         $provider = $active_providers[0];
    488                     }
    489                 }
    490             }
     464            $provider = kaigen_provider_manager()->get_active_provider_id();
    491465
    492466            // Enqueue admin CSS for block editor.
  • kaigen/trunk/inc/class-image-handler.php

    r3424397 r3437262  
    5656        $prompt_slug = substr( $prompt_slug, 0, 50 ); // Limit length.
    5757
     58        $mime_type  = '';
     59        $image_info = getimagesizefromstring( $image_data );
     60        if ( ! empty( $image_info['mime'] ) ) {
     61            $mime_type = $image_info['mime'];
     62        }
     63
     64        // Match the filename extension to the actual image data for reliable attachments (e.g., E2E base64 PNGs).
     65        $extension_map = [
     66            'image/jpeg' => 'jpg',
     67            'image/png'  => 'png',
     68            'image/webp' => 'webp',
     69        ];
     70        $extension     = $extension_map[ $mime_type ] ?? 'webp';
     71
    5872        // Generate a filename with prompt and unique ID.
    59         $filename = 'ai-' . $prompt_slug . '-' . uniqid() . '.webp';
     73        $filename = 'ai-' . $prompt_slug . '-' . uniqid() . '.' . $extension;
    6074
    6175        $upload = wp_upload_bits( $filename, null, $image_data );
  • kaigen/trunk/inc/class-provider-manager.php

    r3424397 r3437262  
    3737     */
    3838    private static $providers_loaded = false;
     39
     40    /**
     41     * Cached alt text generator class name.
     42     *
     43     * @var string|null
     44     */
     45    private static $alt_text_generator_class = null;
    3946
    4047    /**
     
    132139
    133140    /**
     141     * Gets the active provider ID based on settings and stored keys.
     142     *
     143     * @return string Active provider ID or empty string if none configured.
     144     */
     145    public function get_active_provider_id() {
     146        $provider = get_option( 'kaigen_provider', '' );
     147        if ( '' !== $provider ) {
     148            return strtolower( trim( (string) $provider ) );
     149        }
     150
     151        $api_keys = get_option( 'kaigen_provider_api_keys', [] );
     152        if ( ! empty( $api_keys['openai'] ) ) {
     153            return 'openai';
     154        }
     155
     156        foreach ( $api_keys as $provider_id => $api_key ) {
     157            if ( ! empty( $api_key ) ) {
     158                return strtolower( trim( (string) $provider_id ) );
     159            }
     160        }
     161
     162        return '';
     163    }
     164
     165    /**
     166     * Gets the loaded alt text generator class for the active provider.
     167     *
     168     * @return string Loaded generator class name or empty string.
     169     */
     170    public function get_active_alt_text_generator_class() {
     171        $this->load_active_alt_text_generator();
     172
     173        $active = $this->get_active_provider_id();
     174        if ( '' === $active ) {
     175            return '';
     176        }
     177
     178        if ( null !== self::$alt_text_generator_class ) {
     179            return self::$alt_text_generator_class;
     180        }
     181
     182        foreach ( get_declared_classes() as $class ) {
     183            if ( 0 !== strpos( $class, __NAMESPACE__ . '\\' ) ) {
     184                continue;
     185            }
     186
     187            $implements = class_implements( $class );
     188            if ( empty( $implements ) ) {
     189                continue;
     190            }
     191
     192            if ( in_array( Alt_Text_Generator_Core::class, $implements, true ) ) {
     193                self::$alt_text_generator_class = $class;
     194                return $class;
     195            }
     196        }
     197
     198        self::$alt_text_generator_class = '';
     199        return '';
     200    }
     201
     202    /**
     203     * Loads the active alt text generator class file when available.
     204     *
     205     * @return void
     206     */
     207    private function load_active_alt_text_generator() {
     208        $provider = $this->get_active_provider_id();
     209        if ( '' === $provider ) {
     210            return;
     211        }
     212
     213        $provider = sanitize_key( $provider );
     214        $base_dir = plugin_dir_path( __DIR__ ) . 'inc/alt-text/';
     215
     216        require_once $base_dir . 'trait-alt-text-generator-helpers.php';
     217
     218        $class_file = $base_dir . 'class-alt-text-generator-' . $provider . '.php';
     219        if ( file_exists( $class_file ) ) {
     220            require_once $class_file;
     221        }
     222    }
     223
     224    /**
    134225     * Formats a provider name from filename format to class name format.
    135226     *
  • kaigen/trunk/inc/class-rest-api.php

    r3428748 r3437262  
    123123            ]
    124124        );
     125
     126        // Register the alt text generation endpoint.
     127        register_rest_route(
     128            self::API_NAMESPACE,
     129            '/generate-alt-text',
     130            [
     131                'methods'             => 'POST',
     132                'callback'            => [ $this, 'handle_generate_alt_text_request' ],
     133                'permission_callback' => [ $this, 'check_permission' ],
     134            ]
     135        );
    125136    }
    126137
     
    231242
    232243        return new \WP_REST_Response( $meta, 200 );
     244    }
     245
     246    /**
     247     * Generates alt text for an image based on a prompt.
     248     *
     249     * @param WP_REST_Request $request The request object.
     250     * @return \WP_REST_Response|WP_Error The response or error.
     251     */
     252    public function handle_generate_alt_text_request( $request ) {
     253        $prompt = sanitize_textarea_field( (string) $request->get_param( 'prompt' ) );
     254
     255        $provider = sanitize_text_field( (string) $request->get_param( 'provider' ) );
     256        if ( '' === $provider ) {
     257            return new WP_Error( 'invalid_provider', 'Provider is required.', [ 'status' => 400 ] );
     258        }
     259
     260        $attachment_id = absint( $request->get_param( 'attachment_id' ) );
     261        if ( ! $attachment_id ) {
     262            return new WP_Error( 'missing_image', 'Image is required for alt text generation.', [ 'status' => 400 ] );
     263        }
     264
     265        if ( ! current_user_can( 'edit_post', $attachment_id ) ) {
     266            return new WP_Error( 'forbidden', 'You do not have permission to access this attachment.', [ 'status' => 403 ] );
     267        }
     268
     269        $image_data = $this->get_attachment_data_url( $attachment_id );
     270        if ( is_wp_error( $image_data ) ) {
     271            return $image_data;
     272        }
     273
     274        $structured = $this->sanitize_prompt_structured( $request->get_param( 'prompt_structured' ) );
     275        $alt_prompt = $prompt;
     276        if ( ! empty( $structured ) ) {
     277            $alt_prompt = $prompt . "\nStructured details: " . wp_json_encode( $structured );
     278        }
     279
     280        $generator = $this->get_alt_text_generator( $provider );
     281        if ( is_wp_error( $generator ) ) {
     282            return $generator;
     283        }
     284
     285        $result = $generator->generate( $alt_prompt, $image_data );
     286        if ( is_wp_error( $result ) ) {
     287            return $result;
     288        }
     289
     290        return new \WP_REST_Response(
     291            [
     292                'alt_text' => $result,
     293            ],
     294            200
     295        );
    233296    }
    234297
     
    274337
    275338        update_post_meta( $attachment_id, 'kaigen_generation_meta', $meta );
     339    }
     340
     341    /**
     342     * Resolves an alt text generator for the selected provider.
     343     *
     344     * @param string $provider The provider key.
     345     * @return Alt_Text_Generator_Core|WP_Error Generator instance or error.
     346     */
     347    private function get_alt_text_generator( $provider ) {
     348        $provider = strtolower( trim( (string) $provider ) );
     349        $active   = kaigen_provider_manager()->get_active_provider_id();
     350
     351        if ( '' === $active || $provider !== $active ) {
     352            return new WP_Error( 'invalid_provider', 'Alt text generator is not available for the selected provider.', [ 'status' => 400 ] );
     353        }
     354
     355        $class = kaigen_provider_manager()->get_active_alt_text_generator_class();
     356        if ( '' === $class ) {
     357            return new WP_Error( 'invalid_provider', 'Alt text generator class not found.', [ 'status' => 400 ] );
     358        }
     359
     360        if ( ! class_exists( $class ) ) {
     361            return new WP_Error( 'invalid_provider', 'Alt text generator class not found.', [ 'status' => 400 ] );
     362        }
     363
     364        if ( ! in_array( Alt_Text_Generator_Core::class, class_implements( $class ), true ) ) {
     365            return new WP_Error( 'invalid_provider', 'Alt text generator class is invalid.', [ 'status' => 400 ] );
     366        }
     367
     368        return new $class();
     369    }
     370
     371    /**
     372     * Sanitizes a structured prompt into a safe array.
     373     *
     374     * @param mixed $structured The structured prompt data.
     375     * @return array Sanitized structured prompt.
     376     */
     377    private function sanitize_prompt_structured( $structured ) {
     378        if ( is_string( $structured ) ) {
     379            $decoded = json_decode( $structured, true );
     380            if ( is_array( $decoded ) ) {
     381                $structured = $decoded;
     382            }
     383        }
     384
     385        if ( ! is_array( $structured ) ) {
     386            return [];
     387        }
     388
     389        $clean = [];
     390        foreach ( $structured as $key => $phrases ) {
     391            if ( ! is_array( $phrases ) ) {
     392                continue;
     393            }
     394            $key               = sanitize_key( $key );
     395            $sanitized_phrases = [];
     396            foreach ( $phrases as $phrase ) {
     397                if ( ! is_string( $phrase ) ) {
     398                    continue;
     399                }
     400                $phrase = sanitize_text_field( $phrase );
     401                if ( '' !== $phrase ) {
     402                    $sanitized_phrases[] = $phrase;
     403                }
     404            }
     405            if ( ! empty( $sanitized_phrases ) ) {
     406                $clean[ $key ] = $sanitized_phrases;
     407            }
     408        }
     409
     410        return $clean;
     411    }
     412
     413    /**
     414     * Builds a base64 data URL for an attachment image.
     415     *
     416     * @param int $attachment_id Attachment ID.
     417     * @return string|WP_Error Base64 data URL or error.
     418     */
     419    private function get_attachment_data_url( $attachment_id ) {
     420        $file_path = get_attached_file( $attachment_id );
     421        if ( empty( $file_path ) || ! file_exists( $file_path ) ) {
     422            $attachment_url = wp_get_attachment_url( $attachment_id );
     423            if ( empty( $attachment_url ) ) {
     424                return new WP_Error( 'missing_file', 'Attachment file not found.', [ 'status' => 404 ] );
     425            }
     426
     427            $response = wp_remote_get(
     428                $attachment_url,
     429                [
     430                    'timeout' => 10,
     431                ]
     432            );
     433
     434            if ( is_wp_error( $response ) ) {
     435                return new WP_Error( 'file_read_error', 'Unable to fetch attachment data.', [ 'status' => 500 ] );
     436            }
     437
     438            $body         = wp_remote_retrieve_body( $response );
     439            $content_type = wp_remote_retrieve_header( $response, 'content-type' );
     440            $content_len  = wp_remote_retrieve_header( $response, 'content-length' );
     441            if ( $content_len && (int) $content_len > 10 * 1024 * 1024 ) {
     442                return new WP_Error( 'file_too_large', 'Attachment is too large for alt text generation.', [ 'status' => 400 ] );
     443            }
     444
     445            if ( '' === $body ) {
     446                return new WP_Error( 'file_read_error', 'Unable to read attachment data.', [ 'status' => 500 ] );
     447            }
     448
     449            $mime = $content_type ? $content_type : 'image/jpeg';
     450            // phpcs:ignore WordPress.PHP.DiscouragedPHPFunctions.obfuscation_base64_encode -- Needed for image data URL.
     451            return 'data:' . $mime . ';base64,' . base64_encode( $body );
     452        }
     453
     454        $max_size  = 10 * 1024 * 1024;
     455        $file_size = filesize( $file_path );
     456        if ( $file_size && $file_size > $max_size ) {
     457            return new WP_Error( 'file_too_large', 'Attachment is too large for alt text generation.', [ 'status' => 400 ] );
     458        }
     459
     460        // phpcs:ignore WordPress.WP.AlternativeFunctions.file_get_contents_file_get_contents -- Local file read for attachment data.
     461        $contents = file_get_contents( $file_path );
     462        if ( false === $contents ) {
     463            return new WP_Error( 'file_read_error', 'Unable to read attachment data.', [ 'status' => 500 ] );
     464        }
     465
     466        $filetype = wp_check_filetype( $file_path );
     467        $mime     = $filetype['type'] ?? 'image/jpeg';
     468
     469        // phpcs:ignore WordPress.PHP.DiscouragedPHPFunctions.obfuscation_base64_encode -- Needed for image data URL.
     470        return 'data:' . $mime . ';base64,' . base64_encode( $contents );
    276471    }
    277472
  • kaigen/trunk/kaigen.php

    r3428748 r3437262  
    55 * Requires at least: 6.1
    66 * Requires PHP:      7.0
    7  * Version:           0.2.7
     7 * Version:           0.2.8
    88 * Author:            Jacob Schweitzer
    99 * License:           GPL-2.0-or-later
     
    3636require_once __DIR__ . '/inc/class-provider-manager.php';
    3737require_once __DIR__ . '/inc/class-admin.php';
     38require_once __DIR__ . '/inc/alt-text/class-alt-text-generator-core.php';
    3839
    3940// Load REST API functionality.
Note: See TracChangeset for help on using the changeset viewer.