mirror of
https://github.com/rowboatlabs/rowboat.git
synced 2026-07-12 21:02:17 +02:00
feat(mini-apps): open built app in middle pane from chat
After the copilot builds + verifies + populates an app, it opens it in the middle pane under Mini Apps / <title>, with an 'Opened <app>' card in chat. - app-navigation: new open-app action (reads manifest title) - renderer: pending-nav opens the Mini Apps view with the app pre-selected; MiniAppsView accepts initialAppId/initialVersion - chat: open-app app-action card labels - build-mini-app skill: finalize step opens the app once data is populated
This commit is contained in:
parent
4a4dcb1fa0
commit
6d7fd7b013
5 changed files with 46 additions and 8 deletions
|
|
@ -2001,6 +2001,9 @@ function App() {
|
|||
}>>([])
|
||||
const [bgTaskInitialSlug, setBgTaskInitialSlug] = useState<string | null>(null)
|
||||
const [bgTaskSlugVersion, setBgTaskSlugVersion] = useState(0)
|
||||
// Mini App to auto-open in the Mini Apps view (set by app-navigation open-app).
|
||||
const [appInitialId, setAppInitialId] = useState<string | null>(null)
|
||||
const [appIdVersion, setAppIdVersion] = useState(0)
|
||||
|
||||
const loadBgTaskSummaries = useCallback(async () => {
|
||||
try {
|
||||
|
|
@ -4506,6 +4509,13 @@ function App() {
|
|||
void navigateToView({ type: 'file', path: BASES_DEFAULT_TAB_PATH })
|
||||
}
|
||||
break
|
||||
case 'open-app':
|
||||
if (result.appId) {
|
||||
setAppInitialId(result.appId as string)
|
||||
setAppIdVersion((v) => v + 1)
|
||||
openAppsView()
|
||||
}
|
||||
break
|
||||
case 'update-base-view': {
|
||||
// Navigate to bases if not already there
|
||||
const targetPath = selectedPath && isBaseFilePath(selectedPath) ? selectedPath : BASES_DEFAULT_TAB_PATH
|
||||
|
|
@ -5983,7 +5993,7 @@ function App() {
|
|||
</div>
|
||||
) : isAppsOpen ? (
|
||||
<div className="flex-1 min-h-0 flex flex-col overflow-hidden">
|
||||
<MiniAppsView />
|
||||
<MiniAppsView initialAppId={appInitialId} initialVersion={appIdVersion} />
|
||||
</div>
|
||||
) : isEmailOpen ? (
|
||||
<div className="flex-1 min-h-0 flex flex-col overflow-hidden">
|
||||
|
|
|
|||
|
|
@ -153,10 +153,15 @@ function Card({ app, index, onOpen }: { app: miniApp.MiniAppManifest; index: num
|
|||
)
|
||||
}
|
||||
|
||||
export function MiniAppsView() {
|
||||
const [selectedId, setSelectedId] = useState<string | null>(null)
|
||||
export function MiniAppsView({ initialAppId, initialVersion }: { initialAppId?: string | null; initialVersion?: number } = {}) {
|
||||
const [selectedId, setSelectedId] = useState<string | null>(initialAppId ?? null)
|
||||
const [manifests, setManifests] = useState<miniApp.MiniAppManifest[]>([])
|
||||
|
||||
// Open a specific app when asked from outside (app-navigation open-app).
|
||||
useEffect(() => {
|
||||
if (initialAppId) setSelectedId(initialAppId)
|
||||
}, [initialAppId, initialVersion])
|
||||
|
||||
// List apps installed under ~/.rowboat/apps. Apps are created there by the
|
||||
// copilot builder (or placed manually); none are bundled in the repo.
|
||||
useEffect(() => {
|
||||
|
|
|
|||
|
|
@ -210,6 +210,7 @@ export const getAppActionCardData = (tool: ToolCall): AppActionCardData | null =
|
|||
switch (action) {
|
||||
case 'open-note': return { action, label: `Opening ${(input.path as string || '').split('/').pop()?.replace(/\.md$/, '') || 'note'}...` }
|
||||
case 'open-view': return { action, label: `Opening ${input.view} view...` }
|
||||
case 'open-app': return { action, label: `Opening ${input.appId || 'app'}...` }
|
||||
case 'update-base-view': return { action, label: 'Updating view...' }
|
||||
case 'create-base': return { action, label: `Creating "${input.name}"...` }
|
||||
case 'get-base-state': return null // renders as normal tool block
|
||||
|
|
@ -225,6 +226,8 @@ export const getAppActionCardData = (tool: ToolCall): AppActionCardData | null =
|
|||
}
|
||||
case 'open-view':
|
||||
return { action: 'open-view', label: `Opened ${result.view} view` }
|
||||
case 'open-app':
|
||||
return { action: 'open-app', label: `Opened ${result.appName || result.appId || 'app'}` }
|
||||
case 'update-base-view':
|
||||
return {
|
||||
action: 'update-base-view',
|
||||
|
|
|
|||
|
|
@ -95,10 +95,14 @@ manifest's \`agent\` field to the task's slug. Give the task a sensible trigger
|
|||
|
||||
## 5. Finalize
|
||||
|
||||
Call \`mini-app-install\` with the manifest + html (+ optional seed data). Then
|
||||
confirm to the user, and ideally open it so they see it populate. For agent-backed
|
||||
apps, trigger the agent once (\`run-background-task-agent\`) so \`data.json\` exists
|
||||
immediately instead of waiting for the first scheduled run.
|
||||
Call \`mini-app-install\` with the manifest + html (+ optional seed data). For
|
||||
agent-backed apps, trigger the agent once (\`run-background-task-agent\`) so
|
||||
\`data.json\` exists immediately instead of waiting for the first scheduled run.
|
||||
|
||||
Then **open it for the user**: call \`app-navigation\` with
|
||||
\`{ action: "open-app", appId: "<id>" }\`. This opens the app in the middle pane
|
||||
(under Mini Apps / its title) and shows an "Opened <app>" card in the chat. Only
|
||||
do this once the app is installed AND its data is populated, so it renders ready.
|
||||
|
||||
## Manifest schema (manifest.json)
|
||||
|
||||
|
|
|
|||
|
|
@ -1068,9 +1068,11 @@ export const BuiltinTools: z.infer<typeof BuiltinToolsSchema> = {
|
|||
'app-navigation': {
|
||||
description: 'Control the app UI - navigate to notes, switch views, filter/search the knowledge base, and manage saved views.',
|
||||
inputSchema: z.object({
|
||||
action: z.enum(["open-note", "open-view", "update-base-view", "get-base-state", "create-base"]).describe("The navigation action to perform"),
|
||||
action: z.enum(["open-note", "open-view", "open-app", "update-base-view", "get-base-state", "create-base"]).describe("The navigation action to perform"),
|
||||
// open-note
|
||||
path: z.string().optional().describe("Knowledge file path for open-note, e.g. knowledge/People/John.md"),
|
||||
// open-app
|
||||
appId: z.string().optional().describe("Mini App id (folder name under ~/.rowboat/apps) for open-app — opens it in the middle pane under Mini Apps."),
|
||||
// open-view
|
||||
view: z.enum(["bases", "graph"]).optional().describe("Which view to open (for open-view action)"),
|
||||
// update-base-view
|
||||
|
|
@ -1118,6 +1120,20 @@ export const BuiltinTools: z.infer<typeof BuiltinToolsSchema> = {
|
|||
return { success: true, action: 'open-view', view };
|
||||
}
|
||||
|
||||
case 'open-app': {
|
||||
const appId = input.appId as string;
|
||||
if (!appId) return { success: false, error: 'open-app requires appId' };
|
||||
let appName = appId;
|
||||
try {
|
||||
const raw = await fs.readFile(path.join(WorkDir, 'apps', appId, 'manifest.json'), 'utf-8');
|
||||
const m = JSON.parse(raw) as { title?: string };
|
||||
if (m.title) appName = m.title;
|
||||
} catch {
|
||||
return { success: false, error: `Mini App not found: ${appId}` };
|
||||
}
|
||||
return { success: true, action: 'open-app', appId, appName };
|
||||
}
|
||||
|
||||
case 'update-base-view': {
|
||||
const updates: Record<string, unknown> = {};
|
||||
if (input.filters) updates.filters = input.filters;
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue