Compact dashboard room progress UI

Compact the Rooms progress panel into a Discord-like current progress strip, show only the latest output by default, and move history/compose controls behind details.
This commit is contained in:
Eyejoker
2026-04-27 12:06:47 +09:00
committed by GitHub
parent d28068f646
commit a35433b454
3 changed files with 238 additions and 134 deletions

View File

@@ -1315,103 +1315,138 @@ function RoomActivityPanel({
const task = activity.pairedTask;
const turn = task?.currentTurn ?? null;
const outputs = task?.outputs ?? [];
const latestOutput = outputs.at(-1) ?? null;
const progressText = turn
? `${turn.role} · ${turn.intentKind} · ${t.rooms.attempt} ${turn.attemptNo}`
: t.rooms.noTurn;
return (
<div className="room-activity">
<div className="room-activity-grid">
<span>
<small>{t.rooms.task}</small>
<strong>{task?.title || task?.id || t.rooms.noTask}</strong>
</span>
<span>
<small>{t.rooms.currentTurn}</small>
<strong>
{turn
? `${turn.role} · ${t.rooms.attempt} ${turn.attemptNo}`
: t.rooms.noTurn}
</strong>
</span>
<span>
<small>{t.rooms.round}</small>
<strong>{task ? task.roundTripCount : '-'}</strong>
</span>
<span>
<small>{t.rooms.updated}</small>
<strong>
{formatDate(turn?.updatedAt ?? task?.updatedAt, locale)}
</strong>
</span>
</div>
{turn ? (
<div className="room-turn-line">
<div className="room-progress-strip">
{turn ? (
<span className={`pill pill-${turn.state}`}>
{statusLabel(turn.state, t)}
</span>
<span>{turn.intentKind}</span>
{turn.executorServiceId ? (
<span>{turn.executorServiceId}</span>
) : null}
{turn.activeRunId ? (
<span className="mono-chip">{turn.activeRunId}</span>
) : null}
</div>
) : null}
{turn?.lastError ? (
<p className="room-activity-error">{turn.lastError}</p>
) : null}
<div className="room-activity-columns">
<section>
<strong>{t.rooms.output}</strong>
{outputs.length === 0 ? (
<p className="room-muted">{t.rooms.noOutput}</p>
) : (
<div className="room-output-list">
{[...outputs].reverse().map((output) => (
<article key={output.id} className="room-output-item">
<header>
<span>
#{output.turnNumber} · {output.role}
{output.verdict ? ` · ${output.verdict}` : ''}
</span>
<time>{formatDate(output.createdAt, locale)}</time>
</header>
<p>{output.outputText}</p>
</article>
))}
</div>
)}
</section>
<section>
<strong>{t.rooms.recentMessages}</strong>
{activity.messages.length === 0 ? (
<p className="room-muted">{t.rooms.noMessages}</p>
) : (
<div className="room-message-list">
{activity.messages.map((message) => (
<article
className={`room-message-item ${
message.isBotMessage ? 'room-message-bot' : ''
}`}
key={message.id}
>
<header>
<span>{message.senderName}</span>
<time>{formatDate(message.timestamp, locale)}</time>
</header>
<p>{message.content}</p>
</article>
))}
</div>
)}
</section>
) : (
<span className="pill pill-inactive">{t.rooms.noTurn}</span>
)}
<strong>{progressText}</strong>
<span>{task ? `${t.rooms.round} ${task.roundTripCount}` : '-'}</span>
{turn?.executorServiceId ? <span>{turn.executorServiceId}</span> : null}
<time>{formatDate(turn?.updatedAt ?? task?.updatedAt, locale)}</time>
</div>
<section className="room-latest-output">
<header>
<strong>{t.rooms.latestOutput}</strong>
{latestOutput ? (
<span>
#{latestOutput.turnNumber} · {latestOutput.role}
{latestOutput.verdict ? ` · ${latestOutput.verdict}` : ''}
</span>
) : null}
</header>
{latestOutput ? (
<p>{latestOutput.outputText}</p>
) : (
<p className="room-muted">{t.rooms.noOutput}</p>
)}
</section>
<details className="room-activity-details">
<summary>{t.rooms.details}</summary>
<div className="room-detail-grid">
<section>
<strong>{t.rooms.task}</strong>
<p className="room-muted">
{task?.title || task?.id || t.rooms.noTask}
</p>
<div className="room-turn-line">
{turn?.activeRunId ? (
<span className="mono-chip">{turn.activeRunId}</span>
) : null}
{turn?.lastError ? (
<span className="room-activity-error">{turn.lastError}</span>
) : null}
</div>
</section>
<section>
<strong>{t.rooms.outputHistory}</strong>
{outputs.length === 0 ? (
<p className="room-muted">{t.rooms.noOutput}</p>
) : (
<div className="room-output-list">
{[...outputs].reverse().map((output) => (
<article key={output.id} className="room-output-item">
<header>
<span>
#{output.turnNumber} · {output.role}
{output.verdict ? ` · ${output.verdict}` : ''}
</span>
<time>{formatDate(output.createdAt, locale)}</time>
</header>
<p>{output.outputText}</p>
</article>
))}
</div>
)}
</section>
<section>
<strong>{t.rooms.messageHistory}</strong>
{activity.messages.length === 0 ? (
<p className="room-muted">{t.rooms.noMessages}</p>
) : (
<div className="room-message-list">
{activity.messages.map((message) => (
<article
className={`room-message-item ${
message.isBotMessage ? 'room-message-bot' : ''
}`}
key={message.id}
>
<header>
<span>{message.senderName}</span>
<time>{formatDate(message.timestamp, locale)}</time>
</header>
<p>{message.content}</p>
</article>
))}
</div>
)}
</section>
</div>
</details>
</div>
);
}
function RoomComposeDetails({
busy,
onChange,
onSubmit,
t,
value,
}: {
busy: boolean;
onChange: (value: string) => void;
onSubmit: () => void;
t: Messages;
value: string;
}) {
return (
<details className="room-compose-details">
<summary>{t.rooms.message}</summary>
<RoomMessageForm
busy={busy}
onChange={onChange}
onSubmit={onSubmit}
t={t}
value={value}
/>
</details>
);
}
function RoomPanel({
onSendRoomMessage,
roomActivity,
@@ -1502,8 +1537,8 @@ function RoomPanel({
t={t}
/>
</td>
<td>
<RoomMessageForm
<td className="room-message-cell">
<RoomComposeDetails
busy={roomMessageKey === entry.jid}
onChange={(value) => setDraft(entry.jid, value)}
onSubmit={() => void submitRoomMessage(entry.jid)}
@@ -1550,7 +1585,7 @@ function RoomPanel({
locale={locale}
t={t}
/>
<RoomMessageForm
<RoomComposeDetails
busy={roomMessageKey === entry.jid}
onChange={(value) => setDraft(entry.jid, value)}
onSubmit={() => void submitRoomMessage(entry.jid)}

View File

@@ -158,8 +158,11 @@ export interface Messages {
attempt: string;
updated: string;
output: string;
latestOutput: string;
outputHistory: string;
noOutput: string;
recentMessages: string;
messageHistory: string;
noMessages: string;
details: string;
message: string;
@@ -446,8 +449,11 @@ export const messages = {
attempt: '시도',
updated: '갱신',
output: '출력',
latestOutput: '마지막 출력',
outputHistory: '출력 기록',
noOutput: '출력 없음',
recentMessages: '최근 메시지',
recentMessages: '메시지 기록',
messageHistory: '메시지 기록',
noMessages: '메시지 없음',
details: '세부',
message: 'Message',
@@ -718,8 +724,11 @@ export const messages = {
attempt: 'attempt',
updated: 'updated',
output: 'output',
latestOutput: 'latest output',
outputHistory: 'output log',
noOutput: 'No output',
recentMessages: 'Recent messages',
recentMessages: 'Message log',
messageHistory: 'Message log',
noMessages: 'No messages',
details: 'details',
message: 'message',
@@ -990,8 +999,11 @@ export const messages = {
attempt: '尝试',
updated: '更新',
output: '输出',
latestOutput: '最新输出',
outputHistory: '输出记录',
noOutput: '暂无输出',
recentMessages: '最近消息',
recentMessages: '消息记录',
messageHistory: '消息记录',
noMessages: '暂无消息',
details: '详情',
message: 'Message',
@@ -1262,8 +1274,11 @@ export const messages = {
attempt: '試行',
updated: '更新',
output: '出力',
latestOutput: '最新出力',
outputHistory: '出力履歴',
noOutput: '出力なし',
recentMessages: '最近のメッセージ',
recentMessages: 'メッセージ履歴',
messageHistory: 'メッセージ履歴',
noMessages: 'メッセージなし',
details: '詳細',
message: 'Message',

View File

@@ -1328,16 +1328,16 @@ progress::-moz-progress-bar {
}
.room-activity-cell {
min-width: 360px;
min-width: 300px;
}
.room-activity {
display: grid;
min-width: min(360px, 100%);
gap: 9px;
padding: 10px;
min-width: min(300px, 100%);
gap: 7px;
padding: 8px;
border: 1px solid rgba(43, 55, 38, 0.1);
border-radius: 18px;
border-radius: 15px;
background: rgba(255, 250, 240, 0.58);
}
@@ -1348,38 +1348,28 @@ progress::-moz-progress-bar {
font-weight: 800;
}
.room-activity-grid {
.room-progress-strip {
display: grid;
grid-template-columns: repeat(4, minmax(0, 1fr));
gap: 7px;
}
.room-activity-grid > span {
grid-template-columns: auto minmax(96px, 1fr) auto auto auto;
gap: 6px;
align-items: center;
min-width: 0;
padding: 8px;
border-radius: 13px;
background: rgba(43, 55, 38, 0.045);
color: var(--muted);
font-size: 11px;
font-weight: 850;
}
.room-activity-grid small,
.room-activity-grid strong {
display: block;
.room-progress-strip strong,
.room-progress-strip span,
.room-progress-strip time {
min-width: 0;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
.room-activity-grid small {
color: var(--muted);
font-size: 10px;
font-weight: 850;
letter-spacing: 0.04em;
text-transform: uppercase;
}
.room-activity-grid strong {
margin-top: 3px;
.room-progress-strip strong {
color: var(--ink);
font-size: 12px;
}
@@ -1398,33 +1388,85 @@ progress::-moz-progress-bar {
}
.room-activity-error {
margin: 0;
color: #7c2518;
font-size: 12px;
font-weight: 850;
overflow-wrap: anywhere;
}
.room-activity-columns {
display: grid;
grid-template-columns: minmax(0, 1fr) minmax(0, 1fr);
gap: 9px;
}
.room-activity-columns > section {
.room-latest-output {
display: grid;
gap: 4px;
min-width: 0;
align-content: start;
gap: 7px;
padding: 8px;
border-radius: 12px;
background: rgba(43, 55, 38, 0.045);
}
.room-activity-columns > section > strong {
.room-latest-output header {
display: flex;
gap: 8px;
align-items: baseline;
justify-content: space-between;
color: var(--muted);
font-size: 11px;
font-weight: 850;
}
.room-latest-output strong,
.room-detail-grid > section > strong {
color: var(--bg-ink);
font-size: 12px;
letter-spacing: 0.06em;
text-transform: uppercase;
}
.room-latest-output header span,
.room-latest-output p {
min-width: 0;
overflow: hidden;
text-overflow: ellipsis;
}
.room-latest-output p {
display: -webkit-box;
margin: 0;
color: var(--ink);
font-size: 12px;
line-height: 1.32;
overflow-wrap: anywhere;
-webkit-box-orient: vertical;
-webkit-line-clamp: 2;
}
.room-activity-details,
.room-compose-details {
min-width: 0;
}
.room-activity-details > summary,
.room-compose-details > summary {
min-height: 32px;
color: var(--accent);
font-size: 12px;
font-weight: 900;
cursor: pointer;
}
.room-detail-grid {
display: grid;
gap: 8px;
min-width: 0;
padding-top: 6px;
}
.room-detail-grid > section {
display: grid;
min-width: 0;
align-content: start;
gap: 7px;
}
.room-output-list,
.room-message-list {
display: grid;
@@ -1486,6 +1528,14 @@ progress::-moz-progress-bar {
-webkit-line-clamp: 4;
}
.room-message-cell {
width: 150px;
}
.room-compose-details .room-compose {
margin-top: 6px;
}
.task-board {
display: grid;
gap: 12px;
@@ -2165,9 +2215,13 @@ progress::-moz-progress-bar {
min-width: 0;
}
.room-activity-grid,
.room-activity-columns {
grid-template-columns: 1fr;
.room-progress-strip {
grid-template-columns: auto minmax(0, 1fr) auto;
}
.room-progress-strip span:nth-of-type(n + 3),
.room-progress-strip time {
display: none;
}
.inbox-summary,