fix(chat): fix mutation card rendering, reappearance after approve, and pseudocode tab switching
- MutationSuggestionCard: call onResolved immediately on approve/reject so resolvedIds is set even if component unmounts before dismiss timer fires. Invalidate mutations list query to refresh pendingSuggestions. - AIChatPanel: use explicit null checks instead of falsy for oldValue/newValue so empty-string fields (e.g. decompiledCode) render correctly. Remove resolvedIds filter from streaming mutationSuggestions — the card manages its own lifecycle internally. - CodeViewer: auto-switch to assembly tab when selecting a function that has no pseudocode (derived effectiveTab via useMemo).
This commit is contained in:
parent
914005e00e
commit
e67a99671b
@ -3,6 +3,8 @@ import { formatDistanceToNow } from "date-fns";
|
|||||||
import { Send, Bot, User, Loader2, Sparkles, Plus, Trash2 } from "lucide-react";
|
import { Send, Bot, User, Loader2, Sparkles, Plus, Trash2 } from "lucide-react";
|
||||||
import ReactMarkdown from "react-markdown";
|
import ReactMarkdown from "react-markdown";
|
||||||
import remarkGfm from "remark-gfm";
|
import remarkGfm from "remark-gfm";
|
||||||
|
import remarkMath from "remark-math";
|
||||||
|
import rehypeKatex from "rehype-katex";
|
||||||
import { Prism as SyntaxHighlighter } from "react-syntax-highlighter";
|
import { Prism as SyntaxHighlighter } from "react-syntax-highlighter";
|
||||||
import { oneDark } from "react-syntax-highlighter/dist/esm/styles/prism";
|
import { oneDark } from "react-syntax-highlighter/dist/esm/styles/prism";
|
||||||
import { useAIChat } from "@/features/binary/hooks/useAIChat";
|
import { useAIChat } from "@/features/binary/hooks/useAIChat";
|
||||||
@ -25,7 +27,7 @@ const quickPrompts = [
|
|||||||
"Summarize the binary",
|
"Summarize the binary",
|
||||||
];
|
];
|
||||||
|
|
||||||
const remarkPlugins = [remarkGfm];
|
const remarkPlugins = [remarkGfm, remarkMath];
|
||||||
|
|
||||||
const codeBlockTheme = {
|
const codeBlockTheme = {
|
||||||
...oneDark,
|
...oneDark,
|
||||||
@ -352,7 +354,13 @@ export function AIChatPanel({
|
|||||||
{mergedMessages.map((item, idx) => {
|
{mergedMessages.map((item, idx) => {
|
||||||
if (item.kind === "suggestion") {
|
if (item.kind === "suggestion") {
|
||||||
const s = item.data;
|
const s = item.data;
|
||||||
if (!s.id || !s.targetId || !s.oldValue || !s.newValue) return null;
|
if (
|
||||||
|
s.id == null ||
|
||||||
|
s.targetId == null ||
|
||||||
|
s.oldValue == null ||
|
||||||
|
s.newValue == null
|
||||||
|
)
|
||||||
|
return null;
|
||||||
if (resolvedIds.has(s.id)) return null;
|
if (resolvedIds.has(s.id)) return null;
|
||||||
return (
|
return (
|
||||||
<MutationSuggestionCard
|
<MutationSuggestionCard
|
||||||
@ -403,6 +411,7 @@ export function AIChatPanel({
|
|||||||
<div className={markdownContainerClass}>
|
<div className={markdownContainerClass}>
|
||||||
<ReactMarkdown
|
<ReactMarkdown
|
||||||
remarkPlugins={remarkPlugins}
|
remarkPlugins={remarkPlugins}
|
||||||
|
rehypePlugins={[rehypeKatex]}
|
||||||
components={markdownComponents}
|
components={markdownComponents}
|
||||||
>
|
>
|
||||||
{normalizeMarkdown(message.content ?? "")}
|
{normalizeMarkdown(message.content ?? "")}
|
||||||
@ -477,21 +486,19 @@ export function AIChatPanel({
|
|||||||
<span>{toolStatus}</span>
|
<span>{toolStatus}</span>
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
{mutationSuggestions
|
{mutationSuggestions.map((s) => (
|
||||||
.filter((s) => !resolvedIds.has(s.mutationId))
|
<MutationSuggestionCard
|
||||||
.map((s) => (
|
key={s.mutationId}
|
||||||
<MutationSuggestionCard
|
mutationId={s.mutationId}
|
||||||
key={s.mutationId}
|
targetType={s.targetType}
|
||||||
mutationId={s.mutationId}
|
targetId={s.targetId}
|
||||||
targetType={s.targetType}
|
fieldName={s.fieldName}
|
||||||
targetId={s.targetId}
|
oldValue={s.oldValue}
|
||||||
fieldName={s.fieldName}
|
newValue={s.newValue}
|
||||||
oldValue={s.oldValue}
|
onRenameApplied={onRenameApplied}
|
||||||
newValue={s.newValue}
|
onResolved={handleResolved}
|
||||||
onRenameApplied={onRenameApplied}
|
/>
|
||||||
onResolved={handleResolved}
|
))}
|
||||||
/>
|
|
||||||
))}
|
|
||||||
</>
|
</>
|
||||||
)}
|
)}
|
||||||
<div ref={messagesEndRef} />
|
<div ref={messagesEndRef} />
|
||||||
|
|||||||
@ -153,8 +153,14 @@ export function CodeViewer({
|
|||||||
const hasCfg = cfgBlocks.length > 0;
|
const hasCfg = cfgBlocks.length > 0;
|
||||||
const hasPseudocode = !!func?.decompiledCode;
|
const hasPseudocode = !!func?.decompiledCode;
|
||||||
|
|
||||||
|
const effectiveTab = useMemo(() => {
|
||||||
|
if (activeTab === "pseudocode" && !hasPseudocode) return "assembly";
|
||||||
|
if (activeTab === "cfg" && !hasCfg) return "assembly";
|
||||||
|
return activeTab;
|
||||||
|
}, [activeTab, hasPseudocode, hasCfg]);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Tabs value={activeTab} onValueChange={setActiveTab} className="flex h-full flex-col">
|
<Tabs value={effectiveTab} onValueChange={setActiveTab} className="flex h-full flex-col">
|
||||||
<div className="border-border flex shrink-0 items-center justify-between gap-2 border-b px-4 py-2">
|
<div className="border-border flex shrink-0 items-center justify-between gap-2 border-b px-4 py-2">
|
||||||
<div className="flex items-center gap-3">
|
<div className="flex items-center gap-3">
|
||||||
<TabsList className="h-8">
|
<TabsList className="h-8">
|
||||||
|
|||||||
@ -158,6 +158,13 @@ export function MutationSuggestionCard({
|
|||||||
if (invalidationKey) {
|
if (invalidationKey) {
|
||||||
queryClient.invalidateQueries({ queryKey: [invalidationKey] });
|
queryClient.invalidateQueries({ queryKey: [invalidationKey] });
|
||||||
}
|
}
|
||||||
|
queryClient.invalidateQueries({
|
||||||
|
predicate: (query) =>
|
||||||
|
typeof query.queryKey[0] === "string" &&
|
||||||
|
query.queryKey[0].startsWith("/binaries/") &&
|
||||||
|
query.queryKey[0].endsWith("/mutations"),
|
||||||
|
});
|
||||||
|
onResolved?.(mutationId);
|
||||||
onRenameApplied?.();
|
onRenameApplied?.();
|
||||||
dismissTimerRef.current = setTimeout(resolve, 1500);
|
dismissTimerRef.current = setTimeout(resolve, 1500);
|
||||||
},
|
},
|
||||||
@ -168,6 +175,13 @@ export function MutationSuggestionCard({
|
|||||||
mutation: {
|
mutation: {
|
||||||
onSuccess: () => {
|
onSuccess: () => {
|
||||||
setStatus("REJECTED");
|
setStatus("REJECTED");
|
||||||
|
queryClient.invalidateQueries({
|
||||||
|
predicate: (query) =>
|
||||||
|
typeof query.queryKey[0] === "string" &&
|
||||||
|
query.queryKey[0].startsWith("/binaries/") &&
|
||||||
|
query.queryKey[0].endsWith("/mutations"),
|
||||||
|
});
|
||||||
|
onResolved?.(mutationId);
|
||||||
onRenameApplied?.();
|
onRenameApplied?.();
|
||||||
dismissTimerRef.current = setTimeout(resolve, 1500);
|
dismissTimerRef.current = setTimeout(resolve, 1500);
|
||||||
},
|
},
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user