mirror of
https://github.com/marimo-team/marimo.git
synced 2025-12-03 13:34:58 +00:00
improvement: use frontend markdown rendering for mo.ui.chat so content stays clean (#7066)
This removes the automatic markdown rendering from the backend (`chat.py`) and instead just renders the markdown on the fly in the frontend so the `ChatMessage.content` and stay clean in markdown and not be converted to HTML
This commit is contained in:
@@ -10,7 +10,7 @@
|
||||
|
||||
import marimo
|
||||
|
||||
__generated_with = "0.15.5"
|
||||
__generated_with = "0.17.6"
|
||||
app = marimo.App(width="medium")
|
||||
|
||||
|
||||
@@ -19,6 +19,7 @@ def _():
|
||||
import polars as pl
|
||||
import marimo as mo
|
||||
import os
|
||||
import altair
|
||||
|
||||
has_api_key = os.environ.get("OPENAI_API_KEY") is not None
|
||||
mo.stop(
|
||||
|
||||
@@ -42,6 +42,7 @@ export default defineConfig({
|
||||
define: {
|
||||
"process.env.NODE_ENV": JSON.stringify(process.env.NODE_ENV),
|
||||
"process.env.LOG": JSON.stringify(""),
|
||||
"process.env.VSCODE_TEXTMATE_DEBUG": JSON.stringify(false),
|
||||
"process.env.NODE_DEBUG": JSON.stringify(false),
|
||||
// Precedence: VITE_MARIMO_VERSION > package.json version > "latest"
|
||||
"import.meta.env.VITE_MARIMO_VERSION": process.env.VITE_MARIMO_VERSION
|
||||
|
||||
@@ -1,4 +1,6 @@
|
||||
/* Copyright 2024 Marimo. All rights reserved. */
|
||||
|
||||
import { Suspense } from "react";
|
||||
import { z } from "zod";
|
||||
import { TooltipProvider } from "@/components/ui/tooltip";
|
||||
import { createPlugin } from "@/plugins/core/builder";
|
||||
@@ -74,18 +76,20 @@ export const ChatPlugin = createPlugin<{ messages: ChatMessage[] }>(
|
||||
})
|
||||
.renderer((props) => (
|
||||
<TooltipProvider>
|
||||
<Chatbot
|
||||
prompts={props.data.prompts}
|
||||
showConfigurationControls={props.data.showConfigurationControls}
|
||||
maxHeight={props.data.maxHeight}
|
||||
allowAttachments={props.data.allowAttachments}
|
||||
config={props.data.config}
|
||||
get_chat_history={props.functions.get_chat_history}
|
||||
delete_chat_history={props.functions.delete_chat_history}
|
||||
delete_chat_message={props.functions.delete_chat_message}
|
||||
send_prompt={props.functions.send_prompt}
|
||||
value={props.value?.messages || Arrays.EMPTY}
|
||||
setValue={(messages) => props.setValue({ messages })}
|
||||
/>
|
||||
<Suspense>
|
||||
<Chatbot
|
||||
prompts={props.data.prompts}
|
||||
showConfigurationControls={props.data.showConfigurationControls}
|
||||
maxHeight={props.data.maxHeight}
|
||||
allowAttachments={props.data.allowAttachments}
|
||||
config={props.data.config}
|
||||
get_chat_history={props.functions.get_chat_history}
|
||||
delete_chat_history={props.functions.delete_chat_history}
|
||||
delete_chat_message={props.functions.delete_chat_message}
|
||||
send_prompt={props.functions.send_prompt}
|
||||
value={props.value?.messages || Arrays.EMPTY}
|
||||
setValue={(messages) => props.setValue({ messages })}
|
||||
/>
|
||||
</Suspense>
|
||||
</TooltipProvider>
|
||||
));
|
||||
|
||||
@@ -17,7 +17,7 @@ import {
|
||||
Trash2Icon,
|
||||
X,
|
||||
} from "lucide-react";
|
||||
import React, { useEffect, useRef, useState } from "react";
|
||||
import React, { lazy, useEffect, useRef, useState } from "react";
|
||||
import { convertToFileUIPart } from "@/components/chat/chat-utils";
|
||||
import {
|
||||
type AdditionalCompletions,
|
||||
@@ -43,7 +43,6 @@ import { Tooltip } from "@/components/ui/tooltip";
|
||||
import { toast } from "@/components/ui/use-toast";
|
||||
import { moveToEndOfEditor } from "@/core/codemirror/utils";
|
||||
import { useAsyncData } from "@/hooks/useAsyncData";
|
||||
import { renderHTML } from "@/plugins/core/RenderHTML";
|
||||
import { cn } from "@/utils/cn";
|
||||
import { copyToClipboard } from "@/utils/copy";
|
||||
import { Logger } from "@/utils/Logger";
|
||||
@@ -52,6 +51,10 @@ import { ErrorBanner } from "../common/error-banner";
|
||||
import type { PluginFunctions } from "./ChatPlugin";
|
||||
import type { ChatConfig, ChatMessage } from "./types";
|
||||
|
||||
const LazyStreamdown = lazy(() =>
|
||||
import("streamdown").then((module) => ({ default: module.Streamdown })),
|
||||
);
|
||||
|
||||
interface Props extends PluginFunctions {
|
||||
prompts: string[];
|
||||
config: ChatConfig;
|
||||
@@ -194,9 +197,13 @@ export const Chatbot: React.FC<Props> = (props) => {
|
||||
const textParts = message.parts?.filter((p) => p.type === "text");
|
||||
const textContent = textParts?.map((p) => p.text).join("\n");
|
||||
const content =
|
||||
message.role === "assistant"
|
||||
? renderHTML({ html: textContent })
|
||||
: textContent;
|
||||
message.role === "assistant" ? (
|
||||
<LazyStreamdown className="mo-markdown-renderer">
|
||||
{textContent}
|
||||
</LazyStreamdown>
|
||||
) : (
|
||||
textContent
|
||||
);
|
||||
|
||||
const attachments = message.parts?.filter((p) => p.type === "file");
|
||||
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
"tasks": {
|
||||
"build": {
|
||||
"outputs": ["dist/**"],
|
||||
"env": ["VITE_MARIMO_ISLANDS", "NODE_ENV"]
|
||||
"env": ["VITE_MARIMO_ISLANDS", "VITE_MARIMO_VERSION", "NODE_ENV"]
|
||||
},
|
||||
"build-storybook": {
|
||||
"outputs": ["storybook-static/**"]
|
||||
|
||||
@@ -11,7 +11,6 @@ from marimo._ai._types import (
|
||||
ChatModelConfigDict,
|
||||
)
|
||||
from marimo._output.formatting import as_html
|
||||
from marimo._output.md import md
|
||||
from marimo._output.rich_help import mddoc
|
||||
from marimo._plugins.core.web_component import JSONType
|
||||
from marimo._plugins.ui._core.ui_element import UIElement
|
||||
@@ -270,7 +269,7 @@ class chat(UIElement[dict[str, Any], list[ChatMessage]]):
|
||||
# Return the response as HTML
|
||||
# If the response is a string, convert it to markdown
|
||||
if isinstance(response, str):
|
||||
return md(response).text
|
||||
return response
|
||||
return as_html(response).text
|
||||
|
||||
def _convert_value(self, value: dict[str, Any]) -> list[ChatMessage]:
|
||||
|
||||
@@ -11,7 +11,6 @@ from marimo._ai._types import (
|
||||
ChatModelConfig,
|
||||
ChatModelConfigDict,
|
||||
)
|
||||
from marimo._output.md import md
|
||||
from marimo._plugins import ui
|
||||
from marimo._plugins.ui._impl.chat.chat import (
|
||||
DEFAULT_CONFIG,
|
||||
@@ -83,7 +82,7 @@ async def test_chat_send_prompt():
|
||||
)
|
||||
response: str = await chat._send_prompt(request)
|
||||
|
||||
assert response == md("Response to: Hello").text
|
||||
assert response == "Response to: Hello"
|
||||
assert len(chat._chat_history) == 2
|
||||
assert chat._chat_history[0].role == "user"
|
||||
assert chat._chat_history[0].content == "Hello"
|
||||
@@ -106,7 +105,7 @@ async def test_chat_send_prompt_async_function():
|
||||
)
|
||||
response: str = await chat._send_prompt(request)
|
||||
|
||||
assert response == md("Response to: Hello").text
|
||||
assert response == "Response to: Hello"
|
||||
assert len(chat._chat_history) == 2
|
||||
assert chat._chat_history[0].role == "user"
|
||||
assert chat._chat_history[0].content == "Hello"
|
||||
@@ -132,7 +131,7 @@ async def test_chat_send_prompt_async_generator():
|
||||
response: str = await chat._send_prompt(request)
|
||||
|
||||
# the last yielded value is the response
|
||||
assert response == md("2").text
|
||||
assert response == "2"
|
||||
assert len(chat._chat_history) == 2
|
||||
assert chat._chat_history[0].role == "user"
|
||||
assert chat._chat_history[0].content == "Hello"
|
||||
|
||||
@@ -3,7 +3,8 @@
|
||||
"tasks": {
|
||||
"build": {
|
||||
"dependsOn": ["^build", "codegen"],
|
||||
"outputs": ["dist/**"]
|
||||
"outputs": ["dist/**"],
|
||||
"env": ["NODE_ENV", "VITE_MARIMO_VERSION", "VITE_MARIMO_ISLANDS"]
|
||||
},
|
||||
"typecheck": {
|
||||
"dependsOn": ["codegen", "^codegen"]
|
||||
|
||||
Reference in New Issue
Block a user