@amd-gaia/agent-ui 0.24.0 → 0.25.0-rc.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 +4 -3
- package/bin/gaia-ui.cjs +30 -12
- package/dist/assets/AdvancedSettings-Ce6EvcAe.css +1 -0
- package/dist/assets/AdvancedSettings-DabrR6sf.js +1 -0
- package/dist/assets/ConnectorsPane-CG7atLb2.css +1 -0
- package/dist/assets/ConnectorsPane-KLjT1y3F.js +1 -0
- package/dist/assets/DocumentLibrary-CElkmNvc.js +1 -0
- package/dist/assets/DocumentLibrary-DrIP_032.css +1 -0
- package/dist/assets/FileBrowser-Bj6aB6Ro.css +1 -0
- package/dist/assets/FileBrowser-ROrpBhVm.js +4 -0
- package/dist/assets/MemoryDashboard-B-YH4b1Z.css +1 -0
- package/dist/assets/MemoryDashboard-DG9P8Bqf.js +1 -0
- package/dist/assets/MobileAccessModal-C_t8DG0n.css +1 -0
- package/dist/assets/MobileAccessModal-Jub4U6L2.js +2 -0
- package/dist/assets/ScheduleManager-BStuwsjn.js +2 -0
- package/dist/assets/ScheduleManager-CIescbX6.css +1 -0
- package/dist/assets/{browser-D6fty1nl.js → browser-CCrYwriZ.js} +2 -2
- package/dist/assets/index-B8E7IBmc.css +1 -0
- package/dist/assets/index-CXbVMk3u.js +58 -0
- package/dist/assets/jsx-runtime-DVeaULmT.js +1 -0
- package/dist/assets/message-square-Ccmbk96j.js +1 -0
- package/dist/assets/rolldown-runtime-hePW80VL.js +1 -0
- package/dist/index.html +7 -2
- package/main.cjs +133 -42
- package/package.json +9 -9
- package/services/backend-installer-progress-dialog.cjs +34 -15
- package/services/backend-installer.cjs +29 -13
- package/services/deep-link-queue.cjs +77 -0
- package/services/link-policy.cjs +90 -0
- package/services/notification-service.cjs +36 -14
- package/services/tray-manager.cjs +163 -23
- package/dist/assets/index-C0wmkHcD.js +0 -69
- package/dist/assets/index-D1S2dWYe.css +0 -1
|
@@ -0,0 +1 @@
|
|
|
1
|
+
import{r as e,t}from"./rolldown-runtime-hePW80VL.js";var n=t((e=>{var t=Symbol.for(`react.transitional.element`),n=Symbol.for(`react.portal`),r=Symbol.for(`react.fragment`),i=Symbol.for(`react.strict_mode`),a=Symbol.for(`react.profiler`),o=Symbol.for(`react.consumer`),s=Symbol.for(`react.context`),c=Symbol.for(`react.forward_ref`),l=Symbol.for(`react.suspense`),u=Symbol.for(`react.memo`),d=Symbol.for(`react.lazy`),f=Symbol.for(`react.activity`),p=Symbol.for(`react.view_transition`),m=Symbol.iterator;function h(e){return typeof e!=`object`||!e?null:(e=m&&e[m]||e[`@@iterator`],typeof e==`function`?e:null)}var g={isMounted:function(){return!1},enqueueForceUpdate:function(){},enqueueReplaceState:function(){},enqueueSetState:function(){}},_=Object.assign,v={};function y(e,t,n){this.props=e,this.context=t,this.refs=v,this.updater=n||g}y.prototype.isReactComponent={},y.prototype.setState=function(e,t){if(typeof e!=`object`&&typeof e!=`function`&&e!=null)throw Error(`takes an object of state variables to update or a function which returns an object of state variables.`);this.updater.enqueueSetState(this,e,t,`setState`)},y.prototype.forceUpdate=function(e){this.updater.enqueueForceUpdate(this,e,`forceUpdate`)};function b(){}b.prototype=y.prototype;function x(e,t,n){this.props=e,this.context=t,this.refs=v,this.updater=n||g}var S=x.prototype=new b;S.constructor=x,_(S,y.prototype),S.isPureReactComponent=!0;var C=Array.isArray;function w(){}var T={H:null,A:null,T:null,S:null},E=Object.prototype.hasOwnProperty;function D(e,n,r){var i=r.ref;return{$$typeof:t,type:e,key:n,ref:i===void 0?null:i,props:r}}function O(e,t){return D(e.type,t,e.props)}function k(e){return typeof e==`object`&&!!e&&e.$$typeof===t}function A(e){var t={"=":`=0`,":":`=2`};return`$`+e.replace(/[=:]/g,function(e){return t[e]})}var j=/\/+/g;function M(e,t){return typeof e==`object`&&e&&e.key!=null?A(``+e.key):t.toString(36)}function N(e){switch(e.status){case`fulfilled`:return e.value;case`rejected`:throw e.reason;default:switch(typeof e.status==`string`?e.then(w,w):(e.status=`pending`,e.then(function(t){e.status===`pending`&&(e.status=`fulfilled`,e.value=t)},function(t){e.status===`pending`&&(e.status=`rejected`,e.reason=t)})),e.status){case`fulfilled`:return e.value;case`rejected`:throw e.reason}}throw e}function P(e,r,i,a,o){var s=typeof e;(s===`undefined`||s===`boolean`)&&(e=null);var c=!1;if(e===null)c=!0;else switch(s){case`bigint`:case`string`:case`number`:c=!0;break;case`object`:switch(e.$$typeof){case t:case n:c=!0;break;case d:return c=e._init,P(c(e._payload),r,i,a,o)}}if(c)return o=o(e),c=a===``?`.`+M(e,0):a,C(o)?(i=``,c!=null&&(i=c.replace(j,`$&/`)+`/`),P(o,r,i,``,function(e){return e})):o!=null&&(k(o)&&(o=O(o,i+(o.key==null||e&&e.key===o.key?``:(``+o.key).replace(j,`$&/`)+`/`)+c)),r.push(o)),1;c=0;var l=a===``?`.`:a+`:`;if(C(e))for(var u=0;u<e.length;u++)a=e[u],s=l+M(a,u),c+=P(a,r,i,s,o);else if(u=h(e),typeof u==`function`)for(e=u.call(e),u=0;!(a=e.next()).done;)a=a.value,s=l+M(a,u++),c+=P(a,r,i,s,o);else if(s===`object`){if(typeof e.then==`function`)return P(N(e),r,i,a,o);throw r=String(e),Error(`Objects are not valid as a React child (found: `+(r===`[object Object]`?`object with keys {`+Object.keys(e).join(`, `)+`}`:r)+`). If you meant to render a collection of children, use an array instead.`)}return c}function F(e,t,n){if(e==null)return e;var r=[],i=0;return P(e,r,``,``,function(e){return t.call(n,e,i++)}),r}function I(e){if(e._status===-1){var t=e._result,n=t();n.then(function(t){(e._status===0||e._status===-1)&&(e._status=1,e._result=t,n.status===void 0&&(n.status=`fulfilled`,n.value=t))},function(t){(e._status===0||e._status===-1)&&(e._status=2,e._result=t,n.status===void 0&&(n.status=`rejected`,n.reason=t))}),e._status===-1&&(e._status=0,e._result=n)}if(e._status===1)return e._result.default;throw e._result}var L=typeof reportError==`function`?reportError:function(e){if(typeof window==`object`&&typeof window.ErrorEvent==`function`){var t=new window.ErrorEvent(`error`,{bubbles:!0,cancelable:!0,message:typeof e==`object`&&e&&typeof e.message==`string`?String(e.message):String(e),error:e});if(!window.dispatchEvent(t))return}else if(typeof process==`object`&&typeof process.emit==`function`){process.emit(`uncaughtException`,e);return}console.error(e)};function R(e){var t=T.T,n={};n.types=t===null?null:t.types,T.T=n;try{var r=e(),i=T.S;i!==null&&i(n,r),typeof r==`object`&&r&&typeof r.then==`function`&&r.then(w,L)}catch(e){L(e)}finally{t!==null&&n.types!==null&&(t.types=n.types),T.T=t}}function z(e){var t=T.T;if(t!==null){var n=t.types;n===null?t.types=[e]:n.indexOf(e)===-1&&n.push(e)}else R(z.bind(null,e))}var B={map:F,forEach:function(e,t,n){F(e,function(){t.apply(this,arguments)},n)},count:function(e){var t=0;return F(e,function(){t++}),t},toArray:function(e){return F(e,function(e){return e})||[]},only:function(e){if(!k(e))throw Error(`React.Children.only expected to receive a single React element child.`);return e}};e.Activity=f,e.Children=B,e.Component=y,e.Fragment=r,e.Profiler=a,e.PureComponent=x,e.StrictMode=i,e.Suspense=l,e.ViewTransition=p,e.__CLIENT_INTERNALS_DO_NOT_USE_OR_WARN_USERS_THEY_CANNOT_UPGRADE=T,e.__COMPILER_RUNTIME={__proto__:null,c:function(e){return T.H.useMemoCache(e)}},e.addTransitionType=z,e.cache=function(e){return function(){return e.apply(null,arguments)}},e.cacheSignal=function(){return null},e.cloneElement=function(e,t,n){if(e==null)throw Error(`The argument must be a React element, but you passed `+e+`.`);var r=_({},e.props),i=e.key;if(t!=null)for(a in t.key!==void 0&&(i=``+t.key),t)!E.call(t,a)||a===`key`||a===`__self`||a===`__source`||a===`ref`&&t.ref===void 0||(r[a]=t[a]);var a=arguments.length-2;if(a===1)r.children=n;else if(1<a){for(var o=Array(a),s=0;s<a;s++)o[s]=arguments[s+2];r.children=o}return D(e.type,i,r)},e.createContext=function(e){return e={$$typeof:s,_currentValue:e,_currentValue2:e,_threadCount:0,Provider:null,Consumer:null},e.Provider=e,e.Consumer={$$typeof:o,_context:e},e},e.createElement=function(e,t,n){var r,i={},a=null;if(t!=null)for(r in t.key!==void 0&&(a=``+t.key),t)E.call(t,r)&&r!==`key`&&r!==`__self`&&r!==`__source`&&(i[r]=t[r]);var o=arguments.length-2;if(o===1)i.children=n;else if(1<o){for(var s=Array(o),c=0;c<o;c++)s[c]=arguments[c+2];i.children=s}if(e&&e.defaultProps)for(r in o=e.defaultProps,o)i[r]===void 0&&(i[r]=o[r]);return D(e,a,i)},e.createRef=function(){return{current:null}},e.forwardRef=function(e){return{$$typeof:c,render:e}},e.isValidElement=k,e.lazy=function(e){return{$$typeof:d,_payload:{_status:-1,_result:e},_init:I}},e.memo=function(e,t){return{$$typeof:u,type:e,compare:t===void 0?null:t}},e.startTransition=R,e.unstable_useCacheRefresh=function(){return T.H.useCacheRefresh()},e.use=function(e){return T.H.use(e)},e.useActionState=function(e,t,n){return T.H.useActionState(e,t,n)},e.useCallback=function(e,t){return T.H.useCallback(e,t)},e.useContext=function(e){return T.H.useContext(e)},e.useDebugValue=function(){},e.useDeferredValue=function(e,t){return T.H.useDeferredValue(e,t)},e.useEffect=function(e,t){return T.H.useEffect(e,t)},e.useEffectEvent=function(e){return T.H.useEffectEvent(e)},e.useId=function(){return T.H.useId()},e.useImperativeHandle=function(e,t,n){return T.H.useImperativeHandle(e,t,n)},e.useInsertionEffect=function(e,t){return T.H.useInsertionEffect(e,t)},e.useLayoutEffect=function(e,t){return T.H.useLayoutEffect(e,t)},e.useMemo=function(e,t){return T.H.useMemo(e,t)},e.useOptimistic=function(e,t){return T.H.useOptimistic(e,t)},e.useReducer=function(e,t,n){return T.H.useReducer(e,t,n)},e.useRef=function(e){return T.H.useRef(e)},e.useState=function(e){return T.H.useState(e)},e.useSyncExternalStore=function(e,t,n){return T.H.useSyncExternalStore(e,t,n)},e.useTransition=function(){return T.H.useTransition()},e.version=`19.3.0`})),r=t(((e,t)=>{t.exports=n()})),i=e(r(),1),a=e=>e?.replace(/([a-z0-9])([A-Z])/g,`$1-$2`).toLowerCase();function o(e,t,n=[]){if(t==null)throw Error(`[lucide]: iconNode is required when icon name is used`);return{name:a(e),size:24,node:t,...n.length>0?{aliases:n}:{}}}var s=e=>{let t=``,n=!1;for(let r of e){if(r===`-`||r===`_`||r<=` `){n=t.length>0;continue}t.length===0?t+=r.toLowerCase():t+=n?r.toUpperCase():r,n=!1}return t},c=e=>{let t=s(e);return t.charAt(0).toUpperCase()+t.slice(1)},l=(...e)=>e.filter((e,t,n)=>!!e&&e.trim()!==``&&n.indexOf(e)===t).join(` `).trim(),u={xmlns:`http://www.w3.org/2000/svg`,width:24,height:24,viewBox:`0 0 24 24`,fill:`none`,stroke:`currentColor`,"stroke-width":2,"stroke-linecap":`round`,"stroke-linejoin":`round`};function d(e){return e!=null}function f(e,t={}){let n=t.attributeNames??{},r=e=>n[e]??e,i=e.size??e.width??u.width,a=e.size??e.height??u.height,o=e.aliases?.filter(e=>typeof e==`string`&&e.trim()!==``).map(e=>`lucide-${e}`)??[],s=[...e.name?[`lucide-${e.name}`]:[],...o],c=t.className?.split(` `).filter(Boolean)??[],f=t.includeDefaultClasses===!1?l(...c):l(`lucide`,...s,...c),p=t.absoluteStrokeWidth?Number(t.strokeWidth??u[`stroke-width`])*Number(e.size??e.width??u.width)/Number(t.size??t.width??u.width):t.strokeWidth??u[`stroke-width`];return[`svg`,{...Object.entries(u).reduce((e,[t,n])=>(e[r(t)]=n,e),{}),...`color`in t&&t.color&&{[r(`stroke`)]:t.color},...`size`in t&&d(t.size)&&{[r(`width`)]:t.size,[r(`height`)]:t.size},...`width`in t&&d(t.width)&&{[r(`width`)]:t.width},...`height`in t&&d(t.height)&&{[r(`height`)]:t.height},[r(`stroke-width`)]:p,...f&&{[r(`class`)]:f},[r(`viewBox`)]:`0 0 ${i} ${a}`,...t.hasA11yProp===!1?{[r(`aria-hidden`)]:`true`}:{},...`attributes`in t&&t.attributes},e.node.map(e=>{let[n,i,a]=e,o=t.nonScalingStroke?{[r(`vector-effect`)]:`non-scaling-stroke`,...i}:i;return a?[n,o,a]:[n,o]})]}function p(e,t={}){return f(e,{...t,attributeNames:{...t.attributeNames,class:`className`,"stroke-width":`strokeWidth`,"stroke-linecap":`strokeLinecap`,"stroke-linejoin":`strokeLinejoin`,"vector-effect":`vectorEffect`}})}var m=e=>{for(let t in e)if(t.startsWith(`aria-`)||t===`role`||t===`title`)return!0;return!1},h=(0,i.createContext)({}),g=()=>(0,i.useContext)(h),_=(0,i.forwardRef)(({color:e,size:t,width:n,height:r,strokeWidth:a,absoluteStrokeWidth:o,nonScalingStroke:s,className:c=``,children:u,iconNode:d=[],icon:f={node:d,aliases:[],size:24},...h},_)=>{let{size:v=24,strokeWidth:y=2,absoluteStrokeWidth:b=!1,nonScalingStroke:x=!1,color:S=`currentColor`,className:C=``}=g()??{},w=!!u||m(h),[T,E,D=[]]=p(f,{color:e??S,width:n??t??v,height:r??t??v,strokeWidth:a??y,absoluteStrokeWidth:o??b,nonScalingStroke:s??x,className:l(C,c),hasA11yProp:w,attributes:h});return(0,i.createElement)(T,{ref:_,...E},[...D.map(([e,t])=>(0,i.createElement)(e,t)),...Array.isArray(u)?u:[u]])});function v(e,t=[],n=[]){let r=typeof e==`string`?o(e,t,n):e,a=(0,i.forwardRef)(({className:e,...t},n)=>(0,i.createElement)(_,{ref:n,icon:r,className:e,...t}));return r.name&&(a.displayName=c(r.name)),a}var y={api:`#4fc3f7`,store:`#ce93d8`,chat:`#81c784`,stream:`#ffb74d`,ui:`#90a4ae`,doc:`#a1887f`,system:`#e57373`,nav:`#fff176`},b={api:`fetch`,store:`state`,chat:`chat`,stream:`stream`,ui:`ui`,doc:`docs`,system:`sys`,nav:`nav`},x=(()=>{try{if(localStorage.getItem(`gaia-debug`)===`true`)return!0;if(typeof location<`u`){let e=new URLSearchParams(location.search);if(e.has(`debug`)||e.has(`verbose`))return!0}}catch{}return!1})();function S(){let e=new Date;return e.toLocaleTimeString(`en-US`,{hour12:!1})+`.`+String(e.getMilliseconds()).padStart(3,`0`)}function C(e){let t=y[e]||`#ccc`,n=`%c[${b[e]||e}]`,r=`color: ${t}; font-weight: bold;`,i=`color: #888; font-weight: normal;`;return{info:(e,...t)=>{if(!x)return;let a=S();t.length>0?console.log(`${n} %c${a} %c${e}`,r,i,`color: inherit;`,...t):console.log(`${n} %c${a} %c${e}`,r,i,`color: inherit;`)},warn:(e,...t)=>{let a=S();console.warn(`${n} %c${a} %c${e}`,r,i,`color: inherit;`,...t)},error:(e,...t)=>{let a=S();console.error(`${n} %c${a} %c${e}`,r,i,`color: inherit;`,...t)},debug:(e,...t)=>{if(!x)return;let a=S();console.debug(`${n} %c${a} %c${e}`,r,i,`color: #999;`,...t)},timed:(e,t,...a)=>{if(!x)return;let o=Math.round(performance.now()-t),s=S(),c=o>2e3?`color: #e57373;`:o>500?`color: #ffb74d;`:`color: #81c784;`;a.length>0?console.log(`${n} %c${s} %c${e} %c(${o}ms)`,r,i,`color: inherit;`,c,...a):console.log(`${n} %c${s} %c${e} %c(${o}ms)`,r,i,`color: inherit;`,c)},time:()=>performance.now()}}var w={api:C(`api`),store:C(`store`),chat:C(`chat`),stream:C(`stream`),ui:C(`ui`),doc:C(`doc`),system:C(`system`),nav:C(`nav`)};function T(e){console.log(`%c GAIA %c v`+e+` %c Local AI Desktop`,`background: #9A4930; color: white; font-weight: bold; padding: 4px 8px; border-radius: 4px 0 0 4px;`,`background: #333; color: #fff; padding: 4px 8px;`,`background: #1a1a1a; color: #888; padding: 4px 8px; border-radius: 0 4px 4px 0;`),console.log(x?`%cVerbose logging enabled. Categories: [fetch] [state] [chat] [stream] [ui] [docs] [sys] [nav]`:`%cVerbose logging: add ?debug to URL or run localStorage.setItem("gaia-debug","true")`,`color: #666; font-style: italic;`)}var E=/^https?:\/\/(127\.0\.0\.1|localhost):\d+\/api$/,D=`http://localhost:4200/api`;function O(){if(typeof window>`u`)return`/api`;if(window.location.protocol===`file:`){let e=new URLSearchParams(window.location.search).get(`api`);return e&&E.test(e)?e:D}return`/api`}var k=t((e=>{var t=Symbol.for(`react.transitional.element`),n=Symbol.for(`react.fragment`);function r(e,n,r){var i=null;if(r!==void 0&&(i=``+r),n.key!==void 0&&(i=``+n.key),`key`in n)for(var a in r={},n)a!==`key`&&(r[a]=n[a]);else r=n;return n=r.ref,{$$typeof:t,type:e,key:i,ref:n===void 0?null:n,props:r}}e.Fragment=n,e.jsx=r,e.jsxs=r})),A=t(((e,t)=>{t.exports=k()}));export{v as a,T as i,O as n,r as o,w as r,A as t};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
import{a as e}from"./jsx-runtime-DVeaULmT.js";var t={name:`message-square`,size:24,node:[[`path`,{d:`M22 17a2 2 0 0 1-2 2H6.828a2 2 0 0 0-1.414.586l-2.202 2.202A.71.71 0 0 1 2 21.286V5a2 2 0 0 1 2-2h16a2 2 0 0 1 2 2z`,key:`18887p`}]]};t.node;var n=e(t);export{n as t};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
var e=Object.create,t=Object.defineProperty,n=Object.getOwnPropertyDescriptor,r=Object.getOwnPropertyNames,i=Object.getPrototypeOf,a=Object.prototype.hasOwnProperty,o=(e,t)=>()=>(t||(e((t={exports:{}}).exports,t),e=null),t.exports),s=(e,n)=>{let r={};for(var i in e)t(r,i,{get:e[i],enumerable:!0});return n||t(r,Symbol.toStringTag,{value:`Module`}),r},c=(e,i,o,s)=>{if(i&&typeof i==`object`||typeof i==`function`)for(var c=r(i),l=0,u=c.length,d;l<u;l++)d=c[l],!a.call(e,d)&&d!==o&&t(e,d,{get:(e=>i[e]).bind(null,d),enumerable:!(s=n(i,d))||s.enumerable});return e},l=(n,r,o)=>(o=n==null?{}:e(i(n)),c(r||!n||!n.__esModule||!a.call(n,`default`)?t(o,`default`,{value:n,enumerable:!0}):o,n));export{s as n,l as r,o as t};
|
package/dist/index.html
CHANGED
|
@@ -3,10 +3,15 @@
|
|
|
3
3
|
<head>
|
|
4
4
|
<meta charset="UTF-8" />
|
|
5
5
|
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
|
6
|
+
<!-- Graphite canvas; rewritten per theme by applyTheme(). Dark is the
|
|
7
|
+
designed default, so an unset preference stays dark. -->
|
|
8
|
+
<meta name="theme-color" content="#17161C" />
|
|
6
9
|
<link rel="icon" type="image/png" href="./favicon.png" />
|
|
7
10
|
<title>GAIA Agent UI</title>
|
|
8
|
-
<script type="module" crossorigin src="./assets/index-
|
|
9
|
-
<link rel="
|
|
11
|
+
<script type="module" crossorigin src="./assets/index-CXbVMk3u.js"></script>
|
|
12
|
+
<link rel="modulepreload" crossorigin href="./assets/rolldown-runtime-hePW80VL.js">
|
|
13
|
+
<link rel="modulepreload" crossorigin href="./assets/jsx-runtime-DVeaULmT.js">
|
|
14
|
+
<link rel="stylesheet" crossorigin href="./assets/index-B8E7IBmc.css">
|
|
10
15
|
</head>
|
|
11
16
|
<body>
|
|
12
17
|
<div id="root"></div>
|
package/main.cjs
CHANGED
|
@@ -70,6 +70,12 @@ const {
|
|
|
70
70
|
dispatchDeepLink,
|
|
71
71
|
buildInstallPrompt,
|
|
72
72
|
} = require("./services/deep-link.cjs");
|
|
73
|
+
const {
|
|
74
|
+
isAllowedExternalUrl,
|
|
75
|
+
isInAppNavigation,
|
|
76
|
+
describeBlockedUrl,
|
|
77
|
+
} = require("./services/link-policy.cjs");
|
|
78
|
+
const { createStartupDeepLinkQueue } = require("./services/deep-link-queue.cjs");
|
|
73
79
|
|
|
74
80
|
// ── F7: Ozone hint (issue #782) ─────────────────────────────────────────────
|
|
75
81
|
// Electron-recommended switch for distro-agnostic Linux behaviour: picks
|
|
@@ -306,10 +312,12 @@ async function startBackend() {
|
|
|
306
312
|
try {
|
|
307
313
|
backendPort = await portManager.findFreePort();
|
|
308
314
|
} catch (err) {
|
|
309
|
-
|
|
310
|
-
`
|
|
315
|
+
throw new Error(
|
|
316
|
+
`Could not reserve a local port for the GAIA backend: ${err.message}. ` +
|
|
317
|
+
"Check that security software isn't blocking 127.0.0.1, then relaunch GAIA. " +
|
|
318
|
+
`Logs: ${_MAIN_LOG_PATH}`,
|
|
319
|
+
{ cause: err }
|
|
311
320
|
);
|
|
312
|
-
backendPort = DEFAULT_BACKEND_PORT;
|
|
313
321
|
}
|
|
314
322
|
healthCheckUrl = `http://localhost:${backendPort}/api/health`;
|
|
315
323
|
// Defensively clean up any orphaned backend in case bootstrap was skipped
|
|
@@ -492,6 +500,27 @@ function findDistPath() {
|
|
|
492
500
|
return null;
|
|
493
501
|
}
|
|
494
502
|
|
|
503
|
+
/** Refuse a link loudly — the user clicked it, so they get told why nothing happened. */
|
|
504
|
+
function reportBlockedLink(url) {
|
|
505
|
+
const message = describeBlockedUrl(url);
|
|
506
|
+
console.error(`[main] ${message}`);
|
|
507
|
+
// Non-blocking and parented: showErrorBox would freeze the main process.
|
|
508
|
+
dialog
|
|
509
|
+
.showMessageBox(mainWindow && !mainWindow.isDestroyed() ? mainWindow : null, {
|
|
510
|
+
type: "warning",
|
|
511
|
+
title: "Link blocked",
|
|
512
|
+
message: "Link blocked",
|
|
513
|
+
detail: message,
|
|
514
|
+
buttons: ["OK"],
|
|
515
|
+
noLink: true,
|
|
516
|
+
})
|
|
517
|
+
.catch((err) =>
|
|
518
|
+
console.error(
|
|
519
|
+
`[main] Could not show the link-blocked dialog: ${err && err.message ? err.message : err}`
|
|
520
|
+
)
|
|
521
|
+
);
|
|
522
|
+
}
|
|
523
|
+
|
|
495
524
|
function createWindow() {
|
|
496
525
|
mainWindow = new BrowserWindow({
|
|
497
526
|
width: windowConfig.width,
|
|
@@ -511,12 +540,31 @@ function createWindow() {
|
|
|
511
540
|
// Remove default menu bar
|
|
512
541
|
mainWindow.setMenuBarVisibility(false);
|
|
513
542
|
|
|
514
|
-
// Open external links in the default browser
|
|
543
|
+
// Open external links in the default browser. The URL here is already
|
|
544
|
+
// RESOLVED against the file:// document, so a scheme-less markdown link
|
|
545
|
+
// arrives as file:///C:/… — check the scheme before handing it to the OS.
|
|
515
546
|
mainWindow.webContents.setWindowOpenHandler(({ url }) => {
|
|
516
|
-
|
|
547
|
+
if (isAllowedExternalUrl(url)) {
|
|
548
|
+
shell.openExternal(url);
|
|
549
|
+
} else {
|
|
550
|
+
reportBlockedLink(url);
|
|
551
|
+
}
|
|
517
552
|
return { action: "deny" };
|
|
518
553
|
});
|
|
519
554
|
|
|
555
|
+
// Nothing may navigate the app window away from its own document. In-page
|
|
556
|
+
// anchors pass; web links open in the browser; everything else is refused.
|
|
557
|
+
mainWindow.webContents.on("will-navigate", (event, url) => {
|
|
558
|
+
const currentUrl = mainWindow.webContents.getURL();
|
|
559
|
+
if (isInAppNavigation(url, currentUrl)) return;
|
|
560
|
+
event.preventDefault();
|
|
561
|
+
if (isAllowedExternalUrl(url)) {
|
|
562
|
+
shell.openExternal(url);
|
|
563
|
+
return;
|
|
564
|
+
}
|
|
565
|
+
reportBlockedLink(url);
|
|
566
|
+
});
|
|
567
|
+
|
|
520
568
|
// ── Minimize-to-tray on close (C4 fix) ──────────────────────────────
|
|
521
569
|
// Intercept window close — hide instead of closing when tray mode is active
|
|
522
570
|
mainWindow.on("close", (event) => {
|
|
@@ -570,19 +618,44 @@ async function loadApp() {
|
|
|
570
618
|
fileUrl.search = new URLSearchParams(indexQuery).toString();
|
|
571
619
|
await mainWindow.loadURL(fileUrl.href);
|
|
572
620
|
} else {
|
|
573
|
-
// Show a simple loading/error page
|
|
574
|
-
|
|
575
|
-
|
|
576
|
-
<html>
|
|
577
|
-
<head
|
|
578
|
-
|
|
621
|
+
// Show a simple loading/error page. Percent-encoded: an unescaped `#`
|
|
622
|
+
// in a data: URL starts the fragment and truncates the document there.
|
|
623
|
+
const waitingHtml = `<!DOCTYPE html>
|
|
624
|
+
<html lang="en">
|
|
625
|
+
<head>
|
|
626
|
+
<title>${APP_NAME}</title>
|
|
627
|
+
<style>
|
|
628
|
+
/* Standalone page: it loads from a data: URL and never sees
|
|
629
|
+
* src/styles/index.css, so the dark theme's role values are
|
|
630
|
+
* mirrored here. Keep them in step with that file. */
|
|
631
|
+
:root {
|
|
632
|
+
--bg-primary: #17161C;
|
|
633
|
+
--text-primary: #F0EDE7;
|
|
634
|
+
--text-secondary: #AAA5B0;
|
|
635
|
+
}
|
|
636
|
+
body {
|
|
637
|
+
font-family: -apple-system, BlinkMacSystemFont, sans-serif;
|
|
638
|
+
display: flex;
|
|
639
|
+
align-items: center;
|
|
640
|
+
justify-content: center;
|
|
641
|
+
height: 100vh;
|
|
642
|
+
margin: 0;
|
|
643
|
+
background: var(--bg-primary);
|
|
644
|
+
color: var(--text-primary);
|
|
645
|
+
}
|
|
646
|
+
.hint { color: var(--text-secondary); font-size: 12px; }
|
|
647
|
+
</style>
|
|
648
|
+
</head>
|
|
649
|
+
<body>
|
|
579
650
|
<div style="text-align:center;">
|
|
580
651
|
<h1>${APP_NAME}</h1>
|
|
581
652
|
<p>Waiting for backend to start...</p>
|
|
582
|
-
<p
|
|
653
|
+
<p class="hint">Backend: http://localhost:${backendPort}</p>
|
|
583
654
|
</div>
|
|
584
655
|
</body>
|
|
585
|
-
</html
|
|
656
|
+
</html>`;
|
|
657
|
+
mainWindow.loadURL(
|
|
658
|
+
"data:text/html;charset=utf-8," + encodeURIComponent(waitingHtml)
|
|
586
659
|
);
|
|
587
660
|
}
|
|
588
661
|
}
|
|
@@ -751,8 +824,15 @@ async function bootstrapBackend() {
|
|
|
751
824
|
// A malformed/unsupported link surfaces a loud error dialog — never a silent
|
|
752
825
|
// no-op (deep-link.cjs throws with an actionable message).
|
|
753
826
|
|
|
754
|
-
|
|
755
|
-
|
|
827
|
+
// Holds gaia:// links until startup has waited for the backend (the install
|
|
828
|
+
// gate verifies the agent through its catalog API), then dispatches each once.
|
|
829
|
+
const startupDeepLinks = createStartupDeepLinkQueue({
|
|
830
|
+
dispatch: (rawUrl) => {
|
|
831
|
+
void runDeepLink(parseDeepLink(rawUrl));
|
|
832
|
+
},
|
|
833
|
+
reportNotReady: reportDeepLinksNotReady,
|
|
834
|
+
logger: console,
|
|
835
|
+
});
|
|
756
836
|
|
|
757
837
|
/**
|
|
758
838
|
* Register this app as the handler for the gaia:// scheme. In dev (`electron .`)
|
|
@@ -785,13 +865,13 @@ function registerProtocolHandler() {
|
|
|
785
865
|
}
|
|
786
866
|
|
|
787
867
|
/**
|
|
788
|
-
* Entry point for any inbound deep link.
|
|
789
|
-
*
|
|
868
|
+
* Entry point for any inbound deep link. Validates it now (a bad link is
|
|
869
|
+
* refused loudly and immediately), then hands it to the startup queue, which
|
|
870
|
+
* holds it until the backend is ready and dispatches it immediately after.
|
|
790
871
|
*/
|
|
791
872
|
function handleDeepLink(rawUrl) {
|
|
792
|
-
let command;
|
|
793
873
|
try {
|
|
794
|
-
|
|
874
|
+
parseDeepLink(rawUrl);
|
|
795
875
|
} catch (err) {
|
|
796
876
|
const message = err && err.message ? err.message : String(err);
|
|
797
877
|
console.error(`[main] Rejected deep link: ${message}`);
|
|
@@ -803,14 +883,7 @@ function handleDeepLink(rawUrl) {
|
|
|
803
883
|
return;
|
|
804
884
|
}
|
|
805
885
|
|
|
806
|
-
|
|
807
|
-
if (!agentProcessManager) {
|
|
808
|
-
console.log("[main] Deep link received before services ready — queuing");
|
|
809
|
-
pendingDeepLinkUrl = rawUrl;
|
|
810
|
-
return;
|
|
811
|
-
}
|
|
812
|
-
|
|
813
|
-
void runDeepLink(command);
|
|
886
|
+
startupDeepLinks.accept(rawUrl);
|
|
814
887
|
}
|
|
815
888
|
|
|
816
889
|
/**
|
|
@@ -889,16 +962,32 @@ async function runDeepLink(command) {
|
|
|
889
962
|
}
|
|
890
963
|
}
|
|
891
964
|
|
|
892
|
-
/**
|
|
893
|
-
|
|
894
|
-
|
|
895
|
-
|
|
896
|
-
|
|
897
|
-
|
|
898
|
-
|
|
965
|
+
/**
|
|
966
|
+
* Release queued deep links, plus any gaia:// URL in the cold-start argv. Call
|
|
967
|
+
* once, after startup has waited for the backend.
|
|
968
|
+
*
|
|
969
|
+
* @param {boolean} backendReady Whether the backend API answered its health check.
|
|
970
|
+
*/
|
|
971
|
+
function processStartupDeepLinks(backendReady) {
|
|
972
|
+
startupDeepLinks.drain({ backendReady, argvUrl: extractDeepLinkFromArgv(process.argv) });
|
|
973
|
+
}
|
|
974
|
+
|
|
975
|
+
/** The backend never came up: tell the user instead of dispatching blind. */
|
|
976
|
+
function reportDeepLinksNotReady(urls) {
|
|
977
|
+
const why = backendProcess
|
|
978
|
+
? `did not answer within ${Math.round(STARTUP_TIMEOUT / 1000)}s`
|
|
979
|
+
: "could not be started";
|
|
980
|
+
const message =
|
|
981
|
+
`GAIA could not open ${urls.map((u) => `"${u}"`).join(", ")} because ` +
|
|
982
|
+
`its backend ${why}, so the agent cannot be verified before install. ` +
|
|
983
|
+
`Check ${_MAIN_LOG_PATH} for the startup error, then click the link ` +
|
|
984
|
+
"again once GAIA is running.";
|
|
985
|
+
console.error(`[main] ${message}`);
|
|
986
|
+
try {
|
|
987
|
+
dialog.showErrorBox("GAIA is not ready yet", message);
|
|
988
|
+
} catch {
|
|
989
|
+
/* best-effort */
|
|
899
990
|
}
|
|
900
|
-
const fromArgv = extractDeepLinkFromArgv(process.argv);
|
|
901
|
-
if (fromArgv) handleDeepLink(fromArgv);
|
|
902
991
|
}
|
|
903
992
|
|
|
904
993
|
// macOS delivers deep links via this event; it can fire before whenReady, so
|
|
@@ -1029,10 +1118,6 @@ app.whenReady().then(async () => {
|
|
|
1029
1118
|
// Setup Windows Jump List (T11)
|
|
1030
1119
|
setupJumpList();
|
|
1031
1120
|
|
|
1032
|
-
// Act on any gaia:// deep link that arrived during bootstrap or via the
|
|
1033
|
-
// cold-start command line (issue #1725). Services are now wired.
|
|
1034
|
-
processStartupDeepLinks();
|
|
1035
|
-
|
|
1036
1121
|
// Show loading state
|
|
1037
1122
|
await loadApp();
|
|
1038
1123
|
|
|
@@ -1041,16 +1126,22 @@ app.whenReady().then(async () => {
|
|
|
1041
1126
|
// API becomes available and dismisses its "Cannot connect" banner.
|
|
1042
1127
|
// We do NOT reload the window with http://localhost:4200/ because
|
|
1043
1128
|
// the pip-installed backend has no frontend files — only the API.
|
|
1129
|
+
let backendReady = false;
|
|
1044
1130
|
if (backendProcess) {
|
|
1045
1131
|
console.log("Waiting for backend to start...");
|
|
1046
|
-
|
|
1047
|
-
if (
|
|
1132
|
+
backendReady = await waitForBackend(STARTUP_TIMEOUT);
|
|
1133
|
+
if (backendReady) {
|
|
1048
1134
|
console.log("Backend API is ready on port", backendPort);
|
|
1049
1135
|
} else {
|
|
1050
1136
|
console.warn("Backend did not respond within timeout.");
|
|
1051
1137
|
}
|
|
1052
1138
|
}
|
|
1053
1139
|
|
|
1140
|
+
// Act on any gaia:// deep link that arrived during bootstrap or via the
|
|
1141
|
+
// cold-start command line (issue #1725) — after the backend is listening,
|
|
1142
|
+
// because the install gate queries its catalog API.
|
|
1143
|
+
processStartupDeepLinks(backendReady);
|
|
1144
|
+
|
|
1054
1145
|
// Auto-start enabled agents (T2)
|
|
1055
1146
|
if (agentProcessManager) {
|
|
1056
1147
|
try {
|
package/package.json
CHANGED
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@amd-gaia/agent-ui",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.25.0-rc.2",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"productName": "GAIA Agent UI",
|
|
6
|
-
"description": "
|
|
6
|
+
"description": "Chat with the GAIA agent - local and private by default on AMD Ryzen AI, cloud models when you choose them",
|
|
7
7
|
"author": "AMD AI Group",
|
|
8
8
|
"license": "MIT",
|
|
9
9
|
"repository": {
|
|
@@ -58,7 +58,8 @@
|
|
|
58
58
|
"package:linux": "npm run build && electron-builder --linux --config electron-builder.yml",
|
|
59
59
|
"prepublishOnly": "npm run build",
|
|
60
60
|
"test": "vitest run",
|
|
61
|
-
"test:watch": "vitest"
|
|
61
|
+
"test:watch": "vitest",
|
|
62
|
+
"gen:api-types": "node scripts/gen-api-types.mjs"
|
|
62
63
|
},
|
|
63
64
|
"dependencies": {
|
|
64
65
|
"electron-updater": "^6.8.3"
|
|
@@ -70,20 +71,19 @@
|
|
|
70
71
|
"@types/react": "^19.2.17",
|
|
71
72
|
"@types/react-dom": "^19.2.3",
|
|
72
73
|
"@vitejs/plugin-react": "^6.0.2",
|
|
73
|
-
"electron": "^44.
|
|
74
|
+
"electron": "^44.5.1",
|
|
74
75
|
"electron-builder": "^26.8.1",
|
|
75
76
|
"jsdom": "^30.0.1",
|
|
76
|
-
"
|
|
77
|
-
"
|
|
77
|
+
"json-schema-to-typescript": "16.0.0",
|
|
78
|
+
"lucide-react": "^1.50.0",
|
|
78
79
|
"qrcode": "^1.5.4",
|
|
79
80
|
"react": "^19.2.7",
|
|
80
81
|
"react-dom": "^19.2.7",
|
|
81
82
|
"react-markdown": "^10.1.0",
|
|
82
|
-
"rehype-sanitize": "^6.0.0",
|
|
83
83
|
"remark-gfm": "^4.0.1",
|
|
84
84
|
"typescript": "^7.0.2",
|
|
85
|
-
"vite": "^8.
|
|
86
|
-
"vitest": "^5.0.
|
|
85
|
+
"vite": "^8.3.2",
|
|
86
|
+
"vitest": "^5.0.3",
|
|
87
87
|
"zustand": "^5.0.14"
|
|
88
88
|
}
|
|
89
89
|
}
|
|
@@ -86,12 +86,24 @@ function buildProgressHtml({ logPath }) {
|
|
|
86
86
|
<meta charset="UTF-8" />
|
|
87
87
|
<title>Installing GAIA</title>
|
|
88
88
|
<style>
|
|
89
|
+
/* Standalone window: it loads from a data: URL and never sees
|
|
90
|
+
* src/styles/index.css, so the dark theme's role values are mirrored
|
|
91
|
+
* here. Keep them in step with that file; nothing below names a hex. */
|
|
92
|
+
:root {
|
|
93
|
+
--bg-primary: #17161C;
|
|
94
|
+
--bg-tertiary: #2A2831;
|
|
95
|
+
--border: #3A3842;
|
|
96
|
+
--text-primary: #F0EDE7;
|
|
97
|
+
--text-secondary: #AAA5B0;
|
|
98
|
+
--text-muted: #8C8794;
|
|
99
|
+
--accent-fill: #9A4930;
|
|
100
|
+
}
|
|
89
101
|
html, body {
|
|
90
102
|
margin: 0;
|
|
91
103
|
padding: 0;
|
|
92
104
|
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif;
|
|
93
|
-
background:
|
|
94
|
-
color:
|
|
105
|
+
background: var(--bg-primary);
|
|
106
|
+
color: var(--text-primary);
|
|
95
107
|
user-select: none;
|
|
96
108
|
-webkit-user-select: none;
|
|
97
109
|
overflow: hidden;
|
|
@@ -110,35 +122,38 @@ function buildProgressHtml({ logPath }) {
|
|
|
110
122
|
font-size: 18px;
|
|
111
123
|
font-weight: 600;
|
|
112
124
|
margin: 0 0 8px 0;
|
|
113
|
-
color:
|
|
125
|
+
color: var(--text-primary);
|
|
114
126
|
}
|
|
115
127
|
.subtitle {
|
|
116
128
|
font-size: 13px;
|
|
117
|
-
color:
|
|
129
|
+
color: var(--text-secondary);
|
|
118
130
|
margin: 0 0 24px 0;
|
|
119
131
|
}
|
|
120
132
|
.stage {
|
|
121
133
|
font-size: 12px;
|
|
122
|
-
color:
|
|
134
|
+
color: var(--text-secondary);
|
|
123
135
|
text-transform: uppercase;
|
|
124
136
|
letter-spacing: 0.5px;
|
|
125
137
|
margin-bottom: 6px;
|
|
126
138
|
min-height: 15px;
|
|
127
139
|
}
|
|
140
|
+
/* The rule carries the track's edge: no fill clears the 1.5:1 chrome
|
|
141
|
+
* floor against the canvas, but --border does at 1.56:1. */
|
|
128
142
|
.bar {
|
|
129
143
|
width: 360px;
|
|
130
144
|
max-width: 100%;
|
|
131
145
|
height: 10px;
|
|
132
|
-
|
|
133
|
-
|
|
146
|
+
box-sizing: border-box;
|
|
147
|
+
background: var(--bg-tertiary);
|
|
148
|
+
border: 1px solid var(--border);
|
|
149
|
+
border-radius: 9999px;
|
|
134
150
|
overflow: hidden;
|
|
135
151
|
margin-bottom: 12px;
|
|
136
|
-
box-shadow: inset 0 1px 2px rgba(0,0,0,0.3);
|
|
137
152
|
}
|
|
138
153
|
.fill {
|
|
139
154
|
height: 100%;
|
|
140
|
-
background:
|
|
141
|
-
border-radius:
|
|
155
|
+
background: var(--accent-fill);
|
|
156
|
+
border-radius: 9999px;
|
|
142
157
|
transition: width 300ms ease-out;
|
|
143
158
|
width: 0%;
|
|
144
159
|
}
|
|
@@ -151,15 +166,19 @@ function buildProgressHtml({ logPath }) {
|
|
|
151
166
|
50% { margin-left: 70%; }
|
|
152
167
|
100% { margin-left: 0%; }
|
|
153
168
|
}
|
|
169
|
+
@media (prefers-reduced-motion: reduce) {
|
|
170
|
+
.fill { transition: none; }
|
|
171
|
+
.fill.indeterminate { animation: none; margin-left: 35%; }
|
|
172
|
+
}
|
|
154
173
|
.percent {
|
|
155
174
|
font-size: 12px;
|
|
156
|
-
color:
|
|
175
|
+
color: var(--text-secondary);
|
|
157
176
|
margin-bottom: 16px;
|
|
158
177
|
min-height: 14px;
|
|
159
178
|
}
|
|
160
179
|
.message {
|
|
161
180
|
font-size: 13px;
|
|
162
|
-
color:
|
|
181
|
+
color: var(--text-primary);
|
|
163
182
|
max-width: 420px;
|
|
164
183
|
min-height: 36px;
|
|
165
184
|
line-height: 1.4;
|
|
@@ -171,12 +190,12 @@ function buildProgressHtml({ logPath }) {
|
|
|
171
190
|
right: 0;
|
|
172
191
|
text-align: center;
|
|
173
192
|
font-size: 10px;
|
|
174
|
-
color:
|
|
193
|
+
color: var(--text-muted);
|
|
175
194
|
}
|
|
176
195
|
code {
|
|
177
196
|
font-family: "SF Mono", Monaco, Consolas, monospace;
|
|
178
197
|
font-size: 10px;
|
|
179
|
-
color:
|
|
198
|
+
color: var(--text-muted);
|
|
180
199
|
}
|
|
181
200
|
</style>
|
|
182
201
|
</head>
|
|
@@ -249,7 +268,7 @@ function createProgressWindow() {
|
|
|
249
268
|
frame: false,
|
|
250
269
|
transparent: false,
|
|
251
270
|
show: false,
|
|
252
|
-
backgroundColor: "#
|
|
271
|
+
backgroundColor: "#17161C", // --bg-primary, dark theme (see the :root block above)
|
|
253
272
|
title: "Installing GAIA",
|
|
254
273
|
webPreferences: {
|
|
255
274
|
nodeIntegration: false,
|
|
@@ -493,7 +493,7 @@ function isFileLockedError(output) {
|
|
|
493
493
|
/**
|
|
494
494
|
* Detect a transient network failure in `uv pip install` output so the
|
|
495
495
|
* install stage can retry instead of failing the whole bootstrap. The
|
|
496
|
-
* install stage downloads
|
|
496
|
+
* install stage downloads transitive deps (numpy, faiss, pymupdf, …) from
|
|
497
497
|
* PyPI; a single mid-stream hiccup ("stream closed because of a broken pipe")
|
|
498
498
|
* otherwise fails the entire backend install — and, in the release pipeline,
|
|
499
499
|
* the whole AppImage smoke test that gates publishing.
|
|
@@ -1366,6 +1366,15 @@ function resolveBackendVersion(opts = {}) {
|
|
|
1366
1366
|
}
|
|
1367
1367
|
}
|
|
1368
1368
|
|
|
1369
|
+
/**
|
|
1370
|
+
* The PEP 440 spelling of the app's npm version: `0.25.0-rc.1` -> `0.25.0rc1`.
|
|
1371
|
+
* pip pins and `gaia --version` use this form; a final version is unchanged.
|
|
1372
|
+
*/
|
|
1373
|
+
function toPep440(version) {
|
|
1374
|
+
if (!version) return version;
|
|
1375
|
+
return version.replace(/^(\d+\.\d+\.\d+)-rc\.(\d+)$/, "$1rc$2");
|
|
1376
|
+
}
|
|
1377
|
+
|
|
1369
1378
|
/**
|
|
1370
1379
|
* Install the GAIA Python backend from scratch.
|
|
1371
1380
|
*
|
|
@@ -1389,7 +1398,7 @@ async function installBackend(opts = {}) {
|
|
|
1389
1398
|
const localWheel = process.env.GAIA_LOCAL_WHEEL || null;
|
|
1390
1399
|
const pipPackage = localWheel
|
|
1391
1400
|
? `${localWheel}[ui]`
|
|
1392
|
-
: `amd-gaia[ui]==${version}`;
|
|
1401
|
+
: `amd-gaia[ui]==${toPep440(version)}`;
|
|
1393
1402
|
const skipGaiaInit =
|
|
1394
1403
|
Boolean(opts.skipGaiaInit) || isTruthyEnv(process.env.GAIA_SKIP_GAIA_INIT);
|
|
1395
1404
|
|
|
@@ -1456,14 +1465,8 @@ async function installBackend(opts = {}) {
|
|
|
1456
1465
|
"--python",
|
|
1457
1466
|
GAIA_PYTHON_BIN,
|
|
1458
1467
|
];
|
|
1459
|
-
//
|
|
1460
|
-
//
|
|
1461
|
-
if (!IS_WINDOWS && !localWheel) {
|
|
1462
|
-
pipArgs.push("--extra-index-url", "https://download.pytorch.org/whl/cpu");
|
|
1463
|
-
}
|
|
1464
|
-
|
|
1465
|
-
// Retry the install on transient PyPI/network failures. The heavy
|
|
1466
|
-
// transitive deps (scipy, numpy, torch) are fetched live from PyPI even
|
|
1468
|
+
// Retry the install on transient PyPI/network failures. The transitive
|
|
1469
|
+
// deps (numpy, faiss, pymupdf, …) are fetched live from PyPI even
|
|
1467
1470
|
// when the gaia wheel itself is local, so a single broken-pipe mid-download
|
|
1468
1471
|
// would otherwise fail the whole bootstrap (and block a release). File-lock
|
|
1469
1472
|
// failures (Windows os-error-32) are NOT retried — they need user action.
|
|
@@ -1540,8 +1543,18 @@ async function installBackend(opts = {}) {
|
|
|
1540
1543
|
IS_WINDOWS ? `${GAIA_VENV_DISPLAY}/Scripts/python.exe` : `${GAIA_VENV_DISPLAY}/bin/python`
|
|
1541
1544
|
}\nThen restart GAIA. See https://amd-gaia.ai/docs/quickstart#cli-install`;
|
|
1542
1545
|
}
|
|
1546
|
+
// Carry pip's own last words. Without them the only record of a failed
|
|
1547
|
+
// install is an exit code, which is not something a user or a CI log can
|
|
1548
|
+
// act on.
|
|
1549
|
+
const tail = output
|
|
1550
|
+
.split("\n")
|
|
1551
|
+
.map((line) => line.trimEnd())
|
|
1552
|
+
.filter(Boolean)
|
|
1553
|
+
.slice(-20)
|
|
1554
|
+
.join("\n");
|
|
1543
1555
|
throw new InstallError(
|
|
1544
|
-
`Failed to install ${pipPackage} (pip exit ${installResult.code})
|
|
1556
|
+
`Failed to install ${pipPackage} (pip exit ${installResult.code}).` +
|
|
1557
|
+
(tail ? `\npip said:\n${tail}` : ""),
|
|
1545
1558
|
{
|
|
1546
1559
|
stage: STAGES.INSTALL_PACKAGE,
|
|
1547
1560
|
code: installResult.code,
|
|
@@ -1664,7 +1677,9 @@ function getInstalledVersion(gaiaBin) {
|
|
|
1664
1677
|
windowsHide: true,
|
|
1665
1678
|
});
|
|
1666
1679
|
if (result.status === 0 && result.stdout) {
|
|
1667
|
-
|
|
1680
|
+
// Keep an rcN suffix: truncating it would let a release candidate's
|
|
1681
|
+
// backend pass for the final's and never be upgraded.
|
|
1682
|
+
const match = result.stdout.toString().trim().match(/(\d+\.\d+\.\d+(?:rc\d+)?)/);
|
|
1668
1683
|
return match ? match[1] : null;
|
|
1669
1684
|
}
|
|
1670
1685
|
} catch {
|
|
@@ -1764,7 +1779,7 @@ async function ensureBackend(opts = {}) {
|
|
|
1764
1779
|
const existingBin = findGaiaBin();
|
|
1765
1780
|
if (existingBin) {
|
|
1766
1781
|
const installedVersion = getInstalledVersion(existingBin);
|
|
1767
|
-
if (expectedVersion !== null && installedVersion === expectedVersion) {
|
|
1782
|
+
if (expectedVersion !== null && installedVersion === toPep440(expectedVersion)) {
|
|
1768
1783
|
log(
|
|
1769
1784
|
`GAIA backend already installed at version ${installedVersion} — nothing to do`
|
|
1770
1785
|
);
|
|
@@ -1831,6 +1846,7 @@ module.exports = {
|
|
|
1831
1846
|
ensureBackend,
|
|
1832
1847
|
getInstalledVersion,
|
|
1833
1848
|
findGaiaBin,
|
|
1849
|
+
toPep440,
|
|
1834
1850
|
|
|
1835
1851
|
// Pre-checks
|
|
1836
1852
|
runPreChecks,
|