feat(frontend): groupes repliables par plugin pour skills et tools MCP, bouton Joindre harmonisé avec Envoyer — nouveau composant PluginContributionGroup (en-tête cliquable aria-expanded/aria-controls, badge Chargé/Non chargé, compteur de contributions, replié par défaut pour les plugins non loadés, dépliage forcé pendant la recherche) adopté par SkillsPanel et McpToolPermissionsPanel avec déduplication des plugins — #367 ; bouton Joindre du chat aligné sur le format d'Envoyer (size md, variante secondary, état loading « Ajout… », aria-label dédié, conteneur custom-agent-chat-composer-actions avec data-busy, Stop aligné md, container queries 419.99px en colonne et 279.99px wrap puis pleine largeur en busy) — #368 (QA verte: npm test ciblé ProjectPluginSettings + skills + McpToolPermissionsPanel + CustomAgentChatView + visibleSourceScan 5 fichiers 152 tests OK, Firefox headless layout 600/360/240 et busy 240 PASS, npm run build OK, git diff --check OK)
This commit is contained in:
@ -1093,6 +1093,7 @@ export function CustomAgentChatView({
|
||||
? (chatAttachmentDraftCache.get(initialAttachmentCacheKey) ?? [])
|
||||
: [],
|
||||
);
|
||||
const [attachmentPicking, setAttachmentPicking] = useState(false);
|
||||
const [queuedMessages, setQueuedMessages] = useState<QueuedChatMessage[]>(
|
||||
() => chatMessageQueueCache.get(initialMessageQueueKey) ?? [],
|
||||
);
|
||||
@ -2597,16 +2598,21 @@ export function CustomAgentChatView({
|
||||
}
|
||||
|
||||
async function pickAttachment() {
|
||||
const path = await system.pickFile();
|
||||
if (path) {
|
||||
setAttachments((prev) => [
|
||||
...prev,
|
||||
{
|
||||
id: `path-${Date.now()}-${Math.random().toString(36).slice(2)}`,
|
||||
label: pathBasename(path),
|
||||
input: { path, sourceKind: "localFile" },
|
||||
},
|
||||
]);
|
||||
setAttachmentPicking(true);
|
||||
try {
|
||||
const path = await system.pickFile();
|
||||
if (path) {
|
||||
setAttachments((prev) => [
|
||||
...prev,
|
||||
{
|
||||
id: `path-${Date.now()}-${Math.random().toString(36).slice(2)}`,
|
||||
label: pathBasename(path),
|
||||
input: { path, sourceKind: "localFile" },
|
||||
},
|
||||
]);
|
||||
}
|
||||
} finally {
|
||||
setAttachmentPicking(false);
|
||||
}
|
||||
}
|
||||
|
||||
@ -3608,8 +3614,8 @@ export function CustomAgentChatView({
|
||||
{composerHistoryWarning}
|
||||
</p>
|
||||
)}
|
||||
<div className="flex min-w-0 items-end gap-2">
|
||||
<div className="relative flex min-w-0 flex-1">
|
||||
<div className="custom-agent-chat-composer flex min-w-0 items-end gap-2">
|
||||
<div className="custom-agent-chat-composer-editor relative flex min-w-0 flex-1">
|
||||
<textarea
|
||||
ref={composerRef}
|
||||
aria-label={`${tr("Message CLI personnalisée", "Custom CLI message")} ${domId}`}
|
||||
@ -3677,39 +3683,47 @@ export function CustomAgentChatView({
|
||||
↕
|
||||
</button>
|
||||
</div>
|
||||
<Button
|
||||
size="sm"
|
||||
variant="ghost"
|
||||
className="shrink-0 whitespace-nowrap"
|
||||
disabled={!supported || opening || Boolean(savedConversationLoadFailure)}
|
||||
onClick={() => void pickAttachment()}
|
||||
<div
|
||||
className="custom-agent-chat-composer-actions flex shrink-0 items-end gap-2"
|
||||
data-busy={effectiveBusy ? "true" : "false"}
|
||||
>
|
||||
{t("custom.attach")}
|
||||
</Button>
|
||||
{effectiveBusy && (
|
||||
<Button
|
||||
size="sm"
|
||||
variant="danger"
|
||||
className="min-h-11 shrink-0 whitespace-nowrap"
|
||||
disabled={!supported || !currentSession || !agent.cancelAgentChat || stopping}
|
||||
loading={stopping}
|
||||
title={t("custom.stop")}
|
||||
aria-label={t("custom.stop")}
|
||||
onClick={() => void stopCurrentRequest()}
|
||||
size="md"
|
||||
variant="secondary"
|
||||
className="custom-agent-chat-composer-action min-h-11 whitespace-nowrap"
|
||||
aria-label={tr("Joindre un fichier", "Attach a file")}
|
||||
disabled={!supported || opening || attachmentPicking || Boolean(savedConversationLoadFailure)}
|
||||
loading={attachmentPicking}
|
||||
onClick={() => void pickAttachment()}
|
||||
>
|
||||
{stopping ? t("custom.stopping") : t("custom.stop")}
|
||||
{attachmentPicking ? tr("Ajout…", "Adding…") : t("custom.attach")}
|
||||
</Button>
|
||||
)}
|
||||
<Button
|
||||
size="sm"
|
||||
className="min-h-11 shrink-0 whitespace-nowrap"
|
||||
disabled={!canSubmit}
|
||||
onClick={() => void send()}
|
||||
>
|
||||
{effectiveBusy
|
||||
? tr("Mettre en attente", "Queue message")
|
||||
: t("custom.send")}
|
||||
</Button>
|
||||
{effectiveBusy && (
|
||||
<Button
|
||||
size="md"
|
||||
variant="danger"
|
||||
className="custom-agent-chat-composer-action min-h-11 whitespace-nowrap"
|
||||
disabled={!supported || !currentSession || !agent.cancelAgentChat || stopping}
|
||||
loading={stopping}
|
||||
title={t("custom.stop")}
|
||||
aria-label={t("custom.stop")}
|
||||
onClick={() => void stopCurrentRequest()}
|
||||
>
|
||||
{stopping ? t("custom.stopping") : t("custom.stop")}
|
||||
</Button>
|
||||
)}
|
||||
<Button
|
||||
size="md"
|
||||
variant="primary"
|
||||
className="custom-agent-chat-composer-action min-h-11 whitespace-nowrap"
|
||||
disabled={!canSubmit}
|
||||
onClick={() => void send()}
|
||||
>
|
||||
{effectiveBusy
|
||||
? tr("Mettre en attente", "Queue message")
|
||||
: t("custom.send")}
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
Reference in New Issue
Block a user