Studio: fix edit/run UI and write worldbook on advance (R6)

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
2026-05-31 22:46:28 +08:00
parent 63a32bfa7c
commit 5bfe7a733f
7 changed files with 185 additions and 154 deletions

View File

@@ -19,12 +19,11 @@
--studio-top-control-h: 2.75rem;
display: grid;
grid-template-columns: minmax(200px, 240px) minmax(280px, 1fr) minmax(160px, 220px);
grid-template-rows: var(--studio-top-label-h) var(--studio-top-control-h) auto;
grid-template-columns: minmax(200px, 1fr) minmax(280px, 1.4fr) minmax(160px, 220px);
grid-template-rows: var(--studio-top-label-h) var(--studio-top-control-h);
grid-template-areas:
'label-project label-goal label-desc'
'ctrl-project ctrl-goal ctrl-desc'
'actions . .';
'ctrl-project ctrl-goal ctrl-desc';
align-items: stretch;
gap: 6px var(--spacing-md);
padding: var(--spacing-md) var(--spacing-lg);
@@ -40,7 +39,6 @@
grid-template-areas:
'label-project label-project'
'ctrl-project ctrl-project'
'actions actions'
'label-goal label-goal'
'ctrl-goal ctrl-goal'
'label-desc label-desc'
@@ -89,6 +87,10 @@
.studio-edit-top__control--project {
grid-area: ctrl-project;
display: flex;
align-items: stretch;
gap: var(--spacing-xs);
min-width: 0;
}
.studio-edit-top__control--goal {
@@ -100,20 +102,32 @@
}
.studio-edit-top__actions {
grid-area: actions;
display: flex;
flex-wrap: wrap;
flex-wrap: nowrap;
align-items: center;
gap: var(--spacing-xs);
padding-top: 2px;
flex-shrink: 0;
}
.studio-edit-select--project {
width: 100%;
flex: 1;
min-width: 0;
min-height: var(--studio-top-control-h);
box-sizing: border-box;
}
@media (max-width: 960px) {
.studio-edit-top__control--project {
flex-wrap: wrap;
}
.studio-edit-top__actions {
flex: 1 1 100%;
justify-content: flex-end;
flex-wrap: wrap;
}
}
.studio-edit-textarea--goal {
width: 100%;
min-height: var(--studio-top-control-h);

View File

@@ -286,6 +286,33 @@ function StudioEditPage() {
</option>
))}
</select>
<div className="studio-edit-top__actions">
<button
type="button"
className="studio-edit-btn studio-edit-btn-danger studio-edit-btn-sm"
onClick={handleDeleteProject}
disabled={!currentProjectId || loading || saving}
title="删除当前项目及其运行记录"
>
删除项目
</button>
<button
type="button"
className="studio-edit-btn studio-edit-btn-sm"
onClick={openRenameProjectModal}
disabled={!currentProjectId || loading || saving}
>
改名
</button>
<button
type="button"
className="studio-edit-btn studio-edit-btn-sm"
onClick={openNewProjectModal}
disabled={loading}
>
新建项目
</button>
</div>
</div>
<div className="studio-edit-top__control studio-edit-top__control--goal">
<textarea
@@ -313,33 +340,6 @@ function StudioEditPage() {
/>
</div>
<div className="studio-edit-top__actions">
<button
type="button"
className="studio-edit-btn studio-edit-btn-danger studio-edit-btn-sm"
onClick={handleDeleteProject}
disabled={!currentProjectId || loading || saving}
title="删除当前项目及其运行记录"
>
删除项目
</button>
<button
type="button"
className="studio-edit-btn studio-edit-btn-sm"
onClick={openRenameProjectModal}
disabled={!currentProjectId || loading || saving}
>
改名
</button>
<button
type="button"
className="studio-edit-btn studio-edit-btn-sm"
onClick={openNewProjectModal}
disabled={loading}
>
新建项目
</button>
</div>
</header>
{(error || saveMessage) && (
<div

View File

@@ -50,39 +50,6 @@
color: var(--color-text-primary);
}
.studio-run-chat-history {
display: flex;
flex-direction: column;
gap: 2px;
margin-bottom: var(--spacing-md);
opacity: 0.35;
max-height: 120px;
overflow: hidden;
pointer-events: none;
user-select: none;
}
.studio-run-chat-history-item {
display: flex;
gap: var(--spacing-xs);
font-size: 0.72rem;
line-height: 1.35;
color: var(--color-text-muted);
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
}
.studio-run-chat-history-role {
flex-shrink: 0;
font-weight: 600;
}
.studio-run-chat-history-text {
overflow: hidden;
text-overflow: ellipsis;
}
.studio-run-chat-focus {
display: flex;
flex-direction: column;
@@ -90,38 +57,6 @@
min-height: min(40vh, 320px);
}
.studio-run-chat-last-user {
display: flex;
gap: var(--spacing-sm);
align-items: flex-start;
padding: var(--spacing-sm) var(--spacing-md);
border-radius: var(--radius-md);
background: rgba(102, 126, 234, 0.08);
border-left: 3px solid var(--color-accent);
}
[data-color-theme='dark'] .studio-run-chat-last-user {
background: rgba(212, 167, 106, 0.1);
}
.studio-run-chat-last-user--pending {
opacity: 0.85;
}
.studio-run-chat-last-user-label {
flex-shrink: 0;
font-size: 0.7rem;
font-weight: 600;
color: var(--color-text-muted);
}
.studio-run-chat-last-user-text {
font-size: 0.85rem;
color: var(--color-text-primary);
line-height: 1.5;
white-space: pre-wrap;
}
.studio-run-chat-thinking,
.studio-run-chat-summary {
padding: var(--spacing-md) var(--spacing-lg);

View File

@@ -1,7 +1,6 @@
import React, { useEffect, useRef, useState } from 'react';
import MarkdownRenderer from '../shared/MarkdownRenderer';
import { findLastUserMessageIndex } from './studioRunUtils';
import './StudioRunChat.css';
@@ -31,7 +30,6 @@ function StudioRunChat({
stepMessages = [],
thinking,
evaluation,
pendingUserText,
inputValue,
onInputChange,
onSend,
@@ -47,27 +45,16 @@ function StudioRunChat({
const [showOptions, setShowOptions] = useState(false);
const [renderMode, setRenderMode] = useState('markdown');
const lastUserIdx = findLastUserMessageIndex(stepMessages);
const historyMessages =
lastUserIdx > 0
? stepMessages.slice(0, lastUserIdx).filter((msg) => msg.role === 'user')
: [];
const lastUserMessage =
lastUserIdx >= 0 ? stepMessages[lastUserIdx] : pendingUserText
? { role: 'user', content: pendingUserText }
: null;
const summaryText = evaluation || null;
const hasFocusContent =
thinking || summaryText || pendingUserText || sending || lastUserMessage;
const hasFocusContent = thinking || summaryText || sending;
useEffect(() => {
const container = containerRef.current;
const anchor = focusAnchorRef.current;
if (!container || !anchor) return;
container.scrollTop = Math.max(0, anchor.offsetTop - container.offsetTop);
}, [stepMessages, thinking, evaluation, pendingUserText, sending]);
}, [stepMessages, thinking, evaluation, sending]);
useEffect(() => {
const handleClickOutside = (event) => {
@@ -117,20 +104,6 @@ function StudioRunChat({
return (
<div className="studio-run-chat">
<div className="studio-run-chat-messages" ref={containerRef}>
{historyMessages.length > 0 && (
<div className="studio-run-chat-history" aria-hidden="true">
{historyMessages.map((msg) => (
<div
key={msg.id}
className="studio-run-chat-history-item role-user"
>
<span className="studio-run-chat-history-role"></span>
<span className="studio-run-chat-history-text">{msg.content}</span>
</div>
))}
</div>
)}
<div ref={focusAnchorRef} className="studio-run-chat-focus">
{!hasFocusContent ? (
<div className="studio-run-chat-empty">
@@ -138,24 +111,6 @@ function StudioRunChat({
</div>
) : (
<>
{lastUserMessage && !pendingUserText && (
<div className="studio-run-chat-last-user">
<span className="studio-run-chat-last-user-label"></span>
<span className="studio-run-chat-last-user-text">
{lastUserMessage.content}
</span>
</div>
)}
{pendingUserText && (
<div className="studio-run-chat-last-user studio-run-chat-last-user--pending">
<span className="studio-run-chat-last-user-label"></span>
<span className="studio-run-chat-last-user-text">
{pendingUserText}
</span>
</div>
)}
{sending && !thinking && (
<div className="studio-run-chat-thinking studio-run-chat-thinking--pending">
<span className="studio-run-chat-block-label">思考</span>

View File

@@ -515,7 +515,6 @@ function StudioRunPage() {
const [chatInput, setChatInput] = useState('');
const [streamOutput, setStreamOutput] = useState(false);
const [toolOptionIndex, setToolOptionIndex] = useState(0);
const [pendingUserText, setPendingUserText] = useState(null);
const [runListExpanded, setRunListExpanded] = useState(false);
const [renamingId, setRenamingId] = useState(null);
const [renameValue, setRenameValue] = useState('');
@@ -534,7 +533,6 @@ function StudioRunPage() {
setRunSessionEntered(false);
setInsertionPopupOpen(false);
setContextBlockPopup(null);
setPendingUserText(null);
}, [currentRunId, runProjectId, setRunSessionEntered]);
const activeNodeState = useMemo(() => {
@@ -621,11 +619,9 @@ function StudioRunPage() {
const handleChatSend = async (text) => {
if (!text || isInitBindActive || !isWorldbookActive || runMessaging) return;
setPendingUserText(text);
setChatInput('');
const run = await sendRunMessage(text, { stream: streamOutput });
setPendingUserText(null);
if (!run) {
/* error surfaced via runError */
}
@@ -649,7 +645,6 @@ function StudioRunPage() {
if (run) {
setChatInput('');
setToolOptionIndex(0);
setPendingUserText(null);
}
};
@@ -864,7 +859,6 @@ function StudioRunPage() {
stepMessages={stepMessages}
thinking={runStreamingThinking ?? lastToolResponse?.thinking}
evaluation={runStreamingThinking ? null : lastToolResponse?.evaluation}
pendingUserText={pendingUserText}
inputValue={chatInput}
onInputChange={setChatInput}
onSend={handleChatSend}