feat(frontend): système de plugins — runtime, menus, layouts custom (#43)
Lots F1-F4 : runtime de chargement/registre plugin, extension des menus existants, panneau de gestion des plugins, types de layout custom (sélecteur, fallback, cellule dédiée) branchés sur le port plugin. Suite npm typecheck/test verte (947/947). Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
@ -38,6 +38,7 @@ import {
|
||||
TargetAnnouncementsOverlay,
|
||||
useTargetAnnouncements,
|
||||
} from "@/features/announcements";
|
||||
import { PluginLayoutCellView } from "@/features/plugins";
|
||||
import {
|
||||
modelServerOverlayText,
|
||||
describeModelServerDownload,
|
||||
@ -79,9 +80,21 @@ interface LayoutGridProps {
|
||||
layoutId?: string;
|
||||
/** Opens the read-only canonical transcript for a conversation. */
|
||||
onOpenConversation?: (conversationId: string) => void;
|
||||
/**
|
||||
* Navigates to `Paramètres > Plugins` (#43, F4) — the "Ouvrir Plugins" action
|
||||
* of a plugin layout's unavailable fallback. Threaded down to
|
||||
* `PluginLayoutCellView` the same way `onOpenConversation` is.
|
||||
*/
|
||||
onOpenPluginsSettings?: () => void;
|
||||
}
|
||||
|
||||
export function LayoutGrid({ projectId, cwd, layoutId, onOpenConversation }: LayoutGridProps) {
|
||||
export function LayoutGrid({
|
||||
projectId,
|
||||
cwd,
|
||||
layoutId,
|
||||
onOpenConversation,
|
||||
onOpenPluginsSettings,
|
||||
}: LayoutGridProps) {
|
||||
const vm = useLayout(projectId, layoutId);
|
||||
const work = useProjectWorkState(projectId);
|
||||
|
||||
@ -117,6 +130,7 @@ export function LayoutGrid({ projectId, cwd, layoutId, onOpenConversation }: Lay
|
||||
workState={work.state}
|
||||
refreshWorkState={work.refresh}
|
||||
onOpenConversation={onOpenConversation}
|
||||
onOpenPluginsSettings={onOpenPluginsSettings}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
@ -132,6 +146,7 @@ interface NodeViewProps {
|
||||
workState: ProjectWorkState | null;
|
||||
refreshWorkState: () => Promise<void>;
|
||||
onOpenConversation?: (conversationId: string) => void;
|
||||
onOpenPluginsSettings?: () => void;
|
||||
}
|
||||
|
||||
function NodeView({
|
||||
@ -143,8 +158,24 @@ function NodeView({
|
||||
workState,
|
||||
refreshWorkState,
|
||||
onOpenConversation,
|
||||
onOpenPluginsSettings,
|
||||
}: NodeViewProps) {
|
||||
switch (node.type) {
|
||||
case "customPluginLayout":
|
||||
// A true top-level `LayoutNode` variant (#43, F4, carnet v2 §3) — a
|
||||
// plugin layout occupies a slot in the tree at the same level as a
|
||||
// terminal leaf, split or grid. Rendered separately from `LeafView`
|
||||
// (which owns a lot of terminal-only concerns — write-portal,
|
||||
// model-server overlay, agent dropdown — none of which apply here).
|
||||
return (
|
||||
<PluginLayoutCellView
|
||||
projectId={projectId}
|
||||
cell={node.node}
|
||||
onStateChange={(nextState) => vm.setPluginLayoutState(node.node.id, nextState)}
|
||||
onOpenPlugins={() => onOpenPluginsSettings?.()}
|
||||
onChooseAnotherLayout={() => vm.replacePluginLayoutWithTerminal(node.node.id)}
|
||||
/>
|
||||
);
|
||||
case "leaf":
|
||||
return (
|
||||
<LeafView
|
||||
@ -172,6 +203,7 @@ function NodeView({
|
||||
workState={workState}
|
||||
refreshWorkState={refreshWorkState}
|
||||
onOpenConversation={onOpenConversation}
|
||||
onOpenPluginsSettings={onOpenPluginsSettings}
|
||||
/>
|
||||
);
|
||||
case "grid":
|
||||
@ -184,6 +216,7 @@ function NodeView({
|
||||
workState={workState}
|
||||
refreshWorkState={refreshWorkState}
|
||||
onOpenConversation={onOpenConversation}
|
||||
onOpenPluginsSettings={onOpenPluginsSettings}
|
||||
/>
|
||||
);
|
||||
}
|
||||
@ -1153,6 +1186,7 @@ interface SplitViewProps {
|
||||
workState: ProjectWorkState | null;
|
||||
refreshWorkState: () => Promise<void>;
|
||||
onOpenConversation?: (conversationId: string) => void;
|
||||
onOpenPluginsSettings?: () => void;
|
||||
}
|
||||
|
||||
function SplitView({
|
||||
@ -1163,6 +1197,7 @@ function SplitView({
|
||||
workState,
|
||||
refreshWorkState,
|
||||
onOpenConversation,
|
||||
onOpenPluginsSettings,
|
||||
}: SplitViewProps) {
|
||||
const isRow = split.direction === "row";
|
||||
const baseWeights = split.children.map((c) => c.weight);
|
||||
@ -1207,6 +1242,7 @@ function SplitView({
|
||||
workState={workState}
|
||||
refreshWorkState={refreshWorkState}
|
||||
onOpenConversation={onOpenConversation}
|
||||
onOpenPluginsSettings={onOpenPluginsSettings}
|
||||
parentSplit={{
|
||||
container: split.id,
|
||||
index: i,
|
||||
@ -1301,6 +1337,7 @@ interface GridViewProps {
|
||||
workState: ProjectWorkState | null;
|
||||
refreshWorkState: () => Promise<void>;
|
||||
onOpenConversation?: (conversationId: string) => void;
|
||||
onOpenPluginsSettings?: () => void;
|
||||
}
|
||||
|
||||
function GridView({
|
||||
@ -1311,6 +1348,7 @@ function GridView({
|
||||
workState,
|
||||
refreshWorkState,
|
||||
onOpenConversation,
|
||||
onOpenPluginsSettings,
|
||||
}: GridViewProps) {
|
||||
const cols = normalizeWeights(grid.colWeights)
|
||||
.map((p) => `${p}fr`)
|
||||
@ -1351,6 +1389,7 @@ function GridView({
|
||||
workState={workState}
|
||||
refreshWorkState={refreshWorkState}
|
||||
onOpenConversation={onOpenConversation}
|
||||
onOpenPluginsSettings={onOpenPluginsSettings}
|
||||
/>
|
||||
</div>
|
||||
))}
|
||||
|
||||
Reference in New Issue
Block a user