@@ -191,7 +208,7 @@ import { computed, ref, onBeforeUnmount } from 'vue'
import { useRouter, RouterLink } from 'vue-router'
import { useAppStore } from '../stores/app'
import { useAppLauncherStore } from '../stores/appLauncher'
-import { PackageState } from '../types/api'
+import { PackageState, type PackageDataEntry } from '../types/api'
import { useModalKeyboard } from '@/composables/useModalKeyboard'
const router = useRouter()
@@ -203,6 +220,16 @@ const searchQuery = ref('')
// Track loading states for each app action
const loadingActions = ref
>({})
+// Action error toast
+const actionError = ref('')
+let errorTimer: ReturnType | undefined
+
+function showActionError(msg: string) {
+ actionError.value = msg
+ if (errorTimer) clearTimeout(errorTimer)
+ errorTimer = setTimeout(() => { actionError.value = '' }, 5000)
+}
+
// Use real packages from store - no more dummy apps
const packages = computed(() => {
const realPackages = store.packages
@@ -246,13 +273,13 @@ useModalKeyboard(
{ restoreFocusRef: uninstallRestoreFocusRef }
)
-function canLaunch(pkg: any): boolean {
+function canLaunch(pkg: PackageDataEntry): boolean {
// For dummy apps, allow launch if running (they have interface addresses)
// For real apps, check for UI interface
const hasUI = pkg.manifest.interfaces?.main?.ui || pkg.installed?.['interface-addresses']?.main
// Allow launch when running or starting (so buttons show even while backend reports "starting")
const canLaunchState = pkg.state === 'running' || pkg.state === 'starting'
- return hasUI && canLaunchState
+ return !!hasUI && canLaunchState
}
function launchApp(id: string) {
@@ -341,7 +368,8 @@ async function startApp(id: string) {
actionTimers.delete(id)
}, 5000))
} catch (err) {
- console.error('Failed to start app:', err)
+ if (import.meta.env.DEV) console.error('Failed to start app:', err)
+ showActionError(`Failed to start app: ${err instanceof Error ? err.message : 'Unknown error'}`)
loadingActions.value[id] = false
}
}
@@ -356,7 +384,8 @@ async function stopApp(id: string) {
actionTimers.delete(id)
}, 5000))
} catch (err) {
- console.error('Failed to stop app:', err)
+ if (import.meta.env.DEV) console.error('Failed to stop app:', err)
+ showActionError(`Failed to stop app: ${err instanceof Error ? err.message : 'Unknown error'}`)
loadingActions.value[id] = false
}
}
@@ -373,11 +402,11 @@ async function _restartApp(_id: string) {
try {
await store.restartPackage(_id)
} catch (err) {
- console.error('Failed to restart app:', err)
+ if (import.meta.env.DEV) console.error('Failed to restart app:', err)
}
}
-function showUninstallModal(id: string, pkg: any) {
+function showUninstallModal(id: string, pkg: PackageDataEntry) {
uninstallModal.value = {
show: true,
appId: id,
@@ -392,8 +421,8 @@ async function confirmUninstall() {
try {
await store.uninstallPackage(appId)
} catch (err) {
- console.error('Failed to uninstall app:', err)
- alert('Failed to uninstall app')
+ if (import.meta.env.DEV) console.error('Failed to uninstall app:', err)
+ showActionError(`Failed to uninstall app: ${err instanceof Error ? err.message : 'Unknown error'}`)
}
}
diff --git a/neode-ui/src/views/Chat.vue b/neode-ui/src/views/Chat.vue
index 37259a35..db0ff7a0 100644
--- a/neode-ui/src/views/Chat.vue
+++ b/neode-ui/src/views/Chat.vue
@@ -2,8 +2,8 @@
-
+
+
+
+
+
+
+
-
+
-
+
Loading AI assistant...
@@ -30,6 +37,7 @@
v-if="aiuiUrl"
ref="aiuiFrame"
:src="aiuiUrl"
+ title="AI Assistant"
class="chat-iframe chat-iframe-mobile"
sandbox="allow-scripts allow-same-origin allow-forms"
allow="microphone"
@@ -40,16 +48,16 @@
AI Assistant
- AIUI is not connected. Configure the AIUI URL in your environment settings.
+ AI Assistant is not yet configured on this node.
- Set VITE_AIUI_URL or deploy the AIUI container.
+ Deploy the AIUI app from the App Store to enable this feature.
@@ -142,4 +150,21 @@ onBeforeUnmount(() => {
.fade-leave-to {
opacity: 0;
}
+
+.chat-mobile-back {
+ position: absolute;
+ top: 0.75rem;
+ left: 0.75rem;
+ z-index: 20;
+ width: 36px;
+ height: 36px;
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ border-radius: 50%;
+ background: rgba(0, 0, 0, 0.5);
+ backdrop-filter: blur(8px);
+ color: rgba(255, 255, 255, 0.8);
+ border: 1px solid rgba(255, 255, 255, 0.15);
+}
diff --git a/neode-ui/src/views/Dashboard.vue b/neode-ui/src/views/Dashboard.vue
index 2fddfd04..13fe3857 100644
--- a/neode-ui/src/views/Dashboard.vue
+++ b/neode-ui/src/views/Dashboard.vue
@@ -74,7 +74,7 @@
-