feat: enhance AI Chat with context management, file integration, image support, and improved code handling

- Added context tabs system for managing multiple types of context (files, code snippets, images)
   - Added preview functionality for context items
   - Added ability to expand/collapse context previews
   - Added file selection popup/dropdown
   - Added file search functionality
   - Added image upload button
   - Added image paste support
   - Added image preview in context tabs
   - Added automatic code detection on paste
   - Added line number tracking for code snippets
   - Added source file name preservation
   - Added line range display for code contexts
   - Added model selection dropdown (Claude 3.5 Sonnet/Claude 3)
   - Added Ctrl+Enter for sending with full context
   - Added Backspace to remove last context tab when input is empty
   - Added smart code detection on paste
This commit is contained in:
Akhileshrangani4
2024-10-29 01:37:46 -04:00
parent 9c98e41ebb
commit 9c6067dcd9
6 changed files with 665 additions and 83 deletions

View File

@ -6,6 +6,8 @@ import { vscDarkPlus } from "react-syntax-highlighter/dist/esm/styles/prism"
import remarkGfm from "remark-gfm"
import { Button } from "../../ui/button"
import { copyToClipboard, stringifyContent } from "./lib/chatUtils"
import ContextTabs from "./ContextTabs"
import { Socket } from "socket.io-client"
interface MessageProps {
message: {
@ -15,12 +17,14 @@ interface MessageProps {
}
setContext: (context: string | null) => void
setIsContextExpanded: (isExpanded: boolean) => void
socket: Socket | null
}
export default function ChatMessage({
message,
setContext,
setIsContextExpanded,
socket,
}: MessageProps) {
const [expandedMessageIndex, setExpandedMessageIndex] = useState<
number | null
@ -88,34 +92,18 @@ export default function ChatMessage({
: "bg-transparent text-white"
} max-w-full`}
>
{message.role === "user" && (
<div className="absolute top-0 right-0 flex opacity-0 group-hover:opacity-30 transition-opacity">
{renderCopyButton(message.content)}
<Button
onClick={() => askAboutCode(message.content)}
size="sm"
variant="ghost"
className="p-1 h-6"
>
<CornerUpLeft className="w-4 h-4" />
</Button>
</div>
)}
{message.context && (
{message.role === "user" && message.context && (
<div className="mb-2 bg-input rounded-lg">
<div
className="flex justify-between items-center cursor-pointer"
onClick={() =>
setExpandedMessageIndex(expandedMessageIndex === 0 ? null : 0)
}
>
<span className="text-sm text-gray-300">Context</span>
{expandedMessageIndex === 0 ? (
<ChevronUp size={16} />
) : (
<ChevronDown size={16} />
)}
</div>
<ContextTabs
socket={socket}
activeFileName=""
onAddFile={() => {}}
contextTabs={parseContextToTabs(message.context)}
onRemoveTab={() => {}}
isExpanded={expandedMessageIndex === 0}
onToggleExpand={() => setExpandedMessageIndex(expandedMessageIndex === 0 ? null : 0)}
className="[&_div:first-child>div:first-child>div]:bg-[#0D0D0D] [&_button:first-child]:hidden [&_button:last-child]:hidden"
/>
{expandedMessageIndex === 0 && (
<div className="relative">
<div className="absolute top-0 right-0 flex p-1">
@ -153,6 +141,19 @@ export default function ChatMessage({
)}
</div>
)}
{message.role === "user" && (
<div className="absolute top-0 right-0 flex opacity-0 group-hover:opacity-30 transition-opacity">
{renderCopyButton(message.content)}
<Button
onClick={() => askAboutCode(message.content)}
size="sm"
variant="ghost"
className="p-1 h-6"
>
<CornerUpLeft className="w-4 h-4" />
</Button>
</div>
)}
{message.role === "assistant" ? (
<ReactMarkdown
remarkPlugins={[remarkGfm]}
@ -224,3 +225,25 @@ export default function ChatMessage({
</div>
)
}
function parseContextToTabs(context: string) {
const sections = context.split(/(?=File |Code from )/)
return sections.map((section, index) => {
const lines = section.trim().split('\n')
const titleLine = lines[0]
let content = lines.slice(1).join('\n').trim()
// Remove code block markers for display
content = content.replace(/^```[\w-]*\n/, '').replace(/\n```$/, '')
const isFile = titleLine.startsWith('File ')
const name = titleLine.replace(/^(File |Code from )/, '').replace(':', '')
return {
id: `context-${index}`,
type: isFile ? "file" as const : "code" as const,
name: name,
content: content
}
}).filter(tab => tab.content.length > 0)
}