644 lines
26 KiB
TypeScript
644 lines
26 KiB
TypeScript
"use client";
|
|
|
|
import { useState, useEffect } from 'react';
|
|
import { Card, CardContent, CardDescription, CardHeader, CardTitle } from '@/components/ui/card';
|
|
import { Button } from '@/components/ui/button';
|
|
import { Input } from '@/components/ui/input';
|
|
import { Label } from '@/components/ui/label';
|
|
import { Textarea } from '@/components/ui/textarea';
|
|
import { auth } from '@/lib/firebase/config';
|
|
import { toast } from 'sonner';
|
|
import { Download, ExternalLink, Eye, EyeOff, MessageSquare, FolderKanban, Bot, Upload } from 'lucide-react';
|
|
import { Tabs, TabsContent, TabsList, TabsTrigger } from '@/components/ui/tabs';
|
|
import {
|
|
Dialog,
|
|
DialogContent,
|
|
DialogDescription,
|
|
DialogFooter,
|
|
DialogHeader,
|
|
DialogTitle,
|
|
DialogTrigger,
|
|
} from "@/components/ui/dialog";
|
|
import { OpenAIIcon } from '@/components/icons/custom-icons';
|
|
|
|
interface ChatGPTImportCardProps {
|
|
projectId?: string;
|
|
onImportComplete?: (importData: any) => void;
|
|
}
|
|
|
|
export function ChatGPTImportCard({ projectId, onImportComplete }: ChatGPTImportCardProps) {
|
|
const [conversationUrl, setConversationUrl] = useState('');
|
|
const [openaiApiKey, setOpenaiApiKey] = useState('');
|
|
const [showApiKey, setShowApiKey] = useState(false);
|
|
const [loading, setLoading] = useState(false);
|
|
const [importedData, setImportedData] = useState<any>(null);
|
|
const [dialogOpen, setDialogOpen] = useState(false);
|
|
const [hasStoredKey, setHasStoredKey] = useState(false);
|
|
const [checkingKey, setCheckingKey] = useState(true);
|
|
const [importType, setImportType] = useState<'conversation' | 'project' | 'gpt' | 'file'>('file');
|
|
|
|
// Check for stored OpenAI key on mount
|
|
useEffect(() => {
|
|
const checkStoredKey = async () => {
|
|
try {
|
|
const user = auth.currentUser;
|
|
if (!user) {
|
|
setCheckingKey(false);
|
|
return;
|
|
}
|
|
|
|
const token = await user.getIdToken();
|
|
const response = await fetch('/api/keys', {
|
|
headers: {
|
|
'Authorization': `Bearer ${token}`,
|
|
},
|
|
});
|
|
|
|
if (response.ok) {
|
|
const data = await response.json();
|
|
const hasOpenAI = data.keys.some((k: any) => k.service === 'openai');
|
|
setHasStoredKey(hasOpenAI);
|
|
}
|
|
} catch (error) {
|
|
console.error('Error checking for stored key:', error);
|
|
} finally {
|
|
setCheckingKey(false);
|
|
}
|
|
};
|
|
|
|
checkStoredKey();
|
|
}, []);
|
|
|
|
const extractConversationId = (urlOrId: string): { id: string | null; isShareLink: boolean } => {
|
|
// If it's already just an ID
|
|
if (!urlOrId.includes('/') && !urlOrId.includes('http')) {
|
|
const trimmed = urlOrId.trim();
|
|
// Check if it's a share link ID (UUID format)
|
|
const isUUID = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i.test(trimmed);
|
|
return { id: trimmed, isShareLink: isUUID };
|
|
}
|
|
|
|
// Extract from URL patterns:
|
|
// https://chat.openai.com/c/{id} - regular conversation (old)
|
|
// https://chatgpt.com/c/{id} - regular conversation (new)
|
|
// https://chat.openai.com/share/{id} - shared conversation (not supported)
|
|
// https://chatgpt.com/share/{id} - shared conversation (not supported)
|
|
|
|
// Check for share links first
|
|
const sharePatterns = [
|
|
/chat\.openai\.com\/share\/([a-zA-Z0-9-]+)/,
|
|
/chatgpt\.com\/share\/([a-zA-Z0-9-]+)/,
|
|
];
|
|
|
|
for (const pattern of sharePatterns) {
|
|
const match = urlOrId.match(pattern);
|
|
if (match) {
|
|
return { id: match[1], isShareLink: true };
|
|
}
|
|
}
|
|
|
|
// Regular conversation patterns
|
|
const conversationPatterns = [
|
|
/chat\.openai\.com\/c\/([a-zA-Z0-9-]+)/,
|
|
/chatgpt\.com\/c\/([a-zA-Z0-9-]+)/,
|
|
// GPT project conversations: https://chatgpt.com/g/g-p-[id]-[name]/c/[conversation-id]
|
|
/chatgpt\.com\/g\/g-p-[a-zA-Z0-9-]+\/c\/([a-zA-Z0-9-]+)/,
|
|
];
|
|
|
|
for (const pattern of conversationPatterns) {
|
|
const match = urlOrId.match(pattern);
|
|
if (match) {
|
|
return { id: match[1], isShareLink: false };
|
|
}
|
|
}
|
|
|
|
return { id: null, isShareLink: false };
|
|
};
|
|
|
|
const handleImport = async () => {
|
|
if (!conversationUrl.trim()) {
|
|
toast.error('Please enter a conversation ID or project ID');
|
|
return;
|
|
}
|
|
|
|
// If no stored key and no manual key provided, show error
|
|
if (!hasStoredKey && !openaiApiKey) {
|
|
toast.error('Please enter your OpenAI API key or add one in Keys page');
|
|
return;
|
|
}
|
|
|
|
setLoading(true);
|
|
try {
|
|
const user = auth.currentUser;
|
|
if (!user) {
|
|
toast.error('Please sign in to import');
|
|
return;
|
|
}
|
|
|
|
const token = await user.getIdToken();
|
|
|
|
if (importType === 'file') {
|
|
// Import from uploaded conversations.json file
|
|
try {
|
|
const conversations = JSON.parse(conversationUrl);
|
|
|
|
const response = await fetch('/api/chatgpt/import-file', {
|
|
method: 'POST',
|
|
headers: {
|
|
'Authorization': `Bearer ${token}`,
|
|
'Content-Type': 'application/json',
|
|
},
|
|
body: JSON.stringify({
|
|
conversations,
|
|
projectId,
|
|
}),
|
|
});
|
|
|
|
if (!response.ok) {
|
|
const error = await response.json();
|
|
throw new Error(error.details || error.error || 'Import failed');
|
|
}
|
|
|
|
const data = await response.json();
|
|
toast.success(`✅ Imported ${data.imported} conversations!`);
|
|
setImportedData({
|
|
messageCount: data.imported,
|
|
title: `${data.imported} conversations`
|
|
});
|
|
|
|
if (onImportComplete) {
|
|
onImportComplete(data);
|
|
}
|
|
} catch (error: any) {
|
|
throw new Error(`File import failed: ${error.message}`);
|
|
}
|
|
|
|
// Reset form
|
|
setConversationUrl('');
|
|
setDialogOpen(false);
|
|
return;
|
|
}
|
|
|
|
// Determine which key to use
|
|
let keyToUse = openaiApiKey;
|
|
|
|
// If no manual key provided, try to get stored key
|
|
if (!keyToUse && hasStoredKey) {
|
|
const keyResponse = await fetch('/api/keys/get', {
|
|
method: 'POST',
|
|
headers: {
|
|
'Authorization': `Bearer ${token}`,
|
|
'Content-Type': 'application/json',
|
|
},
|
|
body: JSON.stringify({ service: 'openai' }),
|
|
});
|
|
|
|
if (keyResponse.ok) {
|
|
const keyData = await keyResponse.json();
|
|
keyToUse = keyData.keyValue;
|
|
}
|
|
}
|
|
|
|
if (importType === 'project') {
|
|
// Import OpenAI Project
|
|
const response = await fetch('/api/openai/projects', {
|
|
method: 'POST',
|
|
headers: {
|
|
'Authorization': `Bearer ${token}`,
|
|
'Content-Type': 'application/json',
|
|
},
|
|
body: JSON.stringify({
|
|
openaiApiKey: keyToUse,
|
|
projectId: conversationUrl.trim(),
|
|
}),
|
|
});
|
|
|
|
if (!response.ok) {
|
|
const error = await response.json();
|
|
throw new Error(error.details || error.error || 'Failed to fetch project');
|
|
}
|
|
|
|
const data = await response.json();
|
|
toast.success(`Retrieved OpenAI Project: ${data.data.name || data.data.id}`);
|
|
setImportedData(data.data);
|
|
|
|
if (onImportComplete) {
|
|
onImportComplete(data.data);
|
|
}
|
|
} else {
|
|
// Import ChatGPT Conversation
|
|
const { id: conversationId, isShareLink } = extractConversationId(conversationUrl);
|
|
|
|
if (!conversationId) {
|
|
toast.error('Invalid ChatGPT URL or conversation ID');
|
|
return;
|
|
}
|
|
|
|
// Check if it's a share link
|
|
if (isShareLink) {
|
|
toast.error('Share links are not supported. Please use the direct conversation URL from your ChatGPT chat history.', {
|
|
description: 'Look for URLs like: chatgpt.com/c/... or chat.openai.com/c/...',
|
|
duration: 5000,
|
|
});
|
|
return;
|
|
}
|
|
|
|
const response = await fetch('/api/chatgpt/import', {
|
|
method: 'POST',
|
|
headers: {
|
|
'Authorization': `Bearer ${token}`,
|
|
'Content-Type': 'application/json',
|
|
},
|
|
body: JSON.stringify({
|
|
conversationId,
|
|
openaiApiKey: keyToUse,
|
|
projectId,
|
|
}),
|
|
});
|
|
|
|
if (!response.ok) {
|
|
const error = await response.json();
|
|
throw new Error(error.details || error.error || 'Import failed');
|
|
}
|
|
|
|
const data = await response.json();
|
|
setImportedData(data);
|
|
toast.success(`Imported: ${data.title} (${data.messageCount} messages)`);
|
|
|
|
if (onImportComplete) {
|
|
onImportComplete(data);
|
|
}
|
|
}
|
|
|
|
// Reset form
|
|
setConversationUrl('');
|
|
setDialogOpen(false);
|
|
} catch (error) {
|
|
console.error('Import error:', error);
|
|
toast.error(error instanceof Error ? error.message : 'Failed to import');
|
|
} finally {
|
|
setLoading(false);
|
|
}
|
|
};
|
|
|
|
return (
|
|
<>
|
|
<Card>
|
|
<CardHeader>
|
|
<div className="flex items-center justify-between">
|
|
<div>
|
|
<CardTitle>Import from OpenAI</CardTitle>
|
|
<CardDescription>
|
|
Import ChatGPT conversations or OpenAI Projects
|
|
</CardDescription>
|
|
</div>
|
|
<Dialog open={dialogOpen} onOpenChange={setDialogOpen}>
|
|
<DialogTrigger asChild>
|
|
<Button>
|
|
<Download className="mr-2 h-4 w-4" />
|
|
Import from OpenAI
|
|
</Button>
|
|
</DialogTrigger>
|
|
<DialogContent className="sm:max-w-[600px]">
|
|
<DialogHeader>
|
|
<DialogTitle>Import from OpenAI</DialogTitle>
|
|
<DialogDescription>
|
|
Import ChatGPT conversations or OpenAI Projects
|
|
</DialogDescription>
|
|
</DialogHeader>
|
|
|
|
<Tabs value={importType} onValueChange={(v) => setImportType(v as 'conversation' | 'project' | 'gpt' | 'file')} className="w-full">
|
|
<TabsList className="grid w-full grid-cols-2">
|
|
<TabsTrigger value="file" className="flex items-center gap-2">
|
|
<Upload className="h-4 w-4" />
|
|
Upload File
|
|
</TabsTrigger>
|
|
<TabsTrigger value="conversation" className="flex items-center gap-2">
|
|
<MessageSquare className="h-4 w-4" />
|
|
Single Chat
|
|
</TabsTrigger>
|
|
</TabsList>
|
|
|
|
<TabsContent value="file" className="space-y-4 mt-4">
|
|
{/* File Upload */}
|
|
<div className="space-y-2">
|
|
<Label htmlFor="conversations-file">conversations.json File</Label>
|
|
<Input
|
|
id="conversations-file"
|
|
type="file"
|
|
accept=".json,application/json"
|
|
onChange={(e) => {
|
|
const file = e.target.files?.[0];
|
|
if (file) {
|
|
const reader = new FileReader();
|
|
reader.onload = (event) => {
|
|
try {
|
|
const conversations = JSON.parse(event.target?.result as string);
|
|
setConversationUrl(JSON.stringify(conversations)); // Store in existing state
|
|
toast.success(`Loaded ${conversations.length} conversations`);
|
|
} catch (error) {
|
|
toast.error('Invalid JSON file');
|
|
}
|
|
};
|
|
reader.readAsText(file);
|
|
}
|
|
}}
|
|
/>
|
|
<p className="text-xs text-muted-foreground">
|
|
Upload your ChatGPT exported conversations.json file
|
|
</p>
|
|
</div>
|
|
|
|
{/* Instructions */}
|
|
<div className="rounded-lg border bg-muted/50 p-4">
|
|
<p className="text-sm font-medium mb-2">How to export your ChatGPT data:</p>
|
|
<ol className="text-sm text-muted-foreground space-y-1 list-decimal list-inside">
|
|
<li>Go to <a href="https://chatgpt.com/settings/data-controls" target="_blank" rel="noopener noreferrer" className="text-primary hover:underline">ChatGPT Settings → Data Controls</a></li>
|
|
<li>Click "Export data"</li>
|
|
<li>Wait for the email from OpenAI (can take up to 24 hours)</li>
|
|
<li>Download and extract the ZIP file</li>
|
|
<li>Upload the <code className="text-xs">conversations.json</code> file here</li>
|
|
</ol>
|
|
</div>
|
|
|
|
{/* Info banner */}
|
|
<div className="rounded-lg bg-blue-500/10 border border-blue-500/20 p-4">
|
|
<p className="text-sm font-medium text-blue-700 dark:text-blue-400 mb-2">
|
|
💡 Privacy-friendly import
|
|
</p>
|
|
<p className="text-sm text-muted-foreground">
|
|
Your conversations are processed locally and only stored in your Vibn account.
|
|
We never send your data to third parties.
|
|
</p>
|
|
</div>
|
|
</TabsContent>
|
|
|
|
<TabsContent value="conversation" className="space-y-4 mt-4">
|
|
{/* Show stored key status */}
|
|
{hasStoredKey && (
|
|
<div className="rounded-lg border border-green-500/20 bg-green-500/5 p-3">
|
|
<p className="text-sm text-green-700 dark:text-green-400">
|
|
✓ Using your stored OpenAI API key
|
|
</p>
|
|
</div>
|
|
)}
|
|
|
|
{/* OpenAI API Key (optional if stored) */}
|
|
{!hasStoredKey && (
|
|
<div className="space-y-2">
|
|
<Label htmlFor="openai-key">OpenAI API Key</Label>
|
|
<div className="flex gap-2">
|
|
<div className="relative flex-1">
|
|
<Input
|
|
id="openai-key"
|
|
type={showApiKey ? 'text' : 'password'}
|
|
placeholder="sk-..."
|
|
value={openaiApiKey}
|
|
onChange={(e) => setOpenaiApiKey(e.target.value)}
|
|
className="pr-10"
|
|
/>
|
|
<Button
|
|
variant="ghost"
|
|
size="icon"
|
|
className="absolute right-0 top-0 h-full"
|
|
onClick={() => setShowApiKey(!showApiKey)}
|
|
>
|
|
{showApiKey ? <EyeOff className="h-4 w-4" /> : <Eye className="h-4 w-4" />}
|
|
</Button>
|
|
</div>
|
|
</div>
|
|
<div className="flex items-center justify-between text-xs">
|
|
<a
|
|
href="https://platform.openai.com/api-keys"
|
|
target="_blank"
|
|
rel="noopener noreferrer"
|
|
className="text-primary hover:underline inline-flex items-center gap-1"
|
|
>
|
|
Get your API key <ExternalLink className="h-3 w-3" />
|
|
</a>
|
|
<a
|
|
href="/keys"
|
|
className="text-primary hover:underline"
|
|
>
|
|
Or save it in Keys page
|
|
</a>
|
|
</div>
|
|
</div>
|
|
)}
|
|
|
|
{/* Conversation URL */}
|
|
<div className="space-y-2">
|
|
<Label htmlFor="conversation-url">ChatGPT Conversation URL or ID</Label>
|
|
<Input
|
|
id="conversation-url"
|
|
placeholder="https://chatgpt.com/c/abc-123 or just abc-123"
|
|
value={conversationUrl}
|
|
onChange={(e) => setConversationUrl(e.target.value)}
|
|
/>
|
|
<p className="text-xs text-muted-foreground">
|
|
Copy the URL from your ChatGPT conversation or paste the conversation ID
|
|
</p>
|
|
</div>
|
|
|
|
{/* Instructions */}
|
|
<div className="rounded-lg border bg-muted/50 p-4">
|
|
<p className="text-sm font-medium mb-2">How to find your conversation URL:</p>
|
|
<ol className="text-sm text-muted-foreground space-y-1 list-decimal list-inside">
|
|
<li>Open the ChatGPT conversation you want to import</li>
|
|
<li>Look at the URL in your browser (must show <code className="text-xs">/c/</code>)</li>
|
|
<li>Copy the full URL: <code className="text-xs">chatgpt.com/c/...</code></li>
|
|
<li><strong>Note:</strong> Share links (<code className="text-xs">/share/</code>) won't work - you need the direct conversation URL</li>
|
|
</ol>
|
|
</div>
|
|
</TabsContent>
|
|
|
|
<TabsContent value="gpt" className="space-y-4 mt-4">
|
|
{/* GPT URL Input */}
|
|
<div className="space-y-2">
|
|
<Label htmlFor="gpt-url">ChatGPT GPT URL</Label>
|
|
<Input
|
|
id="gpt-url"
|
|
placeholder="https://chatgpt.com/g/g-p-abc123-your-gpt-name/project"
|
|
value={conversationUrl}
|
|
onChange={(e) => setConversationUrl(e.target.value)}
|
|
/>
|
|
<p className="text-xs text-muted-foreground">
|
|
Paste the full URL of your custom GPT
|
|
</p>
|
|
</div>
|
|
|
|
{/* Instructions */}
|
|
<div className="rounded-lg border bg-muted/50 p-4">
|
|
<p className="text-sm font-medium mb-2">How to find your GPT URL:</p>
|
|
<ol className="text-sm text-muted-foreground space-y-1 list-decimal list-inside">
|
|
<li>Open your custom GPT in ChatGPT</li>
|
|
<li>Copy the full URL from your browser</li>
|
|
<li>Paste it here</li>
|
|
<li>To import conversations with this GPT, switch to the "Chat" tab</li>
|
|
</ol>
|
|
</div>
|
|
|
|
{/* Info banner */}
|
|
<div className="rounded-lg bg-blue-500/10 border border-blue-500/20 p-4">
|
|
<p className="text-sm font-medium text-blue-700 dark:text-blue-400 mb-2">
|
|
💡 About GPT imports
|
|
</p>
|
|
<p className="text-sm text-muted-foreground">
|
|
This saves a reference to your custom GPT. To import actual conversations
|
|
with this GPT, go to a specific chat and use the "Chat" tab to import
|
|
that conversation.
|
|
</p>
|
|
</div>
|
|
</TabsContent>
|
|
|
|
<TabsContent value="project" className="space-y-4 mt-4">
|
|
{/* Show stored key status */}
|
|
{hasStoredKey && (
|
|
<div className="rounded-lg border border-green-500/20 bg-green-500/5 p-3">
|
|
<p className="text-sm text-green-700 dark:text-green-400">
|
|
✓ Using your stored OpenAI API key
|
|
</p>
|
|
</div>
|
|
)}
|
|
|
|
{/* OpenAI API Key (optional if stored) */}
|
|
{!hasStoredKey && (
|
|
<div className="space-y-2">
|
|
<Label htmlFor="openai-key-project">OpenAI API Key</Label>
|
|
<div className="flex gap-2">
|
|
<div className="relative flex-1">
|
|
<Input
|
|
id="openai-key-project"
|
|
type={showApiKey ? 'text' : 'password'}
|
|
placeholder="sk-..."
|
|
value={openaiApiKey}
|
|
onChange={(e) => setOpenaiApiKey(e.target.value)}
|
|
className="pr-10"
|
|
/>
|
|
<Button
|
|
variant="ghost"
|
|
size="icon"
|
|
className="absolute right-0 top-0 h-full"
|
|
onClick={() => setShowApiKey(!showApiKey)}
|
|
>
|
|
{showApiKey ? <EyeOff className="h-4 w-4" /> : <Eye className="h-4 w-4" />}
|
|
</Button>
|
|
</div>
|
|
</div>
|
|
<div className="flex items-center justify-between text-xs">
|
|
<a
|
|
href="https://platform.openai.com/api-keys"
|
|
target="_blank"
|
|
rel="noopener noreferrer"
|
|
className="text-primary hover:underline inline-flex items-center gap-1"
|
|
>
|
|
Get your API key <ExternalLink className="h-3 w-3" />
|
|
</a>
|
|
<a
|
|
href="/keys"
|
|
className="text-primary hover:underline"
|
|
>
|
|
Or save it in Keys page
|
|
</a>
|
|
</div>
|
|
</div>
|
|
)}
|
|
|
|
{/* Project ID */}
|
|
<div className="space-y-2">
|
|
<Label htmlFor="project-id">OpenAI Project ID</Label>
|
|
<Input
|
|
id="project-id"
|
|
placeholder="proj_abc123..."
|
|
value={conversationUrl}
|
|
onChange={(e) => setConversationUrl(e.target.value)}
|
|
/>
|
|
<p className="text-xs text-muted-foreground">
|
|
Enter your OpenAI Project ID
|
|
</p>
|
|
</div>
|
|
|
|
{/* Instructions */}
|
|
<div className="rounded-lg border bg-muted/50 p-4">
|
|
<p className="text-sm font-medium mb-2">How to find your Project ID:</p>
|
|
<ol className="text-sm text-muted-foreground space-y-1 list-decimal list-inside">
|
|
<li>Go to <a href="https://platform.openai.com/settings/organization/projects" target="_blank" rel="noopener noreferrer" className="text-primary hover:underline">OpenAI Projects</a></li>
|
|
<li>Click on the project you want to import</li>
|
|
<li>Copy the Project ID (starts with <code className="text-xs">proj_</code>)</li>
|
|
<li>Or use the API to list all projects</li>
|
|
</ol>
|
|
</div>
|
|
</TabsContent>
|
|
</Tabs>
|
|
|
|
<DialogFooter>
|
|
<Button variant="outline" onClick={() => setDialogOpen(false)}>
|
|
Cancel
|
|
</Button>
|
|
<Button
|
|
onClick={handleImport}
|
|
disabled={loading || !conversationUrl || (importType !== 'file' && !hasStoredKey && !openaiApiKey)}
|
|
>
|
|
{loading
|
|
? (importType === 'file' ? 'Importing...' : importType === 'project' ? 'Fetching Project...' : 'Importing Conversation...')
|
|
: (importType === 'file' ? 'Import Conversations' : importType === 'project' ? 'Fetch Project' : 'Import Conversation')
|
|
}
|
|
</Button>
|
|
</DialogFooter>
|
|
</DialogContent>
|
|
</Dialog>
|
|
</div>
|
|
</CardHeader>
|
|
<CardContent>
|
|
<div className="space-y-4">
|
|
<div className="space-y-2 text-sm text-muted-foreground">
|
|
<p className="font-medium text-foreground">What you can import:</p>
|
|
<ul className="list-disc list-inside space-y-1 ml-2">
|
|
<li><strong>Conversations:</strong> Full ChatGPT chat history with planning & brainstorming</li>
|
|
<li><strong>Projects:</strong> OpenAI Platform projects with API keys, usage, and settings</li>
|
|
<li>Requirements, specifications, and design decisions</li>
|
|
<li>Architecture notes and technical discussions</li>
|
|
</ul>
|
|
</div>
|
|
|
|
{importedData && (
|
|
<div className="rounded-lg border border-green-500/20 bg-green-500/5 p-4">
|
|
<div className="flex items-start gap-3">
|
|
{importedData.messageCount ? (
|
|
<MessageSquare className="h-5 w-5 text-green-600 mt-0.5" />
|
|
) : (
|
|
<FolderKanban className="h-5 w-5 text-green-600 mt-0.5" />
|
|
)}
|
|
<div className="flex-1">
|
|
<p className="text-sm font-medium text-green-700 dark:text-green-400">
|
|
Recently imported: {importedData.title || importedData.name || importedData.id}
|
|
</p>
|
|
<p className="text-xs text-muted-foreground mt-1">
|
|
{importedData.messageCount
|
|
? `${importedData.messageCount} messages • Conversation ID: ${importedData.conversationId}`
|
|
: `Project ID: ${importedData.id}`
|
|
}
|
|
</p>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
)}
|
|
|
|
<div className="rounded-lg bg-blue-500/10 border border-blue-500/20 p-4">
|
|
<p className="text-sm font-medium text-blue-700 dark:text-blue-400 mb-2">
|
|
💡 Why import from OpenAI?
|
|
</p>
|
|
<p className="text-sm text-muted-foreground">
|
|
Connect your planning discussions from ChatGPT and your OpenAI Platform projects
|
|
with your actual coding sessions in Vibn. Our AI can reference everything to provide
|
|
better context and insights.
|
|
</p>
|
|
</div>
|
|
</div>
|
|
</CardContent>
|
|
</Card>
|
|
</>
|
|
);
|
|
}
|
|
|