|
<script lang="ts"> |
|
import { onMount } from 'svelte'; |
|
import { authStore } from './lib/stores/auth'; |
|
import { uiStore } from './lib/stores/ui'; |
|
import AppHeader from './lib/components/Layout/AppHeader.svelte'; |
|
import ProgressBar from './lib/components/Layout/ProgressBar.svelte'; |
|
import TabBar, { type TabId } from './lib/components/Layout/TabBar.svelte'; |
|
import Scanner from './lib/components/Pages/Scanner.svelte'; |
|
import Encounters from './lib/components/Pages/Encounters.svelte'; |
|
import Pictuary from './lib/components/Pages/Pictuary.svelte'; |
|
import type { HuggingFaceLibs, GradioLibs, GradioClient } from './lib/types'; |
|
{ setQwenClientResetter } from './lib/utils/qwenTimeout'; |
|
|
|
|
|
let hfAuth: HuggingFaceLibs | null = $state(null); |
|
let gradioClient: GradioLibs | null = $state(null); |
|
|
|
|
|
let fluxClient: GradioClient | null = $state(null); |
|
let joyCaptionClient: GradioClient | null = $state(null); |
|
let hunyuanClient: GradioClient | null = $state(null); |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
let activeTab: TabId = $state('scanner'); |
|
|
|
|
|
const tabNames: Record<TabId, string> = { |
|
scanner: 'Scanner', |
|
encounters: 'Encounters', |
|
pictuary: 'Pictuary' |
|
}; |
|
|
|
|
|
|
|
|
|
|
|
let isDetailPageOpen = $state(false); |
|
let isInBattle = $state(false); |
|
|
|
$effect(() => { |
|
const unsubscribe = uiStore.subscribe(state => { |
|
isDetailPageOpen = state.isDetailPageOpen; |
|
isInBattle = state.isInBattle; |
|
}); |
|
return unsubscribe; |
|
}); |
|
|
|
onMount(async () => { |
|
// Load HF libraries |
|
const script1 = document.createElement('script'); |
|
script1.type = 'module'; |
|
script1.textContent = ` |
|
import { |
|
oauthLoginUrl, |
|
oauthHandleRedirectIfPresent |
|
} from "https://cdn.jsdelivr.net/npm/@huggingface/hub@0.21/+esm"; |
|
import { Client } from "https://cdn.jsdelivr.net/npm/@gradio/client/dist/index.min.js"; |
|
|
|
window.hfAuth = { oauthLoginUrl, oauthHandleRedirectIfPresent }; |
|
window.gradioClient = { Client }; |
|
`; |
|
document.head.appendChild(script1); |
|
|
|
// Wait for libraries to load |
|
await new Promise(resolve => { |
|
const checkLibs = setInterval(() => { |
|
if (window.hfAuth && window.gradioClient) { |
|
clearInterval(checkLibs); |
|
resolve(undefined); |
|
} |
|
}, 100); |
|
}); |
|
|
|
hfAuth = window.hfAuth as HuggingFaceLibs; |
|
gradioClient = window.gradioClient as GradioLibs; |
|
|
|
|
|
try { |
|
const session = await hfAuth.oauthHandleRedirectIfPresent(); |
|
authStore.setSession(session); |
|
|
|
// Start the app |
|
await initializeClients(session?.accessToken || null); |
|
} catch (err) { |
|
console.error("OAuth handling error:", err); |
|
authStore.setSession(null); |
|
await initializeClients(null); |
|
} |
|
}); |
|
|
|
async function initializeClients(hfToken: string | null) { |
|
if (!gradioClient) return; |
|
|
|
authStore.setBannerMessage("Connecting to AI services..."); |
|
|
|
try { |
|
const opts = hfToken ? { hf_token: hfToken } : {}; |
|
|
|
|
|
fluxClient = await gradioClient.Client.connect( |
|
"black-forest-labs/FLUX.1-schnell", |
|
opts |
|
); |
|
|
|
joyCaptionClient = await gradioClient.Client.connect( |
|
"fancyfeast/joy-caption-alpha-two", |
|
opts |
|
); |
|
|
|
hunyuanClient = await gradioClient.Client.connect( |
|
"tencent/hunyuan-turbos", |
|
opts |
|
); |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
authStore.setBannerMessage(""); |
|
|
|
|
|
{ |
|
// console.log('🔄 Resetting qwen client connection...'); |
|
// const opts = hfToken ? { hf_token: hfToken } : {}; |
|
|
|
|
|
|
|
|
|
|
|
|
|
} catch (err) { |
|
console.error(err); |
|
authStore.setBannerMessage(`❌ Failed to connect: ${err}`); |
|
} |
|
} |
|
|
|
function handleTabChange(tab: TabId) { |
|
activeTab = tab; |
|
} |
|
</script> |
|
|
|
<div class="app"> |
|
{#if !isDetailPageOpen && !isInBattle} |
|
<ProgressBar /> |
|
<AppHeader {hfAuth} currentTab={tabNames[activeTab]} /> |
|
{/if} |
|
|
|
<main class="app-content" class:detail-open={isDetailPageOpen}> |
|
{#if activeTab === 'scanner'} |
|
<Scanner |
|
{fluxClient} |
|
{joyCaptionClient} |
|
{hunyuanClient} |
|
/> |
|
{:else if activeTab === 'encounters'} |
|
<Encounters /> |
|
{:else if activeTab === 'pictuary'} |
|
<Pictuary /> |
|
{/if} |
|
</main> |
|
|
|
{#if !isDetailPageOpen && !isInBattle} |
|
<TabBar {activeTab} onTabChange={handleTabChange} /> |
|
{/if} |
|
</div> |
|
|
|
<style> |
|
.app { |
|
display: flex; |
|
flex-direction: column; |
|
height: 100vh; |
|
height: 100dvh; /* Dynamic viewport height for mobile */ |
|
background: white; |
|
overflow: hidden; |
|
} |
|
|
|
.app-content { |
|
flex: 1; |
|
overflow: hidden; |
|
position: relative; |
|
padding-bottom: calc(70px + env(safe-area-inset-bottom, 0)); |
|
} |
|
|
|
.app-content.detail-open { |
|
padding-bottom: 0; |
|
} |
|
|
|
|
|
:global(.app-content > *) { |
|
height: 100%; |
|
} |
|
</style> |