jim800121chen c54f16fca0 Initial commit: visionA monorepo with local-tool subproject
local-tool/: visionA-local desktop app
- M1: Wails shell + Go server + Next.js UI + Mock mode (macOS dmg ready)
- M2: i18n (zh-TW/en) + Settings 4-tab refactor
- M3: Embedded Python 3.12 runtime (python-build-standalone) + KneronPLUS wheels
- M4: Windows Inno Setup script (build on Windows runner)
- M5: Linux AppImage script + udev rule (build on Linux runner)
- M6: ffmpeg (GPL, pending legal review) + yt-dlp bundled
- Lifecycle: watchServer health check, fatal native dialog,
            Wails IPC raise endpoint, stale process cleanup

.autoflow/: full PRD / Design Spec / Architecture / Testing docs
            (4 rounds tri-party discussion + cross review)
.github/workflows/: macOS / Windows / Linux build CI

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-11 22:10:38 +08:00

93 lines
3.0 KiB
TypeScript

import type { TranslationKey } from '@/lib/i18n/types';
import { useDeviceStore } from '@/stores/device-store';
import { useInferenceStore } from '@/stores/inference-store';
export interface TourStep {
id: string;
page: string;
elementSelector: string;
titleKey: TranslationKey;
descriptionKey: TranslationKey;
pendingDescKey: TranslationKey;
side: 'top' | 'bottom' | 'left' | 'right';
/** Returns true when the step's goal has been achieved */
isComplete: () => boolean;
}
export const tourSteps: TourStep[] = [
{
id: 'scan-devices',
page: '/devices',
elementSelector: '[data-tour-id="scan-devices-btn"]',
titleKey: 'tour.step1Title',
descriptionKey: 'tour.step1Desc',
pendingDescKey: 'tour.step1Pending',
side: 'bottom',
isComplete: () => useDeviceStore.getState().devices.length > 0,
},
{
id: 'connect-device',
page: '/devices',
elementSelector: '[data-tour-id="connect-device-btn"]',
titleKey: 'tour.step2Title',
descriptionKey: 'tour.step2Desc',
pendingDescKey: 'tour.step2Pending',
side: 'bottom',
isComplete: () => {
const devices = useDeviceStore.getState().devices;
return devices.some((d) => d.status === 'connected' || d.status === 'flashing' || d.status === 'inferencing');
},
},
{
id: 'manage-device',
page: '/devices',
elementSelector: '[data-tour-id="manage-device-btn"]',
titleKey: 'tour.step3Title',
descriptionKey: 'tour.step3Desc',
pendingDescKey: 'tour.step3Pending',
side: 'bottom',
// "Manage" is always available once a device is connected (previous step guarantees this)
isComplete: () => {
const devices = useDeviceStore.getState().devices;
return devices.some((d) => d.status === 'connected' || d.status === 'flashing' || d.status === 'inferencing');
},
},
{
id: 'flash-model',
page: '/devices/__DEVICE_ID__',
elementSelector: '[data-tour-id="flash-model-btn"]',
titleKey: 'tour.step4Title',
descriptionKey: 'tour.step4Desc',
pendingDescKey: 'tour.step4Pending',
side: 'bottom',
isComplete: () => {
const device = useDeviceStore.getState().selectedDevice;
return !!device?.flashedModel;
},
},
{
id: 'open-workspace',
page: '/devices/__DEVICE_ID__',
elementSelector: '[data-tour-id="open-workspace-btn"]',
titleKey: 'tour.step5Title',
descriptionKey: 'tour.step5Desc',
pendingDescKey: 'tour.step5Pending',
side: 'bottom',
// Workspace button only shows when flashedModel exists, so same condition
isComplete: () => {
const device = useDeviceStore.getState().selectedDevice;
return !!device?.flashedModel;
},
},
{
id: 'start-inference',
page: '/workspace/__DEVICE_ID__',
elementSelector: '[data-tour-id="start-inference-btn"]',
titleKey: 'tour.step6Title',
descriptionKey: 'tour.step6Desc',
pendingDescKey: 'tour.step6Pending',
side: 'bottom',
isComplete: () => useInferenceStore.getState().isRunning,
},
];