feat: introduce apply button functionality (v0.1)
### Summary - Added a new "Apply" button to code snippets provided by the AI assistant. - The button is designed to seamlessly merge the AI-generated snippet into the relevant file in the editor. ### Current Issues 1. **Sticky Accept/Decline Buttons:** These activate for every snippet instead of being limited to the relevant snippet. 2. **Discard Button:** Currently non-functional. 3. **Highlight Inconsistencies:** The green-red code highlights for old and new code are inconsistent. ### To Do - Implement a toast notification when the "Apply" button is pressed on an irrelevant tab to prevent code application errors. ### Workflow Implemented 1. The "Apply" button is added alongside "Copy" and "Reply" for AI-generated code snippets. 2. Upon clicking "Apply," the code snippet and relevant file content (active file) are sent to a secondary model (GPT-4O). 3. The system prompt for GPT-4O instructs it to merge the snippet with the file content: - Ensure the original file functionality remains intact. - Integrate the code snippet seamlessly. 4. The output from GPT-4O is injected directly into the code editor. 5. Changes are visually highlighted: - Green for new code. - Red for removed code. 6. Highlights remain until the user explicitly accepts or discards the changes.
This commit is contained in:
parent
534b148b86
commit
6612692d98
67
frontend/app/api/merge/route.ts
Normal file
67
frontend/app/api/merge/route.ts
Normal file
@ -0,0 +1,67 @@
|
|||||||
|
import OpenAI from "openai"
|
||||||
|
|
||||||
|
const openai = new OpenAI({
|
||||||
|
apiKey: process.env.OPENAI_API_KEY,
|
||||||
|
})
|
||||||
|
|
||||||
|
export async function POST(request: Request) {
|
||||||
|
try {
|
||||||
|
const { originalCode, newCode, fileName } = await request.json()
|
||||||
|
|
||||||
|
const systemPrompt = `You are a code merging assistant. Your task is to merge the new code snippet with the original file content while:
|
||||||
|
1. Preserving the original file's functionality
|
||||||
|
2. Ensuring proper integration of the new code
|
||||||
|
3. Maintaining consistent style and formatting
|
||||||
|
4. Resolving any potential conflicts
|
||||||
|
5. Output ONLY the raw code without any:
|
||||||
|
- Code fence markers (\`\`\`)
|
||||||
|
- Language identifiers (typescript, javascript, etc.)
|
||||||
|
- Explanations or comments
|
||||||
|
- Markdown formatting
|
||||||
|
|
||||||
|
The output should be the exact code that will replace the existing code, nothing more and nothing less.`
|
||||||
|
|
||||||
|
const mergedCode = `Original file (${fileName}):\n${originalCode}\n\nNew code to merge:\n${newCode}`
|
||||||
|
|
||||||
|
const response = await openai.chat.completions.create({
|
||||||
|
model: "gpt-4o",
|
||||||
|
messages: [
|
||||||
|
{ role: "system", content: systemPrompt },
|
||||||
|
{ role: "user", content: mergedCode },
|
||||||
|
],
|
||||||
|
prediction: {
|
||||||
|
type: "content",
|
||||||
|
content: mergedCode,
|
||||||
|
},
|
||||||
|
stream: true,
|
||||||
|
})
|
||||||
|
|
||||||
|
// Clean and stream response
|
||||||
|
const encoder = new TextEncoder()
|
||||||
|
return new Response(
|
||||||
|
new ReadableStream({
|
||||||
|
async start(controller) {
|
||||||
|
let buffer = ""
|
||||||
|
for await (const chunk of response) {
|
||||||
|
if (chunk.choices[0]?.delta?.content) {
|
||||||
|
buffer += chunk.choices[0].delta.content
|
||||||
|
// Clean any code fence markers that might appear in the stream
|
||||||
|
const cleanedContent = buffer
|
||||||
|
.replace(/^```[\w-]*\n|```\s*$/gm, "") // Remove code fences
|
||||||
|
.replace(/^(javascript|typescript|python|html|css)\n/gm, "") // Remove language identifiers
|
||||||
|
controller.enqueue(encoder.encode(cleanedContent))
|
||||||
|
buffer = ""
|
||||||
|
}
|
||||||
|
}
|
||||||
|
controller.close()
|
||||||
|
},
|
||||||
|
})
|
||||||
|
)
|
||||||
|
} catch (error) {
|
||||||
|
console.error("Merge error:", error)
|
||||||
|
return new Response(
|
||||||
|
error instanceof Error ? error.message : "Failed to merge code",
|
||||||
|
{ status: 500 }
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
@ -175,3 +175,23 @@
|
|||||||
.tab-scroll::-webkit-scrollbar {
|
.tab-scroll::-webkit-scrollbar {
|
||||||
display: none;
|
display: none;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.added-line-decoration {
|
||||||
|
background-color: rgba(0, 255, 0, 0.1);
|
||||||
|
}
|
||||||
|
|
||||||
|
.removed-line-decoration {
|
||||||
|
background-color: rgba(255, 0, 0, 0.1);
|
||||||
|
}
|
||||||
|
|
||||||
|
.added-line-glyph {
|
||||||
|
background-color: #28a745;
|
||||||
|
width: 4px !important;
|
||||||
|
margin-left: 3px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.removed-line-glyph {
|
||||||
|
background-color: #dc3545;
|
||||||
|
width: 4px !important;
|
||||||
|
margin-left: 3px;
|
||||||
|
}
|
||||||
|
77
frontend/components/editor/AIChat/ApplyButton.tsx
Normal file
77
frontend/components/editor/AIChat/ApplyButton.tsx
Normal file
@ -0,0 +1,77 @@
|
|||||||
|
import { Check, Loader2 } from "lucide-react"
|
||||||
|
import { useState } from "react"
|
||||||
|
import { toast } from "sonner"
|
||||||
|
import { Button } from "../../ui/button"
|
||||||
|
|
||||||
|
interface ApplyButtonProps {
|
||||||
|
code: string
|
||||||
|
activeFileName: string
|
||||||
|
activeFileContent: string
|
||||||
|
editorRef: { current: any }
|
||||||
|
onApply: (mergedCode: string) => void
|
||||||
|
}
|
||||||
|
|
||||||
|
export default function ApplyButton({
|
||||||
|
code,
|
||||||
|
activeFileName,
|
||||||
|
activeFileContent,
|
||||||
|
editorRef,
|
||||||
|
onApply,
|
||||||
|
}: ApplyButtonProps) {
|
||||||
|
const [isApplying, setIsApplying] = useState(false)
|
||||||
|
|
||||||
|
const handleApply = async () => {
|
||||||
|
setIsApplying(true)
|
||||||
|
try {
|
||||||
|
const response = await fetch("/api/merge", {
|
||||||
|
method: "POST",
|
||||||
|
headers: { "Content-Type": "application/json" },
|
||||||
|
body: JSON.stringify({
|
||||||
|
originalCode: activeFileContent,
|
||||||
|
newCode: String(code),
|
||||||
|
fileName: activeFileName,
|
||||||
|
}),
|
||||||
|
})
|
||||||
|
|
||||||
|
if (!response.ok) {
|
||||||
|
throw new Error(await response.text())
|
||||||
|
}
|
||||||
|
|
||||||
|
const reader = response.body?.getReader()
|
||||||
|
const decoder = new TextDecoder()
|
||||||
|
let mergedCode = ""
|
||||||
|
|
||||||
|
if (reader) {
|
||||||
|
while (true) {
|
||||||
|
const { done, value } = await reader.read()
|
||||||
|
if (done) break
|
||||||
|
mergedCode += decoder.decode(value, { stream: true })
|
||||||
|
}
|
||||||
|
}
|
||||||
|
onApply(mergedCode.trim())
|
||||||
|
} catch (error) {
|
||||||
|
console.error("Error applying code:", error)
|
||||||
|
toast.error(
|
||||||
|
error instanceof Error ? error.message : "Failed to apply code changes"
|
||||||
|
)
|
||||||
|
} finally {
|
||||||
|
setIsApplying(false)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Button
|
||||||
|
onClick={handleApply}
|
||||||
|
size="sm"
|
||||||
|
variant="ghost"
|
||||||
|
className="p-1 h-6"
|
||||||
|
disabled={isApplying}
|
||||||
|
>
|
||||||
|
{isApplying ? (
|
||||||
|
<Loader2 className="w-4 h-4 animate-spin" />
|
||||||
|
) : (
|
||||||
|
<Check className="w-4 h-4" />
|
||||||
|
)}
|
||||||
|
</Button>
|
||||||
|
)
|
||||||
|
}
|
@ -13,6 +13,12 @@ export default function ChatMessage({
|
|||||||
setContext,
|
setContext,
|
||||||
setIsContextExpanded,
|
setIsContextExpanded,
|
||||||
socket,
|
socket,
|
||||||
|
handleApplyCode,
|
||||||
|
activeFileName,
|
||||||
|
activeFileContent,
|
||||||
|
editorRef,
|
||||||
|
mergeDecorationsCollection,
|
||||||
|
setMergeDecorationsCollection,
|
||||||
}: MessageProps) {
|
}: MessageProps) {
|
||||||
// State for expanded message index
|
// State for expanded message index
|
||||||
const [expandedMessageIndex, setExpandedMessageIndex] = useState<
|
const [expandedMessageIndex, setExpandedMessageIndex] = useState<
|
||||||
@ -104,7 +110,13 @@ export default function ChatMessage({
|
|||||||
const components = createMarkdownComponents(
|
const components = createMarkdownComponents(
|
||||||
renderCopyButton,
|
renderCopyButton,
|
||||||
renderMarkdownElement,
|
renderMarkdownElement,
|
||||||
askAboutCode
|
askAboutCode,
|
||||||
|
activeFileName,
|
||||||
|
activeFileContent,
|
||||||
|
editorRef,
|
||||||
|
handleApplyCode,
|
||||||
|
mergeDecorationsCollection,
|
||||||
|
setMergeDecorationsCollection
|
||||||
)
|
)
|
||||||
|
|
||||||
return (
|
return (
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
import { useSocket } from "@/context/SocketContext"
|
import { useSocket } from "@/context/SocketContext"
|
||||||
import { TFile } from "@/lib/types"
|
import { TFile } from "@/lib/types"
|
||||||
import { X, ChevronDown } from "lucide-react"
|
import { ChevronDown, X } from "lucide-react"
|
||||||
import { nanoid } from "nanoid"
|
import { nanoid } from "nanoid"
|
||||||
import { useEffect, useRef, useState } from "react"
|
import { useEffect, useRef, useState } from "react"
|
||||||
import LoadingDots from "../../ui/LoadingDots"
|
import LoadingDots from "../../ui/LoadingDots"
|
||||||
@ -18,6 +18,9 @@ export default function AIChat({
|
|||||||
lastCopiedRangeRef,
|
lastCopiedRangeRef,
|
||||||
files,
|
files,
|
||||||
templateType,
|
templateType,
|
||||||
|
handleApplyCode,
|
||||||
|
mergeDecorationsCollection,
|
||||||
|
setMergeDecorationsCollection,
|
||||||
}: AIChatProps) {
|
}: AIChatProps) {
|
||||||
// Initialize socket and messages
|
// Initialize socket and messages
|
||||||
const { socket } = useSocket()
|
const { socket } = useSocket()
|
||||||
@ -176,7 +179,7 @@ export default function AIChat({
|
|||||||
const fileExt = tab.name.split(".").pop() || "txt"
|
const fileExt = tab.name.split(".").pop() || "txt"
|
||||||
return {
|
return {
|
||||||
...tab,
|
...tab,
|
||||||
content: `\`\`\`${fileExt}\n${activeFileContent}\n\`\`\``
|
content: `\`\`\`${fileExt}\n${activeFileContent}\n\`\`\``,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return tab
|
return tab
|
||||||
@ -214,6 +217,12 @@ export default function AIChat({
|
|||||||
setContext={setContext}
|
setContext={setContext}
|
||||||
setIsContextExpanded={setIsContextExpanded}
|
setIsContextExpanded={setIsContextExpanded}
|
||||||
socket={socket}
|
socket={socket}
|
||||||
|
handleApplyCode={handleApplyCode}
|
||||||
|
activeFileName={activeFileName}
|
||||||
|
activeFileContent={activeFileContent}
|
||||||
|
editorRef={editorRef}
|
||||||
|
mergeDecorationsCollection={mergeDecorationsCollection}
|
||||||
|
setMergeDecorationsCollection={setMergeDecorationsCollection}
|
||||||
/>
|
/>
|
||||||
))}
|
))}
|
||||||
{isLoading && <LoadingDots />}
|
{isLoading && <LoadingDots />}
|
||||||
|
@ -1,15 +1,23 @@
|
|||||||
import { CornerUpLeft } from "lucide-react"
|
import { Check, CornerUpLeft, X } from "lucide-react"
|
||||||
|
import monaco from "monaco-editor"
|
||||||
import { Components } from "react-markdown"
|
import { Components } from "react-markdown"
|
||||||
import { Prism as SyntaxHighlighter } from "react-syntax-highlighter"
|
import { Prism as SyntaxHighlighter } from "react-syntax-highlighter"
|
||||||
import { vscDarkPlus } from "react-syntax-highlighter/dist/esm/styles/prism"
|
import { vscDarkPlus } from "react-syntax-highlighter/dist/esm/styles/prism"
|
||||||
import { Button } from "../../../ui/button"
|
import { Button } from "../../../ui/button"
|
||||||
|
import ApplyButton from "../ApplyButton"
|
||||||
import { stringifyContent } from "./chatUtils"
|
import { stringifyContent } from "./chatUtils"
|
||||||
|
|
||||||
// Create markdown components for chat message component
|
// Create markdown components for chat message component
|
||||||
export const createMarkdownComponents = (
|
export const createMarkdownComponents = (
|
||||||
renderCopyButton: (text: any) => JSX.Element,
|
renderCopyButton: (text: any) => JSX.Element,
|
||||||
renderMarkdownElement: (props: any) => JSX.Element,
|
renderMarkdownElement: (props: any) => JSX.Element,
|
||||||
askAboutCode: (code: any) => void
|
askAboutCode: (code: any) => void,
|
||||||
|
activeFileName: string,
|
||||||
|
activeFileContent: string,
|
||||||
|
editorRef: any,
|
||||||
|
handleApplyCode: (mergedCode: string) => void,
|
||||||
|
mergeDecorationsCollection?: monaco.editor.IEditorDecorationsCollection,
|
||||||
|
setMergeDecorationsCollection?: (collection: undefined) => void
|
||||||
): Components => ({
|
): Components => ({
|
||||||
code: ({
|
code: ({
|
||||||
node,
|
node,
|
||||||
@ -33,6 +41,57 @@ export const createMarkdownComponents = (
|
|||||||
<div className="flex border border-input shadow-lg bg-background rounded-md">
|
<div className="flex border border-input shadow-lg bg-background rounded-md">
|
||||||
{renderCopyButton(children)}
|
{renderCopyButton(children)}
|
||||||
<div className="w-px bg-input"></div>
|
<div className="w-px bg-input"></div>
|
||||||
|
{!mergeDecorationsCollection ? (
|
||||||
|
<ApplyButton
|
||||||
|
code={String(children)}
|
||||||
|
activeFileName={activeFileName}
|
||||||
|
activeFileContent={activeFileContent}
|
||||||
|
editorRef={editorRef}
|
||||||
|
onApply={handleApplyCode}
|
||||||
|
/>
|
||||||
|
) : (
|
||||||
|
<>
|
||||||
|
<Button
|
||||||
|
onClick={() => {
|
||||||
|
if (
|
||||||
|
setMergeDecorationsCollection &&
|
||||||
|
mergeDecorationsCollection &&
|
||||||
|
editorRef?.current
|
||||||
|
) {
|
||||||
|
mergeDecorationsCollection.clear()
|
||||||
|
setMergeDecorationsCollection(undefined)
|
||||||
|
}
|
||||||
|
}}
|
||||||
|
size="sm"
|
||||||
|
variant="ghost"
|
||||||
|
className="p-1 h-6"
|
||||||
|
title="Accept Changes"
|
||||||
|
>
|
||||||
|
<Check className="w-4 h-4 text-green-500" />
|
||||||
|
</Button>
|
||||||
|
<div className="w-px bg-input"></div>
|
||||||
|
<Button
|
||||||
|
onClick={() => {
|
||||||
|
if (
|
||||||
|
setMergeDecorationsCollection &&
|
||||||
|
mergeDecorationsCollection &&
|
||||||
|
editorRef?.current
|
||||||
|
) {
|
||||||
|
editorRef.current.getModel()?.setValue(activeFileContent)
|
||||||
|
mergeDecorationsCollection.clear()
|
||||||
|
setMergeDecorationsCollection(undefined)
|
||||||
|
}
|
||||||
|
}}
|
||||||
|
size="sm"
|
||||||
|
variant="ghost"
|
||||||
|
className="p-1 h-6"
|
||||||
|
title="Discard Changes"
|
||||||
|
>
|
||||||
|
<X className="w-4 h-4 text-red-500" />
|
||||||
|
</Button>
|
||||||
|
</>
|
||||||
|
)}
|
||||||
|
<div className="w-px bg-input"></div>
|
||||||
<Button
|
<Button
|
||||||
onClick={(e) => {
|
onClick={(e) => {
|
||||||
e.preventDefault()
|
e.preventDefault()
|
||||||
|
@ -56,6 +56,9 @@ export interface AIChatProps {
|
|||||||
files: (TFile | TFolder)[]
|
files: (TFile | TFolder)[]
|
||||||
templateType: string
|
templateType: string
|
||||||
templateConfig?: TemplateConfig
|
templateConfig?: TemplateConfig
|
||||||
|
handleApplyCode: (mergedCode: string) => void
|
||||||
|
mergeDecorationsCollection?: monaco.editor.IEditorDecorationsCollection
|
||||||
|
setMergeDecorationsCollection?: (collection: undefined) => void
|
||||||
}
|
}
|
||||||
|
|
||||||
// Chat input props interface
|
// Chat input props interface
|
||||||
@ -105,6 +108,12 @@ export interface MessageProps {
|
|||||||
) => void
|
) => void
|
||||||
setIsContextExpanded: (isExpanded: boolean) => void
|
setIsContextExpanded: (isExpanded: boolean) => void
|
||||||
socket: Socket | null
|
socket: Socket | null
|
||||||
|
handleApplyCode: (mergedCode: string) => void
|
||||||
|
activeFileName: string
|
||||||
|
activeFileContent: string
|
||||||
|
editorRef: any
|
||||||
|
mergeDecorationsCollection?: monaco.editor.IEditorDecorationsCollection
|
||||||
|
setMergeDecorationsCollection?: (collection: undefined) => void
|
||||||
}
|
}
|
||||||
|
|
||||||
// Context tabs props interface
|
// Context tabs props interface
|
||||||
|
@ -104,6 +104,13 @@ export default function CodeEditor({
|
|||||||
// Added this state to track the most recent content for each file
|
// Added this state to track the most recent content for each file
|
||||||
const [fileContents, setFileContents] = useState<Record<string, string>>({})
|
const [fileContents, setFileContents] = useState<Record<string, string>>({})
|
||||||
|
|
||||||
|
// Apply Button merger decoration state
|
||||||
|
const [mergeDecorations, setMergeDecorations] = useState<
|
||||||
|
monaco.editor.IModelDeltaDecoration[]
|
||||||
|
>([])
|
||||||
|
const [mergeDecorationsCollection, setMergeDecorationsCollection] =
|
||||||
|
useState<monaco.editor.IEditorDecorationsCollection>()
|
||||||
|
|
||||||
// Editor state
|
// Editor state
|
||||||
const [editorLanguage, setEditorLanguage] = useState("plaintext")
|
const [editorLanguage, setEditorLanguage] = useState("plaintext")
|
||||||
const [cursorLine, setCursorLine] = useState(0)
|
const [cursorLine, setCursorLine] = useState(0)
|
||||||
@ -375,6 +382,49 @@ export default function CodeEditor({
|
|||||||
})
|
})
|
||||||
}, [editorRef])
|
}, [editorRef])
|
||||||
|
|
||||||
|
// handle apply code
|
||||||
|
const handleApplyCode = useCallback(
|
||||||
|
(mergedCode: string) => {
|
||||||
|
if (!editorRef) return
|
||||||
|
|
||||||
|
const originalLines = activeFileContent.split("\n")
|
||||||
|
const mergedLines = mergedCode.split("\n")
|
||||||
|
|
||||||
|
const decorations: monaco.editor.IModelDeltaDecoration[] = []
|
||||||
|
|
||||||
|
for (
|
||||||
|
let i = 0;
|
||||||
|
i < Math.max(originalLines.length, mergedLines.length);
|
||||||
|
i++
|
||||||
|
) {
|
||||||
|
if (originalLines[i] !== mergedLines[i]) {
|
||||||
|
decorations.push({
|
||||||
|
range: new monaco.Range(i + 1, 1, i + 1, 1),
|
||||||
|
options: {
|
||||||
|
isWholeLine: true,
|
||||||
|
className:
|
||||||
|
i < originalLines.length
|
||||||
|
? "removed-line-decoration"
|
||||||
|
: "added-line-decoration",
|
||||||
|
glyphMarginClassName:
|
||||||
|
i < originalLines.length
|
||||||
|
? "removed-line-glyph"
|
||||||
|
: "added-line-glyph",
|
||||||
|
},
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Update editor content
|
||||||
|
editorRef.setValue(mergedCode)
|
||||||
|
|
||||||
|
// Apply decorations
|
||||||
|
const newDecorations = editorRef.createDecorationsCollection(decorations)
|
||||||
|
setMergeDecorationsCollection(newDecorations)
|
||||||
|
},
|
||||||
|
[editorRef, activeFileContent]
|
||||||
|
)
|
||||||
|
|
||||||
// Generate widget effect
|
// Generate widget effect
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (generate.show) {
|
if (generate.show) {
|
||||||
@ -1234,6 +1284,9 @@ export default function CodeEditor({
|
|||||||
lastCopiedRangeRef={lastCopiedRangeRef}
|
lastCopiedRangeRef={lastCopiedRangeRef}
|
||||||
files={files}
|
files={files}
|
||||||
templateType={sandboxData.type}
|
templateType={sandboxData.type}
|
||||||
|
handleApplyCode={handleApplyCode}
|
||||||
|
mergeDecorationsCollection={mergeDecorationsCollection}
|
||||||
|
setMergeDecorationsCollection={setMergeDecorationsCollection}
|
||||||
/>
|
/>
|
||||||
</ResizablePanel>
|
</ResizablePanel>
|
||||||
</>
|
</>
|
||||||
|
60
frontend/package-lock.json
generated
60
frontend/package-lock.json
generated
@ -54,6 +54,7 @@
|
|||||||
"monaco-themes": "^0.4.4",
|
"monaco-themes": "^0.4.4",
|
||||||
"next": "14.1.3",
|
"next": "14.1.3",
|
||||||
"next-themes": "^0.3.0",
|
"next-themes": "^0.3.0",
|
||||||
|
"openai": "^4.73.1",
|
||||||
"posthog-js": "^1.147.0",
|
"posthog-js": "^1.147.0",
|
||||||
"react": "^18.3.1",
|
"react": "^18.3.1",
|
||||||
"react-dom": "^18",
|
"react-dom": "^18",
|
||||||
@ -7492,6 +7493,65 @@
|
|||||||
"url": "https://github.com/sponsors/sindresorhus"
|
"url": "https://github.com/sponsors/sindresorhus"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/openai": {
|
||||||
|
"version": "4.73.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/openai/-/openai-4.73.1.tgz",
|
||||||
|
"integrity": "sha512-nWImDJBcUsqrhy7yJScXB4+iqjzbUEgzfA3un/6UnHFdwWhjX24oztj69Ped/njABfOdLcO/F7CeWTI5dt8Xmg==",
|
||||||
|
"license": "Apache-2.0",
|
||||||
|
"dependencies": {
|
||||||
|
"@types/node": "^18.11.18",
|
||||||
|
"@types/node-fetch": "^2.6.4",
|
||||||
|
"abort-controller": "^3.0.0",
|
||||||
|
"agentkeepalive": "^4.2.1",
|
||||||
|
"form-data-encoder": "1.7.2",
|
||||||
|
"formdata-node": "^4.3.2",
|
||||||
|
"node-fetch": "^2.6.7"
|
||||||
|
},
|
||||||
|
"bin": {
|
||||||
|
"openai": "bin/cli"
|
||||||
|
},
|
||||||
|
"peerDependencies": {
|
||||||
|
"zod": "^3.23.8"
|
||||||
|
},
|
||||||
|
"peerDependenciesMeta": {
|
||||||
|
"zod": {
|
||||||
|
"optional": true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/openai/node_modules/@types/node": {
|
||||||
|
"version": "18.19.67",
|
||||||
|
"resolved": "https://registry.npmjs.org/@types/node/-/node-18.19.67.tgz",
|
||||||
|
"integrity": "sha512-wI8uHusga+0ZugNp0Ol/3BqQfEcCCNfojtO6Oou9iVNGPTL6QNSdnUdqq85fRgIorLhLMuPIKpsN98QE9Nh+KQ==",
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"undici-types": "~5.26.4"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/openai/node_modules/@types/node-fetch": {
|
||||||
|
"version": "2.6.12",
|
||||||
|
"resolved": "https://registry.npmjs.org/@types/node-fetch/-/node-fetch-2.6.12.tgz",
|
||||||
|
"integrity": "sha512-8nneRWKCg3rMtF69nLQJnOYUcbafYeFSjqkw3jCRLsqkWFlHaoQrr5mXmofFGOx3DKn7UfmBMyov8ySvLRVldA==",
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"@types/node": "*",
|
||||||
|
"form-data": "^4.0.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/openai/node_modules/form-data": {
|
||||||
|
"version": "4.0.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.1.tgz",
|
||||||
|
"integrity": "sha512-tzN8e4TX8+kkxGPK8D5u0FNmjPUjw3lwC9lSLxxoB/+GtsJG91CO8bSWy73APlgAZzZbXEYZJuxjkHH2w+Ezhw==",
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"asynckit": "^0.4.0",
|
||||||
|
"combined-stream": "^1.0.8",
|
||||||
|
"mime-types": "^2.1.12"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">= 6"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/ora": {
|
"node_modules/ora": {
|
||||||
"version": "6.3.1",
|
"version": "6.3.1",
|
||||||
"resolved": "https://registry.npmjs.org/ora/-/ora-6.3.1.tgz",
|
"resolved": "https://registry.npmjs.org/ora/-/ora-6.3.1.tgz",
|
||||||
|
@ -55,6 +55,7 @@
|
|||||||
"monaco-themes": "^0.4.4",
|
"monaco-themes": "^0.4.4",
|
||||||
"next": "14.1.3",
|
"next": "14.1.3",
|
||||||
"next-themes": "^0.3.0",
|
"next-themes": "^0.3.0",
|
||||||
|
"openai": "^4.73.1",
|
||||||
"posthog-js": "^1.147.0",
|
"posthog-js": "^1.147.0",
|
||||||
"react": "^18.3.1",
|
"react": "^18.3.1",
|
||||||
"react-dom": "^18",
|
"react-dom": "^18",
|
||||||
|
Loading…
x
Reference in New Issue
Block a user