From 588d0323352b1404fafa8a34c67e35c09cf66e35 Mon Sep 17 00:00:00 2001 From: Hitonabi Date: Thu, 2 Jul 2026 14:47:10 +0200 Subject: [PATCH] =?UTF-8?q?Frontend:=20Cockpit=20entflochten=20(Review=20P?= =?UTF-8?q?2-14,=20Teil=202)=20=E2=80=94=20990=20->=20909=20Zeilen?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - views/models/cockpit/RoleAssignModal.tsx: Rollen-Zuweisung inkl. Empfehlungs-Sortierung und Schutzgelaender als eigenstaendige Komponente - Preview-verifiziert gegen die Live-Box: Modal oeffnet per Slot-Karte ('Lucys Hirn festlegen'), Schutzgelaender greift, 9 Modell-Optionen, schliesst sauber - Zone C (Library, ~440 Z) bleibt fuer eine Folge-Session (tief mit Aktions-State verwoben) Co-Authored-By: Claude Fable 5 --- frontend/src/views/models/Cockpit.tsx | 96 ++-------------- .../views/models/cockpit/RoleAssignModal.tsx | 104 ++++++++++++++++++ 2 files changed, 111 insertions(+), 89 deletions(-) create mode 100644 frontend/src/views/models/cockpit/RoleAssignModal.tsx diff --git a/frontend/src/views/models/Cockpit.tsx b/frontend/src/views/models/Cockpit.tsx index 1acda08..0553577 100644 --- a/frontend/src/views/models/Cockpit.tsx +++ b/frontend/src/views/models/Cockpit.tsx @@ -1,5 +1,5 @@ import { useState } from "react" -import { Download, Trash2, Edit3, Activity, HardDrive, X, Check, Zap, Bot } from "lucide-react" +import { Download, Trash2, Edit3, Activity, HardDrive, Check, Zap, Bot } from "lucide-react" import { api, setGroup, type ModelInfo, type RoleRecResp } from "@/lib/api" import { useModels, useGroups, useUpdates, useHermesBrain, useSystemStatus, useQueryClient, qk } from "@/lib/queries" import { useDialog } from "@/lib/useDialog" @@ -8,6 +8,7 @@ import { cn } from "@/lib/utils" import { fmtSize, fmtCtx } from "@/lib/format" import { getBrandInfo, ROLES, RoleLabel, roleMeta } from "@/components/models/ModelBadges" import { SpecDraftModal } from "@/components/models/SpecDraftModal" +import { RoleAssignModal } from "./cockpit/RoleAssignModal" import { LaneEditor } from "@/components/models/LaneEditor" import { WarmSetManager } from "@/components/models/WarmSetManager" import { useExpertMode } from "@/lib/useExpertMode" @@ -887,94 +888,11 @@ export function Cockpit() { {/* Role Assignment Modal */} - {activeRoleForAssign && (() => { - const rec = roleRec && roleRec.role === activeRoleForAssign ? roleRec : null - const recByName: Record = {} - rec?.models.forEach((r) => { recByName[r.name] = r }) - // Empfohlene Reihenfolge (nach Score) wenn vorhanden, sonst Bibliotheks-Reihenfolge. - const ordered = rec - ? rec.models.map((r) => models.find((m) => m.name === r.name)).filter(Boolean) as ModelInfo[] - : models - const assign = (name: string) => { handleRoleChange(activeRoleForAssign, name); setActiveRoleForAssign(null) } - return ( -
-
-
-

- festlegen -

- -
-
-

- Wähle ein Modell für {roleMeta(activeRoleForAssign).label} - {roleMeta(activeRoleForAssign).desc} -

- {rec?.recommended && ( - - )} -
- -
- {/* Schutzgeländer: eine lebenswichtige Rolle (Hirn/Gedächtnis) lässt sich nicht leeren. */} - {isProtected(activeRoleForAssign) ? ( -
- 🔒 Diese Rolle ist lebenswichtig und kann nicht leer bleiben — wähle stattdessen ein anderes Modell. -
- ) : ( - - )} - {ordered.map((m) => { - const r = recByName[m.name] - const isCur = m.role === activeRoleForAssign - const isRec = !!r?.recommended - const unfit = !!r && !r.suitable - return ( - - ) - })} -
-
-
- ) - })()} + {activeRoleForAssign && ( + { handleRoleChange(activeRoleForAssign, name); setActiveRoleForAssign(null) }} + onClose={() => setActiveRoleForAssign(null)} /> + )} {specModel && ( !!roleMeta(role).protected +export function RoleAssignModal({ role, roleRec, models, onAssign, onClose }: { + role: string + roleRec: RoleRecResp | null + models: ModelInfo[] + onAssign: (modelName: string) => void + onClose: () => void +}) { + const rec = roleRec && roleRec.role === role ? roleRec : null + const recByName: Record = {} + rec?.models.forEach((r) => { recByName[r.name] = r }) + // Empfohlene Reihenfolge (nach Score) wenn vorhanden, sonst Bibliotheks-Reihenfolge. + const ordered = rec + ? rec.models.map((r) => models.find((m) => m.name === r.name)).filter(Boolean) as ModelInfo[] + : models + return ( +
+
+
+

+ festlegen +

+ +
+
+

+ Wähle ein Modell für {roleMeta(role).label} + {roleMeta(role).desc} +

+ {rec?.recommended && ( + + )} +
+ +
+ {/* Schutzgeländer: eine lebenswichtige Rolle (Hirn/Gedächtnis) lässt sich nicht leeren. */} + {isProtected(role) ? ( +
+ 🔒 Diese Rolle ist lebenswichtig und kann nicht leer bleiben — wähle stattdessen ein anderes Modell. +
+ ) : ( + + )} + {ordered.map((m) => { + const r = recByName[m.name] + const isCur = m.role === role + const isRec = !!r?.recommended + const unfit = !!r && !r.suitable + return ( + + ) + })} +
+
+
+ ) +}