plugin-ai-chat-file-preview 1.0.16 → 1.1.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -1,44 +1,15 @@
1
- # plugin-ai-chat-file-preview
2
-
3
- Enhances the NocoBase AI Employee chat interface by providing a seamless, in-browser popup preview for uploaded file attachments.
4
-
5
- ## Features
6
-
7
- - **Instant File Preview**: Click on any document, image, or spreadsheet attachment in the AI chat window to open a rich modal preview instantly, without navigating away or opening a new tab.
8
- - **Smart Client-side Caching**: Leverages IndexedDB to cache files locally as soon as they are fetched or uploaded, dramatically improving load times and reducing server bandwidth for subsequent previews.
9
- - **Multi-format Support**:
10
- - **PDF**: Native browser iframe rendering
11
- - **Images**: PNG, JPG, GIF, WebP, SVG, BMP
12
- - **Text**: TXT, CSV, HTML, JSON, XML, YAML, MD
13
- - **Documents**: Word (.docx) via `docx-preview`
14
- - **Spreadsheets**: Excel (.xlsx, .xls) via `xlsx`
15
- - **Zero Configuration**: 100% plug-and-play. Hooks directly into the existing NocoBase AI chat system using non-intrusive DOM event interception and global React Providers.
16
- - **Graceful Fallback**: Automatically provides a secure download link containing the proper Bearer Token for un-previewable formats.
17
-
18
- ## Installation
19
-
20
- You can install this plugin in your NocoBase project via the Plugin Manager:
21
-
22
- ```bash
23
- yarn pm add plugin-ai-chat-file-preview
24
- yarn pm enable plugin-ai-chat-file-preview
25
- ```
26
-
27
- *(If utilizing the `.tgz` package directly offline, place it in the application or upload it via UI)*
28
-
29
- ## Technical Details
30
-
31
- - **No Core Modifications**: This plugin relies on catching global DOM click events targeted at `.ant-attachments-file-card` components rendered by `@ant-design/x`, avoiding any necessity to modify the core `@nocobase/plugin-ai`.
32
- - **Session Continuity**: Hooks into the Axios instance to track NocoBase's `sessionId`, ensuring that files are grouped naturally and their Blob caches are completely cleared when a conversation is destroyed.
33
-
34
- ## Usage
35
-
36
- Simply enable the plugin. Once activated:
37
- 1. Open the **AI Chat** panel on any page.
38
- 2. Drag and drop, or select a file to upload.
39
- 3. Once the file appears as a chip in the chat, click it!
40
- 4. The file will pop up in a gorgeous, centered overlay window.
41
-
42
- ## License
43
-
44
- Apache-2.0
1
+ # plugin-ai-chat-file-preview
2
+
3
+ ## Overview
4
+ NocoBase AI Assistant file preview plugin supporting Word, Excel, PDF, CSV, and S3 authentication.
5
+
6
+ ## Features
7
+ - **Multi-format Support**: Natively preview `.docx`, `.xlsx`, `.pdf`, and `.csv` files directly in the browser.
8
+ - **Secure Access**: Integrates with S3 authentication for securely loading private files.
9
+ - **AI Chat Integration**: Allows users to preview attachments uploaded during AI conversations without downloading them.
10
+
11
+ ## Usage
12
+ 1. Ensure the plugin is activated in the NocoBase Plugin Manager.
13
+ 2. In the AI Chat interface, upload a supported document.
14
+ 3. Click on the document thumbnail or link within the chat.
15
+ 4. A modal or inline viewer will render the document content securely.
@@ -7,4 +7,4 @@
7
7
  * For more information, please refer to: https://www.nocobase.com/agreement.
8
8
  */
9
9
 
