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 ReactMarkdown from "react-markdown";
|
||||
import remarkGfm from "remark-gfm";
|
||||
import remarkMath from "remark-math";
|
||||
import rehypeKatex from "rehype-katex";
|
||||
import { Prism as SyntaxHighlighter } from "react-syntax-highlighter";
|
||||
import { oneDark } from "react-syntax-highlighter/dist/esm/styles/prism";
|
||||
import { useAIChat } from "@/features/binary/hooks/useAIChat";
|
||||
@ -25,7 +27,7 @@ const quickPrompts = [
|
||||
"Summarize the binary",
|
||||
];
|
||||
|
||||
const remarkPlugins = [remarkGfm];
|
||||
const remarkPlugins = [remarkGfm, remarkMath];
|
||||
|
||||
const codeBlockTheme = {
|
||||
...oneDark,
|
||||
@ -352,7 +354,13 @@ export function AIChatPanel({
|
||||
{mergedMessages.map((item, idx) => {
|
||||
if (item.kind === "suggestion") {
|
||||
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;
|
||||
return (
|
||||
<MutationSuggestionCard
|
||||
@ -403,6 +411,7 @@ export function AIChatPanel({
|
||||
<div className={markdownContainerClass}>
|
||||
<ReactMarkdown
|
||||
remarkPlugins={remarkPlugins}
|
||||
rehypePlugins={[rehypeKatex]}
|
||||
components={markdownComponents}
|
||||
>
|
||||
{normalizeMarkdown(message.content ?? "")}
|
||||
@ -477,21 +486,19 @@ export function AIChatPanel({
|
||||
<span>{toolStatus}</span>
|
||||
</div>
|
||||
)}
|
||||
{mutationSuggestions
|
||||
.filter((s) => !resolvedIds.has(s.mutationId))
|
||||
.map((s) => (
|
||||
<MutationSuggestionCard
|
||||
key={s.mutationId}
|
||||
mutationId={s.mutationId}
|
||||
targetType={s.targetType}
|
||||
targetId={s.targetId}
|
||||
fieldName={s.fieldName}
|
||||
oldValue={s.oldValue}
|
||||
newValue={s.newValue}
|
||||
onRenameApplied={onRenameApplied}
|
||||
onResolved={handleResolved}
|
||||
/>
|
||||
))}
|
||||
{mutationSuggestions.map((s) => (
|
||||
<MutationSuggestionCard
|
||||
key={s.mutationId}
|
||||
mutationId={s.mutationId}
|
||||
targetType={s.targetType}
|
||||
targetId={s.targetId}
|
||||
fieldName={s.fieldName}
|
||||
oldValue={s.oldValue}
|
||||
newValue={s.newValue}
|
||||
onRenameApplied={onRenameApplied}
|
||||
onResolved={handleResolved}
|
||||
/>
|
||||
))}
|
||||
</>
|
||||
)}
|
||||
<div ref={messagesEndRef} />
|
||||
|
||||
@ -153,8 +153,14 @@ export function CodeViewer({
|
||||
const hasCfg = cfgBlocks.length > 0;
|
||||
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 (
|
||||
<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="flex items-center gap-3">
|
||||
<TabsList className="h-8">
|
||||
|
||||
@ -158,6 +158,13 @@ export function MutationSuggestionCard({
|
||||
if (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?.();
|
||||
dismissTimerRef.current = setTimeout(resolve, 1500);
|
||||
},
|
||||
@ -168,6 +175,13 @@ export function MutationSuggestionCard({
|
||||
mutation: {
|
||||
onSuccess: () => {
|
||||
setStatus("REJECTED");
|
||||
queryClient.invalidateQueries({
|
||||
predicate: (query) =>
|
||||
typeof query.queryKey[0] === "string" &&
|
||||
query.queryKey[0].startsWith("/binaries/") &&
|
||||
query.queryKey[0].endsWith("/mutations"),
|
||||
});
|
||||
onResolved?.(mutationId);
|
||||
onRenameApplied?.();
|
||||
dismissTimerRef.current = setTimeout(resolve, 1500);
|
||||
},
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user