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 []
}
},
}