315 lines
9.7 KiB
TypeScript
315 lines
9.7 KiB
TypeScript
import { useState, useMemo } from 'react';
|
|
|
|
import { usePrompts, usePromptOperations, Prompt } from '../../hooks/usePrompts';
|
|
import { useLanguage } from '../../contexts/LanguageContext';
|
|
import type { EditFieldConfig } from '../ui/Popup/EditForm';
|
|
|
|
// Helper function to determine if a prompt can be deleted
|
|
const isPromptDeletable = (prompt: Prompt): boolean => {
|
|
// Primary check: If backend explicitly sets _hideDelete, respect that
|
|
if (prompt._hideDelete === true) {
|
|
return false;
|
|
}
|
|
|
|
// Hardcoded list of the six default system prompt IDs that cannot be deleted
|
|
const systemPromptIds = [
|
|
"097d34f0-9fcc-4233-bf1e-e64e13818464",
|
|
"17546dc6-b792-40e1-9aa1-0fcc4860d0c1",
|
|
"17c42519-2bf6-49b4-83f9-3cde7498310c",
|
|
"2bb85d1e-4e02-4de8-98ae-0e815267d864",
|
|
"93343a5b-49f0-4dbf-9513-2ab5f6938fd8",
|
|
"cfb51260-486f-4b42-96fe-ef03f406dcf1"
|
|
];
|
|
|
|
// Check if this prompt is one of the system default prompts
|
|
return !systemPromptIds.includes(prompt.id);
|
|
};
|
|
|
|
import type {
|
|
PromptsLogicReturn,
|
|
PromptActionConfig,
|
|
PromptColumnConfig
|
|
} from './promptsTypes';
|
|
|
|
export function usePromptsLogic(): PromptsLogicReturn {
|
|
const { prompts, loading, error, refetch } = usePrompts();
|
|
const { t } = useLanguage();
|
|
const {
|
|
handlePromptDelete,
|
|
handlePromptUpdate,
|
|
deletingPrompts
|
|
} = usePromptOperations();
|
|
|
|
// Edit modal state
|
|
const [editModalOpen, setEditModalOpen] = useState(false);
|
|
const [editingPrompt, setEditingPrompt] = useState<Prompt | null>(null);
|
|
|
|
// Configure edit fields for prompt editing
|
|
const editPromptFields: EditFieldConfig[] = useMemo(() => [
|
|
{
|
|
key: 'name',
|
|
label: t('prompts.field.name', 'Prompt Name'),
|
|
type: 'string',
|
|
editable: true,
|
|
required: true,
|
|
validator: (value: string) => {
|
|
if (!value || value.trim() === '') {
|
|
return t('prompts.validation.nameRequired', 'Prompt name cannot be empty');
|
|
}
|
|
if (value.length > 100) {
|
|
return t('prompts.validation.nameTooLong', 'Prompt name cannot exceed 100 characters');
|
|
}
|
|
return null;
|
|
}
|
|
},
|
|
{
|
|
key: 'content',
|
|
label: t('prompts.field.content', 'Prompt Content'),
|
|
type: 'textarea',
|
|
editable: true,
|
|
required: true,
|
|
minRows: 4,
|
|
maxRows: 8,
|
|
validator: (value: string) => {
|
|
if (!value || value.trim() === '') {
|
|
return t('prompts.validation.contentRequired', 'Prompt content cannot be empty');
|
|
}
|
|
if (value.length > 10000) {
|
|
return t('prompts.validation.contentTooLong', 'Prompt content cannot exceed 10,000 characters');
|
|
}
|
|
return null;
|
|
}
|
|
}
|
|
], [t]);
|
|
|
|
// Configure columns for the prompts table
|
|
const columns: PromptColumnConfig[] = useMemo(() => [
|
|
{
|
|
key: 'name',
|
|
label: t('prompts.column.name', 'Name'),
|
|
type: 'string',
|
|
width: 200,
|
|
minWidth: 150,
|
|
maxWidth: 300,
|
|
sortable: true,
|
|
filterable: true,
|
|
searchable: true,
|
|
formatter: (value: string | undefined) => (
|
|
<span className="promptName">
|
|
{value || t('prompts.unnamed', 'Unnamed')}
|
|
</span>
|
|
)
|
|
},
|
|
{
|
|
key: 'content',
|
|
label: t('prompts.column.content', 'Content'),
|
|
type: 'string',
|
|
width: 400,
|
|
minWidth: 200,
|
|
maxWidth: 600,
|
|
sortable: true,
|
|
filterable: true,
|
|
searchable: true,
|
|
formatter: (value: string | undefined) => (
|
|
<span className="promptContent" title={value}>
|
|
{value && value.length > 100 ? `${value.substring(0, 100)}...` : value || '-'}
|
|
</span>
|
|
)
|
|
}
|
|
], [t]);
|
|
|
|
// Handle prompt actions
|
|
const handleDeletePrompt = async (prompt: Prompt) => {
|
|
const promptName = prompt.name || prompt.id;
|
|
if (window.confirm(t('prompts.delete.confirm', 'Are you sure you want to delete "{name}"?').replace('{name}', promptName))) {
|
|
const success = await handlePromptDelete(prompt.id);
|
|
if (success) {
|
|
refetch(); // Refresh the prompts list
|
|
}
|
|
}
|
|
};
|
|
|
|
// Handle single prompt deletion for bulk delete
|
|
const handleDeleteSingle = async (prompt: Prompt) => {
|
|
const promptName = prompt.name || prompt.id;
|
|
if (window.confirm(t('prompts.delete.confirm', 'Are you sure you want to delete "{name}"?').replace('{name}', promptName))) {
|
|
const success = await handlePromptDelete(prompt.id);
|
|
if (success) {
|
|
refetch(); // Refresh the prompts list
|
|
} else {
|
|
console.error('Delete failed for prompt:', prompt.id);
|
|
}
|
|
}
|
|
};
|
|
|
|
// Handle multiple prompt deletion
|
|
const handleDeleteMultiple = async (promptsToDelete: Prompt[]) => {
|
|
const promptCount = promptsToDelete.length;
|
|
if (window.confirm(t('prompts.delete.confirmMultiple', 'Are you sure you want to delete {count} prompts?').replace('{count}', promptCount.toString()))) {
|
|
// Start all delete operations simultaneously
|
|
const deletePromises = promptsToDelete.map(async (prompt) => {
|
|
try {
|
|
const success = await handlePromptDelete(prompt.id);
|
|
return { promptId: prompt.id, success };
|
|
} catch (error) {
|
|
console.error('Failed to delete prompt:', prompt.id, error);
|
|
return { promptId: prompt.id, success: false };
|
|
}
|
|
});
|
|
|
|
// Wait for all deletions to complete
|
|
const results = await Promise.all(deletePromises);
|
|
|
|
// Check if any deletions failed
|
|
const failedDeletions = results.filter(result => !result.success);
|
|
if (failedDeletions.length > 0) {
|
|
console.error('Some prompt deletions failed:', failedDeletions);
|
|
}
|
|
|
|
// Refresh the prompt list regardless of individual failures
|
|
refetch();
|
|
}
|
|
};
|
|
|
|
// Handle edit prompt
|
|
const handleEditPrompt = (prompt: Prompt) => {
|
|
setEditingPrompt(prompt);
|
|
setEditModalOpen(true);
|
|
};
|
|
|
|
// Handle save prompt
|
|
const handleSavePrompt = async (updatedPrompt: Prompt) => {
|
|
if (!editingPrompt) return;
|
|
|
|
try {
|
|
// Call API to update prompt - backend requires full Prompt object
|
|
const updateData = {
|
|
id: editingPrompt.id,
|
|
name: updatedPrompt.name,
|
|
content: updatedPrompt.content,
|
|
mandateId: editingPrompt.mandateId
|
|
};
|
|
|
|
const result = await handlePromptUpdate(editingPrompt.id, updateData);
|
|
|
|
if (result.success) {
|
|
// Close modal
|
|
setEditModalOpen(false);
|
|
setEditingPrompt(null);
|
|
|
|
// Refresh prompt list
|
|
await refetch();
|
|
|
|
// Notify other components that prompts have been updated
|
|
window.dispatchEvent(new CustomEvent('promptUpdated', {
|
|
detail: { promptId: editingPrompt.id, newName: updatedPrompt.name }
|
|
}));
|
|
} else {
|
|
console.error('Failed to update prompt:', result.error);
|
|
// TODO: Show error message to user
|
|
}
|
|
} catch (error) {
|
|
console.error('Failed to update prompt:', error);
|
|
// TODO: Show error message to user
|
|
}
|
|
};
|
|
|
|
// Handle cancel edit
|
|
const handleCancelEdit = () => {
|
|
setEditModalOpen(false);
|
|
setEditingPrompt(null);
|
|
};
|
|
|
|
// Handle copy prompt
|
|
const handleCopyPrompt = async (prompt: Prompt) => {
|
|
try {
|
|
// Use the modern Clipboard API if available
|
|
if (navigator.clipboard && window.isSecureContext) {
|
|
await navigator.clipboard.writeText(prompt.content);
|
|
} else {
|
|
// Fallback for older browsers or non-secure contexts
|
|
const textArea = document.createElement('textarea');
|
|
textArea.value = prompt.content;
|
|
textArea.style.position = 'fixed';
|
|
textArea.style.left = '-999999px';
|
|
textArea.style.top = '-999999px';
|
|
document.body.appendChild(textArea);
|
|
textArea.focus();
|
|
textArea.select();
|
|
document.execCommand('copy');
|
|
document.body.removeChild(textArea);
|
|
}
|
|
|
|
// Show success feedback
|
|
const promptName = prompt.name || t('prompts.unnamed', 'Unnamed');
|
|
console.log(`Copied content of "${promptName}" to clipboard`);
|
|
|
|
// Optional: You could add a toast notification here in the future
|
|
// showToast(t('prompts.copy.success', 'Prompt content copied to clipboard'), 'success');
|
|
|
|
} catch (error) {
|
|
console.error('Failed to copy prompt content:', error);
|
|
|
|
// Optional: You could add a toast notification here in the future
|
|
// showToast(t('prompts.copy.error', 'Failed to copy prompt content'), 'error');
|
|
}
|
|
};
|
|
|
|
// Configure action buttons
|
|
const actions: PromptActionConfig[] = useMemo(() => [
|
|
{
|
|
type: 'edit',
|
|
title: t('prompts.action.edit', 'Edit'),
|
|
onAction: (row: Prompt) => {
|
|
handleEditPrompt(row);
|
|
}
|
|
},
|
|
{
|
|
type: 'copy',
|
|
title: t('prompts.action.copy', 'Copy'),
|
|
onAction: (row: Prompt) => {
|
|
handleCopyPrompt(row);
|
|
}
|
|
},
|
|
{
|
|
type: 'delete',
|
|
title: (row: Prompt) => {
|
|
const isDeletable = isPromptDeletable(row);
|
|
return isDeletable
|
|
? t('prompts.action.delete', 'Delete')
|
|
: t('prompts.action.delete.disabled', 'No permission to delete prompt');
|
|
},
|
|
disabled: (row: Prompt) => !isPromptDeletable(row)
|
|
// onAction is handled by FormGenerator for delete confirmation
|
|
},
|
|
] as any, [t, deletingPrompts, handleDeletePrompt, handleEditPrompt, handleCopyPrompt]);
|
|
|
|
return {
|
|
// Data
|
|
prompts,
|
|
loading,
|
|
error,
|
|
|
|
// Actions
|
|
handleDeleteSingle,
|
|
handleDeleteMultiple,
|
|
handleEditPrompt,
|
|
handleCopyPrompt,
|
|
|
|
// Refetch function
|
|
refetch,
|
|
|
|
// Additional data for rendering
|
|
columns,
|
|
actions,
|
|
|
|
// Edit modal state
|
|
editModalOpen,
|
|
editingPrompt,
|
|
editPromptFields,
|
|
|
|
// Edit modal actions
|
|
handleSavePrompt,
|
|
handleCancelEdit
|
|
};
|
|
}
|