feat: I-MINI-RENDER — MiniAppBlock from placeholder to real rendering (loads manifest from backend, renders schema fields + config), tsc clean

This commit is contained in:
Agent Zero
2026-08-21 00:07:17 +02:00
parent 62793a001c
commit e635f2cf06
@@ -1,37 +1,95 @@
import React from 'react'; import React, { useState, useEffect } from 'react';
import type { MessageBlock } from '@/store/commStore'; import type { MessageBlock } from '@/store/commStore';
import { AppWindow, Loader2 } from 'lucide-react';
import { AppWindow } from 'lucide-react'; import { apiClient } from '@/api/client';
interface MiniAppBlockProps { interface MiniAppBlockProps {
block: MessageBlock; block: MessageBlock;
} }
interface MiniAppDef {
app_id: string;
name: string;
icon: string;
description: string;
plugin_name: string;
render_schema: Record<string, any>;
}
const MiniAppBlock: React.FC<MiniAppBlockProps> = ({ block }) => { const MiniAppBlock: React.FC<MiniAppBlockProps> = ({ block }) => {
const { app_id, config } = block.block_data; const { app_id, config } = block.block_data;
const [appDef, setAppDef] = useState<MiniAppDef | null>(null);
const [loading, setLoading] = useState(false);
useEffect(() => {
if (!app_id) return;
setLoading(true);
apiClient.get('/comm/miniapps')
.then((res) => {
const apps: MiniAppDef[] = res.data || [];
const found = apps.find((a) => a.app_id === app_id);
setAppDef(found || null);
})
.catch(() => setAppDef(null))
.finally(() => setLoading(false));
}, [app_id]);
const appId: string = app_id || 'Unbekannt'; const appId: string = app_id || 'Unbekannt';
const hasConfig = config && typeof config === 'object' && Object.keys(config).length > 0; const hasConfig = config && typeof config === 'object' && Object.keys(config).length > 0;
const schema = appDef?.render_schema;
const hasSchema = schema && Object.keys(schema).length > 0;
if (loading) {
return (
<div className="border-2 border-dashed border-secondary-300 rounded-lg p-4 text-center bg-secondary-50">
<Loader2 className="w-6 h-6 text-secondary-400 animate-spin mx-auto" />
</div>
);
}
return ( return (
<div className="border-2 border-dashed border-secondary-300 rounded-lg p-4 text-center bg-secondary-50"> <div className="border border-secondary-200 rounded-lg p-4 bg-white">
<div className="flex flex-col items-center gap-2"> <div className="flex items-center gap-2 mb-3">
<AppWindow className="w-8 h-8 text-secondary-400" aria-hidden="true" strokeWidth={1.5} /> <div className="w-8 h-8 rounded-lg bg-primary-100 flex items-center justify-center flex-shrink-0">
<p className="text-sm font-medium text-secondary-600"> <AppWindow className="w-4 h-4 text-primary-600" aria-hidden="true" strokeWidth={2} />
Mini-App: {appId} </div>
</p> <div className="flex-1 min-w-0">
<p className="text-xs text-secondary-400"> <p className="text-sm font-medium text-secondary-900">{appDef?.name || appId}</p>
Mini-Apps werden in Zukunft vollständig gerendert. {appDef?.description && <p className="text-xs text-secondary-500 truncate">{appDef.description}</p>}
</p> </div>
{hasConfig && ( {appDef?.plugin_name && (
<details className="text-xs text-secondary-400 mt-1"> <span className="text-xs px-2 py-0.5 rounded-full bg-secondary-100 text-secondary-600">{appDef.plugin_name}</span>
<summary className="cursor-pointer hover:text-secondary-600">Konfiguration</summary>
<pre className="mt-1 p-2 rounded bg-white text-left overflow-x-auto">
{JSON.stringify(config, null, 2)}
</pre>
</details>
)} )}
</div> </div>
{hasSchema && schema.fields && (
<div className="space-y-2">
{schema.fields.map((field: any) => {
const value = config?.[field.name];
return (
<div key={field.name} className="flex items-center justify-between gap-2">
<span className="text-xs text-secondary-500">{field.label || field.name}</span>
<span className="text-sm text-secondary-900 font-medium">{value ?? '—'}</span>
</div>
);
})}
</div>
)}
{hasConfig && !hasSchema && (
<div className="space-y-1">
{Object.entries(config).slice(0, 5).map(([key, value]) => (
<div key={key} className="flex items-center justify-between gap-2">
<span className="text-xs text-secondary-500">{key}</span>
<span className="text-sm text-secondary-900 font-medium truncate max-w-[200px]">{String(value)}</span>
</div>
))}
</div>
)}
{!hasConfig && !hasSchema && (
<p className="text-xs text-secondary-400 text-center py-2">Keine Konfiguration</p>
)}
</div> </div>
); );
}; };