10
- !function(e,t){"object"==typeof exports&&"object"==typeof module?module.exports=t(require("react"),require("@nocobase/plugin-ai/client"),require("@nocobase/client"),require("antd")):"function"==typeof define&&define.amd?define("plugin-ai-chat-file-preview",["react","@nocobase/plugin-ai/client","@nocobase/client","antd"],t):"object"==typeof exports?exports["plugin-ai-chat-file-preview"]=t(require("react"),require("@nocobase/plugin-ai/client"),require("@nocobase/client"),require("antd")):e["plugin-ai-chat-file-preview"]=t(e.react,e["@nocobase/plugin-ai/client"],e["@nocobase/client"],e.antd)}(self,function(e,t,n,r){return function(){"use strict";var o={772:function(e){e.exports=n},645:function(e){e.exports=t},721:function(e){e.exports=r},156:function(t){t.exports=e}},a={};function i(e){var t=a[e];if(void 0!==t)return t.exports;var n=a[e]={exports:{}};return o[e](n,n.exports,i),n.exports}i.n=function(e){var t=e&&e.__esModule?function(){return e.default}:function(){return e};return i.d(t,{a:t}),t},i.d=function(e,t){for(var n in t)i.o(t,n)&&!i.o(e,n)&&Object.defineProperty(e,n,{enumerable:!0,get:t[n]})},i.o=function(e,t){return Object.prototype.hasOwnProperty.call(e,t)},i.r=function(e){"undefined"!=typeof Symbol&&Symbol.toStringTag&&Object.defineProperty(e,Symbol.toStringTag,{value:"Module"}),Object.defineProperty(e,"__esModule",{value:!0})};var l={};return!function(){i.r(l),i.d(l,{PluginAIChatFilePreviewClient:function(){return A},default:function(){return q}});var e=i(772),t=i(156),n=i.n(t),r=i(645),o=i(721);function a(e,t){(null==t||t>e.length)&&(t=e.length);for(var n=0,r=Array(t);n<t;n++)r[n]=e[n];return r}function c(e,t){for(var n=0;n<t.length;n++){var r=t[n];r.enumerable=r.enumerable||!1,r.configurable=!0,"value"in r&&(r.writable=!0),Object.defineProperty(e,r.key,r)}}function u(e){return(u=Object.setPrototypeOf?Object.getPrototypeOf:function(e){return e.__proto__||Object.getPrototypeOf(e)})(e)}function s(e,t){return null!=t&&"undefined"!=typeof Symbol&&t[Symbol.hasInstance]?!!t[Symbol.hasInstance](e):e instanceof t}function f(e,t){return(f=Object.setPrototypeOf||function(e,t){return e.__proto__=t,e})(e,t)}function d(e,t){return function(e){if(Array.isArray(e))return e}(e)||function(e,t){var n,r,o=null==e?null:"undefined"!=typeof Symbol&&e[Symbol.iterator]||e["@@iterator"];if(null!=o){var a=[],i=!0,l=!1;try{for(o=o.call(e);!(i=(n=o.next()).done)&&(a.push(n.value),!t||a.length!==t);i=!0);}catch(e){l=!0,r=e}finally{try{i||null==o.return||o.return()}finally{if(l)throw r}}return a}}(e,t)||function(e,t){if(e){if("string"==typeof e)return a(e,t);var n=Object.prototype.toString.call(e).slice(8,-1);if("Object"===n&&e.constructor&&(n=e.constructor.name),"Map"===n||"Set"===n)return Array.from(n);if("Arguments"===n||/^(?:Ui|I)nt(?:8|16|32)(?:Clamped)?Array$/.test(n))return a(e,t)}}(e,t)||function(){throw TypeError("Invalid attempt to destructure non-iterable instance.\\nIn order to be iterable, non-array objects must have a [Symbol.iterator]() method.")}()}function p(){try{var e=!Boolean.prototype.valueOf.call(Reflect.construct(Boolean,[],function(){}))}catch(e){}return(p=function(){return!!e})()}function v(e){var t=e.index,r=e.list,a=e.onSwitchIndex,i=null==r?void 0:r[t];if(!i)return null;var l="string"==typeof i?i:null==i?void 0:i.url,c=l&&(l.startsWith("https://")||l.startsWith("http://"))?l:"".concat(window.location.origin,"/").concat((l||"").replace(/^\//,""));return n().createElement(o.Modal,{open:null!=t,title:(null==i?void 0:i.title)||(null==i?void 0:i.filename)||(null==i?void 0:i.name)||"File Preview (Fallback)",onCancel:function(){return a(null)},footer:n().createElement("div",{style:{display:"flex",justifyContent:"flex-end",gap:8}},n().createElement(o.Button,{onClick:function(){return window.open(c,"_blank")}},"Open Default"),n().createElement(o.Button,{onClick:function(){return a(null)}},"Close")),width:"90%",centered:!0},n().createElement("div",{style:{width:"100%",height:"70vh",background:"white",display:"flex",flexDirection:"column"}},n().createElement("iframe",{src:c,style:{width:"100%",height:"100%",border:"none",flex:1}})))}var h=new Map;function m(e){if("A"===e.tagName)return(null==(o=e.textContent)?void 0:o.trim())||"";var t=e.querySelector('[class*="-name"]');if(null==t?void 0:t.textContent)return t.textContent.trim();for(var n=e.querySelectorAll("a"),r=0;r<n.length;r++){var o,a,i,l=null==(i=n[r].textContent)?void 0:i.trim();if(l)return l}var c=e.querySelector('[class*="ellipsis-prefix"]'),u=e.querySelector('[class*="ellipsis-suffix"]');return c&&u?(c.textContent||"")+(u.textContent||""):(null==(a=e.textContent)?void 0:a.trim())||""}function y(e){return{id:e.id,uid:e.uid,url:e.url,filename:e.filename||e.name,name:e.name||e.filename,title:e.title,extname:e.extname,mimetype:e.mimetype,size:e.size}}function b(e,t,n){if(!e)return null;var r=!0,o=!1,a=void 0;try{for(var i,l=t[Symbol.iterator]();!(r=(i=l.next()).done);r=!0){var c=i.value,u=c.content||c,s=null==u?void 0:u.attachments;if(null==s?void 0:s.length){var f=!0,d=!1,p=void 0;try{for(var v,h=s[Symbol.iterator]();!(f=(v=h.next()).done);f=!0){var m=v.value,b=m.filename||m.name||"",g="".concat(m.title||"").concat(m.extname||"");if(b===e||g===e||b&&e.includes(b.replace(/\.[^/.]+$/,""))||m.title&&e.includes(m.title))return y(m)}}catch(e){d=!0,p=e}finally{try{f||null==h.return||h.return()}finally{if(d)throw p}}}}}catch(e){o=!0,a=e}finally{try{r||null==l.return||l.return()}finally{if(o)throw a}}var w=!0,x=!1,E=void 0;try{for(var O,S=(n||[])[Symbol.iterator]();!(w=(O=S.next()).done);w=!0){var k=O.value,C=k.filename||k.name||"",P="".concat(k.title||"").concat(k.extname||"");if(C===e||P===e||C&&e.includes(C.replace(/\.[^/.]+$/,""))||k.title&&e.includes(k.title))return y(k)}}catch(e){x=!0,E=e}finally{try{w||null==S.return||S.return()}finally{if(x)throw E}}return null}function g(e,t,n){if(!e)return null;var r=function(e,t){return!!e&&!!t&&e.split("?")[0].replace(location.origin,"").replace(/^\//,"")===t.split("?")[0].replace(location.origin,"").replace(/^\//,"")},o=!0,a=!1,i=void 0;try{for(var l,c=t[Symbol.iterator]();!(o=(l=c.next()).done);o=!0){var u=l.value,s=u.content||u,f=null==s?void 0:s.attachments;if(null==f?void 0:f.length){var d=!0,p=!1,v=void 0;try{for(var h,m=f[Symbol.iterator]();!(d=(h=m.next()).done);d=!0){var b=h.value;if(r(b.url,e)||r(b.preview,e))return y(b)}}catch(e){p=!0,v=e}finally{try{d||null==m.return||m.return()}finally{if(p)throw v}}}}}catch(e){a=!0,i=e}finally{try{o||null==c.return||c.return()}finally{if(a)throw i}}var g=!0,w=!1,x=void 0;try{for(var E,O=(n||[])[Symbol.iterator]();!(g=(E=O.next()).done);g=!0){var S=E.value;if(r(S.url,e)||r(S.preview,e))return y(S)}}catch(e){w=!0,x=e}finally{try{g||null==O.return||O.return()}finally{if(w)throw x}}return null}var w=function(o){var a=o.children,i=d((0,t.useState)(!1),2),l=i[0],c=i[1],u=d((0,t.useState)(null),2),f=u[0],p=u[1],y=d((0,t.useState)(""),2),w=(y[0],y[1]),x=(0,e.useAPIClient)(),E=r.useChatMessagesStore.use.messages(),O=r.useChatMessagesStore.use.attachments(),S=(0,t.useRef)(E),k=(0,t.useRef)(O);S.current=E,k.current=O;var C=(0,t.useRef)("");(0,t.useEffect)(function(){var e=x.axios.interceptors.request.use(function(e){var t=e.url||"";if(t.includes("aiConversations:getMessages")){var n=t.match(/sessionId=([^&]+)/);n&&(C.current=decodeURIComponent(n[1]))}if(t.includes("aiConversations:sendMessages")&&e.data)try{var r="string"==typeof e.data?JSON.parse(e.data):e.data;(null==r?void 0:r.sessionId)&&(C.current=r.sessionId)}catch(e){}return e});return function(){x.axios.interceptors.request.eject(e)}},[x]),(0,t.useEffect)(function(){var e=setInterval(function(){document.querySelectorAll(".ant-attachment-list-card-name").forEach(function(e){var t=e.getAttribute("href");t&&!t.includes("/api/filePreviewAuth:download")&&e.setAttribute("href","/api/filePreviewAuth:download?url=".concat(encodeURIComponent(t)))}),document.querySelectorAll("a.ai-attachment-link").forEach(function(e){var t=e.getAttribute("href");!t||t.includes("/api/filePreviewAuth:download")||t.includes("skillHub:download")||t.includes("worker-monitor")||e.setAttribute("href","/api/filePreviewAuth:download?url=".concat(encodeURIComponent(t)))}),document.querySelectorAll('.nb-markdown a[href*="skillHub:download"], .nb-markdown a[href*="worker-monitor"]').forEach(function(e){e.classList.contains("ai-attachment-link")||e.classList.add("ai-attachment-link")})},1e3);return function(){return clearInterval(e)}},[]),(0,t.useEffect)(function(){var e=function(e){var t,n=e.target;n&&n.closest&&(n.closest(".ant-x-sender")||n.closest(".ant-x-attachments"))&&(null==(t=e.dataTransfer)?void 0:t.files)&&Array.from(e.dataTransfer.files).forEach(function(e){e.name&&h.set(e.name,e)})},t=function(e){var t=e.target;t&&t.closest&&(t.closest(".ant-x-sender")||t.closest(".ant-x-attachments"))&&(null==t?void 0:t.type)==="file"&&t.files&&Array.from(t.files).forEach(function(e){e.name&&h.set(e.name,e)})};return window.addEventListener("drop",e,!0),document.addEventListener("change",t,!0),function(){window.removeEventListener("drop",e,!0),document.removeEventListener("change",t,!0)}},[]),(0,t.useEffect)(function(){var e;(null==(e=k.current)?void 0:e.length)&&k.current.forEach(function(e){var t=e.originFileObj||e;if(t&&(s(t,Blob)||s(t,File)||"size"in t)){var n=e.name||e.filename||t.name;n&&h.set(n,t)}})},[O]),(0,t.useEffect)(function(){var e=setInterval(function(){var e=document.querySelectorAll(".ant-x-sender, .ant-x-attachments, .ant-x-message"),t=[];e.forEach(function(e){e.querySelectorAll('div[class*="attachment-list-card"]:not([class*="attachment-list-card-"])').forEach(function(e){return t.push(e)}),e.querySelectorAll("a").forEach(function(e){var n=e.href;n&&(n.includes("/api/attachments/")||n.includes("/api/files/download/")||n.includes("/api/worker-monitor/")||n.includes("/api/skillHub:download"))&&(t.push(e),e.classList.contains("ai-attachment-link")||(e.classList.add("ai-attachment-link"),e.classList.add("attachment-list-card")))})}),t.forEach(function(e){var t=m(e),n="";"A"===e.tagName?n=e.href:e.querySelectorAll("a").forEach(function(e){e.href&&(n=e.href)});var r=b(t,S.current,k.current)||g(n,S.current,k.current),o=(null==r?void 0:r.filename)||(null==r?void 0:r.name)||(null==r?void 0:r.title),a=o&&h.has(o)?o:t&&h.has(t)?t:null,i=n&&(n.includes("/api/attachments/")||n.includes("/api/files/download/")||n.includes("/api/worker-monitor/")||n.includes("/api/skillHub:download"));r||a||i?e.classList.add("is-cached-previewable"):e.classList.remove("is-cached-previewable")})},1e3);return function(){return clearInterval(e)}},[]),(0,t.useEffect)(function(){var e=document.createElement("style");return e.innerHTML="\n .ant-attachment-list-card {\n position: relative !important;\n cursor: pointer !important;\n }\n /* Prevent pointer events on inner text and icons so the outer div receives the absolute click */\n .ant-attachment-list-card a,\n .ant-attachment-list-card [class*=\"-icon\"],\n .ant-attachment-list-card span {\n pointer-events: none !important;\n }\n /* Re-enable pointer events for the delete button specifically */\n .ant-attachment-list-card [class*=\"-remove\"],\n .ant-attachment-list-card button,\n .ant-attachment-list-card .ant-btn {\n pointer-events: auto !important;\n }\n /* Visual \"Preview\" badge at the top-left corner using proper SVG */\n .ant-attachment-list-card.is-cached-previewable::after {\n content: '';\n background-image: url(\"data:image/svg+xml,%3Csvg viewBox='64 64 896 896' xmlns='http://www.w3.org/2000/svg' fill='rgba(0,0,0,0.65)'%3E%3Cpath d='M942.2 486.2C847.4 286.5 704.1 186 512 186c-192.2 0-335.4 100.5-430.2 300.3a60.3 60.3 0 000 51.5C176.6 737.5 319.9 838 512 838c192.2 0 335.4-100.5 430.2-300.3 7.7-16.2 7.7-35 0-51.5zM512 766c-161.3 0-279.4-81.8-362.7-254C232.6 339.8 350.7 258 512 258c161.3 0 279.4 81.8 362.7 254C791.5 684.2 673.4 766 512 766zm-4-430c-97.2 0-176 78.8-176 176s78.8 176 176 176 176-78.8 176-176-78.8-176-176-176zm0 288c-61.9 0-112-50.1-112-112s50.1-112 112-112 112 50.1 112 112-50.1 112-112 112z'/%3E%3C/svg%3E\");\n background-size: contain;\n background-repeat: no-repeat;\n position: absolute;\n top: 6px;\n left: 6px;\n width: 14px;\n height: 14px;\n z-index: 10;\n pointer-events: none;\n }\n /* Hide native antd thumbnail icon if we placed an eye so it doesnt look messy */\n .ant-attachment-list-card.is-cached-previewable .ant-upload-list-item-thumbnail {\n opacity: 0.2;\n }\n /* Custom aesthetics for raw AI-generated links to match cards */\n a.ai-attachment-link {\n display: inline-flex;\n align-items: center;\n padding: 8px 12px;\n margin: 4px;\n border: 1px solid #d9d9d9;\n border-radius: 8px;\n background: #fafafa;\n color: rgba(0, 0, 0, 0.88);\n text-decoration: none !important;\n position: relative;\n cursor: pointer !important;\n transition: all 0.2s;\n line-height: 1.5;\n }\n a.ai-attachment-link:hover {\n background: #f0f0f0;\n }\n a.ai-attachment-link::before {\n content: '\uD83D\uDCC4 ';\n margin-right: 8px;\n font-size: 14px;\n }\n a.ai-attachment-link.is-cached-previewable::after {\n content: '';\n background-image: url(\"data:image/svg+xml,%3Csvg viewBox='64 64 896 896' xmlns='http://www.w3.org/2000/svg' fill='rgba(0,0,0,0.65)'%3E%3Cpath d='M942.2 486.2C847.4 286.5 704.1 186 512 186c-192.2 0-335.4 100.5-430.2 300.3a60.3 60.3 0 000 51.5C176.6 737.5 319.9 838 512 838c192.2 0 335.4-100.5 430.2-300.3 7.7-16.2 7.7-35 0-51.5zM512 766c-161.3 0-279.4-81.8-362.7-254C232.6 339.8 350.7 258 512 258c161.3 0 279.4 81.8 362.7 254C791.5 684.2 673.4 766 512 766zm-4-430c-97.2 0-176 78.8-176 176s78.8 176 176 176 176-78.8 176-176-78.8-176-176-176zm0 288c-61.9 0-112-50.1-112-112s50.1-112 112-112 112 50.1 112 112-50.1 112-112 112z'/%3E%3C/svg%3E\");\n background-size: contain;\n background-repeat: no-repeat;\n position: absolute;\n top: -6px;\n left: -6px;\n width: 14px;\n height: 14px;\n z-index: 10;\n pointer-events: none;\n }\n ",document.head.appendChild(e),function(){e.remove()}},[]),(0,t.useEffect)(function(){var e=function(e){var t=e.target;if(!t||"function"!=typeof t.closest)return;var n="",r="",o=t.closest("a"),a=t.closest(".ant-attachment-list-card"),i=t.closest(".ant-tag");if(!(!o&&!a&&!i||t.closest('[class*="-remove"]')||t.closest(".ant-tag-close-icon")||t.closest(".ant-btn"))){a?(r=m(a),"A"===a.tagName?n=a.href:a.querySelectorAll("a").forEach(function(e){e.href&&(n=e.href)})):o?(n=o.href,r=o.textContent||"download"):i&&(r=(null==(d=i.textContent)?void 0:d.trim())||"");var l=n;if(n&&n.includes("/api/filePreviewAuth:download?url="))try{var u=new URL(n,window.location.origin);l=decodeURIComponent(u.searchParams.get("url")||n)}catch(e){}var s=b(r,S.current,k.current)||g(l,S.current,k.current),f=l&&(l.includes("/api/attachments/")||l.includes("/api/files/download/")||l.includes("/api/worker-monitor/")||l.includes("/api/skillHub:download")||l.includes("/storage/uploads/")||l.includes("amazonaws.com"));if(s||f||!o||a){if(!s&&f){var d,v,h=null==(v=l.match(/\.([a-z0-9]+)(?:[\?#]|$)/i))?void 0:v[1];s={id:l,uid:l,url:l,filename:r||"attachment",name:r||"attachment",extname:h?".".concat(h):void 0,mimetype:""}}if(s||f){e.preventDefault(),e.stopPropagation();var y,x,E=!l||l.includes("skillHub:download")||l.includes("worker-monitor")?l||s.url:"/api/filePreviewAuth:download?url=".concat(encodeURIComponent(l));y=function(e){for(var t=1;t<arguments.length;t++){var n=null!=arguments[t]?arguments[t]:{},r=Object.keys(n);"function"==typeof Object.getOwnPropertySymbols&&(r=r.concat(Object.getOwnPropertySymbols(n).filter(function(e){return Object.getOwnPropertyDescriptor(n,e).enumerable}))),r.forEach(function(t){var r;r=n[t],t in e?Object.defineProperty(e,t,{value:r,enumerable:!0,configurable:!0,writable:!0}):e[t]=r})}return e}({},s),x=x={url:E},Object.getOwnPropertyDescriptors?Object.defineProperties(y,Object.getOwnPropertyDescriptors(x)):(function(e,t){var n=Object.keys(e);if(Object.getOwnPropertySymbols){var r=Object.getOwnPropertySymbols(e);n.push.apply(n,r)}return n})(Object(x)).forEach(function(e){Object.defineProperty(y,e,Object.getOwnPropertyDescriptor(x,e))}),s=y,w(C.current||""),p(s),c(!0)}}}};return document.addEventListener("click",e,{capture:!0}),function(){return document.removeEventListener("click",e,{capture:!0})}},[]);var P=(0,t.useCallback)(function(){c(!1),p(null)},[]),j=(0,t.useMemo)(function(){if(!f||!l)return null;var t=e.attachmentFileTypes.getTypeByFile(f);return(null==t?void 0:t.Previewer)||v},[f,l]);return n().createElement(n().Fragment,null,a,j&&l&&f&&n().createElement(j,{index:0,list:[f],onSwitchIndex:function(e){null===e&&P()}}))},x=function(e){var t,n;if("function"!=typeof e&&null!==e)throw TypeError("Super expression must either be null or a function");function r(e){var t,n,o;if(!(this instanceof r))throw TypeError("Cannot call a class as a function");return n=r,o=[e],n=u(n),(t=function(e,t){var n;if(t&&("object"==((n=t)&&"undefined"!=typeof Symbol&&n.constructor===Symbol?"symbol":typeof n)||"function"==typeof t))return t;if(void 0===e)throw ReferenceError("this hasn't been initialised - super() hasn't been called");return e}(this,p()?Reflect.construct(n,o||[],u(this).constructor):n.apply(this,o))).state={hasError:!1},t}return r.prototype=Object.create(e&&e.prototype,{constructor:{value:r,writable:!0,configurable:!0}}),e&&f(r,e),n=[{key:"getDerivedStateFromError",value:function(){return{hasError:!0}}}],t=[{key:"render",value:function(){return this.state.hasError,this.props.children}}],c(r.prototype,t),n&&c(r,n),r}(n().Component),E=function(e){var t=e.children;return n().createElement(x,null,n().createElement(w,null,t))};function O(e,t,n,r,o,a,i){try{var l=e[a](i),c=l.value}catch(e){n(e);return}l.done?t(c):Promise.resolve(c).then(r,o)}function S(e,t,n){return(S=j()?Reflect.construct:function(e,t,n){var r=[null];r.push.apply(r,t);var o=new(Function.bind.apply(e,r));return n&&C(o,n.prototype),o}).apply(null,arguments)}function k(e){return(k=Object.setPrototypeOf?Object.getPrototypeOf:function(e){return e.__proto__||Object.getPrototypeOf(e)})(e)}function C(e,t){return(C=Object.setPrototypeOf||function(e,t){return e.__proto__=t,e})(e,t)}function P(e){var t="function"==typeof Map?new Map:void 0;return(P=function(e){if(null===e||-1===Function.toString.call(e).indexOf("[native code]"))return e;if("function"!=typeof e)throw TypeError("Super expression must either be null or a function");if(void 0!==t){if(t.has(e))return t.get(e);t.set(e,n)}function n(){return S(e,arguments,k(this).constructor)}return n.prototype=Object.create(e.prototype,{constructor:{value:n,enumerable:!1,writable:!0,configurable:!0}}),C(n,e)})(e)}function j(){try{var e=!Boolean.prototype.valueOf.call(Reflect.construct(Boolean,[],function(){}))}catch(e){}return(j=function(){return!!e})()}var A=function(e){var t;if("function"!=typeof e&&null!==e)throw TypeError("Super expression must either be null or a function");function n(){var e,t;if(!(this instanceof n))throw TypeError("Cannot call a class as a function");return e=n,t=arguments,e=k(e),function(e,t){var n;if(t&&("object"==((n=t)&&"undefined"!=typeof Symbol&&n.constructor===Symbol?"symbol":typeof n)||"function"==typeof t))return t;if(void 0===e)throw ReferenceError("this hasn't been initialised - super() hasn't been called");return e}(this,j()?Reflect.construct(e,t||[],k(this).constructor):e.apply(this,t))}return n.prototype=Object.create(e&&e.prototype,{constructor:{value:n,writable:!0,configurable:!0}}),e&&C(n,e),t=[{key:"load",value:function(){var e,t=this;return(e=function(){return function(e,t){var n,r,o,a,i={label:0,sent:function(){if(1&o[0])throw o[1];return o[1]},trys:[],ops:[]};return a={next:l(0),throw:l(1),return:l(2)},"function"==typeof Symbol&&(a[Symbol.iterator]=function(){return this}),a;function l(a){return function(l){var c=[a,l];if(n)throw TypeError("Generator is already executing.");for(;i;)try{if(n=1,r&&(o=2&c[0]?r.return:c[0]?r.throw||((o=r.return)&&o.call(r),0):r.next)&&!(o=o.call(r,c[1])).done)return o;switch(r=0,o&&(c=[2&c[0],o.value]),c[0]){case 0:case 1:o=c;break;case 4:return i.label++,{value:c[1],done:!1};case 5:i.label++,r=c[1],c=[0];continue;case 7:c=i.ops.pop(),i.trys.pop();continue;default:if(!(o=(o=i.trys).length>0&&o[o.length-1])&&(6===c[0]||2===c[0])){i=0;continue}if(3===c[0]&&(!o||c[1]>o[0]&&c[1]<o[3])){i.label=c[1];break}if(6===c[0]&&i.label<o[1]){i.label=o[1],o=c;break}if(o&&i.label<o[2]){i.label=o[2],i.ops.push(c);break}o[2]&&i.ops.pop(),i.trys.pop();continue}c=t.call(e,i)}catch(e){c=[6,e],r=0}finally{n=o=0}if(5&c[0])throw c[1];return{value:c[0]?c[1]:void 0,done:!0}}}}(this,function(e){return t.app.use(E),[2]})},function(){var t=this,n=arguments;return new Promise(function(r,o){var a=e.apply(t,n);function i(e){O(a,r,o,i,l,"next",e)}function l(e){O(a,r,o,i,l,"throw",e)}i(void 0)})})()}}],function(e,t){for(var n=0;n<t.length;n++){var r=t[n];r.enumerable=r.enumerable||!1,r.configurable=!0,"value"in r&&(r.writable=!0),Object.defineProperty(e,r.key,r)}}(n.prototype,t),n}(P(e.Plugin)),q=A}(),l}()});
10
+ !function(e,t){"object"==typeof exports&&"object"==typeof module?module.exports=t(require("react"),require("@nocobase/plugin-ai/client"),require("@nocobase/client"),require("antd")):"function"==typeof define&&define.amd?define("plugin-ai-chat-file-preview",["react","@nocobase/plugin-ai/client","@nocobase/client","antd"],t):"object"==typeof exports?exports["plugin-ai-chat-file-preview"]=t(require("react"),require("@nocobase/plugin-ai/client"),require("@nocobase/client"),require("antd")):e["plugin-ai-chat-file-preview"]=t(e.react,e["@nocobase/plugin-ai/client"],e["@nocobase/client"],e.antd)}(self,function(e,t,n,r){return function(){"use strict";var o={772:function(e){e.exports=n},645:function(e){e.exports=t},721:function(e){e.exports=r},156:function(t){t.exports=e}},a={};function i(e){var t=a[e];if(void 0!==t)return t.exports;var n=a[e]={exports:{}};return o[e](n,n.exports,i),n.exports}i.n=function(e){var t=e&&e.__esModule?function(){return e.default}:function(){return e};return i.d(t,{a:t}),t},i.d=function(e,t){for(var n in t)i.o(t,n)&&!i.o(e,n)&&Object.defineProperty(e,n,{enumerable:!0,get:t[n]})},i.o=function(e,t){return Object.prototype.hasOwnProperty.call(e,t)},i.r=function(e){"undefined"!=typeof Symbol&&Symbol.toStringTag&&Object.defineProperty(e,Symbol.toStringTag,{value:"Module"}),Object.defineProperty(e,"__esModule",{value:!0})};var l={};return!function(){i.r(l),i.d(l,{PluginAIChatFilePreviewClient:function(){return z},default:function(){return R}});var e=i(772),t=i(156),n=i.n(t),r=i(645),o=i(721);function a(e,t){(null==t||t>e.length)&&(t=e.length);for(var n=0,r=Array(t);n<t;n++)r[n]=e[n];return r}function c(e,t){for(var n=0;n<t.length;n++){var r=t[n];r.enumerable=r.enumerable||!1,r.configurable=!0,"value"in r&&(r.writable=!0),Object.defineProperty(e,r.key,r)}}function u(e){return(u=Object.setPrototypeOf?Object.getPrototypeOf:function(e){return e.__proto__||Object.getPrototypeOf(e)})(e)}function s(e,t){return null!=t&&"undefined"!=typeof Symbol&&t[Symbol.hasInstance]?!!t[Symbol.hasInstance](e):e instanceof t}function f(e,t){return(f=Object.setPrototypeOf||function(e,t){return e.__proto__=t,e})(e,t)}function d(e,t){return function(e){if(Array.isArray(e))return e}(e)||function(e,t){var n,r,o=null==e?null:"undefined"!=typeof Symbol&&e[Symbol.iterator]||e["@@iterator"];if(null!=o){var a=[],i=!0,l=!1;try{for(o=o.call(e);!(i=(n=o.next()).done)&&(a.push(n.value),!t||a.length!==t);i=!0);}catch(e){l=!0,r=e}finally{try{i||null==o.return||o.return()}finally{if(l)throw r}}return a}}(e,t)||function(e,t){if(e){if("string"==typeof e)return a(e,t);var n=Object.prototype.toString.call(e).slice(8,-1);if("Object"===n&&e.constructor&&(n=e.constructor.name),"Map"===n||"Set"===n)return Array.from(n);if("Arguments"===n||/^(?:Ui|I)nt(?:8|16|32)(?:Clamped)?Array$/.test(n))return a(e,t)}}(e,t)||function(){throw TypeError("Invalid attempt to destructure non-iterable instance.\\nIn order to be iterable, non-array objects must have a [Symbol.iterator]() method.")}()}function p(){try{var e=!Boolean.prototype.valueOf.call(Reflect.construct(Boolean,[],function(){}))}catch(e){}return(p=function(){return!!e})()}function v(e){var t=e.index,r=e.list,a=e.onSwitchIndex,i=null==r?void 0:r[t];if(!i)return null;var l="string"==typeof i?i:null==i?void 0:i.url,c=l&&(l.startsWith("https://")||l.startsWith("http://"))?l:"".concat(window.location.origin,"/").concat((l||"").replace(/^\//,""));return n().createElement(o.Modal,{open:null!=t,title:(null==i?void 0:i.title)||(null==i?void 0:i.filename)||(null==i?void 0:i.name)||"File Preview (Fallback)",onCancel:function(){return a(null)},footer:n().createElement("div",{style:{display:"flex",justifyContent:"flex-end",gap:8}},n().createElement(o.Button,{onClick:function(){return window.open(c,"_blank")}},"Open Default"),n().createElement(o.Button,{onClick:function(){return a(null)}},"Close")),width:"90%",centered:!0},n().createElement("div",{style:{width:"100%",height:"70vh",background:"white",display:"flex",flexDirection:"column"}},n().createElement("iframe",{src:c,style:{width:"100%",height:"100%",border:"none",flex:1}})))}var h=new Map,m=RegExp("[^\\s\"'<>()[\\]{}]+\\.(".concat("doc|docx|xls|xlsx|pdf|csv|txt|ppt|pptx|zip|rar|7z|png|jpg|jpeg|gif|webp|svg",")(?=$|[\\s\"'<>()[\\]{},.;:!?])"),"gi");function y(e){return e.trim().replace(/^[\s"'`(<[{]+/,"").replace(/[\s"'`)>}\].,;:!?]+$/,"")}function b(e){var t=y(e);if(!t)return"";var n=Array.from(t.matchAll(m)),r=n[n.length-1];return(null==r?void 0:r[0])?y(r[0]):t}function g(e){return!!e&&(e.includes("/api/attachments/")||e.includes("/api/files/download/")||e.includes("/api/worker-monitor/")||e.includes("/api/skillHub:download")||e.includes("/storage/uploads/")||e.includes("amazonaws.com"))}function w(e){if("A"===e.tagName)return(null==(o=e.textContent)?void 0:o.trim())||"";var t=e.querySelector('[class*="-name"]');if(null==t?void 0:t.textContent)return t.textContent.trim();for(var n=e.querySelectorAll("a"),r=0;r<n.length;r++){var o,a,i,l=null==(i=n[r].textContent)?void 0:i.trim();if(l)return l}var c=e.querySelector('[class*="ellipsis-prefix"]'),u=e.querySelector('[class*="ellipsis-suffix"]');return c&&u?(c.textContent||"")+(u.textContent||""):(null==(a=e.textContent)?void 0:a.trim())||""}function x(e){return{id:e.id,uid:e.uid,url:e.url,filename:e.filename||e.name,name:e.name||e.filename,title:e.title,extname:e.extname,mimetype:e.mimetype,size:e.size}}function E(e,t,n){if(!e)return null;var r=Array.from(new Set([e,b(e)].map(function(e){return y(e)}).filter(Boolean))),o=!0,a=!1,i=void 0;try{for(var l,c=t[Symbol.iterator]();!(o=(l=c.next()).done);o=!0){var u=l.value,s=u.content||u,f=null==s?void 0:s.attachments;if(null==f?void 0:f.length){var d=!0,p=!1,v=void 0;try{for(var h,m=f[Symbol.iterator]();!(d=(h=m.next()).done);d=!0){var g=h.value,w=g.filename||g.name||"",E="".concat(g.title||"").concat(g.extname||"");if(r.includes(w)||r.includes(E))return x(g);var S=!0,O=!1,k=void 0;try{for(var C,P=r[Symbol.iterator]();!(S=(C=P.next()).done);S=!0){var j=C.value;if(w&&j.includes(w.replace(/\.[^/.]+$/,""))||g.title&&j.includes(g.title))return x(g)}}catch(e){O=!0,k=e}finally{try{S||null==P.return||P.return()}finally{if(O)throw k}}}}catch(e){p=!0,v=e}finally{try{d||null==m.return||m.return()}finally{if(p)throw v}}}}}catch(e){a=!0,i=e}finally{try{o||null==c.return||c.return()}finally{if(a)throw i}}var A=!0,I=!1,q=void 0;try{for(var _,z=(n||[])[Symbol.iterator]();!(A=(_=z.next()).done);A=!0){var R=_.value,M=R.filename||R.name||"",L="".concat(R.title||"").concat(R.extname||"");if(r.includes(M)||r.includes(L))return x(R);var T=!0,F=!1,B=void 0;try{for(var D,U=r[Symbol.iterator]();!(T=(D=U.next()).done);T=!0){var H=D.value;if(M&&H.includes(M.replace(/\.[^/.]+$/,""))||R.title&&H.includes(R.title))return x(R)}}catch(e){F=!0,B=e}finally{try{T||null==U.return||U.return()}finally{if(F)throw B}}}}catch(e){I=!0,q=e}finally{try{A||null==z.return||z.return()}finally{if(I)throw q}}return null}function S(e,t,n){if(!e)return null;var r=function(e,t){return!!e&&!!t&&e.split("?")[0].replace(location.origin,"").replace(/^\//,"")===t.split("?")[0].replace(location.origin,"").replace(/^\//,"")},o=!0,a=!1,i=void 0;try{for(var l,c=t[Symbol.iterator]();!(o=(l=c.next()).done);o=!0){var u=l.value,s=u.content||u,f=null==s?void 0:s.attachments;if(null==f?void 0:f.length){var d=!0,p=!1,v=void 0;try{for(var h,m=f[Symbol.iterator]();!(d=(h=m.next()).done);d=!0){var y=h.value;if(r(y.url,e)||r(y.preview,e))return x(y)}}catch(e){p=!0,v=e}finally{try{d||null==m.return||m.return()}finally{if(p)throw v}}}}}catch(e){a=!0,i=e}finally{try{o||null==c.return||c.return()}finally{if(a)throw i}}var b=!0,g=!1,w=void 0;try{for(var E,S=(n||[])[Symbol.iterator]();!(b=(E=S.next()).done);b=!0){var O=E.value;if(r(O.url,e)||r(O.preview,e))return x(O)}}catch(e){g=!0,w=e}finally{try{b||null==S.return||S.return()}finally{if(g)throw w}}return null}var O=function(o){var a,i=o.children,l=d((0,t.useState)(!1),2),c=l[0],u=l[1],f=d((0,t.useState)(null),2),p=f[0],m=f[1],y=d((0,t.useState)(""),2),x=(y[0],y[1]),O=(0,e.useAPIClient)(),k=r.useChatMessagesStore.use.messages(),C=r.useChatMessagesStore.use.attachments(),P=(0,t.useRef)(k),j=(0,t.useRef)(C);P.current=k,j.current=C;var A=(0,t.useRef)("");(0,t.useEffect)(function(){var e=O.axios.interceptors.request.use(function(e){var t=e.url||"";if(t.includes("aiConversations:getMessages")){var n=t.match(/sessionId=([^&]+)/);n&&(A.current=decodeURIComponent(n[1]))}if(t.includes("aiConversations:sendMessages")&&e.data)try{var r="string"==typeof e.data?JSON.parse(e.data):e.data;(null==r?void 0:r.sessionId)&&(A.current=r.sessionId)}catch(e){}return e});return function(){O.axios.interceptors.request.eject(e)}},[O]);var I=(null==(a=O.auth)?void 0:a.token)||"";(0,t.useEffect)(function(){var e=setInterval(function(){var e=function(e){return!I||e.includes("token=")?e:e+(e.includes("?")?"&":"?")+"token=".concat(I)};document.querySelectorAll('a[href*="/api/attachments"]').forEach(function(t){var n=t.getAttribute("href");n&&!n.includes("/api/filePreviewAuth:download")&&t.setAttribute("href",e("/api/filePreviewAuth:download?url=".concat(encodeURIComponent(n))))}),document.querySelectorAll("a.ai-attachment-link").forEach(function(t){var n=t.getAttribute("href");!n||n.includes("/api/filePreviewAuth:download")||n.includes("skillHub:download")||n.includes("worker-monitor")||t.setAttribute("href",e("/api/filePreviewAuth:download?url=".concat(encodeURIComponent(n))))}),document.querySelectorAll('img[src*="/api/attachments"]').forEach(function(t){var n=t.getAttribute("src");n&&!n.includes("/api/filePreviewAuth:download")&&t.setAttribute("src",e("/api/filePreviewAuth:download?url=".concat(encodeURIComponent(n))))}),document.querySelectorAll('.nb-markdown a[href*="skillHub:download"], .nb-markdown a[href*="worker-monitor"]').forEach(function(e){e.classList.contains("ai-attachment-link")||e.classList.add("ai-attachment-link")})},1e3);return function(){return clearInterval(e)}},[I]),(0,t.useEffect)(function(){var e=function(e){var t,n=e.target;n&&n.closest&&(n.closest(".ant-x-sender")||n.closest(".ant-x-attachments"))&&(null==(t=e.dataTransfer)?void 0:t.files)&&Array.from(e.dataTransfer.files).forEach(function(e){e.name&&h.set(e.name,e)})},t=function(e){var t=e.target;t&&t.closest&&(t.closest(".ant-x-sender")||t.closest(".ant-x-attachments"))&&(null==t?void 0:t.type)==="file"&&t.files&&Array.from(t.files).forEach(function(e){e.name&&h.set(e.name,e)})};return window.addEventListener("drop",e,!0),document.addEventListener("change",t,!0),function(){window.removeEventListener("drop",e,!0),document.removeEventListener("change",t,!0)}},[]),(0,t.useEffect)(function(){var e;(null==(e=j.current)?void 0:e.length)&&j.current.forEach(function(e){var t=e.originFileObj||e;if(t&&(s(t,Blob)||s(t,File)||"size"in t)){var n=e.name||e.filename||t.name;n&&h.set(n,t)}})},[C]),(0,t.useEffect)(function(){var e=setInterval(function(){var e=document.querySelectorAll(".ant-x-sender, .ant-x-attachments, .ant-x-message"),t=[];e.forEach(function(e){e.querySelectorAll('div[class*="attachment-list-card"]:not([class*="attachment-list-card-"])').forEach(function(e){return t.push(e)}),e.querySelectorAll("a").forEach(function(e){var n=e.href;n&&(n.includes("/api/attachments/")||n.includes("/api/files/download/")||n.includes("/api/worker-monitor/")||n.includes("/api/skillHub:download"))&&(t.push(e),e.classList.contains("ai-attachment-link")||(e.classList.add("ai-attachment-link"),e.classList.add("attachment-list-card")))})}),t.forEach(function(e){var t=w(e),n="";"A"===e.tagName?n=e.href:e.querySelectorAll("a").forEach(function(e){e.href&&(n=e.href)});var r=E(t,P.current,j.current)||S(n,P.current,j.current),o=(null==r?void 0:r.filename)||(null==r?void 0:r.name)||(null==r?void 0:r.title),a=b(t),i=o&&h.has(o)?o:t&&h.has(t)?t:a&&h.has(a)?a:null,l=g(n);r||i||l?e.classList.add("is-cached-previewable"):e.classList.remove("is-cached-previewable")})},1e3);return function(){return clearInterval(e)}},[]),(0,t.useEffect)(function(){var e=document.createElement("style");return e.innerHTML="\n .ant-attachment-list-card {\n position: relative !important;\n cursor: pointer !important;\n }\n /* Prevent pointer events on inner text and icons so the outer div receives the absolute click */\n .ant-attachment-list-card a,\n .ant-attachment-list-card [class*=\"-icon\"],\n .ant-attachment-list-card span {\n pointer-events: none !important;\n }\n /* Re-enable pointer events for the delete button specifically */\n .ant-attachment-list-card [class*=\"-remove\"],\n .ant-attachment-list-card button,\n .ant-attachment-list-card .ant-btn {\n pointer-events: auto !important;\n }\n /* Visual \"Preview\" badge at the top-left corner using proper SVG */\n .ant-attachment-list-card.is-cached-previewable::after {\n content: '';\n background-image: url(\"data:image/svg+xml,%3Csvg viewBox='64 64 896 896' xmlns='http://www.w3.org/2000/svg' fill='rgba(0,0,0,0.65)'%3E%3Cpath d='M942.2 486.2C847.4 286.5 704.1 186 512 186c-192.2 0-335.4 100.5-430.2 300.3a60.3 60.3 0 000 51.5C176.6 737.5 319.9 838 512 838c192.2 0 335.4-100.5 430.2-300.3 7.7-16.2 7.7-35 0-51.5zM512 766c-161.3 0-279.4-81.8-362.7-254C232.6 339.8 350.7 258 512 258c161.3 0 279.4 81.8 362.7 254C791.5 684.2 673.4 766 512 766zm-4-430c-97.2 0-176 78.8-176 176s78.8 176 176 176 176-78.8 176-176-78.8-176-176-176zm0 288c-61.9 0-112-50.1-112-112s50.1-112 112-112 112 50.1 112 112-50.1 112-112 112z'/%3E%3C/svg%3E\");\n background-size: contain;\n background-repeat: no-repeat;\n position: absolute;\n top: 6px;\n left: 6px;\n width: 14px;\n height: 14px;\n z-index: 10;\n pointer-events: none;\n }\n /* Hide native antd thumbnail icon if we placed an eye so it doesnt look messy */\n .ant-attachment-list-card.is-cached-previewable .ant-upload-list-item-thumbnail {\n opacity: 0.2;\n }\n /* Custom aesthetics for raw AI-generated links to match cards */\n a.ai-attachment-link {\n display: inline-flex;\n align-items: center;\n padding: 8px 12px;\n margin: 4px;\n border: 1px solid #d9d9d9;\n border-radius: 8px;\n background: #fafafa;\n color: rgba(0, 0, 0, 0.88);\n text-decoration: none !important;\n position: relative;\n cursor: pointer !important;\n transition: all 0.2s;\n line-height: 1.5;\n }\n a.ai-attachment-link:hover {\n background: #f0f0f0;\n }\n a.ai-attachment-link::before {\n content: '\uD83D\uDCC4 ';\n margin-right: 8px;\n font-size: 14px;\n }\n a.ai-attachment-link.is-cached-previewable::after {\n content: '';\n background-image: url(\"data:image/svg+xml,%3Csvg viewBox='64 64 896 896' xmlns='http://www.w3.org/2000/svg' fill='rgba(0,0,0,0.65)'%3E%3Cpath d='M942.2 486.2C847.4 286.5 704.1 186 512 186c-192.2 0-335.4 100.5-430.2 300.3a60.3 60.3 0 000 51.5C176.6 737.5 319.9 838 512 838c192.2 0 335.4-100.5 430.2-300.3 7.7-16.2 7.7-35 0-51.5zM512 766c-161.3 0-279.4-81.8-362.7-254C232.6 339.8 350.7 258 512 258c161.3 0 279.4 81.8 362.7 254C791.5 684.2 673.4 766 512 766zm-4-430c-97.2 0-176 78.8-176 176s78.8 176 176 176 176-78.8 176-176-78.8-176-176-176zm0 288c-61.9 0-112-50.1-112-112s50.1-112 112-112 112 50.1 112 112-50.1 112-112 112z'/%3E%3C/svg%3E\");\n background-size: contain;\n background-repeat: no-repeat;\n position: absolute;\n top: -6px;\n left: -6px;\n width: 14px;\n height: 14px;\n z-index: 10;\n pointer-events: none;\n }\n ",document.head.appendChild(e),function(){e.remove()}},[]),(0,t.useEffect)(function(){var e=function(e){var t=e.target;if(!t||"function"!=typeof t.closest)return;var n="",r="",o=t.closest("a"),a=t.closest(".ant-attachment-list-card"),i=t.closest(".ant-tag");if(!(!o&&!a&&!i||t.closest('[class*="-remove"]')||t.closest(".ant-tag-close-icon")||t.closest(".ant-btn"))){a?(r=w(a),"A"===a.tagName?n=a.href:a.querySelectorAll("a").forEach(function(e){e.href&&(n=e.href)})):o?(n=o.href,r=o.textContent||"download"):i&&(r=(null==(p=i.textContent)?void 0:p.trim())||"");var l=n;if(n&&n.includes("/api/filePreviewAuth:download?url="))try{var c=new URL(n,window.location.origin);l=decodeURIComponent(c.searchParams.get("url")||n)}catch(e){}var s=E(r,P.current,j.current)||S(l,P.current,j.current),f=b(r),d=g(l);if(s||d||!o||a){if(!s&&d){var p,v,h,y=(null==(v=f.match(/\.([a-z0-9]+)$/i))?void 0:v[1])||(null==(h=l.match(/\.([a-z0-9]+)(?:[\?#]|$)/i))?void 0:h[1]);s={id:l,uid:l,url:l,filename:f||"attachment",name:f||"attachment",extname:y?".".concat(y):void 0,mimetype:""}}if(s||d){e.preventDefault(),e.stopPropagation();var O,k,C=s.url||l,I=C&&!C.includes("skillHub:download")&&!C.includes("worker-monitor")&&!C.includes("filePreviewAuth:download"),q=C;if(I){q="/api/filePreviewAuth:download?url=".concat(encodeURIComponent(C));var _=s.collectionName||(d?"aiFiles":""),z=s.storage_id||s.storageId;_&&(q+="&collection=".concat(encodeURIComponent(_))),z&&(q+="&storageId=".concat(encodeURIComponent(z)))}O=function(e){for(var t=1;t<arguments.length;t++){var n=null!=arguments[t]?arguments[t]:{},r=Object.keys(n);"function"==typeof Object.getOwnPropertySymbols&&(r=r.concat(Object.getOwnPropertySymbols(n).filter(function(e){return Object.getOwnPropertyDescriptor(n,e).enumerable}))),r.forEach(function(t){var r;r=n[t],t in e?Object.defineProperty(e,t,{value:r,enumerable:!0,configurable:!0,writable:!0}):e[t]=r})}return e}({},s),k=k={url:q},Object.getOwnPropertyDescriptors?Object.defineProperties(O,Object.getOwnPropertyDescriptors(k)):(function(e,t){var n=Object.keys(e);if(Object.getOwnPropertySymbols){var r=Object.getOwnPropertySymbols(e);n.push.apply(n,r)}return n})(Object(k)).forEach(function(e){Object.defineProperty(O,e,Object.getOwnPropertyDescriptor(k,e))}),s=O,x(A.current||""),m(s),u(!0)}}}};return document.addEventListener("click",e,{capture:!0}),function(){return document.removeEventListener("click",e,{capture:!0})}},[]);var q=(0,t.useCallback)(function(){u(!1),m(null)},[]),_=(0,t.useMemo)(function(){if(!p||!c)return null;var t=e.attachmentFileTypes.getTypeByFile(p);return(null==t?void 0:t.Previewer)||v},[p,c]);return n().createElement(n().Fragment,null,i,_&&c&&p&&n().createElement(_,{index:0,list:[p],onSwitchIndex:function(e){null===e&&q()}}))},k=function(e){var t,n;if("function"!=typeof e&&null!==e)throw TypeError("Super expression must either be null or a function");function r(e){var t,n,o;if(!(this instanceof r))throw TypeError("Cannot call a class as a function");return n=r,o=[e],n=u(n),(t=function(e,t){var n;if(t&&("object"==((n=t)&&"undefined"!=typeof Symbol&&n.constructor===Symbol?"symbol":typeof n)||"function"==typeof t))return t;if(void 0===e)throw ReferenceError("this hasn't been initialised - super() hasn't been called");return e}(this,p()?Reflect.construct(n,o||[],u(this).constructor):n.apply(this,o))).state={hasError:!1},t}return r.prototype=Object.create(e&&e.prototype,{constructor:{value:r,writable:!0,configurable:!0}}),e&&f(r,e),n=[{key:"getDerivedStateFromError",value:function(){return{hasError:!0}}}],t=[{key:"render",value:function(){return this.state.hasError,this.props.children}}],c(r.prototype,t),n&&c(r,n),r}(n().Component),C=function(e){var t=e.children;return n().createElement(k,null,n().createElement(O,null,t))};function P(e,t,n,r,o,a,i){try{var l=e[a](i),c=l.value}catch(e){n(e);return}l.done?t(c):Promise.resolve(c).then(r,o)}function j(e,t,n){return(j=_()?Reflect.construct:function(e,t,n){var r=[null];r.push.apply(r,t);var o=new(Function.bind.apply(e,r));return n&&I(o,n.prototype),o}).apply(null,arguments)}function A(e){return(A=Object.setPrototypeOf?Object.getPrototypeOf:function(e){return e.__proto__||Object.getPrototypeOf(e)})(e)}function I(e,t){return(I=Object.setPrototypeOf||function(e,t){return e.__proto__=t,e})(e,t)}function q(e){var t="function"==typeof Map?new Map:void 0;return(q=function(e){if(null===e||-1===Function.toString.call(e).indexOf("[native code]"))return e;if("function"!=typeof e)throw TypeError("Super expression must either be null or a function");if(void 0!==t){if(t.has(e))return t.get(e);t.set(e,n)}function n(){return j(e,arguments,A(this).constructor)}return n.prototype=Object.create(e.prototype,{constructor:{value:n,enumerable:!1,writable:!0,configurable:!0}}),I(n,e)})(e)}function _(){try{var e=!Boolean.prototype.valueOf.call(Reflect.construct(Boolean,[],function(){}))}catch(e){}return(_=function(){return!!e})()}var z=function(e){var t;if("function"!=typeof e&&null!==e)throw TypeError("Super expression must either be null or a function");function n(){var e,t;if(!(this instanceof n))throw TypeError("Cannot call a class as a function");return e=n,t=arguments,e=A(e),function(e,t){var n;if(t&&("object"==((n=t)&&"undefined"!=typeof Symbol&&n.constructor===Symbol?"symbol":typeof n)||"function"==typeof t))return t;if(void 0===e)throw ReferenceError("this hasn't been initialised - super() hasn't been called");return e}(this,_()?Reflect.construct(e,t||[],A(this).constructor):e.apply(this,t))}return n.prototype=Object.create(e&&e.prototype,{constructor:{value:n,writable:!0,configurable:!0}}),e&&I(n,e),t=[{key:"load",value:function(){var e,t=this;return(e=function(){return function(e,t){var n,r,o,a,i={label:0,sent:function(){if(1&o[0])throw o[1];return o[1]},trys:[],ops:[]};return a={next:l(0),throw:l(1),return:l(2)},"function"==typeof Symbol&&(a[Symbol.iterator]=function(){return this}),a;function l(a){return function(l){var c=[a,l];if(n)throw TypeError("Generator is already executing.");for(;i;)try{if(n=1,r&&(o=2&c[0]?r.return:c[0]?r.throw||((o=r.return)&&o.call(r),0):r.next)&&!(o=o.call(r,c[1])).done)return o;switch(r=0,o&&(c=[2&c[0],o.value]),c[0]){case 0:case 1:o=c;break;case 4:return i.label++,{value:c[1],done:!1};case 5:i.label++,r=c[1],c=[0];continue;case 7:c=i.ops.pop(),i.trys.pop();continue;default:if(!(o=(o=i.trys).length>0&&o[o.length-1])&&(6===c[0]||2===c[0])){i=0;continue}if(3===c[0]&&(!o||c[1]>o[0]&&c[1]<o[3])){i.label=c[1];break}if(6===c[0]&&i.label<o[1]){i.label=o[1],o=c;break}if(o&&i.label<o[2]){i.label=o[2],i.ops.push(c);break}o[2]&&i.ops.pop(),i.trys.pop();continue}c=t.call(e,i)}catch(e){c=[6,e],r=0}finally{n=o=0}if(5&c[0])throw c[1];return{value:c[0]?c[1]:void 0,done:!0}}}}(this,function(e){return t.app.use(C),[2]})},function(){var t=this,n=arguments;return new Promise(function(r,o){var a=e.apply(t,n);function i(e){P(a,r,o,i,l,"next",e)}function l(e){P(a,r,o,i,l,"throw",e)}i(void 0)})})()}}],function(e,t){for(var n=0;n<t.length;n++){var r=t[n];r.enumerable=r.enumerable||!1,r.configurable=!0,"value"in r&&(r.writable=!0),Object.defineProperty(e,r.key,r)}}(n.prototype,t),n}(q(e.Plugin)),R=z}(),l}()});
@@ -9,8 +9,8 @@
9
9
 
10
10
  module.exports = {
11
11
  "react": "18.2.0",
12
- "@nocobase/client": "2.0.38",
13
- "@nocobase/plugin-ai": "2.0.38",
12
+ "@nocobase/client": "2.0.47",
13
+ "@nocobase/plugin-ai": "2.0.47",
14
14
  "antd": "5.24.2",
15
- "@nocobase/server": "2.0.38"
15
+ "@nocobase/server": "2.0.47"
16
16
  };
package/package.json CHANGED
@@ -1,32 +1,32 @@
1
- {
2
- "name": "plugin-ai-chat-file-preview",
3
- "displayName": "AI Chat File Preview",
4
- "displayName.vi-VN": "Xem trước file trong Chat AI",
5
- "displayName.zh-CN": "AI聊天文件预览",
6
- "description": "NocoBase AI Assistant 预览附件(Word, Excel, PDF, CSV...)支持 S3 auth",
7
- "description.vi-VN": "Preview file attachment tren AI Chat (Word, Excel, PDF, CSV...) kem S3 auth",
8
- "description.zh-CN": "在AI员工聊天中直接预览上传的文件附件,支持浏览器缓存。",
9
- "version": "1.0.16",
10
- "main": "dist/server/index.js",
11
- "dependencies": {},
12
- "peerDependencies": {
13
- "@nocobase/client": "2.x",
14
- "@nocobase/server": "2.x",
15
- "@nocobase/database": "2.x",
16
- "@nocobase/plugin-ai": "2.x",
17
- "@nocobase/plugin-file-manager": "2.x"
18
- },
19
- "nocobase": {
20
- "supportedVersions": [
21
- "2.x"
22
- ]
23
- },
24
- "devDependencies": {},
25
- "keywords": [
26
- "AI",
27
- "Chat",
28
- "File preview",
29
- "Cache"
30
- ],
31
- "license": "Apache-2.0"
32
- }
1
+ {
2
+ "name": "plugin-ai-chat-file-preview",
3
+ "displayName": "AI Chat File Preview",
4
+ "displayName.vi-VN": "Xem trước file trong Chat AI",
5
+ "displayName.zh-CN": "AI聊天文件预览",
6
+ "description": "NocoBase AI Assistant 预览附件(Word, Excel, PDF, CSV...)支持 S3 auth",
7
+ "description.vi-VN": "Preview file attachment tren AI Chat (Word, Excel, PDF, CSV...) kem S3 auth",
8
+ "description.zh-CN": "在AI员工聊天中直接预览上传的文件附件,支持浏览器缓存。",
9
+ "version": "1.1.2",
10
+ "main": "dist/server/index.js",
11
+ "dependencies": {},
12
+ "peerDependencies": {
13
+ "@nocobase/client": "2.x",
14
+ "@nocobase/server": "2.x",
15
+ "@nocobase/database": "2.x",
16
+ "@nocobase/plugin-ai": "2.x",
17
+ "@nocobase/plugin-file-manager": "2.x"
18
+ },
19
+ "nocobase": {
20
+ "supportedVersions": [
21
+ "2.x"
22
+ ]
23
+ },
24
+ "devDependencies": {},
25
+ "keywords": [
26
+ "AI",
27
+ "Chat",
28
+ "File preview",
29
+ "Cache"
30
+ ],
31
+ "license": "Apache-2.0"
32
+ }
@@ -73,12 +73,77 @@ function FallbackModalPreviewer({ index, list, onSwitchIndex }: any) {
73
73
  }
74
74
 
75
75
  // Define a reliable, context-isolated module-level RAM cache independent of the window object
76
- export const AppRamCache = new Map<string, File | Blob>();
77
-
78
- /**
79
- * Extract displayed filename from a FileListCard DOM element.
80
- */
81
- function getDisplayNameFromCard(cardEl: HTMLElement): string {
76
+ export const AppRamCache = new Map<string, File | Blob>();
77
+
78
+ const FILE_EXTENSIONS = [
79
+ 'doc',
80
+ 'docx',
81
+ 'xls',
82
+ 'xlsx',
83
+ 'pdf',
84
+ 'csv',
85
+ 'txt',
86
+ 'ppt',
87
+ 'pptx',
88
+ 'zip',
89
+ 'rar',
90
+ '7z',
91
+ 'png',
92
+ 'jpg',
93
+ 'jpeg',
94
+ 'gif',
95
+ 'webp',
96
+ 'svg',
97
+ ];
98
+
99
+ const FILENAME_IN_TEXT_RE = new RegExp(
100
+ `[^\\s"'<>()[\\]{}]+\\.(${FILE_EXTENSIONS.join('|')})(?=$|[\\s"'<>()[\\]{},.;:!?])`,
101
+ 'gi',
102
+ );
103
+
104
+ function stripFilenameNoise(value: string): string {
105
+ return value
106
+ .trim()
107
+ .replace(/^[\s"'`(<[{]+/, '')
108
+ .replace(/[\s"'`)>}\].,;:!?]+$/, '');
109
+ }
110
+
111
+ function extractFilenameFromText(value: string): string {
112
+ const text = stripFilenameNoise(value);
113
+ if (!text) return '';
114
+
115
+ const matches = Array.from(text.matchAll(FILENAME_IN_TEXT_RE));
116
+ const lastMatch = matches[matches.length - 1];
117
+ if (lastMatch?.[0]) {
118
+ return stripFilenameNoise(lastMatch[0]);
119
+ }
120
+
121
+ return text;
122
+ }
123
+
124
+ function getDisplayNameCandidates(displayName: string): string[] {
125
+ const candidates = [displayName, extractFilenameFromText(displayName)]
126
+ .map((value) => stripFilenameNoise(value))
127
+ .filter(Boolean);
128
+
129
+ return Array.from(new Set(candidates));
130
+ }
131
+
132
+ function isKnownFileUrl(url?: string): boolean {
133
+ return !!url && (
134
+ url.includes('/api/attachments/') ||
135
+ url.includes('/api/files/download/') ||
136
+ url.includes('/api/worker-monitor/') ||
137
+ url.includes('/api/skillHub:download') ||
138
+ url.includes('/storage/uploads/') ||
139
+ url.includes('amazonaws.com')
140
+ );
141
+ }
142
+
143
+ /**
144
+ * Extract displayed filename from a FileListCard DOM element.
145
+ */
146
+ function getDisplayNameFromCard(cardEl: HTMLElement): string {
82
147
  if (cardEl.tagName === 'A') return cardEl.textContent?.trim() || '';
83
148
 
84
149
  // 1. Try antd generic filename classes
@@ -120,49 +185,54 @@ function attToPreviewFile(att: any): PreviewFile {
120
185
  /**
121
186
  * Find file metadata matching the displayed filename across all message attachments.
122
187
  */
123
- function findFileByDisplayName(displayName: string, messages: any[], pendingAttachments: any[]): PreviewFile | null {
124
- if (!displayName) return null;
125
-
126
- // Search sent messages
127
- for (const msg of messages) {
188
+ function findFileByDisplayName(displayName: string, messages: any[], pendingAttachments: any[]): PreviewFile | null {
189
+ if (!displayName) return null;
190
+ const displayNames = getDisplayNameCandidates(displayName);
191
+
192
+ // Search sent messages
193
+ for (const msg of messages) {
128
194
  const content = msg.content || msg;
129
195
  const attachments = content?.attachments;
130
196
  if (!attachments?.length) continue;
131
197
 
132
- for (const att of attachments) {
133
- const attName = att.filename || att.name || '';
134
- const attTitleExt = `${att.title || ''}${att.extname || ''}`;
135
- if (attName === displayName || attTitleExt === displayName) {
136
- return attToPreviewFile(att);
137
- }
138
-
139
- // Relaxed match for NocoBase hashed file names
140
- if (
141
- (attName && displayName.includes(attName.replace(/\.[^/.]+$/, ''))) ||
142
- (att.title && displayName.includes(att.title))
143
- ) {
144
- return attToPreviewFile(att);
145
- }
146
- }
147
- }
198
+ for (const att of attachments) {
199
+ const attName = att.filename || att.name || '';
200
+ const attTitleExt = `${att.title || ''}${att.extname || ''}`;
201
+ if (displayNames.includes(attName) || displayNames.includes(attTitleExt)) {
202
+ return attToPreviewFile(att);
203
+ }
204
+
205
+ // Relaxed match for NocoBase hashed file names
206
+ for (const name of displayNames) {
207
+ if (
208
+ (attName && name.includes(attName.replace(/\.[^/.]+$/, ''))) ||
209
+ (att.title && name.includes(att.title))
210
+ ) {
211
+ return attToPreviewFile(att);
212
+ }
213
+ }
214
+ }
215
+ }
148
216
 
149
217
  // Search pending (not yet sent) attachments
150
218
  for (const att of pendingAttachments || []) {
151
- const attName = att.filename || att.name || '';
152
- const attTitleExt = `${att.title || ''}${att.extname || ''}`;
153
- if (attName === displayName || attTitleExt === displayName) {
154
- return attToPreviewFile(att);
155
- }
156
-
157
- // Relaxed match for NocoBase hashed file names (e.g. report.docx-c2ywti.docx)
158
- if (
159
- (attName && displayName.includes(attName.replace(/\.[^/.]+$/, ''))) ||
160
- (att.title && displayName.includes(att.title))
161
- ) {
162
- return attToPreviewFile(att);
163
- }
164
- }
165
-
219
+ const attName = att.filename || att.name || '';
220
+ const attTitleExt = `${att.title || ''}${att.extname || ''}`;
221
+ if (displayNames.includes(attName) || displayNames.includes(attTitleExt)) {
222
+ return attToPreviewFile(att);
223
+ }
224
+
225
+ // Relaxed match for NocoBase hashed file names (e.g. report.docx-c2ywti.docx)
226
+ for (const name of displayNames) {
227
+ if (
228
+ (attName && name.includes(attName.replace(/\.[^/.]+$/, ''))) ||
229
+ (att.title && name.includes(att.title))
230
+ ) {
231
+ return attToPreviewFile(att);
232
+ }
233
+ }
234
+ }
235
+
166
236
  return null;
167
237
  }
168
238
 
@@ -246,26 +316,44 @@ const ChatFilePreviewInner: React.FC<{ children: React.ReactNode }> = ({ childre
246
316
  };
247
317
  }, [apiClient]);
248
318
 
319
+ const token = apiClient.auth?.token || '';
320
+
249
321
  // Track global drop and input change events to intercept file object selection ONLY for AI chat
250
322
  useEffect(() => {
251
- // Modify href attributes of native NocoBase file links in chat to use the proxy
323
+ // Modify href attributes of native NocoBase file links and images in chat to use the proxy and append the token
252
324
  const rewriteObtrusiveLinks = () => {
253
- const links = document.querySelectorAll<HTMLAnchorElement>('.ant-attachment-list-card-name');
325
+ const appendToken = (url: string) => {
326
+ if (!token) return url;
327
+ if (url.includes('token=')) return url;
328
+ return url + (url.includes('?') ? '&' : '?') + `token=${token}`;
329
+ };
330
+
331
+ const links = document.querySelectorAll<HTMLAnchorElement>('a[href*="/api/attachments"]');
254
332
  links.forEach(link => {
255
333
  const href = link.getAttribute('href');
256
334
  if (href && !href.includes('/api/filePreviewAuth:download')) {
257
- link.setAttribute('href', `/api/filePreviewAuth:download?url=${encodeURIComponent(href)}`);
335
+ link.setAttribute('href', appendToken(`/api/filePreviewAuth:download?url=${encodeURIComponent(href)}`));
258
336
  }
259
337
  });
338
+
260
339
  // Also rewrite ai-attachment-link anchors
261
340
  const aiLinks = document.querySelectorAll<HTMLAnchorElement>('a.ai-attachment-link');
262
341
  aiLinks.forEach(link => {
263
342
  const href = link.getAttribute('href');
264
343
  if (href && !href.includes('/api/filePreviewAuth:download') && !href.includes('skillHub:download') && !href.includes('worker-monitor')) {
265
- link.setAttribute('href', `/api/filePreviewAuth:download?url=${encodeURIComponent(href)}`);
344
+ link.setAttribute('href', appendToken(`/api/filePreviewAuth:download?url=${encodeURIComponent(href)}`));
266
345
  }
267
346
  });
268
347
 
348
+ // Also rewrite image tags so thumbnails don't 404
349
+ const imgs = document.querySelectorAll<HTMLImageElement>('img[src*="/api/attachments"]');
350
+ imgs.forEach(img => {
351
+ const src = img.getAttribute('src');
352
+ if (src && !src.includes('/api/filePreviewAuth:download')) {
353
+ img.setAttribute('src', appendToken(`/api/filePreviewAuth:download?url=${encodeURIComponent(src)}`));
354
+ }
355
+ });
356
+
269
357
  // Auto-style raw markdown links for Skill Hub and Worker Monitor as interactive file attachments
270
358
  const rawFileLinks = document.querySelectorAll<HTMLAnchorElement>('.nb-markdown a[href*="skillHub:download"], .nb-markdown a[href*="worker-monitor"]');
271
359
  rawFileLinks.forEach(link => {
@@ -278,7 +366,7 @@ const ChatFilePreviewInner: React.FC<{ children: React.ReactNode }> = ({ childre
278
366
  // Run periodically to catch newly rendered chat messages
279
367
  const timer = setInterval(rewriteObtrusiveLinks, 1000);
280
368
  return () => clearInterval(timer);
281
- }, []);
369
+ }, [token]);
282
370
 
283
371
  useEffect(() => {
284
372
  const handleDrop = (e: DragEvent) => {
@@ -371,13 +459,15 @@ const ChatFilePreviewInner: React.FC<{ children: React.ReactNode }> = ({ childre
371
459
  // Resolve real file name from data store
372
460
  const file = findFileByDisplayName(displayName, messagesRef.current, pendingAttachmentsRef.current) ||
373
461
  findFileByUrl(fallbackUrl, messagesRef.current, pendingAttachmentsRef.current);
374
- const realName = file?.filename || file?.name || file?.title;
375
-
376
- // Strict RAM cache check
377
- const cacheHitName = realName && AppRamCache.has(realName) ? realName
378
- : (displayName && AppRamCache.has(displayName) ? displayName : null);
379
-
380
- const isAIGenerated = fallbackUrl && (fallbackUrl.includes('/api/attachments/') || fallbackUrl.includes('/api/files/download/') || fallbackUrl.includes('/api/worker-monitor/') || fallbackUrl.includes('/api/skillHub:download'));
462
+ const realName = file?.filename || file?.name || file?.title;
463
+ const normalizedDisplayName = extractFilenameFromText(displayName);
464
+
465
+ // Strict RAM cache check
466
+ const cacheHitName = realName && AppRamCache.has(realName) ? realName
467
+ : (displayName && AppRamCache.has(displayName) ? displayName
468
+ : (normalizedDisplayName && AppRamCache.has(normalizedDisplayName) ? normalizedDisplayName : null));
469
+
470
+ const isAIGenerated = isKnownFileUrl(fallbackUrl);
381
471
 
382
472
  if (file || cacheHitName || isAIGenerated) {
383
473
  el.classList.add('is-cached-previewable');
@@ -521,31 +611,27 @@ const ChatFilePreviewInner: React.FC<{ children: React.ReactNode }> = ({ childre
521
611
  let file = findFileByDisplayName(displayName, messagesRef.current, pendingAttachmentsRef.current) ||
522
612
  findFileByUrl(originalFallbackUrl, messagesRef.current, pendingAttachmentsRef.current);
523
613
 
524
- const isAIGenerated = originalFallbackUrl && (
525
- originalFallbackUrl.includes('/api/attachments/') ||
526
- originalFallbackUrl.includes('/api/files/download/') ||
527
- originalFallbackUrl.includes('/api/worker-monitor/') ||
528
- originalFallbackUrl.includes('/api/skillHub:download') ||
529
- originalFallbackUrl.includes('/storage/uploads/') ||
530
- originalFallbackUrl.includes('amazonaws.com') // Ensure only explicitly known S3 formats are caught if not in messagesRef
531
- );
614
+ const normalizedDisplayName = extractFilenameFromText(displayName);
615
+ const isAIGenerated = isKnownFileUrl(originalFallbackUrl);
532
616
 
533
617
  // If we clicked a completely unrelated anchor tag in the admin panel and it's not a known file, abort immediately
534
618
  if (!file && !isAIGenerated && anchorNode && !cardEl) {
535
619
  return;
536
620
  }
537
621
 
538
- if (!file && isAIGenerated) {
539
- const extname = originalFallbackUrl.match(/\.([a-z0-9]+)(?:[\?#]|$)/i)?.[1];
540
- file = {
541
- id: originalFallbackUrl,
542
- uid: originalFallbackUrl,
543
- url: originalFallbackUrl,
544
- filename: displayName || 'attachment',
545
- name: displayName || 'attachment',
546
- extname: extname ? `.${extname}` : undefined,
547
- mimetype: '',
548
- } as PreviewFile;
622
+ if (!file && isAIGenerated) {
623
+ const extname =
624
+ normalizedDisplayName.match(/\.([a-z0-9]+)$/i)?.[1] ||
625
+ originalFallbackUrl.match(/\.([a-z0-9]+)(?:[\?#]|$)/i)?.[1];
626
+ file = {
627
+ id: originalFallbackUrl,
628
+ uid: originalFallbackUrl,
629
+ url: originalFallbackUrl,
630
+ filename: normalizedDisplayName || 'attachment',
631
+ name: normalizedDisplayName || 'attachment',
632
+ extname: extname ? `.${extname}` : undefined,
633
+ mimetype: '',
634
+ } as PreviewFile;
549
635
  }
550
636
 
551
637
  if (!file && !isAIGenerated) return;
@@ -554,8 +640,23 @@ const ChatFilePreviewInner: React.FC<{ children: React.ReactNode }> = ({ childre
554
640
  e.stopPropagation();
555
641
 
556
642
  // Convert to secure proxy URL for everything EXCEPT natively secured endpoints that don't belong to the attachments table
557
- const shouldUseProxy = originalFallbackUrl && !originalFallbackUrl.includes('skillHub:download') && !originalFallbackUrl.includes('worker-monitor');
558
- const secureUrl = shouldUseProxy ? `/api/filePreviewAuth:download?url=${encodeURIComponent(originalFallbackUrl)}` : (originalFallbackUrl || file.url);
643
+ const proxyTargetUrl = file.url || originalFallbackUrl;
644
+ const shouldUseProxy = proxyTargetUrl && !proxyTargetUrl.includes('skillHub:download') && !proxyTargetUrl.includes('worker-monitor') && !proxyTargetUrl.includes('filePreviewAuth:download');
645
+
646
+ let secureUrl = proxyTargetUrl;
647
+ if (shouldUseProxy) {
648
+ secureUrl = `/api/filePreviewAuth:download?url=${encodeURIComponent(proxyTargetUrl)}`;
649
+ const collectionName = (file as any).collectionName || (isAIGenerated ? 'aiFiles' : '');
650
+ const storageId = (file as any).storage_id || (file as any).storageId;
651
+
652
+ if (collectionName) {
653
+ secureUrl += `&collection=${encodeURIComponent(collectionName)}`;
654
+ }
655
+ if (storageId) {
656
+ secureUrl += `&storageId=${encodeURIComponent(storageId)}`;
657
+ }
658
+ }
659
+
559
660
  file = { ...file, url: secureUrl };
560
661
 
561
662
  setSessionId(currentSessionIdRef.current || '');