diff --git a/internal/version/version.go b/internal/version/version.go index 3040116..6466ce3 100644 --- a/internal/version/version.go +++ b/internal/version/version.go @@ -7,7 +7,7 @@ import ( const ( Name = "muyue" - Version = "0.9.2" + Version = "0.9.3" Author = "La Légion de Muyue" ) diff --git a/web/src/components/Studio.jsx b/web/src/components/Studio.jsx index 6390d9f..bbdbd79 100644 --- a/web/src/components/Studio.jsx +++ b/web/src/components/Studio.jsx @@ -1,6 +1,4 @@ -import { useState, useRef, useEffect, useCallback, useMemo } from 'react' -import { useI18n } from '../i18n' -import mermaid from 'mermaid' +import { useState, useRef, useEffect, useCallback } from 'react' import ReactMarkdown from 'react-markdown' import remarkGfm from 'remark-gfm' import remarkMath from 'remark-math' @@ -9,8 +7,6 @@ import rehypeHighlight from 'rehype-highlight' import 'katex/dist/katex.min.css' import 'highlight.js/styles/github-dark.css' -mermaid.initialize({ startOnLoad: false, theme: 'dark', securityLevel: 'loose', fontFamily: 'var(--font-mono)' }) - const RANKS = { commandant: { label: 'Commandant', short: 'CDT', color: '#FFD740' }, general: { label: 'General', short: 'GEN', color: '#FF9100' }, @@ -40,72 +36,6 @@ function RankIcon({ rank }) { ) } -function renderContent(text) { - const parts = [] - const codeBlockRegex = /(```[\s\S]*?```)/g - let match - let lastIndex = 0 - while ((match = codeBlockRegex.exec(text)) !== null) { - if (match.index > lastIndex) { - parts.push({ type: 'text', content: text.slice(lastIndex, match.index) }) - } - const full = match[1] - const firstNewline = full.indexOf('\n') - const lang = firstNewline > -1 ? full.slice(3, firstNewline).trim() : '' - const code = firstNewline > -1 ? full.slice(firstNewline + 1, -3) : full.slice(3, -3) - parts.push({ type: 'code', lang, content: code }) - lastIndex = match.index + full.length - } - if (lastIndex < text.length) { - const remaining = text.slice(lastIndex) - const openBlock = remaining.match(/```(\w*)\n?([\s\S]*)$/) - if (openBlock) { - if (openBlock.index > 0) { - parts.push({ type: 'text', content: remaining.slice(0, openBlock.index) }) - } - parts.push({ type: 'code', lang: openBlock[1] || '', content: openBlock[2] || '' }) - } else { - parts.push({ type: 'text', content: remaining }) - } - } - return parts -} - -function formatText(text) { - let html = text - .replace(/&/g, '&').replace(//g, '>') - - html = html.replace(/^(\|.+\|)\n(\|[\s\-:|]+\|)\n((?:\|.+\|\n?)+)/gm, (match, headerRow, sepRow, bodyRows) => { - const headers = headerRow.split('|').filter(c => c.trim() !== '').map(c => `${c.trim()}`).join('') - const rows = bodyRows.trim().split('\n').map(row => { - const cells = row.split('|').filter(c => c.trim() !== '').map(c => `${c.trim()}`).join('') - return `${cells}` - }).join('') - return `${headers}${rows}
` - }) - - html = html - .replace(/\*\*(.+?)\*\*/g, '$1') - .replace(/`([^`]+)`/g, '$1') - .replace(/^### (.+)$/gm, '

$1

') - .replace(/^## (.+)$/gm, '

$1

') - .replace(/^# (.+)$/gm, '

$1

') - .replace(/^---+$/gm, '
') - .replace(/^\s*[-*] (.+)$/gm, '
\u2022 $1
') - .replace(/^\s*(\d+)[.)] (.+)$/gm, '
$1 $2
') - .replace(/\n/g, '
') - - html = html - .replace(/\s*/g, '
') - .replace(/\s*( @@ -219,64 +149,6 @@ function ToolCallBlock({ call, result, activeAgents, onModeChange }) { ) } -let mermaidIdCounter = 0 - -function MermaidBlock({ code }) { - const ref = useRef(null) - const [svg, setSvg] = useState('') - const [error, setError] = useState(false) - - useEffect(() => { - let cancelled = false - const id = `studio-mermaid-${++mermaidIdCounter}` - mermaid.render(id, code).then(({ svg }) => { - if (!cancelled) setSvg(svg) - }).catch(() => { - if (!cancelled) setError(true) - }) - return () => { cancelled = true } - }, [code]) - - if (error) return
{code}
- if (!svg) return
Chargement...
- return
-} - -function CodeBlockWithCopy({ part, index, copiedIdx, setCopiedIdx }) { - if (part.lang === 'mermaid') { - return ( -
-
- mermaid - -
- -
- ) - } - return ( -
-
- {part.lang && {part.lang}} - -
-
{part.content}
-
- ) -} - function MarkdownContent({ content, raw }) { if (raw) { return
{content}
@@ -292,7 +164,7 @@ function FeedItem({ msg, activeAgents, onModeChange }) { const isUser = msg.role === 'user' const isSystem = msg.role === 'system' const rank = getRank(msg.role) - const [copiedIdx, setCopiedIdx] = useState(null) + const [copiedMsg, setCopiedMsg] = useState(false) const timeStr = msg.time ? new Date(msg.time).toLocaleTimeString([], { hour: '2-digit', minute: '2-digit' }) : '' @@ -365,21 +237,8 @@ function FeedItem({ msg, activeAgents, onModeChange }) { )} {parsedSegments && parsedSegments.some(s => s.type === 'tool') ? ( (() => { - const toolSegs = parsedSegments.filter(s => s.type === 'tool') - const compress = collapseHistory && !forceExpand && toolSegs.length > 1 - const lastTool = toolSegs.length > 0 ? toolSegs[toolSegs.length - 1] : null return ( <> - {compress && ( -
- … {toolSegs.length - 1} action{toolSegs.length - 1 > 1 ? 's' : ''} précédente{toolSegs.length - 1 > 1 ? 's' : ''} masquée{toolSegs.length - 1 > 1 ? 's' : ''} - -
- )} {parsedSegments.map((seg, i) => { if (seg.type === 'text') { if (!seg.content) return null @@ -406,21 +265,9 @@ function FeedItem({ msg, activeAgents, onModeChange }) { ) : ( <> {parsedToolCalls && (() => { - const compress = collapseHistory && !forceExpand && parsedToolCalls.length > 1 - const items = compress ? parsedToolCalls.slice(-1) : parsedToolCalls return ( <> - {compress && ( -
- … {parsedToolCalls.length - 1} action{parsedToolCalls.length - 1 > 1 ? 's' : ''} précédente{parsedToolCalls.length - 1 > 1 ? 's' : ''} masquée{parsedToolCalls.length - 1 > 1 ? 's' : ''} - -
- )} - {items.map((tc, i) => { + {parsedToolCalls.map((tc, i) => { const resultData = parsedToolResults ? parsedToolResults.find(r => r.tool_call_id === tc.tool_call_id) : null @@ -448,17 +295,6 @@ function StreamingItem({ content, thinking, toolCalls, segments, activeAgents, o const rank = RANKS.general const cleanContent = content.replace(/]*>[\s\S]*?<\/think>/gi, '') const hasToolCalls = toolCalls && toolCalls.length > 0 - const [copiedIdx, setCopiedIdx] = useState(null) - - const renderedContent = useMemo(() => { - if (!cleanContent) return null - return null - }, [cleanContent]) - - const formattedThinking = useMemo(() => { - if (!thinking) return '' - return thinking - }, [thinking]) const hasOrderedSegments = segments && segments.some(s => s.type === 'tool') @@ -477,43 +313,27 @@ function StreamingItem({ content, thinking, toolCalls, segments, activeAgents, o {thinking && } {hasOrderedSegments ? ( <> - {compress && ( -
- … {toolSegments.length - 1} action{toolSegments.length - 1 > 1 ? 's' : ''} précédente{toolSegments.length - 1 > 1 ? 's' : ''} masquée{toolSegments.length - 1 > 1 ? 's' : ''} (mode compressé) - -
- )} - {(() => { - const lastToolId = toolSegments.length > 0 ? toolSegments[toolSegments.length - 1] : null - return segments.map((seg, i) => { - if (seg.type === 'text') { - if (!seg.content) return null - return ( -
- -
- ) - } - if (seg.type === 'tool') { - if (compress && seg !== lastToolId) return null - return - } - return null - }) - })()} + {segments.map((seg, i) => { + if (seg.type === 'text') { + if (!seg.content) return null + return ( +
+ +
+ ) + } + if (seg.type === 'tool') { + return + } + return null + })} ) : ( <> - {hasToolCalls && (compress - ? [] - : toolCalls.map((tc, i) => ( + {hasToolCalls && toolCalls.map((tc, i) => ( )) - )} + } {cleanContent && (
@@ -551,7 +371,6 @@ export default function Studio({ api }) { const [sudoModal, setSudoModal] = useState(null) const [attachedImages, setAttachedImages] = useState([]) const [activeAgents, setActiveAgents] = useState({ crush: 0, claude: 0 }) - const [toolModes, setToolModes] = useState({}) const [advancedReflection, setAdvancedReflection] = useState(() => { try { return localStorage.getItem('muyue.advancedReflection') === 'true' } catch { return false } })