diff --git a/.github/ISSUE_TEMPLATE/bug_report.yml b/.github/ISSUE_TEMPLATE/bug_report.yml index feaac640..6a62a81c 100644 --- a/.github/ISSUE_TEMPLATE/bug_report.yml +++ b/.github/ISSUE_TEMPLATE/bug_report.yml @@ -6,7 +6,7 @@ body: - type: textarea id: what attributes: - label: What’s wrong? Maybe some steps to reproduce. + label: What's wrong? Maybe some steps to reproduce. description: What happened and what you expected. placeholder: "Expected … but got …" validations: diff --git a/CHANGELOG.md b/CHANGELOG.md index cfb5a18b..6ed200c8 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,6 +4,7 @@ All notable changes to this project will be documented in this file. ## [Unreleased] +- Added write/create tool preview in permission cards with syntax highlighting ## [1.2.5] - 2025-12-19 diff --git a/packages/ui/src/components/chat/DiffPreview.tsx b/packages/ui/src/components/chat/DiffPreview.tsx new file mode 100644 index 00000000..0d04d152 --- /dev/null +++ b/packages/ui/src/components/chat/DiffPreview.tsx @@ -0,0 +1,131 @@ +import React from 'react'; +import { Prism as SyntaxHighlighter } from 'react-syntax-highlighter'; +import { cn } from '@/lib/utils'; +import { getLanguageFromExtension } from '@/lib/toolHelpers'; +import { parseDiffToUnified } from './message/toolRenderers'; + +interface DiffPreviewProps { + diff: string; + syntaxTheme: { [key: string]: React.CSSProperties }; + filePath?: string; +} + +export const DiffPreview: React.FC = ({ diff, syntaxTheme, filePath }) => ( +
+ {parseDiffToUnified(diff).map((hunk, hunkIdx) => ( +
+
+ {`${hunk.file || filePath?.split('/').pop() || 'file'} (line ${hunk.oldStart})`} +
+ +
+ {hunk.lines.map((line, lineIdx) => ( +
+ + {line.lineNumber || ''} + +
+ + {line.content} + +
+
+ ))} +
+
+ ))} +
+); + +interface WritePreviewProps { + content: string; + syntaxTheme: { [key: string]: React.CSSProperties }; + filePath?: string; +} + +export const WritePreview: React.FC = ({ content, syntaxTheme, filePath }) => { + const lines = content.split('\n'); + const language = getLanguageFromExtension(filePath ?? '') || 'text'; + const displayPath = filePath?.split('/').pop() || 'New file'; + const lineCount = Math.max(lines.length, 1); + const headerLineLabel = lineCount === 1 ? 'line 1' : `lines 1-${lineCount}`; + + return ( +
+
+ {`${displayPath} (${headerLineLabel})`} +
+
+ {lines.map((line, lineIdx) => ( +
+ + {lineIdx + 1} + +
+ + {line || ' '} + +
+
+ ))} +
+
+ ); +}; diff --git a/packages/ui/src/components/chat/PermissionCard.tsx b/packages/ui/src/components/chat/PermissionCard.tsx index c9884a3d..c882f48f 100644 --- a/packages/ui/src/components/chat/PermissionCard.tsx +++ b/packages/ui/src/components/chat/PermissionCard.tsx @@ -1,5 +1,5 @@ import React from 'react'; -import { RiCheckLine, RiCloseLine, RiGlobalLine, RiPencilAiLine, RiQuestionLine, RiTerminalBoxLine, RiTimeLine, RiToolsLine } from '@remixicon/react'; +import { RiCheckLine, RiCloseLine, RiFileEditLine, RiGlobalLine, RiPencilAiLine, RiQuestionLine, RiTerminalBoxLine, RiTimeLine, RiToolsLine } from '@remixicon/react'; import { cn } from '@/lib/utils'; import type { Permission, PermissionResponse } from '@/types/permission'; import { useSessionStore } from '@/stores/useSessionStore'; @@ -7,6 +7,7 @@ import { Prism as SyntaxHighlighter } from 'react-syntax-highlighter'; import { useThemeSystem } from '@/contexts/useThemeSystem'; import { generateSyntaxTheme } from '@/lib/theme/syntaxThemeGenerator'; import { ScrollableOverlay } from '@/components/ui/ScrollableOverlay'; +import { DiffPreview, WritePreview } from './DiffPreview'; interface PermissionCardProps { permission: Permission; @@ -21,6 +22,10 @@ const getToolIcon = (toolName: string) => { return ; } + if (tool === 'write' || tool === 'create' || tool === 'file_write') { + return ; + } + if (tool === 'bash' || tool === 'shell' || tool === 'cmd' || tool === 'terminal' || tool === 'shell_command') { return ; } @@ -38,6 +43,9 @@ const getToolDisplayName = (toolName: string): string => { if (tool === 'edit' || tool === 'multiedit' || tool === 'str_replace' || tool === 'str_replace_based_edit_tool') { return 'edit'; } + if (tool === 'write' || tool === 'create' || tool === 'file_write') { + return 'write'; + } if (tool === 'bash' || tool === 'shell' || tool === 'cmd' || tool === 'terminal' || tool === 'shell_command') { return 'bash'; } @@ -154,75 +162,40 @@ export const PermissionCard: React.FC = ({ if (tool === 'edit' || tool === 'multiedit' || tool === 'str_replace' || tool === 'str_replace_based_edit_tool') { const filePath = getMeta('path') || getMeta('file_path') || getMeta('filename') || getMeta('filePath'); - const oldContent = getMeta('old_str') || getMeta('oldString') || getMeta('old_content') || getMeta('before'); - const newContent = getMeta('new_str') || getMeta('newString') || getMeta('new_content') || getMeta('after'); const changes = getMeta('changes') || getMeta('diff'); const replaceAll = getMetaBool('replace_all') || getMetaBool('replaceAll'); return ( <> - {filePath && ( -
-
File Path:
- - {filePath} - -
- )} {replaceAll && (
⚠️ Replace All Occurrences
)} - {changes ? ( -
-
Changes:
- - - {changes} - - -
- ) : ( - <> - {oldContent && ( -
-
Remove:
- -
-                  {oldContent.length > 500 ? oldContent.substring(0, 500) + '...' : oldContent}
-                
-
-
- )} - {newContent && ( -
-
Replace with:
- -
-                      {newContent.length > 500 ? newContent.substring(0, 500) + '...' : newContent}
-                    
-
-
- )} - + {changes && ( + + + )} ); } + if (tool === 'write' || tool === 'create' || tool === 'file_write') { + const filePath = getMeta('path') || getMeta('file_path') || getMeta('filename') || getMeta('filePath'); + const content = getMeta('content') || getMeta('text') || getMeta('data'); + + if (content) { + return ( + + + + ); + } + + return null; + } + if (tool === 'webfetch' || tool === 'fetch' || tool === 'curl' || tool === 'wget') { const url = getMeta('url') || getMeta('uri') || getMeta('endpoint'); const method = getMeta('method') || 'GET'; @@ -369,7 +342,7 @@ export const PermissionCard: React.FC = ({ shouldHighlight = true; } - else if (tool === 'edit' || tool === 'multiedit' || tool === 'str_replace' || tool === 'str_replace_based_edit_tool') { + else if (tool === 'edit' || tool === 'multiedit' || tool === 'str_replace' || tool === 'str_replace_based_edit_tool' || tool === 'write' || tool === 'create' || tool === 'file_write') { primaryContent = getMeta('path') || getMeta('file_path') || getMeta('filename') || getMeta('filePath'); shouldHighlight = false; }