Skip to content

Commit e18b038

Browse files
authored
Merge pull request #4554 from Agenta-AI/fix/playground-message-view-mode
[fix] Fix inverted Text/Markdown view in playground message editors
2 parents 32914ee + 5e2ccc6 commit e18b038

10 files changed

Lines changed: 105 additions & 22 deletions

File tree

web/oss/src/styles/globals.css

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -184,6 +184,13 @@ body {
184184

185185
> .editor-input.markdown-view > .editor-code {
186186
background-color: transparent;
187+
/* Text mode shows the raw source as plain prose, not code. Use the
188+
editor's proportional font instead of the monospace code face, and
189+
drop the code-block padding/margins so the text aligns with the
190+
rich-text (markdown) view's left edge and top. */
191+
font-family: inherit;
192+
padding: 0;
193+
margin: 0;
187194
}
188195
> .editor-input:not(.markdown-view) > .editor-code {
189196
&:after {
@@ -199,6 +206,30 @@ body {
199206
}
200207
}
201208

209+
/* Align the message text with the first character of the role label above it,
210+
with the same symmetric horizontal inset on the role, the text, and the
211+
placeholder. The single inset value lives in --ag-message-inline-pad.
212+
213+
Notes:
214+
- The role label is an antd Button whose default padding is wider than the
215+
inset (Tailwind's px-2 on it loses to antd), so it is pinned with !important.
216+
- The text is padded here in CSS rather than via an editor prop because
217+
ChatMessageEditor renders the Editor with `noProvider`, a mode where
218+
`className`/`editorClassName` is currently dropped (known bug, tracked
219+
separately). JSON/code editors are excluded; they have a line-number gutter. */
220+
.agenta-chat-message-editor {
221+
--ag-message-inline-pad: 8px;
222+
}
223+
.agenta-chat-message-editor .message-user-select {
224+
padding-inline: var(--ag-message-inline-pad) !important;
225+
}
226+
.agenta-chat-message-editor .editor-input:not(.code-only) {
227+
padding-inline: var(--ag-message-inline-pad);
228+
}
229+
.agenta-chat-message-editor .editor-placeholder {
230+
left: var(--ag-message-inline-pad);
231+
}
232+
202233
/** Align the input search with the search box **/
203234
.ant-input-group-wrapper {
204235
.ant-input {

web/packages/agenta-playground-ui/src/components/adapters/TurnMessageAdapter.tsx

Lines changed: 13 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -21,10 +21,10 @@ import {
2121
PromptImageUpload,
2222
PromptDocumentUpload,
2323
} from "@agenta/ui/components/presentational"
24-
import type {ViewMode} from "@agenta/ui/drill-in"
24+
import {messageViewModeAtom, toMessageViewMode} from "@agenta/ui/drill-in"
2525
import type {UploadFile} from "antd"
2626
import clsx from "clsx"
27-
import {useAtomValue, useSetAtom} from "jotai"
27+
import {useAtom, useAtomValue, useSetAtom} from "jotai"
2828
import JSON5 from "json5"
2929
import {v4 as uuidv4} from "uuid"
3030

@@ -215,9 +215,13 @@ const TurnMessageAdapter: React.FC<Props> = ({
215215

216216
return fallback
217217
}, [computedText, msg])
218-
const [viewMode, setViewMode] = useState<ViewMode>("text")
219-
const isCodeMode = viewMode === "json" || viewMode === "yaml"
220-
const editorLanguage = viewMode === "yaml" ? "yaml" : "json"
218+
// Shared + persisted across all message editors (see messageViewModeAtom).
219+
// The atom is typed `ViewMode` (can hold "form"), so coerce to a mode this
220+
// editor can actually render before deriving any mode-dependent state.
221+
const [viewMode, setViewMode] = useAtom(messageViewModeAtom)
222+
const chatViewMode = toMessageViewMode(viewMode)
223+
const isCodeMode = chatViewMode === "json" || chatViewMode === "yaml"
224+
const editorLanguage = chatViewMode === "yaml" ? "yaml" : "json"
221225

222226
const effectiveDisabled = Boolean(disabled)
223227
const isUserRole = kind === "user" && !isToolKind
@@ -656,7 +660,7 @@ const TurnMessageAdapter: React.FC<Props> = ({
656660
isJSON={isCodeMode}
657661
isTool={isCodeMode}
658662
language={editorLanguage}
659-
markdownView={viewMode === "markdown"}
663+
markdownView={chatViewMode === "text"}
660664
onFocusChange={handleEditorFocusChange}
661665
text={p?.json}
662666
enableTokens={messageProps?.enableTokens ?? !isCodeMode}
@@ -686,7 +690,7 @@ const TurnMessageAdapter: React.FC<Props> = ({
686690
resultHashes={propsResultHashes ?? resultHashes}
687691
results={results}
688692
text={p?.json ?? editorText}
689-
viewMode={viewMode}
693+
viewMode={chatViewMode}
690694
onViewModeChange={setViewMode}
691695
collapsed={isMessageCollapsed}
692696
allowFileUpload={isUserRole && !effectiveDisabled}
@@ -750,7 +754,7 @@ const TurnMessageAdapter: React.FC<Props> = ({
750754
state={editorState}
751755
isJSON={isCodeMode}
752756
language={editorLanguage}
753-
markdownView={viewMode === "markdown"}
757+
markdownView={chatViewMode === "text"}
754758
enableTokens={messageProps?.enableTokens ?? !isCodeMode}
755759
headerRight={
756760
<TurnMessageHeaderOptions
@@ -761,7 +765,7 @@ const TurnMessageAdapter: React.FC<Props> = ({
761765
resultHashes={propsResultHashes ?? resultHashes}
762766
results={results}
763767
text={editorText}
764-
viewMode={viewMode}
768+
viewMode={chatViewMode}
765769
onViewModeChange={setViewMode}
766770
collapsed={isMessageCollapsed}
767771
allowFileUpload={isUserRole && !effectiveDisabled}

web/packages/agenta-playground-ui/src/components/adapters/VariableControlAdapter.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -522,7 +522,7 @@ const VariableControlAdapter: React.FC<VariableControlAdapterProps> = ({
522522
enableTokens={!editorProps?.codeOnly}
523523
disabled={isEffectivelyDisabled}
524524
>
525-
<MarkdownViewSynchronizer enabled={viewMode === "markdown"} />
525+
<MarkdownViewSynchronizer enabled={viewMode === "text"} />
526526
<SharedEditor
527527
id={editorId}
528528
noProvider

web/packages/agenta-ui/src/ChatMessage/components/ChatMessageEditor.tsx

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -214,7 +214,17 @@ const ChatMessageEditorInner: React.FC<ChatMessageEditorProps> = ({
214214
placeholder={placeholder}
215215
disabled={disabled}
216216
state={disabled ? "readOnly" : state}
217-
className={cn("relative", flexLayouts.column, gapClasses.xs, "rounded-md", className)}
217+
// `agenta-chat-message-editor` is the styling hook used in globals.css
218+
// to align the message text with the role label (see that file). The
219+
// padding can't go through `editorClassName` because ChatMessageEditor
220+
// renders the Editor with `noProvider`, where `className` is dropped.
221+
className={cn(
222+
"agenta-chat-message-editor relative",
223+
flexLayouts.column,
224+
gapClasses.xs,
225+
"rounded-md",
226+
className,
227+
)}
218228
footer={footer}
219229
onFocusChange={onFocusChange}
220230
maxPasteChars={maxPasteChars}

web/packages/agenta-ui/src/ChatMessage/components/ChatMessageList.tsx

Lines changed: 12 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -12,10 +12,12 @@ import {
1212
} from "@agenta/shared/utils"
1313
import {Copy, MinusCircle, Plus} from "@phosphor-icons/react"
1414
import {Button, Tooltip} from "antd"
15+
import {useAtom} from "jotai"
1516

1617
import {CollapseToggleButton, getCollapseStyle} from "../../components/presentational/buttons"
1718
import {ViewModeDropdown} from "../../drill-in/core/ViewModeDropdown"
18-
import {getViewOptions, type ViewMode} from "../../drill-in/utils/getViewOptions"
19+
import {messageViewModeAtom} from "../../drill-in/state/messageViewModeAtom"
20+
import {getViewOptions, toMessageViewMode, type ViewMode} from "../../drill-in/utils/getViewOptions"
1921
import {message, modal} from "../../utils/appMessageContext"
2022
import {cn, flexLayouts, gapClasses} from "../../utils/styles"
2123
import {createSnippetPdfAttachment} from "../utils/snippetAttachment"
@@ -89,9 +91,13 @@ const ChatMessageItem: React.FC<{
8991
onToggleMinimize,
9092
}) => {
9193
const containerRef = useRef<HTMLDivElement>(null)
92-
const [viewMode, setViewMode] = useState<ChatViewMode>("text")
93-
const isCodeMode = viewMode === "json" || viewMode === "yaml"
94-
const editorLanguage: "json" | "yaml" = viewMode === "yaml" ? "yaml" : "json"
94+
// Shared + persisted across all message editors (see messageViewModeAtom).
95+
// The atom is typed `ViewMode` (can hold "form"), so coerce to a mode this
96+
// editor can actually render before deriving any mode-dependent state.
97+
const [viewMode, setViewMode] = useAtom(messageViewModeAtom)
98+
const chatViewMode = toMessageViewMode(viewMode)
99+
const isCodeMode = chatViewMode === "json" || chatViewMode === "yaml"
100+
const editorLanguage: "json" | "yaml" = chatViewMode === "yaml" ? "yaml" : "json"
95101

96102
const isToolResponse = msg.role === "tool"
97103
const hasToolCalls = Boolean(msg.tool_calls && msg.tool_calls.length > 0)
@@ -173,7 +179,7 @@ const ChatMessageItem: React.FC<{
173179
onChangeText={(text) => onTextChange(index, text)}
174180
isJSON={isCodeMode}
175181
language={editorLanguage}
176-
markdownView={viewMode === "markdown"}
182+
markdownView={chatViewMode === "text"}
177183
enableTokens={enableTokens && !isCodeMode}
178184
templateFormat={templateFormat}
179185
tokens={tokens}
@@ -196,7 +202,7 @@ const ChatMessageItem: React.FC<{
196202
)}
197203
>
198204
<ViewModeDropdown<ChatViewMode>
199-
value={viewMode}
205+
value={chatViewMode}
200206
options={viewOptions}
201207
onChange={setViewMode}
202208
/>

web/packages/agenta-ui/src/ChatMessage/components/MarkdownToggleButton.tsx

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -22,11 +22,11 @@ const MarkdownToggleButton = ({id}: MarkdownToggleButtonProps) => {
2222
}, [editor])
2323

2424
return (
25-
<Tooltip title={markdownView ? "Preview text" : "Preview markdown"}>
25+
<Tooltip title={markdownView ? "Preview markdown" : "Preview text"}>
2626
<Button
2727
type="text"
2828
size="small"
29-
icon={markdownView ? <TextAa size={14} /> : <MarkdownLogoIcon size={14} />}
29+
icon={markdownView ? <MarkdownLogoIcon size={14} /> : <TextAa size={14} />}
3030
onClick={onToggleMarkdown}
3131
className={cn(flexLayouts.rowCenter, justifyClasses.center)}
3232
/>

web/packages/agenta-ui/src/drill-in/FieldRenderers/JsonObjectField.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -65,7 +65,7 @@ function ChatMessageObjectField({
6565
disabled={!editable}
6666
isJSON={isCodeMode}
6767
language={editorLanguage}
68-
markdownView={viewMode === "markdown"}
68+
markdownView={viewMode === "text"}
6969
enableTokens={!isCodeMode}
7070
templateFormat="curly"
7171
onChangeRole={(newRole: string) => {

web/packages/agenta-ui/src/drill-in/index.ts

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -126,8 +126,9 @@ export {
126126
canToggleRawMode,
127127
detectDataType,
128128
} from "./utils"
129-
export {getViewOptions} from "./utils/getViewOptions"
130-
export type {ViewMode, ViewOption} from "./utils/getViewOptions"
129+
export {getViewOptions, toMessageViewMode} from "./utils/getViewOptions"
130+
export type {ViewMode, MessageViewMode, ViewOption} from "./utils/getViewOptions"
131+
export {messageViewModeAtom} from "./state/messageViewModeAtom"
131132

132133
// ============================================================================
133134
// FIELD RENDERERS
Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
import {atomWithStorage} from "jotai/utils"
2+
3+
import type {ViewMode} from "../utils/getViewOptions"
4+
5+
/**
6+
* Shared, persisted view mode for chat / prompt message editors.
7+
*
8+
* Replaces the per-message local `useState` so that:
9+
* - switching one message's view (Text / Markdown / JSON / YAML) switches every
10+
* message editor at once, and
11+
* - the choice survives a page refresh (persisted to localStorage).
12+
*
13+
* Scope note: this is a single app-wide atom, so it is shared by every consumer
14+
* of the message editors (playground prompt + chat turns, and also the drill-in
15+
* message fields). The key is intentionally not namespaced to "playground".
16+
*
17+
* Defaults to "text" so messages open as plain, raw text.
18+
*/
19+
export const messageViewModeAtom = atomWithStorage<ViewMode>("agenta:message-view-mode", "text")

web/packages/agenta-ui/src/drill-in/utils/getViewOptions.ts

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,17 @@
11
export type ViewMode = "text" | "markdown" | "json" | "yaml" | "form"
22

3+
/** The view modes a chat / prompt message editor can render ("form" is for objects). */
4+
export type MessageViewMode = Exclude<ViewMode, "form">
5+
6+
/**
7+
* Coerce a (possibly app-wide / persisted) view mode to one a message editor can
8+
* render. The shared `messageViewModeAtom` is typed `ViewMode`, so it can hold
9+
* "form"; falling back to "text" keeps the dropdown and editor consistent instead
10+
* of silently casting and rendering an unsupported mode.
11+
*/
12+
export const toMessageViewMode = (mode: ViewMode): MessageViewMode =>
13+
mode === "form" ? "text" : mode
14+
315
export interface ViewOption {
416
value: ViewMode
517
label: string

0 commit comments

Comments
 (0)