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 ``
- })
-
- 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 }
})