Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,7 @@
"website:preview": "pnpm --dir website run preview",
"website:install": "pnpm --dir website install",
"e2e:install": "pnpm --dir tests/e2e install",
"e2e:build": "pnpm run desktop:build:fast",
"e2e:test": "pnpm --dir tests/e2e test",
"e2e:test:l0": "pnpm --dir tests/e2e run test:l0",
"e2e:test:l0:all": "pnpm --dir tests/e2e run test:l0:all",
Expand Down
42 changes: 22 additions & 20 deletions src/web-ui/src/app/scenes/miniapps/views/MiniAppGalleryView.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -29,12 +29,14 @@ import {
import type { SceneTabId } from '@/app/components/SceneBar/types';
import { getMiniAppIconGradient, renderMiniAppIcon } from '../utils/miniAppIcons';
import { useCurrentWorkspace } from '@/infrastructure/contexts/WorkspaceContext';
import { useI18n } from '@/infrastructure/i18n/hooks/useI18n';
import { useMiniAppStore } from '../miniAppStore';
import './MiniAppGalleryView.scss';

const log = createLogger('MiniAppGalleryView');

const MiniAppGalleryView: React.FC = () => {
const { t } = useI18n('common');
const apps = useMiniAppStore((state) => state.apps);
const loading = useMiniAppStore((state) => state.loading);
const runningWorkerIds = useMiniAppStore((state) => state.runningWorkerIds);
Expand Down Expand Up @@ -147,7 +149,7 @@ const MiniAppGalleryView: React.FC = () => {
const selected = await open({
directory: true,
multiple: false,
title: '选择小应用目录(需包含 meta.json 与 source/)',
title: t('miniApps.importFolderTitle'),
});
const path = Array.isArray(selected) ? selected[0] : selected;
if (!path) return;
Expand Down Expand Up @@ -177,8 +179,8 @@ const MiniAppGalleryView: React.FC = () => {
: <LayoutGrid size={36} strokeWidth={1.2} />
}
message={apps.length === 0
? '边聊边生成,马上可用。和 AI 对话生成第一个小应用吧。'
: '没有匹配的应用。'}
? t('miniApps.emptyFirstApp')
: t('miniApps.emptyNoMatch')}
/>
);
}
Expand All @@ -203,17 +205,17 @@ const MiniAppGalleryView: React.FC = () => {
return (
<GalleryLayout className="miniapp-gallery">
<GalleryPageHeader
title="小应用"
subtitle="即时生成的小应用,打开就能用,也能继续迭代。"
title={t('scenes.miniApps')}
subtitle={t('miniApps.subtitle')}
actions={(
<>
<Search value={search} onChange={setSearch} placeholder="搜索小应用..." size="small" />
<Search value={search} onChange={setSearch} placeholder={t('miniApps.searchPlaceholder')} size="small" />
<button
type="button"
className="gallery-action-btn gallery-action-btn--primary"
onClick={handleAddFromFolder}
disabled={loading}
title="从文件夹导入"
title={t('miniApps.importFromFolder')}
>
<FolderPlus size={15} />
</button>
Expand All @@ -222,7 +224,7 @@ const MiniAppGalleryView: React.FC = () => {
className="gallery-action-btn"
onClick={handleRefresh}
disabled={loading}
title="刷新列表"
title={t('miniApps.refreshList')}
>
<RefreshCw
size={15}
Expand All @@ -235,7 +237,7 @@ const MiniAppGalleryView: React.FC = () => {

<div className="gallery-zones">
<GalleryZone
title="已启动"
title={t('miniApps.runningTitle')}
tools={runningApps.length > 0 ? <span className="gallery-zone-badge">{runningApps.length}</span> : null}
>
{runningApps.length > 0 ? (
Expand All @@ -255,13 +257,13 @@ const MiniAppGalleryView: React.FC = () => {
</GalleryGrid>
) : (
<div className="gallery-run-empty">
暂无运行中的应用
{t('miniApps.noRunningApps')}
</div>
)}
</GalleryZone>

<GalleryZone
title="全部应用"
title={t('miniApps.allAppsTitle')}
tools={(
<>
{categories.length > 1 ? (
Expand All @@ -278,12 +280,12 @@ const MiniAppGalleryView: React.FC = () => {
.join(' ')}
onClick={() => setCategoryFilter(category)}
>
{category === 'all' ? '全部' : category}
{category === 'all' ? t('miniApps.categories.all') : category}
</button>
))}
</div>
) : null}
<span className="gallery-zone-count">{filtered.length} 个</span>
<span className="gallery-zone-count">{t('miniApps.count', { count: filtered.length })}</span>
</>
)}
>
Expand All @@ -305,16 +307,16 @@ const MiniAppGalleryView: React.FC = () => {
{runningIdSet.has(selectedApp.id) ? (
<Button variant="secondary" size="small" onClick={() => void handleStopRunning(selectedApp.id)}>
<Square size={14} />
停止
{t('actions.stop')}
</Button>
) : null}
<Button variant="danger" size="small" onClick={() => setPendingDeleteId(selectedApp.id)}>
<Trash2 size={14} />
删除
{t('actions.delete')}
</Button>
<Button variant="primary" size="small" onClick={() => handleOpenApp(selectedApp.id)}>
<Play size={14} />
打开
{t('actions.open')}
</Button>
</>
) : null}
Expand All @@ -335,12 +337,12 @@ const MiniAppGalleryView: React.FC = () => {
isOpen={pendingDeleteId !== null}
onClose={() => setPendingDeleteId(null)}
onConfirm={handleDeleteConfirm}
title={`删除 "${apps.find((app) => app.id === pendingDeleteId)?.name ?? ''}"?`}
message="此操作不可撤销,应用及其所有数据将被永久删除。"
title={t('miniApps.deleteDialog.title', { name: apps.find((app) => app.id === pendingDeleteId)?.name ?? '' })}
message={t('miniApps.deleteDialog.message')}
type="warning"
confirmDanger
confirmText="删除"
cancelText="取消"
confirmText={t('actions.delete')}
cancelText={t('actions.cancel')}
/>
</GalleryLayout>
);
Expand Down
20 changes: 20 additions & 0 deletions src/web-ui/src/locales/en-US/common.json
Original file line number Diff line number Diff line change
Expand Up @@ -780,5 +780,25 @@
"timeOfDay": "Messages by Time of Day",
"lines": "Lines",
"files": "Files"
},
"miniApps": {
"subtitle": "Instant mini apps you can open and use right away, then keep iterating.",
"searchPlaceholder": "Search mini apps...",
"importFolderTitle": "Select a mini app folder (must contain meta.json and source/)",
"importFromFolder": "Import from folder",
"refreshList": "Refresh list",
"runningTitle": "Running",
"noRunningApps": "No running apps",
"allAppsTitle": "All Apps",
"emptyFirstApp": "Create your first mini app by chatting with AI and use it right away.",
"emptyNoMatch": "No matching apps.",
"count": "{{count}} items",
"categories": {
"all": "All"
},
"deleteDialog": {
"title": "Delete \"{{name}}\"?",
"message": "This action cannot be undone. The app and all its data will be permanently deleted."
}
}
}
20 changes: 20 additions & 0 deletions src/web-ui/src/locales/zh-CN/common.json
Original file line number Diff line number Diff line change
Expand Up @@ -780,5 +780,25 @@
"timeOfDay": "按时段分布",
"lines": "行",
"files": "文件"
},
"miniApps": {
"subtitle": "即时生成的小应用,打开就能用,也能继续迭代。",
"searchPlaceholder": "搜索小应用...",
"importFolderTitle": "选择小应用目录(需包含 meta.json 与 source/)",
"importFromFolder": "从文件夹导入",
"refreshList": "刷新列表",
"runningTitle": "已启动",
"noRunningApps": "暂无运行中的应用",
"allAppsTitle": "全部应用",
"emptyFirstApp": "边聊边生成,马上可用。和 AI 对话生成第一个小应用吧。",
"emptyNoMatch": "没有匹配的应用。",
"count": "{{count}} 个",
"categories": {
"all": "全部"
},
"deleteDialog": {
"title": "删除 \"{{name}}\"?",
"message": "此操作不可撤销,应用及其所有数据将被永久删除。"
}
}
}
28 changes: 11 additions & 17 deletions tests/e2e/config/capabilities.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,23 +12,23 @@ const __filename = fileURLToPath(import.meta.url);
const __dirname = dirname(__filename);

