mirror of
https://github.com/jlengrand/Maestro.git
synced 2026-03-10 08:31:19 +00:00
# CHANGES
- Updated app icons with fresh new design aesthetic! 🎨 - Added markdown rendering support for group chat messages 📝 - Implemented toggle between formatted and plain text views 👁️ - Added copy-to-clipboard functionality for chat messages 📋 - Improved phase generation logic for better document detection 🔍 - Enhanced disk document validation with task counting checks ✅ - Fixed Claude Code file output handling in wizard mode 🛠️ - Added keyboard shortcut (⌘E) for markdown view toggle ⌨️ - Improved UI with hover effects on message action buttons ✨ - Better handling of documents written directly to disk 💾
This commit is contained in:
BIN
build/icon-original-square.png
Normal file
BIN
build/icon-original-square.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 1006 KiB |
BIN
build/icon.icns
BIN
build/icon.icns
Binary file not shown.
BIN
build/icon.png
BIN
build/icon.png
Binary file not shown.
|
Before Width: | Height: | Size: 1006 KiB After Width: | Height: | Size: 1.0 MiB |
@@ -5567,6 +5567,8 @@ export default function MaestroConsole() {
|
||||
executionQueue={groupChatExecutionQueue}
|
||||
onRemoveQueuedItem={handleRemoveGroupChatQueueItem}
|
||||
onReorderQueuedItems={handleReorderGroupChatQueueItems}
|
||||
markdownEditMode={markdownEditMode}
|
||||
onToggleMarkdownEditMode={() => setMarkdownEditMode(!markdownEditMode)}
|
||||
/>
|
||||
</div>
|
||||
<GroupChatParticipants
|
||||
|
||||
@@ -6,15 +6,19 @@
|
||||
* align left with colored borders. Includes auto-scroll and typing indicator.
|
||||
*/
|
||||
|
||||
import { useRef, useEffect } from 'react';
|
||||
import { BookOpen } from 'lucide-react';
|
||||
import { useRef, useEffect, useCallback } from 'react';
|
||||
import { BookOpen, Eye, FileText, Copy } from 'lucide-react';
|
||||
import type { GroupChatMessage, GroupChatParticipant, GroupChatState, Theme } from '../types';
|
||||
import { MarkdownRenderer } from './MarkdownRenderer';
|
||||
import { stripMarkdown } from '../utils/textProcessing';
|
||||
|
||||
interface GroupChatMessagesProps {
|
||||
theme: Theme;
|
||||
messages: GroupChatMessage[];
|
||||
participants: GroupChatParticipant[];
|
||||
state: GroupChatState;
|
||||
markdownEditMode?: boolean;
|
||||
onToggleMarkdownEditMode?: () => void;
|
||||
}
|
||||
|
||||
// Color mapping for participants
|
||||
@@ -32,9 +36,15 @@ export function GroupChatMessages({
|
||||
messages,
|
||||
participants,
|
||||
state,
|
||||
markdownEditMode,
|
||||
onToggleMarkdownEditMode,
|
||||
}: GroupChatMessagesProps): JSX.Element {
|
||||
const containerRef = useRef<HTMLDivElement>(null);
|
||||
|
||||
const copyToClipboard = useCallback((text: string) => {
|
||||
navigator.clipboard.writeText(text);
|
||||
}, []);
|
||||
|
||||
// Auto-scroll on new messages
|
||||
useEffect(() => {
|
||||
if (containerRef.current) {
|
||||
@@ -102,7 +112,7 @@ export function GroupChatMessages({
|
||||
className={`flex ${isUser ? 'justify-end' : 'justify-start'}`}
|
||||
>
|
||||
<div
|
||||
className="max-w-[70%] rounded-lg px-4 py-2"
|
||||
className="group relative max-w-[70%] rounded-lg px-4 py-2"
|
||||
style={{
|
||||
backgroundColor: style.bgColor,
|
||||
color: style.textColor,
|
||||
@@ -120,9 +130,19 @@ export function GroupChatMessages({
|
||||
)}
|
||||
|
||||
{/* Message content */}
|
||||
<div className="text-sm whitespace-pre-wrap">
|
||||
{msg.content}
|
||||
</div>
|
||||
{!isUser && !markdownEditMode ? (
|
||||
<div className="text-sm">
|
||||
<MarkdownRenderer
|
||||
content={msg.content}
|
||||
theme={theme}
|
||||
onCopy={copyToClipboard}
|
||||
/>
|
||||
</div>
|
||||
) : (
|
||||
<div className="text-sm whitespace-pre-wrap">
|
||||
{isUser ? msg.content : stripMarkdown(msg.content)}
|
||||
</div>
|
||||
)}
|
||||
|
||||
{/* Timestamp and read-only indicator */}
|
||||
<div className="flex items-center gap-2 mt-1">
|
||||
@@ -143,6 +163,35 @@ export function GroupChatMessages({
|
||||
</span>
|
||||
)}
|
||||
</div>
|
||||
|
||||
{/* Action buttons - bottom right corner (non-user messages only) */}
|
||||
{!isUser && (
|
||||
<div
|
||||
className="absolute bottom-2 right-2 flex items-center gap-1"
|
||||
style={{ transition: 'opacity 0.15s ease-in-out' }}
|
||||
>
|
||||
{/* Markdown toggle button */}
|
||||
{onToggleMarkdownEditMode && (
|
||||
<button
|
||||
onClick={onToggleMarkdownEditMode}
|
||||
className="p-1.5 rounded opacity-0 group-hover:opacity-50 hover:!opacity-100"
|
||||
style={{ color: markdownEditMode ? theme.colors.accent : theme.colors.textDim }}
|
||||
title={markdownEditMode ? "Show formatted (⌘E)" : "Show plain text (⌘E)"}
|
||||
>
|
||||
{markdownEditMode ? <Eye className="w-4 h-4" /> : <FileText className="w-4 h-4" />}
|
||||
</button>
|
||||
)}
|
||||
{/* Copy to Clipboard Button */}
|
||||
<button
|
||||
onClick={() => copyToClipboard(msg.content)}
|
||||
className="p-1.5 rounded opacity-0 group-hover:opacity-50 hover:!opacity-100"
|
||||
style={{ color: theme.colors.textDim }}
|
||||
title="Copy to clipboard"
|
||||
>
|
||||
<Copy className="w-3.5 h-3.5" />
|
||||
</button>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
|
||||
@@ -41,6 +41,9 @@ interface GroupChatPanelProps {
|
||||
executionQueue?: QueuedItem[];
|
||||
onRemoveQueuedItem?: (itemId: string) => void;
|
||||
onReorderQueuedItems?: (fromIndex: number, toIndex: number) => void;
|
||||
// Markdown toggle (Cmd+E)
|
||||
markdownEditMode?: boolean;
|
||||
onToggleMarkdownEditMode?: () => void;
|
||||
}
|
||||
|
||||
export function GroupChatPanel({
|
||||
@@ -68,6 +71,8 @@ export function GroupChatPanel({
|
||||
executionQueue,
|
||||
onRemoveQueuedItem,
|
||||
onReorderQueuedItems,
|
||||
markdownEditMode,
|
||||
onToggleMarkdownEditMode,
|
||||
}: GroupChatPanelProps): JSX.Element {
|
||||
return (
|
||||
<div
|
||||
@@ -91,6 +96,8 @@ export function GroupChatPanel({
|
||||
messages={messages}
|
||||
participants={groupChat.participants}
|
||||
state={state}
|
||||
markdownEditMode={markdownEditMode}
|
||||
onToggleMarkdownEditMode={onToggleMarkdownEditMode}
|
||||
/>
|
||||
|
||||
<GroupChatInput
|
||||
|
||||
@@ -436,6 +436,7 @@ class PhaseGenerator {
|
||||
|
||||
const rawOutput = result.rawOutput || '';
|
||||
let documents = parseGeneratedDocuments(rawOutput);
|
||||
let documentsFromDisk = false;
|
||||
|
||||
// If no documents parsed with markers, try splitting intelligently
|
||||
if (documents.length === 0 && rawOutput.trim()) {
|
||||
@@ -443,16 +444,28 @@ class PhaseGenerator {
|
||||
documents = splitIntoPhases(rawOutput);
|
||||
}
|
||||
|
||||
// If still no documents, check if files were written directly to disk
|
||||
// (Claude Code may write files directly instead of outputting with markers)
|
||||
let documentsFromDisk = false;
|
||||
if (documents.length === 0) {
|
||||
// Validate that parsed documents contain actual tasks
|
||||
// If the agent wrote files directly to disk (Claude Code's normal behavior),
|
||||
// the rawOutput won't contain document content, just status messages.
|
||||
// splitIntoPhases would create a single document from that status text,
|
||||
// which wouldn't contain any valid tasks.
|
||||
const totalTasksFromParsed = documents.reduce((sum, doc) => sum + countTasks(doc.content), 0);
|
||||
const hasValidParsedDocs = documents.length > 0 && totalTasksFromParsed > 0;
|
||||
|
||||
// Check for files on disk if:
|
||||
// 1. No documents were parsed at all, OR
|
||||
// 2. Parsed documents don't contain valid tasks (likely just status output)
|
||||
if (!hasValidParsedDocs) {
|
||||
callbacks?.onProgress?.('Checking for documents on disk...');
|
||||
const diskDocs = await this.readDocumentsFromDisk(config.directoryPath);
|
||||
if (diskDocs.length > 0) {
|
||||
console.log('[PhaseGenerator] Found documents on disk:', diskDocs.length);
|
||||
documents = diskDocs;
|
||||
documentsFromDisk = true;
|
||||
// Prefer disk documents if they have more content/tasks
|
||||
const totalTasksFromDisk = diskDocs.reduce((sum, doc) => sum + countTasks(doc.content), 0);
|
||||
if (totalTasksFromDisk >= totalTasksFromParsed) {
|
||||
documents = diskDocs;
|
||||
documentsFromDisk = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user