Changeset 3437262
- Timestamp:
- 01/11/2026 10:22:51 PM (3 months ago)
- Location:
- kaigen
- Files:
-
- 12 added
- 14 edited
- 1 copied
-
tags/v0.2.8 (copied) (copied from kaigen/trunk)
-
tags/v0.2.8/build/index.asset.php (modified) (1 diff)
-
tags/v0.2.8/build/index.js (modified) (1 diff)
-
tags/v0.2.8/inc/alt-text (added)
-
tags/v0.2.8/inc/alt-text/class-alt-text-generator-core.php (added)
-
tags/v0.2.8/inc/alt-text/class-alt-text-generator-openai.php (added)
-
tags/v0.2.8/inc/alt-text/class-alt-text-generator-replicate.php (added)
-
tags/v0.2.8/inc/alt-text/trait-alt-text-generator-helpers.php (added)
-
tags/v0.2.8/inc/class-admin.php (modified) (2 diffs)
-
tags/v0.2.8/inc/class-image-handler.php (modified) (1 diff)
-
tags/v0.2.8/inc/class-provider-manager.php (modified) (2 diffs)
-
tags/v0.2.8/inc/class-rest-api.php (modified) (3 diffs)
-
tags/v0.2.8/kaigen-wordpress-plugin (added)
-
tags/v0.2.8/kaigen.php (modified) (2 diffs)
-
trunk/build/index.asset.php (modified) (1 diff)
-
trunk/build/index.js (modified) (1 diff)
-
trunk/inc/alt-text (added)
-
trunk/inc/alt-text/class-alt-text-generator-core.php (added)
-
trunk/inc/alt-text/class-alt-text-generator-openai.php (added)
-
trunk/inc/alt-text/class-alt-text-generator-replicate.php (added)
-
trunk/inc/alt-text/trait-alt-text-generator-helpers.php (added)
-
trunk/inc/class-admin.php (modified) (2 diffs)
-
trunk/inc/class-image-handler.php (modified) (1 diff)
-
trunk/inc/class-provider-manager.php (modified) (2 diffs)
-
trunk/inc/class-rest-api.php (modified) (3 diffs)
-
trunk/kaigen-wordpress-plugin (added)
-
trunk/kaigen.php (modified) (2 diffs)
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 73 73 'block_editor_settings_all', 74 74 function ( $settings ) { 75 $provider = get_option( 'kaigen_provider', '');75 $provider = kaigen_provider_manager()->get_active_provider_id(); 76 76 $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 }90 77 91 78 // Ensure settings is an array. … … 475 462 if ( in_array( $hook, [ 'post.php', 'post-new.php' ], true ) ) { 476 463 // 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(); 491 465 492 466 // Enqueue admin CSS for block editor. -
kaigen/tags/v0.2.8/inc/class-image-handler.php
r3424397 r3437262 56 56 $prompt_slug = substr( $prompt_slug, 0, 50 ); // Limit length. 57 57 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 58 72 // Generate a filename with prompt and unique ID. 59 $filename = 'ai-' . $prompt_slug . '-' . uniqid() . '. webp';73 $filename = 'ai-' . $prompt_slug . '-' . uniqid() . '.' . $extension; 60 74 61 75 $upload = wp_upload_bits( $filename, null, $image_data ); -
kaigen/tags/v0.2.8/inc/class-provider-manager.php
r3424397 r3437262 37 37 */ 38 38 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; 39 46 40 47 /** … … 132 139 133 140 /** 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 /** 134 225 * Formats a provider name from filename format to class name format. 135 226 * -
kaigen/tags/v0.2.8/inc/class-rest-api.php
r3428748 r3437262 123 123 ] 124 124 ); 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 ); 125 136 } 126 137 … … 231 242 232 243 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 ); 233 296 } 234 297 … … 274 337 275 338 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 ); 276 471 } 277 472 -
kaigen/tags/v0.2.8/kaigen.php
r3428748 r3437262 5 5 * Requires at least: 6.1 6 6 * Requires PHP: 7.0 7 * Version: 0.2. 77 * Version: 0.2.8 8 8 * Author: Jacob Schweitzer 9 9 * License: GPL-2.0-or-later … … 36 36 require_once __DIR__ . '/inc/class-provider-manager.php'; 37 37 require_once __DIR__ . '/inc/class-admin.php'; 38 require_once __DIR__ . '/inc/alt-text/class-alt-text-generator-core.php'; 38 39 39 40 // 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 73 73 'block_editor_settings_all', 74 74 function ( $settings ) { 75 $provider = get_option( 'kaigen_provider', '');75 $provider = kaigen_provider_manager()->get_active_provider_id(); 76 76 $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 }90 77 91 78 // Ensure settings is an array. … … 475 462 if ( in_array( $hook, [ 'post.php', 'post-new.php' ], true ) ) { 476 463 // 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(); 491 465 492 466 // Enqueue admin CSS for block editor. -
kaigen/trunk/inc/class-image-handler.php
r3424397 r3437262 56 56 $prompt_slug = substr( $prompt_slug, 0, 50 ); // Limit length. 57 57 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 58 72 // Generate a filename with prompt and unique ID. 59 $filename = 'ai-' . $prompt_slug . '-' . uniqid() . '. webp';73 $filename = 'ai-' . $prompt_slug . '-' . uniqid() . '.' . $extension; 60 74 61 75 $upload = wp_upload_bits( $filename, null, $image_data ); -
kaigen/trunk/inc/class-provider-manager.php
r3424397 r3437262 37 37 */ 38 38 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; 39 46 40 47 /** … … 132 139 133 140 /** 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 /** 134 225 * Formats a provider name from filename format to class name format. 135 226 * -
kaigen/trunk/inc/class-rest-api.php
r3428748 r3437262 123 123 ] 124 124 ); 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 ); 125 136 } 126 137 … … 231 242 232 243 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 ); 233 296 } 234 297 … … 274 337 275 338 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 ); 276 471 } 277 472 -
kaigen/trunk/kaigen.php
r3428748 r3437262 5 5 * Requires at least: 6.1 6 6 * Requires PHP: 7.0 7 * Version: 0.2. 77 * Version: 0.2.8 8 8 * Author: Jacob Schweitzer 9 9 * License: GPL-2.0-or-later … … 36 36 require_once __DIR__ . '/inc/class-provider-manager.php'; 37 37 require_once __DIR__ . '/inc/class-admin.php'; 38 require_once __DIR__ . '/inc/alt-text/class-alt-text-generator-core.php'; 38 39 39 40 // Load REST API functionality.
Note: See TracChangeset
for help on using the changeset viewer.