diff --git a/.env.example b/.env.example new file mode 100644 index 0000000..31bf383 --- /dev/null +++ b/.env.example @@ -0,0 +1,5 @@ +# Hugging Face API Key (required) +VITE_HUGGINGFACE_API_KEY=your_huggingface_api_key_here + +# Gemini API Key (optional, used as fallback) +VITE_GEMINI_API_KEY=your_gemini_api_key_here \ No newline at end of file diff --git a/.gitignore b/.gitignore index a547bf3..a120b79 100644 --- a/.gitignore +++ b/.gitignore @@ -8,6 +8,7 @@ pnpm-debug.log* lerna-debug.log* node_modules +.env dist dist-ssr *.local diff --git a/src/.env.d.ts b/src/.env.d.ts new file mode 100644 index 0000000..7598a7c --- /dev/null +++ b/src/.env.d.ts @@ -0,0 +1,10 @@ +/// + +interface ImportMetaEnv { + readonly VITE_HUGGINGFACE_API_KEY: string; + readonly VITE_GEMINI_API_KEY: string; +} + +interface ImportMeta { + readonly env: ImportMetaEnv; +} diff --git a/src/components/AIAssistant/AIButton.tsx b/src/components/AIAssistant/AIButton.tsx new file mode 100644 index 0000000..43a79a1 --- /dev/null +++ b/src/components/AIAssistant/AIButton.tsx @@ -0,0 +1,82 @@ +import { useState } from "react"; +import { FaMagic } from "react-icons/fa"; +import { message } from "antd"; +import { useAI } from "../../hooks/useAI"; +import { AIModal } from "./AIModal"; +import { EditorType } from "../../services/ai/AIService"; +import { DiffModal } from "./DiffModal"; + +interface AIButtonProps { + editorType: EditorType; + currentContent: string; + onComplete: (completion: string) => void; +} + +export function AIButton({ editorType, currentContent, onComplete }: AIButtonProps) { + const [isModalOpen, setIsModalOpen] = useState(false); + const [isDiffModalOpen, setIsDiffModalOpen] = useState(false); + const [diffInfo, setDiffInfo] = useState({ content: "", diff: "", explanation: "" }); + const { isProcessing, getCompletion } = useAI(); + + const handleSubmit = async (prompt: string) => { + try { + const completion = await getCompletion({ + prompt, + editorType, + currentContent, + }); + + if (completion.diff && completion.diff.trim()) { + setDiffInfo({ + content: completion.content, + diff: completion.diff, + explanation: completion.explanation || "", + }); + setIsModalOpen(false); + setIsDiffModalOpen(true); + } else { + onComplete(completion.content); + setIsModalOpen(false); + if (completion.explanation) { + message.info(completion.explanation); + } + } + } catch (error) { + console.error("AI completion error:", error); + message.error("Failed to get AI completion"); + } + }; + + const handleAcceptChanges = () => { + onComplete(diffInfo.content); + setIsDiffModalOpen(false); + }; + + return ( + <> + setIsModalOpen(true)} + title="AI Assist" + style={{ + cursor: isProcessing ? "wait" : "pointer", + opacity: isProcessing ? 0.5 : 1, + marginRight: "8px", + }} + /> + setIsModalOpen(false)} + onSubmit={handleSubmit} + isProcessing={isProcessing} + editorType={editorType} + /> + setIsDiffModalOpen(false)} + onAccept={handleAcceptChanges} + diff={diffInfo.diff} + explanation={diffInfo.explanation} + /> + + ); +} diff --git a/src/components/AIAssistant/AICommandPalette.tsx b/src/components/AIAssistant/AICommandPalette.tsx new file mode 100644 index 0000000..6ca5bbd --- /dev/null +++ b/src/components/AIAssistant/AICommandPalette.tsx @@ -0,0 +1,56 @@ +import { useState } from "react"; +import { Modal, Input, message } from "antd"; +import { useAI } from "../../hooks/useAI"; +import type { EditorType } from "../../services/ai/AIService"; + +interface AICommandPaletteProps { + isOpen: boolean; + onClose: () => void; + editorType: EditorType; + currentContent: string; + onComplete: (completion: any) => void; +} + +const { TextArea } = Input; + +export function AICommandPalette({ isOpen, onClose, editorType, currentContent, onComplete }: AICommandPaletteProps) { + const [prompt, setPrompt] = useState(""); + const { isProcessing, getCompletion } = useAI(); + + const handleSubmit = async () => { + if (!prompt.trim()) { + return; + } + + try { + const completion = await getCompletion({ + prompt, + editorType, + currentContent, + }); + onComplete(completion); + onClose(); + setPrompt(""); + } catch (error) { + console.error("AI completion error:", error); + message.error("Failed to get AI completion. Please try again."); + } + }; + + return ( + +