205 lines
7.4 KiB
TypeScript
205 lines
7.4 KiB
TypeScript
|
|
import React, { useState, useEffect, useCallback } from 'react';
|
|
import Sidebar from './components/Sidebar';
|
|
import PromptEditor from './components/PromptEditor';
|
|
import { AppState, Category, Prompt, PromptVersion } from './types';
|
|
import { generatePromptTitle } from './services/geminiService';
|
|
|
|
const INITIAL_DATA: AppState = {
|
|
categories: [
|
|
{ id: 'cat-1', name: '웹 프론트엔드', parentId: null },
|
|
{ id: 'cat-2', name: 'AI 엔지니어링', parentId: null },
|
|
{ id: 'cat-3', name: '클라우드 아키텍처', parentId: null },
|
|
],
|
|
prompts: [
|
|
{
|
|
id: 'p-1',
|
|
categoryId: 'cat-2',
|
|
name: 'Gemini 최적화 로직',
|
|
description: '프롬프트 최적화를 위한 핵심 시스템 프롬프트 연구',
|
|
tags: ['ai', 'optimization'],
|
|
currentVersionId: 'v-1',
|
|
createdAt: new Date().toISOString(),
|
|
updatedAt: new Date().toISOString()
|
|
}
|
|
],
|
|
versions: [
|
|
{
|
|
id: 'v-1',
|
|
promptId: 'p-1',
|
|
content: '당신은 세계적인 프롬프트 엔지니어입니다. 입력받은 프롬프트를 분석하여 논리적 구조, 제약 조건, 그리고 창의적인 페르소나를 강화하여 재작성하세요.',
|
|
versionNumber: 1,
|
|
changeSummary: '초기 연구 가설 설정',
|
|
createdAt: new Date().toISOString()
|
|
}
|
|
],
|
|
selectedCategoryId: null,
|
|
selectedPromptId: 'p-1',
|
|
};
|
|
|
|
const App: React.FC = () => {
|
|
const [state, setState] = useState<AppState>(() => {
|
|
const saved = localStorage.getItem('sam_prompt_state');
|
|
return saved ? JSON.parse(saved) : INITIAL_DATA;
|
|
});
|
|
|
|
useEffect(() => {
|
|
localStorage.setItem('sam_prompt_state', JSON.stringify(state));
|
|
}, [state]);
|
|
|
|
const handleSelectPrompt = (id: string) => {
|
|
setState(prev => ({ ...prev, selectedPromptId: id }));
|
|
};
|
|
|
|
const handleAddCategory = (parentId: string | null) => {
|
|
const name = prompt('새로운 연구 분야의 이름을 입력하세요 (예: DevOps, Mobile, Security):');
|
|
if (!name) return;
|
|
|
|
const newCategory: Category = {
|
|
id: `cat-${Date.now()}`,
|
|
name,
|
|
parentId
|
|
};
|
|
|
|
setState(prev => ({
|
|
...prev,
|
|
categories: [...prev.categories, newCategory]
|
|
}));
|
|
};
|
|
|
|
const handleDeleteCategory = (id: string) => {
|
|
if (!confirm('정말로 이 분야의 모든 연구 내용을 삭제하시겠습니까?')) return;
|
|
setState(prev => ({
|
|
...prev,
|
|
categories: prev.categories.filter(c => c.id !== id),
|
|
prompts: prev.prompts.filter(p => p.categoryId !== id),
|
|
selectedPromptId: prev.prompts.find(p => p.categoryId === id)?.id === prev.selectedPromptId ? null : prev.selectedPromptId
|
|
}));
|
|
};
|
|
|
|
const handleAddPrompt = async (categoryId: string) => {
|
|
const id = `p-${Date.now()}`;
|
|
const versionId = `v-${Date.now()}`;
|
|
|
|
const newPrompt: Prompt = {
|
|
id,
|
|
categoryId,
|
|
name: '제목 없는 연구 프로젝트',
|
|
description: '',
|
|
tags: [],
|
|
currentVersionId: versionId,
|
|
createdAt: new Date().toISOString(),
|
|
updatedAt: new Date().toISOString(),
|
|
};
|
|
|
|
const newVersion: PromptVersion = {
|
|
id: versionId,
|
|
promptId: id,
|
|
content: '',
|
|
versionNumber: 1,
|
|
changeSummary: '신규 연구 프로젝트 시작',
|
|
createdAt: new Date().toISOString(),
|
|
};
|
|
|
|
setState(prev => ({
|
|
...prev,
|
|
prompts: [...prev.prompts, newPrompt],
|
|
versions: [...prev.versions, newVersion],
|
|
selectedPromptId: id
|
|
}));
|
|
};
|
|
|
|
const handleSaveVersion = async (content: string, summary: string) => {
|
|
if (!state.selectedPromptId) return;
|
|
|
|
const currentPrompt = state.prompts.find(p => p.id === state.selectedPromptId);
|
|
if (!currentPrompt) return;
|
|
|
|
const currentVersions = state.versions.filter(v => v.promptId === state.selectedPromptId);
|
|
const nextVersionNumber = Math.max(0, ...currentVersions.map(v => v.versionNumber)) + 1;
|
|
const newVersionId = `v-${Date.now()}`;
|
|
|
|
// Auto-update name if it's still default
|
|
let updatedName = currentPrompt.name;
|
|
if (updatedName === '제목 없는 연구 프로젝트' && content.length > 10) {
|
|
updatedName = await generatePromptTitle(content);
|
|
}
|
|
|
|
const newVersion: PromptVersion = {
|
|
id: newVersionId,
|
|
promptId: state.selectedPromptId,
|
|
content,
|
|
versionNumber: nextVersionNumber,
|
|
changeSummary: summary,
|
|
createdAt: new Date().toISOString(),
|
|
};
|
|
|
|
setState(prev => ({
|
|
...prev,
|
|
prompts: prev.prompts.map(p => p.id === state.selectedPromptId ? {
|
|
...p,
|
|
name: updatedName,
|
|
currentVersionId: newVersionId,
|
|
updatedAt: new Date().toISOString()
|
|
} : p),
|
|
versions: [...prev.versions, newVersion]
|
|
}));
|
|
};
|
|
|
|
const handleDeletePrompt = (id: string) => {
|
|
if (!confirm('이 프롬프트 연구 기록을 삭제하시겠습니까?')) return;
|
|
setState(prev => ({
|
|
...prev,
|
|
prompts: prev.prompts.filter(p => p.id !== id),
|
|
versions: prev.versions.filter(v => v.promptId !== id),
|
|
selectedPromptId: prev.selectedPromptId === id ? null : prev.selectedPromptId
|
|
}));
|
|
};
|
|
|
|
const selectedPrompt = state.prompts.find(p => p.id === state.selectedPromptId);
|
|
const selectedPromptVersions = state.versions.filter(v => v.promptId === state.selectedPromptId);
|
|
const selectedCategory = state.categories.find(c => c.id === selectedPrompt?.categoryId);
|
|
|
|
return (
|
|
<div className="flex h-screen bg-white text-slate-900 antialiased overflow-hidden">
|
|
<Sidebar
|
|
categories={state.categories}
|
|
prompts={state.prompts}
|
|
onSelectPrompt={handleSelectPrompt}
|
|
onAddCategory={handleAddCategory}
|
|
onAddPrompt={handleAddPrompt}
|
|
onDeleteCategory={handleDeleteCategory}
|
|
selectedPromptId={state.selectedPromptId}
|
|
/>
|
|
|
|
{selectedPrompt ? (
|
|
<PromptEditor
|
|
prompt={selectedPrompt}
|
|
versions={selectedPromptVersions}
|
|
categoryName={selectedCategory?.name || '분류 없음'}
|
|
onSave={handleSaveVersion}
|
|
onDelete={handleDeletePrompt}
|
|
/>
|
|
) : (
|
|
<div className="flex-1 flex flex-col items-center justify-center bg-slate-50/50 p-10 text-center">
|
|
<div className="w-32 h-32 bg-white rounded-[40px] border border-slate-100 shadow-2xl shadow-slate-200/50 flex items-center justify-center mb-10">
|
|
<svg className="w-14 h-14 text-indigo-500 animate-pulse" fill="none" viewBox="0 0 24 24" stroke="currentColor">
|
|
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={1} d="M9.663 17h4.673M12 3v1m6.364 1.636l-.707.707M21 12h-1M4 12H3m3.343-5.657l-.707-.707m2.828 9.9a5 5 0 117.072 0l-.548.547A3.374 3.374 0 0014 18.469V19a2 2 0 11-4 0v-.531c0-.895-.356-1.754-.988-2.386l-.548-.547z" />
|
|
</svg>
|
|
</div>
|
|
<h3 className="text-3xl font-black text-slate-900 mb-4 tracking-tight">연구를 시작할 프롬프트를 선택하세요</h3>
|
|
<p className="max-w-md text-slate-500 leading-relaxed font-bold">
|
|
사이드바에서 기존 연구 과제를 선택하거나 새로운 분야를 추가하여 프롬프트의 진화 과정을 기록하십시오.
|
|
</p>
|
|
<div className="mt-12 flex items-center space-x-2 text-[10px] font-black uppercase tracking-widest text-slate-300">
|
|
<span>혁신 파트너</span>
|
|
<span className="text-indigo-400">Codebridge-X</span>
|
|
</div>
|
|
</div>
|
|
)}
|
|
</div>
|
|
);
|
|
};
|
|
|
|
export default App;
|