import type { ActionItem, AppSearchResult, SearchResult } from './types' import type { App } from '@/types/app' import { RiFileListLine, RiLayoutLine, RiLineChartLine, RiNodeTree, RiTerminalBoxLine } from '@remixicon/react' import * as React from 'react' import { fetchAppList } from '@/service/apps' import { AppModeEnum } from '@/types/app' import { getRedirectionPath } from '@/utils/app-redirection' import { AppTypeIcon } from '../../app/type-selector' import AppIcon from '../../base/app-icon' const WORKFLOW_MODES = new Set([AppModeEnum.WORKFLOW, AppModeEnum.ADVANCED_CHAT]) type AppSection = { id: string, label: string, path: string, icon: React.ElementType } const getAppSections = (app: App): AppSection[] => { const base = `/app/${app.id}` if (WORKFLOW_MODES.has(app.mode)) { return [ { id: 'workflow', label: 'Workflow', path: `${base}/workflow`, icon: RiNodeTree }, { id: 'overview', label: 'Overview', path: `${base}/overview`, icon: RiLineChartLine }, { id: 'logs', label: 'Logs', path: `${base}/logs`, icon: RiFileListLine }, ] } return [ { id: 'configuration', label: 'Configuration', path: `${base}/configuration`, icon: RiLayoutLine }, { id: 'overview', label: 'Overview', path: `${base}/overview`, icon: RiLineChartLine }, { id: 'logs', label: 'Logs', path: `${base}/logs`, icon: RiFileListLine }, { id: 'develop', label: 'Develop', path: `${base}/develop`, icon: RiTerminalBoxLine }, ] } const appIcon = (app: App) => (
) const parser = (apps: App[]): AppSearchResult[] => { return apps.map(app => ({ id: app.id, title: app.name, description: app.description, type: 'app' as const, path: getRedirectionPath(true, { id: app.id, mode: app.mode, }), icon: appIcon(app), data: app, })) } // Generate sub-section results for matched apps when in scoped @app search const parserWithSections = (apps: App[]): SearchResult[] => { const results: SearchResult[] = [] for (const app of apps) { results.push({ id: app.id, title: app.name, description: app.description, type: 'app' as const, path: getRedirectionPath(true, { id: app.id, mode: app.mode }), icon: appIcon(app), data: app, }) for (const section of getAppSections(app)) { results.push({ id: `${app.id}:${section.id}`, title: `${app.name} / ${section.label}`, description: section.path, type: 'app' as const, path: section.path, icon: (
), data: app, }) } } return results } export const appAction: ActionItem = { key: '@app', shortcut: '@app', title: 'Search Applications', description: 'Search and navigate to your applications', search: async (query, searchTerm = '', _locale) => { const isScoped = query.trimStart().startsWith('@app') || query.trimStart().startsWith('@App') try { const response = await fetchAppList({ url: 'apps', params: { page: 1, name: searchTerm, }, }) const apps = response?.data || [] return isScoped ? parserWithSections(apps) : parser(apps) } catch (error) { console.warn('App search failed:', error) return [] } }, }