/**
* Get the application path based on the current platform
* Get the application path based on the current platform.
* Defaults to debug for faster dev iteration.
*/
export function getApplicationPath(buildType: 'debug' | 'release' = 'release'): string {
export function getApplicationPath(buildType: 'debug' | 'release' = 'debug'): string {
const isWindows = process.platform === 'win32';
const isMac = process.platform === 'darwin';
const isLinux = process.platform === 'linux';


let appName: string;

if (isWindows) {
appName = 'bitfun-desktop.exe';
} else if (isMac) {
appName = 'BitFun.app/Contents/MacOS/BitFun';
} else {
appName = 'bitfun-desktop';
}

return path.resolve(__dirname, '..', '..', '..', 'target', buildType, appName);
}

Expand All @@ -38,12 +38,9 @@ export function getApplicationPath(buildType: 'debug' | 'release' = 'release'):
export const windowsCapabilities = {
browserName: 'wry',
'tauri:options': {
application: getApplicationPath('release'),
},
// Edge WebDriver specific options if needed
'ms:edgeOptions': {
// Edge options for WebView2
application: getApplicationPath(),
},
'ms:edgeOptions': {},
};

/**
Expand All @@ -52,12 +49,9 @@ export const windowsCapabilities = {
export const linuxCapabilities = {
browserName: 'wry',
'tauri:options': {
application: getApplicationPath('release'),
},
// WebKitWebDriver specific options if needed
'webkit:browserOptions': {
// WebKit options
application: getApplicationPath(),
},
'webkit:browserOptions': {},
};

/**
Expand All @@ -67,7 +61,7 @@ export const linuxCapabilities = {
export const macOSCapabilities = {
browserName: 'wry',
'tauri:options': {
application: getApplicationPath('release'),
application: getApplicationPath(),
},
};

Expand Down
9 changes: 9 additions & 0 deletions tests/e2e/config/tauri-wdio.d.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
declare namespace WebdriverIO {
interface TauriOptions {
application: string;
}

interface Capabilities {
'tauri:options'?: TauriOptions;
}
}
20 changes: 14 additions & 6 deletions tests/e2e/config/wdio.conf.ts
Original file line number Diff line number Diff line change
Expand Up @@ -42,13 +42,20 @@ function getTauriDriverPath(): string {
return path.join(homeDir, '.cargo', 'bin', driverName);
}

/** Get the path to the built Tauri application. Prefer release build; fall back to debug (requires dev server). */
/**
* Get the path to the built Tauri application.
*
* Resolution order:
* 1. BITFUN_E2E_APP_PATH – explicit full path
* 2. BITFUN_E2E_APP_MODE – "debug" | "release"
* 3. Auto-detect: debug first (fast build), then release
*/
function getApplicationPath(): string {
const isWindows = process.platform === 'win32';
const appName = isWindows ? 'bitfun-desktop.exe' : 'bitfun-desktop';
const projectRoot = path.resolve(__dirname, '..', '..', '..');
const releasePath = path.join(projectRoot, 'target', 'release', appName);
const debugPath = path.join(projectRoot, 'target', 'debug', appName);
const releasePath = path.join(projectRoot, 'target', 'release', appName);
const forcedPath = process.env.BITFUN_E2E_APP_PATH;
const forcedMode = process.env.BITFUN_E2E_APP_MODE?.toLowerCase();

Expand All @@ -64,11 +71,11 @@ function getApplicationPath(): string {
return releasePath;
}

if (fs.existsSync(releasePath)) {
return releasePath;
if (fs.existsSync(debugPath)) {
return debugPath;
}

return debugPath;
return releasePath;
}

/**
Expand Down Expand Up @@ -146,7 +153,8 @@ export const config: Options.Testrunner = {
if (!fs.existsSync(appPath)) {
console.error(`Application not found at: ${appPath}`);
console.error('Please build the application first with:');
console.error('pnpm run desktop:build');
console.error(' pnpm run desktop:build:fast (debug, fast compile, recommended for dev)');
console.error(' pnpm run desktop:build (release, slow compile)');
throw new Error('Application not built');
}
console.log(`application: ${appPath}`);
Expand Down
33 changes: 29 additions & 4 deletions tests/e2e/config/wdio.conf_l0.ts
Original file line number Diff line number Diff line change
Expand Up @@ -42,16 +42,40 @@ function getTauriDriverPath(): string {
return path.join(homeDir, '.cargo', 'bin', driverName);
}

/** Get the path to the built Tauri application. Prefer release build; fall back to debug (requires dev server). */
/**
* Get the path to the built Tauri application.
*
* Resolution order:
* 1. BITFUN_E2E_APP_PATH – explicit full path
* 2. BITFUN_E2E_APP_MODE – "debug" | "release"
* 3. Auto-detect: debug first (fast build), then release
*/
function getApplicationPath(): string {
const isWindows = process.platform === 'win32';
const appName = isWindows ? 'bitfun-desktop.exe' : 'bitfun-desktop';
const projectRoot = path.resolve(__dirname, '..', '..', '..');
const debugPath = path.join(projectRoot, 'target', 'debug', appName);
const releasePath = path.join(projectRoot, 'target', 'release', appName);
if (fs.existsSync(releasePath)) {
const forcedPath = process.env.BITFUN_E2E_APP_PATH;
const forcedMode = process.env.BITFUN_E2E_APP_MODE?.toLowerCase();

if (forcedPath) {
return forcedPath;
}

if (forcedMode === 'debug') {
return debugPath;
}

if (forcedMode === 'release') {
return releasePath;
}
return path.join(projectRoot, 'target', 'debug', appName);

if (fs.existsSync(debugPath)) {
return debugPath;
}

return releasePath;
}

/**
Expand Down Expand Up @@ -139,7 +163,8 @@ export const config: Options.Testrunner = {
if (!fs.existsSync(appPath)) {
console.error(`Application not found at: ${appPath}`);
console.error('Please build the application first with:');
console.error('pnpm run desktop:build');
console.error(' pnpm run desktop:build:fast (debug, fast compile, recommended for dev)');
console.error(' pnpm run desktop:build (release, slow compile)');
throw new Error('Application not built');
}
console.log(`application: ${appPath}`);
Expand Down
Loading
Loading