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>
93 lines
3.0 KiB
TypeScript
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,
|
|
},
|
|
];
|