mirror of
https://github.com/farion1231/cc-switch.git
synced 2026-07-25 13:45:03 +08:00
b8305f281b
Add max-w-[56rem] and mx-auto to Skills, MCP, and Prompts panels to match the header layout constraints, ensuring the back button and action buttons align with the content area.
169 lines
5.1 KiB
TypeScript
169 lines
5.1 KiB
TypeScript
import React, { useEffect, useMemo, useState } from "react";
|
|
import { useTranslation } from "react-i18next";
|
|
import { FileText } from "lucide-react";
|
|
import { type AppId } from "@/lib/api";
|
|
import { usePromptActions } from "@/hooks/usePromptActions";
|
|
import PromptListItem from "./PromptListItem";
|
|
import PromptFormPanel from "./PromptFormPanel";
|
|
import { ConfirmDialog } from "../ConfirmDialog";
|
|
|
|
interface PromptPanelProps {
|
|
open: boolean;
|
|
onOpenChange: (open: boolean) => void;
|
|
appId: AppId;
|
|
}
|
|
|
|
export interface PromptPanelHandle {
|
|
openAdd: () => void;
|
|
}
|
|
|
|
const PromptPanel = React.forwardRef<PromptPanelHandle, PromptPanelProps>(
|
|
({ open, appId }, ref) => {
|
|
const { t } = useTranslation();
|
|
const [isFormOpen, setIsFormOpen] = useState(false);
|
|
const [editingId, setEditingId] = useState<string | null>(null);
|
|
const [confirmDialog, setConfirmDialog] = useState<{
|
|
isOpen: boolean;
|
|
titleKey: string;
|
|
messageKey: string;
|
|
messageParams?: Record<string, unknown>;
|
|
onConfirm: () => void;
|
|
} | null>(null);
|
|
|
|
const {
|
|
prompts,
|
|
loading,
|
|
reload,
|
|
savePrompt,
|
|
deletePrompt,
|
|
toggleEnabled,
|
|
} = usePromptActions(appId);
|
|
|
|
useEffect(() => {
|
|
if (open) reload();
|
|
}, [open, reload]);
|
|
|
|
// Listen for prompt import events from deep link
|
|
useEffect(() => {
|
|
const handlePromptImported = (event: Event) => {
|
|
const customEvent = event as CustomEvent;
|
|
// Reload if the import is for this app
|
|
if (customEvent.detail?.app === appId) {
|
|
reload();
|
|
}
|
|
};
|
|
|
|
window.addEventListener("prompt-imported", handlePromptImported);
|
|
return () => {
|
|
window.removeEventListener("prompt-imported", handlePromptImported);
|
|
};
|
|
}, [appId, reload]);
|
|
|
|
const handleAdd = () => {
|
|
setEditingId(null);
|
|
setIsFormOpen(true);
|
|
};
|
|
|
|
React.useImperativeHandle(ref, () => ({
|
|
openAdd: handleAdd,
|
|
}));
|
|
|
|
const handleEdit = (id: string) => {
|
|
setEditingId(id);
|
|
setIsFormOpen(true);
|
|
};
|
|
|
|
const handleDelete = (id: string) => {
|
|
const prompt = prompts[id];
|
|
setConfirmDialog({
|
|
isOpen: true,
|
|
titleKey: "prompts.confirm.deleteTitle",
|
|
messageKey: "prompts.confirm.deleteMessage",
|
|
messageParams: { name: prompt?.name },
|
|
onConfirm: async () => {
|
|
try {
|
|
await deletePrompt(id);
|
|
setConfirmDialog(null);
|
|
} catch (e) {
|
|
// Error handled by hook
|
|
}
|
|
},
|
|
});
|
|
};
|
|
|
|
const promptEntries = useMemo(() => Object.entries(prompts), [prompts]);
|
|
|
|
const enabledPrompt = promptEntries.find(([_, p]) => p.enabled);
|
|
|
|
return (
|
|
<div className="flex flex-col h-[calc(100vh-8rem)] px-4 max-w-[56rem] mx-auto">
|
|
<div className="flex-shrink-0 py-4 glass rounded-xl border border-white/10 mb-4 px-6">
|
|
<div className="text-sm text-muted-foreground">
|
|
{t("prompts.count", { count: promptEntries.length })} ·{" "}
|
|
{enabledPrompt
|
|
? t("prompts.enabledName", { name: enabledPrompt[1].name })
|
|
: t("prompts.noneEnabled")}
|
|
</div>
|
|
</div>
|
|
|
|
<div className="flex-1 overflow-y-auto pb-16">
|
|
{loading ? (
|
|
<div className="text-center py-12 text-muted-foreground">
|
|
{t("prompts.loading")}
|
|
</div>
|
|
) : promptEntries.length === 0 ? (
|
|
<div className="text-center py-12">
|
|
<div className="w-16 h-16 mx-auto mb-4 bg-muted rounded-full flex items-center justify-center">
|
|
<FileText size={24} className="text-muted-foreground" />
|
|
</div>
|
|
<h3 className="text-lg font-medium text-foreground mb-2">
|
|
{t("prompts.empty")}
|
|
</h3>
|
|
<p className="text-muted-foreground text-sm">
|
|
{t("prompts.emptyDescription")}
|
|
</p>
|
|
</div>
|
|
) : (
|
|
<div className="space-y-3">
|
|
{promptEntries.map(([id, prompt]) => (
|
|
<PromptListItem
|
|
key={id}
|
|
id={id}
|
|
prompt={prompt}
|
|
onToggle={toggleEnabled}
|
|
onEdit={handleEdit}
|
|
onDelete={handleDelete}
|
|
/>
|
|
))}
|
|
</div>
|
|
)}
|
|
</div>
|
|
|
|
{isFormOpen && (
|
|
<PromptFormPanel
|
|
appId={appId}
|
|
editingId={editingId || undefined}
|
|
initialData={editingId ? prompts[editingId] : undefined}
|
|
onSave={savePrompt}
|
|
onClose={() => setIsFormOpen(false)}
|
|
/>
|
|
)}
|
|
|
|
{confirmDialog && (
|
|
<ConfirmDialog
|
|
isOpen={confirmDialog.isOpen}
|
|
title={t(confirmDialog.titleKey)}
|
|
message={t(confirmDialog.messageKey, confirmDialog.messageParams)}
|
|
onConfirm={confirmDialog.onConfirm}
|
|
onCancel={() => setConfirmDialog(null)}
|
|
/>
|
|
)}
|
|
</div>
|
|
);
|
|
},
|
|
);
|
|
|
|
PromptPanel.displayName = "PromptPanel";
|
|
|
|
export default PromptPanel;
|