Fix shell block styling in acme theme
- Add shell-block class with white bg, border, proper border-radius - Fix info message bubble width and margin for acme theme - Parse shell output correctly: first $ line is command, rest is output - Style shell output with proper padding and text color
This commit is contained in:
parent
0af153bf4f
commit
0ca3d38ec8
|
|
@ -240,11 +240,11 @@ export function ChatPane({ session, cfg, messages, onSend, onStop, browsePath, b
|
||||||
<div key={i} class={`message ${msg.role}`}>
|
<div key={i} class={`message ${msg.role}`}>
|
||||||
<div class="msg-bubble">
|
<div class="msg-bubble">
|
||||||
{msg.role === 'user' ? (
|
{msg.role === 'user' ? (
|
||||||
<div class="user-text"><span class="msg-prefix">u: </span>{msg.content}</div>
|
<div class="md" dangerouslySetInnerHTML={{ __html: linkifyPaths((marked.parse(msg.content) as string).replace(/^<p>/, '<p><span class="msg-prefix">u: </span>'), session.cwd) }} />
|
||||||
) : msg.role === 'tool' ? (
|
) : msg.role === 'tool' ? (
|
||||||
<ToolCall content={msg.content} />
|
<ToolCall content={msg.content} />
|
||||||
) : msg.role === 'info' ? (
|
) : msg.role === 'info' ? (
|
||||||
<div class="info-text">{msg.content}</div>
|
<InfoMessage content={msg.content} />
|
||||||
) : (
|
) : (
|
||||||
<div
|
<div
|
||||||
class="md"
|
class="md"
|
||||||
|
|
@ -349,21 +349,27 @@ function DiagramModal({ html, onClose }: { html: string; onClose: () => void })
|
||||||
|
|
||||||
function formatToolContent(raw: string): { name: string; detail: string; body: string; output: string } {
|
function formatToolContent(raw: string): { name: string; detail: string; body: string; output: string } {
|
||||||
// Call is always on the first line; output follows after the newline.
|
// Call is always on the first line; output follows after the newline.
|
||||||
const nl = raw.indexOf('\n')
|
const nl = raw.indexOf("\n")
|
||||||
const callLine = nl === -1 ? raw : raw.slice(0, nl)
|
const callLine = nl === -1 ? raw : raw.slice(0, nl)
|
||||||
const output = nl === -1 ? '' : raw.slice(nl + 1).trimEnd()
|
const output = nl === -1 ? "" : raw.slice(nl + 1).trimEnd()
|
||||||
const m = callLine.match(/^(\w+)\((.*)\)$/)
|
const m = callLine.match(/^(\w+)\((.*)\)$/)
|
||||||
if (!m) return { name: '', detail: '', body: raw, output: '' }
|
if (!m) return { name: "", detail: "", body: raw, output: "" }
|
||||||
const name = m[1]
|
const name = m[1]
|
||||||
const argsRaw = m[2]
|
const argsRaw = m[2]
|
||||||
try {
|
try {
|
||||||
const obj = JSON.parse(argsRaw)
|
const obj = JSON.parse(argsRaw)
|
||||||
let detail = ''
|
let detail = ""
|
||||||
if (obj.steps?.[0]?.tool) detail = obj.steps[0].tool
|
if (name === "call_tool") {
|
||||||
else if (obj.steps?.[0]?.code) detail = obj.language ?? 'bash'
|
detail = obj.calls?.[0]?.tool ?? ""
|
||||||
|
} else if (name === "pipe") {
|
||||||
|
const s = obj.stages?.[0]
|
||||||
|
detail = s?.tool ?? s?.language ?? "bash"
|
||||||
|
} else {
|
||||||
|
detail = obj.steps?.[0]?.language ?? "bash"
|
||||||
|
}
|
||||||
return { name, detail, body: JSON.stringify(obj, null, 2), output }
|
return { name, detail, body: JSON.stringify(obj, null, 2), output }
|
||||||
} catch {
|
} catch {
|
||||||
return { name, detail: '', body: argsRaw, output }
|
return { name, detail: "", body: argsRaw, output }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -383,12 +389,37 @@ const TOOL_ICONS: Record<string, string> = {
|
||||||
task_check: '☑️',
|
task_check: '☑️',
|
||||||
task_clear: '☑️',
|
task_clear: '☑️',
|
||||||
bash: '🖥️',
|
bash: '🖥️',
|
||||||
|
python3: '🐍',
|
||||||
|
perl: '🐪',
|
||||||
|
lua: '🌙',
|
||||||
|
awk: '🔧',
|
||||||
|
sed: '🔧',
|
||||||
|
jq: '🔧',
|
||||||
|
bc: '🔢',
|
||||||
}
|
}
|
||||||
|
|
||||||
function toolIcon(detail: string): string {
|
function toolIcon(detail: string): string {
|
||||||
return TOOL_ICONS[detail] ?? '🖥️'
|
return TOOL_ICONS[detail] ?? '🖥️'
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function InfoMessage({ content }: { content: string }) {
|
||||||
|
const lines = content.split('\n')
|
||||||
|
const shellLines = lines.filter(l => l.startsWith('$ ')).map(l => l.slice(2))
|
||||||
|
if (shellLines.length === 0) return <div class="info-text">{content}</div>
|
||||||
|
const cmd = shellLines[0]
|
||||||
|
const output = shellLines.slice(1).join('\n')
|
||||||
|
return (
|
||||||
|
<div class="tool-call shell-block">
|
||||||
|
<div class="tool-header">
|
||||||
|
<span class="tool-arrow">🖥️</span>
|
||||||
|
<span class="tool-name">shell</span>
|
||||||
|
<span class="tool-detail">{cmd}</span>
|
||||||
|
</div>
|
||||||
|
<pre class="tool-output">{output || '(no output)'}</pre>
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
function ToolCall({ content }: { content: string }) {
|
function ToolCall({ content }: { content: string }) {
|
||||||
const { name, detail, body, output } = formatToolContent(content)
|
const { name, detail, body, output } = formatToolContent(content)
|
||||||
const [open, setOpen] = useState(false)
|
const [open, setOpen] = useState(false)
|
||||||
|
|
|
||||||
|
|
@ -797,6 +797,28 @@ details[open] > .tool-header::before {
|
||||||
[data-theme="acme"] .tool-body {
|
[data-theme="acme"] .tool-body {
|
||||||
padding: 4px 10px;
|
padding: 4px 10px;
|
||||||
}
|
}
|
||||||
|
[data-theme="acme"] .message.info .msg-bubble {
|
||||||
|
width: 100%;
|
||||||
|
padding: 0;
|
||||||
|
margin: 4px 0;
|
||||||
|
}
|
||||||
|
[data-theme="acme"] .shell-block {
|
||||||
|
background: #fff;
|
||||||
|
border: 1px solid var(--border);
|
||||||
|
border-radius: var(--r-sm);
|
||||||
|
}
|
||||||
|
[data-theme="acme"] .shell-block .tool-header {
|
||||||
|
cursor: default;
|
||||||
|
}
|
||||||
|
[data-theme="acme"] .shell-block .tool-header::before {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
[data-theme="acme"] .shell-block .tool-output {
|
||||||
|
display: block;
|
||||||
|
color: var(--text);
|
||||||
|
font-style: normal;
|
||||||
|
padding: 8px 12px;
|
||||||
|
}
|
||||||
[data-theme="acme"] .tool-header {
|
[data-theme="acme"] .tool-header {
|
||||||
padding: 4px 10px;
|
padding: 4px 10px;
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -3,6 +3,7 @@ import preact from '@preact/preset-vite'
|
||||||
|
|
||||||
export default defineConfig({
|
export default defineConfig({
|
||||||
plugins: [preact()],
|
plugins: [preact()],
|
||||||
|
base: './',
|
||||||
server: {
|
server: {
|
||||||
proxy: {
|
proxy: {
|
||||||
'/s/': 'http://localhost:8080',
|
'/s/': 'http://localhost:8080',
|
||||||
|
|
|
||||||
Reference in New Issue