From a842238d1920f76953f6cf079f75b838f27337d7 Mon Sep 17 00:00:00 2001 From: Aditya A P Date: Sun, 21 Dec 2025 23:39:49 +0530 Subject: [PATCH 1/2] feat(settings): edit active prompt content --- components/settings/PromptPanel.test.tsx | 55 ++++++++++++++---- components/settings/PromptPanel.tsx | 73 +++++++++++++++++++++++- 2 files changed, 114 insertions(+), 14 deletions(-) diff --git a/components/settings/PromptPanel.test.tsx b/components/settings/PromptPanel.test.tsx index 7c87b2d..3817b99 100644 --- a/components/settings/PromptPanel.test.tsx +++ b/components/settings/PromptPanel.test.tsx @@ -4,6 +4,7 @@ import { describe, expect, it, vi, beforeEach } from 'vitest'; import PromptPanel from './PromptPanel'; import { SettingsModalProvider } from './SettingsModalContext'; import type { AppSettings } from '../../types'; +import React from 'react'; const baseSettings: AppSettings = { provider: 'Gemini', @@ -38,20 +39,34 @@ vi.mock('../../store', () => ({ })); const renderPanel = (overrides: Partial = {}) => { - const ctxValue = { - currentSettings: { ...baseSettings, ...overrides }, - handleSettingChange: vi.fn(), - parameterSupport: {}, - setParameterSupport: vi.fn(), - novelMetadata: null, - handleNovelMetadataChange: vi.fn(), + const Wrapper: React.FC = () => { + const [currentSettings, setCurrentSettings] = React.useState({ + ...baseSettings, + ...overrides, + }); + + const ctxValue = React.useMemo( + () => ({ + currentSettings, + handleSettingChange: (key: K, value: AppSettings[K]) => { + setCurrentSettings((prev) => ({ ...prev, [key]: value })); + }, + parameterSupport: {}, + setParameterSupport: vi.fn(), + novelMetadata: null, + handleNovelMetadataChange: vi.fn(), + }), + [currentSettings] + ); + + return ( + + + + ); }; - return render( - - - - ); + return render(); }; describe('PromptPanel', () => { @@ -82,4 +97,20 @@ describe('PromptPanel', () => { expect(storeState.setActivePromptTemplate).toHaveBeenCalledWith('2'); expect(storeState.updateSettings).toHaveBeenCalledWith({ systemPrompt: 'B', activePromptId: '2' }); }); + + it('edits the active prompt content and saves it', async () => { + const user = userEvent.setup(); + renderPanel(); + + const editButtons = screen.getAllByRole('button', { name: /^edit$/i }); + await user.click(editButtons[0]); + const textarea = screen.getByLabelText(/system prompt text/i); + await user.clear(textarea); + await user.type(textarea, 'Updated content'); + await user.click(screen.getAllByRole('button', { name: /save/i })[0]); + + expect(storeState.updatePromptTemplate).toHaveBeenCalledWith( + expect.objectContaining({ id: '1', content: 'Updated content' }) + ); + }); }); diff --git a/components/settings/PromptPanel.tsx b/components/settings/PromptPanel.tsx index 29d114b..1cb9a8c 100644 --- a/components/settings/PromptPanel.tsx +++ b/components/settings/PromptPanel.tsx @@ -1,4 +1,4 @@ -import React, { useState } from 'react'; +import React, { useEffect, useRef, useState } from 'react'; import { useSettingsModalContext } from './SettingsModalContext'; import { useAppStore } from '../../store'; import { useShallow } from 'zustand/react/shallow'; @@ -29,6 +29,15 @@ export const PromptPanel: React.FC = () => { const [editingPrompt, setEditingPrompt] = useState(null); const [newPromptName, setNewPromptName] = useState(''); const [newPromptDescription, setNewPromptDescription] = useState(''); + const promptTextareaRef = useRef(null); + + const isEditingActive = editingPrompt === activePromptTemplate?.id; + + useEffect(() => { + if (isEditingActive && promptTextareaRef.current) { + promptTextareaRef.current.focus(); + } + }, [isEditingActive]); const handleCreatePrompt = async () => { if (!newPromptName.trim()) return; @@ -46,6 +55,7 @@ export const PromptPanel: React.FC = () => { }; const handleSelectPrompt = async (templateId: string) => { + setEditingPrompt(null); await setActivePromptTemplate(templateId); const template = promptTemplates.find((t) => t.id === templateId); if (template) { @@ -54,7 +64,7 @@ export const PromptPanel: React.FC = () => { handleSettingChange('activePromptId' as any, templateId as any); requestAnimationFrame(() => { const el = document.getElementById(`prompt-${templateId}`); - if (el) { + if (el && typeof el.scrollIntoView === 'function') { el.scrollIntoView({ behavior: 'smooth', block: 'center' }); el.classList.add('ring-2', 'ring-blue-400'); setTimeout(() => el.classList.remove('ring-2', 'ring-blue-400'), 1200); @@ -77,6 +87,7 @@ export const PromptPanel: React.FC = () => { ...template, content: currentSettings.systemPrompt, }); + updateSettings({ systemPrompt: currentSettings.systemPrompt, activePromptId: templateId }); } setEditingPrompt(null); }; @@ -104,6 +115,64 @@ export const PromptPanel: React.FC = () => { +
+
+

+ Active prompt content +

+
+ {activePromptTemplate && !isEditingActive && ( + + )} + {isEditingActive && ( + <> + + + + )} +
+
+ +