hot fixes to utc-6
This commit is contained in:
@@ -274,7 +274,7 @@ class RPCClient {
|
||||
})
|
||||
}
|
||||
|
||||
async getNostrPubkey(): Promise<{ nostr_pubkey: string }> {
|
||||
async getNostrPubkey(): Promise<{ nostr_pubkey: string; nostr_npub?: string }> {
|
||||
return this.call({
|
||||
method: 'node.nostr-pubkey',
|
||||
params: {},
|
||||
|
||||
@@ -269,7 +269,7 @@ function isIdentityAwareApp(url: string): boolean {
|
||||
async function sendIdentityIfSupported() {
|
||||
if (!store.url || !isIdentityAwareApp(store.url)) return
|
||||
try {
|
||||
const res = await rpcClient.call<{ identities: Array<{ id: string; name: string; did: string; pubkey: string; is_default: boolean; nostr_pubkey?: string }> }>({ method: 'identity.list' })
|
||||
const res = await rpcClient.call<{ identities: Array<{ id: string; name: string; did: string; pubkey: string; is_default: boolean; nostr_pubkey?: string; nostr_npub?: string }> }>({ method: 'identity.list' })
|
||||
const defaultId = res.identities?.find(i => i.is_default) || res.identities?.[0]
|
||||
if (!defaultId) return
|
||||
// Sign a timestamp challenge to prove ownership
|
||||
@@ -286,6 +286,7 @@ async function sendIdentityIfSupported() {
|
||||
name: defaultId.name,
|
||||
pubkey: defaultId.pubkey,
|
||||
nostr_pubkey: defaultId.nostr_pubkey || null,
|
||||
nostr_npub: defaultId.nostr_npub || null,
|
||||
challenge,
|
||||
signature: sigRes.signature
|
||||
}, '*')
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import { defineStore } from 'pinia'
|
||||
import { ref } from 'vue'
|
||||
import { ref, watch } from 'vue'
|
||||
import { rpcClient } from '@/api/rpc-client'
|
||||
|
||||
/** Apps that set X-Frame-Options or CSP frame-ancestors, blocking iframe embedding.
|
||||
* Verified by checking response headers from each app container.
|
||||
@@ -10,11 +11,12 @@ import { ref } from 'vue'
|
||||
*/
|
||||
const IFRAME_BLOCKED_HOSTS: string[] = []
|
||||
|
||||
/** External sites proxied through nginx to strip X-Frame-Options for iframe embedding */
|
||||
const EXTERNAL_PROXY: Record<string, string> = {
|
||||
'botfights.net': '/ext/botfights/',
|
||||
'484.kitchen': '/ext/484-kitchen/',
|
||||
'present.l484.com': '/ext/arch-presentation/',
|
||||
/** External sites proxied through nginx on dedicated ports (strips X-Frame-Options).
|
||||
* Each site gets its own port so SPAs work at root — no subpath rewriting needed. */
|
||||
const EXTERNAL_PROXY_PORT: Record<string, number> = {
|
||||
'botfights.net': 8901,
|
||||
'484.kitchen': 8902,
|
||||
'present.l484.com': 8903,
|
||||
}
|
||||
|
||||
function mustOpenInNewTab(url: string): boolean {
|
||||
@@ -82,10 +84,10 @@ function toEmbeddableUrl(url: string): string {
|
||||
const u = new URL(url)
|
||||
const origin = window.location.origin
|
||||
|
||||
// External sites proxied through nginx to strip X-Frame-Options
|
||||
const extProxy = EXTERNAL_PROXY[u.hostname]
|
||||
if (extProxy) {
|
||||
return `${origin}${extProxy}`
|
||||
// External sites proxied through nginx on dedicated ports
|
||||
const extPort = EXTERNAL_PROXY_PORT[u.hostname]
|
||||
if (extPort) {
|
||||
return `${window.location.protocol}//${window.location.hostname}:${extPort}/`
|
||||
}
|
||||
|
||||
const proxyPath = PORT_TO_PROXY[u.port]
|
||||
@@ -132,6 +134,42 @@ export const useAppLauncherStore = defineStore('appLauncher', () => {
|
||||
}
|
||||
}
|
||||
|
||||
// NIP-07 postMessage handler — responds to nostr-request from iframe apps
|
||||
async function handleNostrRequest(event: MessageEvent) {
|
||||
if (!event.data || event.data.type !== 'nostr-request') return
|
||||
const { id, method, params } = event.data
|
||||
const source = event.source as Window | null
|
||||
if (!source) return
|
||||
|
||||
try {
|
||||
let result: unknown
|
||||
if (method === 'getPublicKey') {
|
||||
const res = await rpcClient.call<{ nostr_pubkey: string }>({ method: 'node.nostr-pubkey' })
|
||||
result = res.nostr_pubkey
|
||||
} else if (method === 'signEvent') {
|
||||
const res = await rpcClient.call<unknown>({ method: 'identity.nostr-sign', params: { event: params.event } })
|
||||
result = res
|
||||
} else if (method === 'getRelays') {
|
||||
result = {}
|
||||
} else {
|
||||
throw new Error(`Unsupported NIP-07 method: ${method}`)
|
||||
}
|
||||
source.postMessage({ type: 'nostr-response', id, result }, '*')
|
||||
} catch (err) {
|
||||
const message = err instanceof Error ? err.message : 'Unknown error'
|
||||
source.postMessage({ type: 'nostr-response', id, error: message }, '*')
|
||||
}
|
||||
}
|
||||
|
||||
// Listen for NIP-07 requests only while an app is open
|
||||
watch(isOpen, (open) => {
|
||||
if (open) {
|
||||
window.addEventListener('message', handleNostrRequest)
|
||||
} else {
|
||||
window.removeEventListener('message', handleNostrRequest)
|
||||
}
|
||||
})
|
||||
|
||||
return {
|
||||
isOpen,
|
||||
url,
|
||||
|
||||
@@ -215,13 +215,13 @@ input[type="radio"]:active + * {
|
||||
|
||||
.chat-mode-pill {
|
||||
position: absolute;
|
||||
top: calc(env(safe-area-inset-top, 0px) + 1.25rem);
|
||||
top: calc(env(safe-area-inset-top, 0px) + 2.25rem);
|
||||
right: calc(env(safe-area-inset-right, 0px) + 1.25rem);
|
||||
z-index: 10;
|
||||
}
|
||||
@media (min-width: 768px) {
|
||||
.chat-mode-pill {
|
||||
top: 1.25rem;
|
||||
top: 2.25rem;
|
||||
right: 1.25rem;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -515,7 +515,7 @@ export const dummyApps: Record<string, PackageDataEntry> = {
|
||||
'static-files': {
|
||||
license: 'MIT',
|
||||
instructions: 'Decentralized media streaming platform',
|
||||
icon: 'https://indeehub.studio/favicon.ico'
|
||||
icon: '/assets/img/app-icons/indeehub.ico'
|
||||
},
|
||||
manifest: {
|
||||
id: 'indeedhub',
|
||||
@@ -545,6 +545,244 @@ export const dummyApps: Record<string, PackageDataEntry> = {
|
||||
},
|
||||
status: ServiceStatus.Running
|
||||
}
|
||||
},
|
||||
'botfights': {
|
||||
state: PackageState.Running,
|
||||
'static-files': {
|
||||
license: 'MIT',
|
||||
instructions: 'AI bot arena',
|
||||
icon: '/assets/img/app-icons/botfights.svg'
|
||||
},
|
||||
manifest: {
|
||||
id: 'botfights',
|
||||
title: 'BotFights',
|
||||
version: '1.0.0',
|
||||
description: {
|
||||
short: 'AI bot arena — build, train, and battle autonomous agents',
|
||||
long: 'BotFights is an AI bot arena where you can build, train, and battle autonomous agents. Create intelligent bots using various strategies, pit them against other players\' creations, and climb the leaderboard. Features real-time battle visualization, multiple game modes, and a growing community of bot builders.'
|
||||
},
|
||||
'release-notes': 'Initial release',
|
||||
license: 'MIT',
|
||||
'wrapper-repo': '',
|
||||
'upstream-repo': '',
|
||||
'support-site': '',
|
||||
'marketing-site': 'https://botfights.net',
|
||||
website: 'https://botfights.net',
|
||||
'donation-url': null
|
||||
},
|
||||
installed: {
|
||||
'current-dependents': {},
|
||||
'current-dependencies': {},
|
||||
'last-backup': null,
|
||||
'interface-addresses': {
|
||||
main: { 'tor-address': '', 'lan-address': 'https://botfights.net' }
|
||||
},
|
||||
status: ServiceStatus.Running
|
||||
}
|
||||
},
|
||||
'nwnn': {
|
||||
state: PackageState.Running,
|
||||
'static-files': {
|
||||
license: 'MIT',
|
||||
instructions: 'Decentralized news aggregator',
|
||||
icon: '/assets/img/app-icons/nwnn.png'
|
||||
},
|
||||
manifest: {
|
||||
id: 'nwnn',
|
||||
title: 'Next Web News Network',
|
||||
version: '1.0.0',
|
||||
description: {
|
||||
short: 'Decentralized news aggregator, synced from Telegram',
|
||||
long: 'Next Web News Network (NWNN) is a decentralized news aggregation platform that curates and syncs content from Telegram channels. Stay informed with the latest developments in Bitcoin, decentralization, and sovereign technology. Clean reading experience with no ads or tracking.'
|
||||
},
|
||||
'release-notes': 'Initial release',
|
||||
license: 'MIT',
|
||||
'wrapper-repo': '',
|
||||
'upstream-repo': '',
|
||||
'support-site': '',
|
||||
'marketing-site': 'https://nwnn.l484.com',
|
||||
website: 'https://nwnn.l484.com',
|
||||
'donation-url': null
|
||||
},
|
||||
installed: {
|
||||
'current-dependents': {},
|
||||
'current-dependencies': {},
|
||||
'last-backup': null,
|
||||
'interface-addresses': {
|
||||
main: { 'tor-address': '', 'lan-address': 'https://nwnn.l484.com' }
|
||||
},
|
||||
status: ServiceStatus.Running
|
||||
}
|
||||
},
|
||||
'484-kitchen': {
|
||||
state: PackageState.Running,
|
||||
'static-files': {
|
||||
license: 'MIT',
|
||||
instructions: 'K484 application platform',
|
||||
icon: '/assets/img/app-icons/484-kitchen.png'
|
||||
},
|
||||
manifest: {
|
||||
id: '484-kitchen',
|
||||
title: '484 Kitchen',
|
||||
version: '1.0.0',
|
||||
description: {
|
||||
short: 'K484 application platform',
|
||||
long: '484 Kitchen is a creative application platform from the K484 collective. Explore experimental tools, interactive experiences, and cutting-edge web applications built with a focus on sovereignty and decentralization.'
|
||||
},
|
||||
'release-notes': 'Initial release',
|
||||
license: 'MIT',
|
||||
'wrapper-repo': '',
|
||||
'upstream-repo': '',
|
||||
'support-site': '',
|
||||
'marketing-site': 'https://484.kitchen',
|
||||
website: 'https://484.kitchen',
|
||||
'donation-url': null
|
||||
},
|
||||
installed: {
|
||||
'current-dependents': {},
|
||||
'current-dependencies': {},
|
||||
'last-backup': null,
|
||||
'interface-addresses': {
|
||||
main: { 'tor-address': '', 'lan-address': 'https://484.kitchen' }
|
||||
},
|
||||
status: ServiceStatus.Running
|
||||
}
|
||||
},
|
||||
'call-the-operator': {
|
||||
state: PackageState.Running,
|
||||
'static-files': {
|
||||
license: 'MIT',
|
||||
instructions: 'Escape the Matrix',
|
||||
icon: '/assets/img/app-icons/call-the-operator.png'
|
||||
},
|
||||
manifest: {
|
||||
id: 'call-the-operator',
|
||||
title: 'Call the Operator',
|
||||
version: '1.0.0',
|
||||
description: {
|
||||
short: 'Escape the Matrix — explore decentralized alternatives',
|
||||
long: 'Call the Operator is an interactive guide to escaping the centralized matrix. Discover decentralized alternatives to mainstream services, learn about self-sovereignty, and take back control of your digital life. Beautiful dreamcore aesthetic with immersive 3D visuals.'
|
||||
},
|
||||
'release-notes': 'Initial release',
|
||||
license: 'MIT',
|
||||
'wrapper-repo': '',
|
||||
'upstream-repo': '',
|
||||
'support-site': '',
|
||||
'marketing-site': 'https://cta.tx1138.com',
|
||||
website: 'https://cta.tx1138.com',
|
||||
'donation-url': null
|
||||
},
|
||||
installed: {
|
||||
'current-dependents': {},
|
||||
'current-dependencies': {},
|
||||
'last-backup': null,
|
||||
'interface-addresses': {
|
||||
main: { 'tor-address': '', 'lan-address': 'https://cta.tx1138.com' }
|
||||
},
|
||||
status: ServiceStatus.Running
|
||||
}
|
||||
},
|
||||
'arch-presentation': {
|
||||
state: PackageState.Running,
|
||||
'static-files': {
|
||||
license: 'MIT',
|
||||
instructions: 'Archipelago presentation',
|
||||
icon: '/assets/img/app-icons/arch-presentation.png'
|
||||
},
|
||||
manifest: {
|
||||
id: 'arch-presentation',
|
||||
title: 'Arch Presentation',
|
||||
version: '1.0.0',
|
||||
description: {
|
||||
short: 'Archipelago: The Future of Decentralized Infrastructure',
|
||||
long: 'The official Archipelago presentation deck. Learn about the vision, architecture, and roadmap of the Archipelago Bitcoin Node OS. Interactive slides showcasing the future of decentralized personal infrastructure, self-sovereign computing, and the Web5 stack.'
|
||||
},
|
||||
'release-notes': 'Initial release',
|
||||
license: 'MIT',
|
||||
'wrapper-repo': '',
|
||||
'upstream-repo': '',
|
||||
'support-site': '',
|
||||
'marketing-site': 'https://present.l484.com',
|
||||
website: 'https://present.l484.com',
|
||||
'donation-url': null
|
||||
},
|
||||
installed: {
|
||||
'current-dependents': {},
|
||||
'current-dependencies': {},
|
||||
'last-backup': null,
|
||||
'interface-addresses': {
|
||||
main: { 'tor-address': '', 'lan-address': 'https://present.l484.com' }
|
||||
},
|
||||
status: ServiceStatus.Running
|
||||
}
|
||||
},
|
||||
'syntropy-institute': {
|
||||
state: PackageState.Running,
|
||||
'static-files': {
|
||||
license: 'MIT',
|
||||
instructions: 'Frequency analysis and therapy',
|
||||
icon: '/assets/img/app-icons/syntropy-institute.png'
|
||||
},
|
||||
manifest: {
|
||||
id: 'syntropy-institute',
|
||||
title: 'Syntropy Institute',
|
||||
version: '1.0.0',
|
||||
description: {
|
||||
short: 'Medicine Reimagined — frequency analysis and therapy',
|
||||
long: 'Syntropy Institute presents a new paradigm in health and wellness through frequency analysis and therapy. Explore cutting-edge research into bioresonance, quantum biology, and the energetic foundations of health. A bridge between ancient healing wisdom and modern technology.'
|
||||
},
|
||||
'release-notes': 'Initial release',
|
||||
license: 'MIT',
|
||||
'wrapper-repo': '',
|
||||
'upstream-repo': '',
|
||||
'support-site': '',
|
||||
'marketing-site': 'https://syntropy.institute',
|
||||
website: 'https://syntropy.institute',
|
||||
'donation-url': null
|
||||
},
|
||||
installed: {
|
||||
'current-dependents': {},
|
||||
'current-dependencies': {},
|
||||
'last-backup': null,
|
||||
'interface-addresses': {
|
||||
main: { 'tor-address': '', 'lan-address': 'https://syntropy.institute' }
|
||||
},
|
||||
status: ServiceStatus.Running
|
||||
}
|
||||
},
|
||||
't-zero': {
|
||||
state: PackageState.Running,
|
||||
'static-files': {
|
||||
license: 'MIT',
|
||||
instructions: 'Documentary series',
|
||||
icon: '/assets/img/app-icons/t-zero.png'
|
||||
},
|
||||
manifest: {
|
||||
id: 't-zero',
|
||||
title: 'T-0',
|
||||
version: '1.0.0',
|
||||
description: {
|
||||
short: 'Documentary series on decentralization and Bitcoin',
|
||||
long: 'T-0 (Tee Minus Zero) is a documentary series exploring the intersection of decentralization, Bitcoin, and personal sovereignty. Follow the stories of builders, dreamers, and freedom advocates creating the infrastructure for a more sovereign future.'
|
||||
},
|
||||
'release-notes': 'Initial release',
|
||||
license: 'MIT',
|
||||
'wrapper-repo': '',
|
||||
'upstream-repo': '',
|
||||
'support-site': '',
|
||||
'marketing-site': 'https://teeminuszero.net',
|
||||
website: 'https://teeminuszero.net',
|
||||
'donation-url': null
|
||||
},
|
||||
installed: {
|
||||
'current-dependents': {},
|
||||
'current-dependencies': {},
|
||||
'last-backup': null,
|
||||
'interface-addresses': {
|
||||
main: { 'tor-address': '', 'lan-address': 'https://teeminuszero.net' }
|
||||
},
|
||||
status: ServiceStatus.Running
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -72,6 +72,7 @@
|
||||
</svg>
|
||||
{{ t('common.launch') }}
|
||||
</button>
|
||||
<template v-if="!isWebOnly">
|
||||
<button
|
||||
v-if="pkg.state === 'stopped'"
|
||||
@click="startApp"
|
||||
@@ -111,6 +112,7 @@
|
||||
</svg>
|
||||
{{ t('common.uninstall') }}
|
||||
</button>
|
||||
</template>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -144,6 +146,7 @@
|
||||
|
||||
<!-- Uninstall Icon Button -->
|
||||
<button
|
||||
v-if="!isWebOnly"
|
||||
@click="uninstallApp"
|
||||
class="flex-shrink-0 w-10 h-10 rounded-lg bg-red-600/20 border border-red-600/40 text-red-300 hover:bg-red-600/30 transition-colors flex items-center justify-center"
|
||||
:title="t('common.uninstall')"
|
||||
@@ -159,6 +162,7 @@
|
||||
<button
|
||||
v-if="canLaunch"
|
||||
@click="launchApp"
|
||||
:class="isWebOnly ? 'col-span-2' : ''"
|
||||
class="glass-button glass-button-sm px-4 py-2.5 rounded-lg text-sm font-semibold flex items-center justify-center gap-2"
|
||||
>
|
||||
<svg class="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
@@ -166,6 +170,7 @@
|
||||
</svg>
|
||||
{{ t('common.launch') }}
|
||||
</button>
|
||||
<template v-if="!isWebOnly">
|
||||
<button
|
||||
v-if="pkg.state === 'stopped'"
|
||||
@click="startApp"
|
||||
@@ -197,6 +202,7 @@
|
||||
</svg>
|
||||
{{ t('common.restart') }}
|
||||
</button>
|
||||
</template>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -330,8 +336,8 @@
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Requirements Card -->
|
||||
<div class="glass-card p-6">
|
||||
<!-- Requirements Card (hidden for web-only apps) -->
|
||||
<div v-if="!isWebOnly" class="glass-card p-6">
|
||||
<h3 class="text-lg font-bold text-white mb-4">{{ t('appDetails.requirements') }}</h3>
|
||||
<div class="space-y-3">
|
||||
<div class="flex items-start gap-3">
|
||||
@@ -478,6 +484,20 @@ const { t } = useI18n()
|
||||
|
||||
const appId = computed(() => route.params.id as string)
|
||||
|
||||
// Web-only app detection (no container — external websites)
|
||||
const WEB_ONLY_APP_URLS: Record<string, string> = {
|
||||
'indeedhub': 'https://archipelago.indeehub.studio',
|
||||
'botfights': 'https://botfights.net',
|
||||
'nwnn': 'https://nwnn.l484.com',
|
||||
'484-kitchen': 'https://484.kitchen',
|
||||
'call-the-operator': 'https://cta.tx1138.com',
|
||||
'arch-presentation': 'https://present.l484.com',
|
||||
'syntropy-institute': 'https://syntropy.institute',
|
||||
't-zero': 'https://teeminuszero.net',
|
||||
}
|
||||
|
||||
const isWebOnly = computed(() => appId.value in WEB_ONLY_APP_URLS)
|
||||
|
||||
/** Map route/marketplace app IDs to backend package keys (container names). */
|
||||
const ROUTE_TO_PACKAGE_KEY: Record<string, string> = {
|
||||
mempool: 'mempool-web',
|
||||
@@ -625,7 +645,8 @@ const backButtonText = computed(() => {
|
||||
// Check if app has a UI interface and is running
|
||||
const canLaunch = computed(() => {
|
||||
if (!pkg.value) return false
|
||||
// For dummy apps, allow launch if running (they have interface addresses)
|
||||
// Web-only apps are always launchable
|
||||
if (isWebOnly.value) return true
|
||||
// For real apps, check for UI interface
|
||||
const hasUI = pkg.value.manifest.interfaces?.main?.ui || pkg.value.installed?.['interface-addresses']?.main
|
||||
const isRunning = pkg.value.state === 'running'
|
||||
@@ -693,12 +714,18 @@ function goBack() {
|
||||
|
||||
function launchApp() {
|
||||
if (!pkg.value) return
|
||||
|
||||
|
||||
const isDev = import.meta.env.DEV
|
||||
const id = appId.value
|
||||
|
||||
|
||||
// Web-only apps — use their external URL directly
|
||||
const webOnlyUrl = WEB_ONLY_APP_URLS[id]
|
||||
if (webOnlyUrl) {
|
||||
useAppLauncherStore().open({ url: webOnlyUrl, title: pkg.value.manifest.title })
|
||||
return
|
||||
}
|
||||
|
||||
// Special handling for apps with Docker containers
|
||||
// TODO: Replace dummy app URLs with real URLs when apps are packaged
|
||||
const appUrls: Record<string, { dev: string, prod: string }> = {
|
||||
'lorabell': {
|
||||
dev: 'http://192.168.1.166',
|
||||
|
||||
@@ -273,7 +273,7 @@ const WEB_ONLY_APPS: Record<string, PackageDataEntry> = {
|
||||
'indeedhub': {
|
||||
state: 'running' as PackageState,
|
||||
manifest: { id: 'indeedhub', title: 'Indeehub', version: '0.1.0', description: { short: 'Bitcoin documentary streaming platform', long: '' }, 'release-notes': '', license: '', 'wrapper-repo': '', 'upstream-repo': '', 'support-site': '', 'marketing-site': '', 'donation-url': null },
|
||||
'static-files': { license: '', instructions: '', icon: '/assets/img/app-icons/indeedhub.png' },
|
||||
'static-files': { license: '', instructions: '', icon: '/assets/img/app-icons/indeehub.ico' },
|
||||
},
|
||||
'botfights': {
|
||||
state: 'running' as PackageState,
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
<template>
|
||||
<div class="chat-fullscreen">
|
||||
<!-- Close button + connection indicator (desktop: top-right pill) -->
|
||||
<div class="chat-mode-pill hidden md:flex">
|
||||
<div class="chat-mode-pill flex">
|
||||
<button class="chat-close-btn" :aria-label="t('chat.closeAssistant')" @click="closeChat">
|
||||
<svg class="w-4 h-4" aria-hidden="true" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M6 18L18 6M6 6l12 12" />
|
||||
@@ -15,19 +15,12 @@
|
||||
/>
|
||||
</div>
|
||||
|
||||
<!-- Mobile back button -->
|
||||
<button class="chat-mobile-back md:hidden" :aria-label="t('common.goBack')" @click="closeChat">
|
||||
<svg class="w-5 h-5" aria-hidden="true" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M15 19l-7-7 7-7" />
|
||||
</svg>
|
||||
</button>
|
||||
|
||||
<!-- Loading indicator while checking availability or iframe loads -->
|
||||
<!-- Loading indicator while checking availability -->
|
||||
<Transition name="fade">
|
||||
<div v-if="aiuiAvailable === null || (aiuiUrl && !aiuiConnected)" class="chat-loading" role="status" aria-live="polite">
|
||||
<div v-if="aiuiAvailable === null" class="chat-loading" role="status" aria-live="polite">
|
||||
<div class="glass-card p-8 flex flex-col items-center gap-4">
|
||||
<div class="chat-loading-spinner" aria-hidden="true" />
|
||||
<p class="text-sm text-white/60">{{ aiuiAvailable === null ? t('chat.loadingAssistant') : t('chat.loadingAssistant') }}</p>
|
||||
<p class="text-sm text-white/60">{{ t('chat.loadingAssistant') }}</p>
|
||||
</div>
|
||||
</div>
|
||||
</Transition>
|
||||
@@ -39,13 +32,13 @@
|
||||
:src="aiuiUrl"
|
||||
:title="t('chat.aiAssistant')"
|
||||
class="chat-iframe chat-iframe-mobile"
|
||||
sandbox="allow-scripts allow-same-origin allow-forms"
|
||||
|
||||
allow="microphone"
|
||||
style="background: transparent"
|
||||
/>
|
||||
|
||||
<!-- Fallback when no AIUI URL configured -->
|
||||
<div v-else class="chat-placeholder">
|
||||
<!-- Fallback when AIUI is not deployed -->
|
||||
<div v-else-if="aiuiAvailable === false" class="chat-placeholder">
|
||||
<div class="chat-placeholder-inner">
|
||||
<div class="chat-placeholder-icon">
|
||||
<svg class="w-8 h-8 text-white/40" aria-hidden="true" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
@@ -84,7 +77,7 @@ const aiuiUrl = computed(() => {
|
||||
const envUrl = import.meta.env.VITE_AIUI_URL
|
||||
if (envUrl) return `${envUrl}?embedded=true`
|
||||
// In production, only return the URL if we've confirmed AIUI files exist
|
||||
if (import.meta.env.PROD && aiuiAvailable.value === true) return '/aiui/?embedded=true'
|
||||
if (import.meta.env.PROD && aiuiAvailable.value === true) return `/aiui/?embedded=true&v=${Date.now()}`
|
||||
return ''
|
||||
})
|
||||
|
||||
@@ -177,20 +170,4 @@ onBeforeUnmount(() => {
|
||||
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);
|
||||
}
|
||||
</style>
|
||||
|
||||
Reference in New Issue
Block a user