Compare commits

..

1 Commits

Author SHA1 Message Date
Henry Li 3131f961a4 feat: add python result and error handling 2025-05-14 18:29:16 +08:00
19 changed files with 47 additions and 327 deletions
+2 -18
View File
@@ -23,23 +23,7 @@ jobs:
uv pip install -e ".[dev]" uv pip install -e ".[dev]"
uv pip install -e ".[test]" uv pip install -e ".[test]"
- name: Run test cases with coverage - name: Run test cases
run: | run: |
source .venv/bin/activate source .venv/bin/activate
TAVILY_API_KEY=mock-key make coverage TAVILY_API_KEY=mock-key make test
- name: Generate HTML Coverage Report
run: |
source .venv/bin/activate
python -m coverage html -d coverage_html
- name: Upload Coverage Report
uses: actions/upload-artifact@v4
with:
name: coverage-report
path: coverage_html/
- name: Display Coverage Summary
run: |
source .venv/bin/activate
python -m coverage report
-3
View File
@@ -21,6 +21,3 @@ conf.yaml
.idea/ .idea/
.langgraph_api/ .langgraph_api/
# coverage report
coverage.xml
coverage/
+1 -1
View File
@@ -1,4 +1,4 @@
FROM ghcr.io/astral-sh/uv:python3.12-bookworm-slim FROM ghcr.io/astral-sh/uv:python3.12-bookworm
# Install uv. # Install uv.
COPY --from=ghcr.io/astral-sh/uv:latest /uv /bin/uv COPY --from=ghcr.io/astral-sh/uv:latest /uv /bin/uv
+1 -1
View File
@@ -19,4 +19,4 @@ langgraph-dev:
uvx --refresh --from "langgraph-cli[inmem]" --with-editable . --python 3.12 langgraph dev --allow-blocking uvx --refresh --from "langgraph-cli[inmem]" --with-editable . --python 3.12 langgraph dev --allow-blocking
coverage: coverage:
uv run pytest --cov=src tests/ --cov-report=term-missing --cov-report=xml uv run pytest --cov=src tests/ --cov-report=term-missing
+2 -2
View File
@@ -2,7 +2,7 @@
[![Python 3.12+](https://img.shields.io/badge/python-3.12+-blue.svg)](https://www.python.org/downloads/) [![Python 3.12+](https://img.shields.io/badge/python-3.12+-blue.svg)](https://www.python.org/downloads/)
[![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://opensource.org/licenses/MIT) [![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://opensource.org/licenses/MIT)
[![DeepWiki](https://img.shields.io/badge/DeepWiki-bytedance%2Fdeer--flow-blue.svg?logo=data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACwAAAAyCAYAAAAnWDnqAAAAAXNSR0IArs4c6QAAA05JREFUaEPtmUtyEzEQhtWTQyQLHNak2AB7ZnyXZMEjXMGeK/AIi+QuHrMnbChYY7MIh8g01fJoopFb0uhhEqqcbWTp06/uv1saEDv4O3n3dV60RfP947Mm9/SQc0ICFQgzfc4CYZoTPAswgSJCCUJUnAAoRHOAUOcATwbmVLWdGoH//PB8mnKqScAhsD0kYP3j/Yt5LPQe2KvcXmGvRHcDnpxfL2zOYJ1mFwrryWTz0advv1Ut4CJgf5uhDuDj5eUcAUoahrdY/56ebRWeraTjMt/00Sh3UDtjgHtQNHwcRGOC98BJEAEymycmYcWwOprTgcB6VZ5JK5TAJ+fXGLBm3FDAmn6oPPjR4rKCAoJCal2eAiQp2x0vxTPB3ALO2CRkwmDy5WohzBDwSEFKRwPbknEggCPB/imwrycgxX2NzoMCHhPkDwqYMr9tRcP5qNrMZHkVnOjRMWwLCcr8ohBVb1OMjxLwGCvjTikrsBOiA6fNyCrm8V1rP93iVPpwaE+gO0SsWmPiXB+jikdf6SizrT5qKasx5j8ABbHpFTx+vFXp9EnYQmLx02h1QTTrl6eDqxLnGjporxl3NL3agEvXdT0WmEost648sQOYAeJS9Q7bfUVoMGnjo4AZdUMQku50McCcMWcBPvr0SzbTAFDfvJqwLzgxwATnCgnp4wDl6Aa+Ax283gghmj+vj7feE2KBBRMW3FzOpLOADl0Isb5587h/U4gGvkt5v60Z1VLG8BhYjbzRwyQZemwAd6cCR5/XFWLYZRIMpX39AR0tjaGGiGzLVyhse5C9RKC6ai42ppWPKiBagOvaYk8lO7DajerabOZP46Lby5wKjw1HCRx7p9sVMOWGzb/vA1hwiWc6jm3MvQDTogQkiqIhJV0nBQBTU+3okKCFDy9WwferkHjtxib7t3xIUQtHxnIwtx4mpg26/HfwVNVDb4oI9RHmx5WGelRVlrtiw43zboCLaxv46AZeB3IlTkwouebTr1y2NjSpHz68WNFjHvupy3q8TFn3Hos2IAk4Ju5dCo8B3wP7VPr/FGaKiG+T+v+TQqIrOqMTL1VdWV1DdmcbO8KXBz6esmYWYKPwDL5b5FA1a0hwapHiom0r/cKaoqr+27/XcrS5UwSMbQAAAABJRU5ErkJggg==)](https://deepwiki.com/bytedance/deer-flow) [![DeepWiki](https://img.shields.io/badge/DeepWiki-bytedance%2Fdeer--flow-blue.svg?logo=data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACwAAAAyCAYAAAAnWDnqAAAAAXNSR0IArs4c6QAAA05JREFUaEPtmUtyEzEQhtWTQyQLHNak2AB7ZnyXZMEjXMGeK/AIi+QuHrMnbChYY7MIh8g01fJoopFb0uhhEqqcbWTp06/uv1saEDv4O3n3dV60RfP947Mm9/SQc0ICFQgzfc4CYZoTPAswgSJCCUJUnAAoRHOAUOcATwbmVLWdGoH//PB8mnKqScAhsD0kYP3j/Yt5LPQe2KvcXmGvRHcDnpxfL2zOYJ1mFwrryWTz0advv1Ut4CJgf5uhDuDj5eUcAUoahrdY/56ebRWeraTjMt/00Sh3UDtjgHtQNHwcRGOC98BJEAEymycmYcWwOprTgcB6VZ5JK5TAJ+fXGLBm3FDAmn6oPPjR4rKCAoJCal2eAiQp2x0vxTPB3ALO2CRkwmDy5WohzBDwSEFKRwPbknEggCPB/imwrycgxX2NzoMCHhPkDwqYMr9tRcP5qNrMZHkVnOjRMWwLCcr8ohBVb1OMjxLwGCvjTikrsBOiA6fNyCrm8V1rP93iVPpwaE+gO0SsWmPiXB+jikdf6SizrT5qKasx5j8ABbHpFTx+vFXp9EnYQmLx02h1QTTrl6eDqxLnGjporxl3NL3agEvXdT0WmEost648sQOYAeJS9Q7bfUVoMGnjo4AZdUMQku50McDcMWcBPvr0SzbTAFDfvJqwLzgxwATnCgnp4wDl6Aa+Ax283gghmj+vj7feE2KBBRMW3FzOpLOADl0Isb5587h/U4gGvkt5v60Z1VLG8BhYjbzRwyQZemwAd6cCR5/XFWLYZRIMpX39AR0tjaGGiGzLVyhse5C9RKC6ai42ppWPKiBagOvaYk8lO7DajerabOZP46Lby5wKjw1HCRx7p9sVMOWGzb/vA1hwiWc6jm3MvQDTogQkiqIhJV0nBQBTU+3okKCFDy9WwferkHjtxib7t3xIUQtHxnIwtx4mpg26/HfwVNVDb4oI9RHmx5WGelRVlrtiw43zboCLaxv46AZeB3IlTkwouebTr1y2NjSpHz68WNFjHvupy3q8TFn3Hos2IAk4Ju5dCo8B3wP7VPr/FGaKiG+T+v+TQqIrOqMTL1VdWV1DdmcbO8KXBz6esmYWYKPwDL5b5FA1a0hwapHiom0r/cKaoqr+27/XcrS5UwSMbQAAAABJRU5ErkJggg==)](https://deepwiki.com/bytedance/deer-flow)
<!-- DeepWiki badge generated by https://deepwiki.ryoppippi.com/ --> <!-- DeepWiki badge generated by https://deepwiki.ryoppippi.com/ -->
@@ -370,7 +370,7 @@ This will enable trace visualization in LangGraph Studio and send your traces to
You can also run this project with Docker. You can also run this project with Docker.
First, you need read the [configuration](docs/configuration_guide.md) below. Make sure `.env`, `.conf.yaml` files are ready. First, you need read the [configuration](#configuration) below. Make sure `.env`, `.conf.yaml` files are ready.
Second, to build a Docker image of your own web server: Second, to build a Docker image of your own web server:
-3
View File
@@ -52,9 +52,6 @@ filterwarnings = [
"ignore::UserWarning", "ignore::UserWarning",
] ]
[tool.coverage.report]
fail_under = 25
[tool.hatch.build.targets.wheel] [tool.hatch.build.targets.wheel]
packages = ["src"] packages = ["src"]
+5 -22
View File
@@ -308,34 +308,17 @@ async def _execute_agent_step(
observations = state.get("observations", []) observations = state.get("observations", [])
# Find the first unexecuted step # Find the first unexecuted step
current_step = None
completed_steps = []
for step in current_plan.steps: for step in current_plan.steps:
if not step.execution_res: if not step.execution_res:
current_step = step
break break
else:
completed_steps.append(step)
if not current_step: logger.info(f"Executing step: {step.title}")
logger.warning("No unexecuted step found")
return Command(goto="research_team")
logger.info(f"Executing step: {current_step.title}") # Prepare the input for the agent
# Format completed steps information
completed_steps_info = ""
if completed_steps:
completed_steps_info = "# Existing Research Findings\n\n"
for i, step in enumerate(completed_steps):
completed_steps_info += f"## Existing Finding {i+1}: {step.title}\n\n"
completed_steps_info += f"<finding>\n{step.execution_res}\n</finding>\n\n"
# Prepare the input for the agent with completed steps info
agent_input = { agent_input = {
"messages": [ "messages": [
HumanMessage( HumanMessage(
content=f"{completed_steps_info}# Current Task\n\n## Title\n\n{current_step.title}\n\n## Description\n\n{current_step.description}\n\n## Locale\n\n{state.get('locale', 'en-US')}" content=f"#Task\n\n##title\n\n{step.title}\n\n##description\n\n{step.description}\n\n##locale\n\n{state.get('locale', 'en-US')}"
) )
] ]
} }
@@ -357,8 +340,8 @@ async def _execute_agent_step(
logger.debug(f"{agent_name.capitalize()} full response: {response_content}") logger.debug(f"{agent_name.capitalize()} full response: {response_content}")
# Update the step with the execution result # Update the step with the execution result
current_step.execution_res = response_content step.execution_res = response_content
logger.info(f"Step '{current_step.title}' execution completed by {agent_name}") logger.info(f"Step '{step.title}' execution completed by {agent_name}")
return Command( return Command(
update={ update={
-24
View File
@@ -1,24 +0,0 @@
#!/usr/bin/env python3
"""
This script manually patches sys.modules to fix the LLM import issue
so that tests can run without requiring LLM configuration.
"""
import sys
from unittest.mock import MagicMock
# Create mocks
mock_llm = MagicMock()
mock_llm.invoke.return_value = "Mock LLM response"
# Create a mock module for llm.py
mock_llm_module = MagicMock()
mock_llm_module.get_llm_by_type = lambda llm_type: mock_llm
mock_llm_module.basic_llm = mock_llm
mock_llm_module._create_llm_use_conf = lambda llm_type, conf: mock_llm
# Set the mock module
sys.modules["src.llms.llm"] = mock_llm_module
print("Successfully patched LLM module. You can now run your tests.")
print("Example: uv run pytest tests/test_types.py -v")
-131
View File
@@ -1,131 +0,0 @@
import pytest
import sys
import os
from typing import Annotated, List, Optional
# Import MessagesState directly from langgraph rather than through our application
from langgraph.graph import MessagesState
# Create stub versions of Plan/Step/StepType to avoid dependencies
class StepType:
RESEARCH = "research"
PROCESSING = "processing"
class Step:
def __init__(self, need_web_search, title, description, step_type):
self.need_web_search = need_web_search
self.title = title
self.description = description
self.step_type = step_type
class Plan:
def __init__(self, locale, has_enough_context, thought, title, steps):
self.locale = locale
self.has_enough_context = has_enough_context
self.thought = thought
self.title = title
self.steps = steps
# Import the actual State class by loading the module directly
# This avoids the cascade of imports that would normally happen
def load_state_class():
# Get the absolute path to the types.py file
src_dir = os.path.abspath(os.path.join(os.path.dirname(__file__), "..", "src"))
types_path = os.path.join(src_dir, "graph", "types.py")
# Create a namespace for the module
import types
module_name = "src.graph.types_direct"
spec = types.ModuleType(module_name)
# Add the module to sys.modules to avoid import loops
sys.modules[module_name] = spec
# Set up the namespace with required imports
spec.__dict__["operator"] = __import__("operator")
spec.__dict__["Annotated"] = Annotated
spec.__dict__["MessagesState"] = MessagesState
spec.__dict__["Plan"] = Plan
# Execute the module code
with open(types_path, "r") as f:
module_code = f.read()
exec(module_code, spec.__dict__)
# Return the State class
return spec.State
# Load the actual State class
State = load_state_class()
def test_state_initialization():
"""Test that State class has correct default attribute definitions."""
# Test that the class has the expected attribute definitions
assert State.locale == "en-US"
assert State.observations == []
assert State.plan_iterations == 0
assert State.current_plan is None
assert State.final_report == ""
assert State.auto_accepted_plan is False
assert State.enable_background_investigation is True
assert State.background_investigation_results is None
# Verify state initialization
state = State(messages=[])
assert "messages" in state
# Without explicitly passing attributes, they're not in the state
assert "locale" not in state
assert "observations" not in state
def test_state_with_custom_values():
"""Test that State can be initialized with custom values."""
test_step = Step(
need_web_search=True,
title="Test Step",
description="Step description",
step_type=StepType.RESEARCH,
)
test_plan = Plan(
locale="en-US",
has_enough_context=False,
thought="Test thought",
title="Test Plan",
steps=[test_step],
)
# Initialize state with custom values and required messages field
state = State(
messages=[],
locale="fr-FR",
observations=["Observation 1"],
plan_iterations=2,
current_plan=test_plan,
final_report="Test report",
auto_accepted_plan=True,
enable_background_investigation=False,
background_investigation_results="Test results",
)
# Access state keys - these are explicitly initialized
assert state["locale"] == "fr-FR"
assert state["observations"] == ["Observation 1"]
assert state["plan_iterations"] == 2
assert state["current_plan"].title == "Test Plan"
assert state["current_plan"].thought == "Test thought"
assert len(state["current_plan"].steps) == 1
assert state["current_plan"].steps[0].title == "Test Step"
assert state["final_report"] == "Test report"
assert state["auto_accepted_plan"] is True
assert state["enable_background_investigation"] is False
assert state["background_investigation_results"] == "Test results"
@@ -75,9 +75,7 @@ function ActivityMessage({ messageId }: { messageId: string }) {
if (message.agent !== "reporter" && message.agent !== "planner") { if (message.agent !== "reporter" && message.agent !== "planner") {
return ( return (
<div className="px-4 py-2"> <div className="px-4 py-2">
<Markdown animated checkLinkCredibility> <Markdown animated>{message.content}</Markdown>
{message.content}
</Markdown>
</div> </div>
); );
} }
+1 -18
View File
@@ -1,7 +1,7 @@
// Copyright (c) 2025 Bytedance Ltd. and/or its affiliates // Copyright (c) 2025 Bytedance Ltd. and/or its affiliates
// SPDX-License-Identifier: MIT // SPDX-License-Identifier: MIT
import { Check, Copy, Headphones, Pencil, Undo2, X } from "lucide-react"; import { Check, Copy, Headphones, X } from "lucide-react";
import { useCallback, useEffect, useState } from "react"; import { useCallback, useEffect, useState } from "react";
import { ScrollContainer } from "~/components/deer-flow/scroll-container"; import { ScrollContainer } from "~/components/deer-flow/scroll-container";
@@ -47,7 +47,6 @@ export function ResearchBlock({
await listenToPodcast(researchId); await listenToPodcast(researchId);
}, [researchId]); }, [researchId]);
const [editing, setEditing] = useState(false);
const [copied, setCopied] = useState(false); const [copied, setCopied] = useState(false);
const handleCopy = useCallback(() => { const handleCopy = useCallback(() => {
if (!reportId) { if (!reportId) {
@@ -64,10 +63,6 @@ export function ResearchBlock({
}, 1000); }, 1000);
}, [reportId]); }, [reportId]);
const handleEdit = useCallback(() => {
setEditing((editing) => !editing);
}, []);
// When the research id changes, set the active tab to activities // When the research id changes, set the active tab to activities
useEffect(() => { useEffect(() => {
if (!hasReport) { if (!hasReport) {
@@ -92,17 +87,6 @@ export function ResearchBlock({
<Headphones /> <Headphones />
</Button> </Button>
</Tooltip> </Tooltip>
<Tooltip title="Edit">
<Button
className="text-gray-400"
size="icon"
variant="ghost"
disabled={isReplay}
onClick={handleEdit}
>
{editing ? <Undo2 /> : <Pencil />}
</Button>
</Tooltip>
<Tooltip title="Copy"> <Tooltip title="Copy">
<Button <Button
className="text-gray-400" className="text-gray-400"
@@ -163,7 +147,6 @@ export function ResearchBlock({
className="mt-4" className="mt-4"
researchId={researchId} researchId={researchId}
messageId={reportId} messageId={reportId}
editing={editing}
/> />
)} )}
</ScrollContainer> </ScrollContainer>
@@ -13,12 +13,10 @@ import { cn } from "~/lib/utils";
export function ResearchReportBlock({ export function ResearchReportBlock({
className, className,
messageId, messageId,
editing,
}: { }: {
className?: string; className?: string;
researchId: string; researchId: string;
messageId: string; messageId: string;
editing: boolean;
}) { }) {
const message = useMessage(messageId); const message = useMessage(messageId);
const { isReplay } = useReplay(); const { isReplay } = useReplay();
@@ -57,16 +55,14 @@ export function ResearchReportBlock({
ref={contentRef} ref={contentRef}
className={cn("relative flex flex-col pt-4 pb-8", className)} className={cn("relative flex flex-col pt-4 pb-8", className)}
> >
{!isReplay && isCompleted && editing ? ( {!isReplay && isCompleted ? (
<ReportEditor <ReportEditor
content={message?.content} content={message?.content}
onMarkdownChange={handleMarkdownChange} onMarkdownChange={handleMarkdownChange}
/> />
) : ( ) : (
<> <>
<Markdown animated checkLinkCredibility> <Markdown animated>{message?.content}</Markdown>
{message?.content}
</Markdown>
{message?.isStreaming && <LoadingAnimation className="my-12" />} {message?.isStreaming && <LoadingAnimation className="my-12" />}
</> </>
)} )}
@@ -147,7 +147,7 @@ export function MultiAgentVisualization({ className }: { className?: string }) {
</Tooltip> </Tooltip>
<div className="text-muted-foreground ml-2 flex items-center justify-center"> <div className="text-muted-foreground ml-2 flex items-center justify-center">
<Slider <Slider
className="w-40 sm:w-80 md:w-100 lg:w-120" className="w-120"
max={playbook.steps.length - 1} max={playbook.steps.length - 1}
min={0} min={0}
step={1} step={1}
-54
View File
@@ -1,54 +0,0 @@
import { useMemo } from "react";
import { useStore, useToolCalls } from "~/core/store";
import { Tooltip } from "./tooltip";
import { WarningFilled } from "@ant-design/icons";
export const Link = ({
href,
children,
checkLinkCredibility = false,
}: {
href: string | undefined;
children: React.ReactNode;
checkLinkCredibility: boolean;
}) => {
const toolCalls = useToolCalls();
const responding = useStore((state) => state.responding);
const credibleLinks = useMemo(() => {
const links = new Set<string>();
if (!checkLinkCredibility) return links;
(toolCalls || []).forEach((call) => {
if (call && call.name === "web_search" && call.result) {
const result = JSON.parse(call.result) as Array<{ url: string }>;
result.forEach((r) => {
links.add(r.url);
});
}
});
return links;
}, [toolCalls]);
const isCredible = useMemo(() => {
return checkLinkCredibility && href && !responding
? credibleLinks.has(href)
: true;
}, [credibleLinks, href, responding, checkLinkCredibility]);
return (
<span className="flex items-center gap-1.5">
<a href={href} target="_blank" rel="noopener noreferrer">
{children}
</a>
{!isCredible && (
<Tooltip
title="This link might be a hallucination from AI model and may not be reliable."
delayDuration={300}
>
<WarningFilled className="text-sx transition-colors hover:!text-yellow-500" />
</Tooltip>
)}
</span>
);
};
+20 -19
View File
@@ -18,7 +18,19 @@ import { cn } from "~/lib/utils";
import Image from "./image"; import Image from "./image";
import { Tooltip } from "./tooltip"; import { Tooltip } from "./tooltip";
import { Link } from "./link";
const components: ReactMarkdownOptions["components"] = {
a: ({ href, children }) => (
<a href={href} target="_blank" rel="noopener noreferrer">
{children}
</a>
),
img: ({ src, alt }) => (
<a href={src as string} target="_blank" rel="noopener noreferrer">
<Image className="rounded" src={src as string} alt={alt ?? ""} />
</a>
),
};
export function Markdown({ export function Markdown({
className, className,
@@ -26,30 +38,13 @@ export function Markdown({
style, style,
enableCopy, enableCopy,
animated = false, animated = false,
checkLinkCredibility = false,
...props ...props
}: ReactMarkdownOptions & { }: ReactMarkdownOptions & {
className?: string; className?: string;
enableCopy?: boolean; enableCopy?: boolean;
style?: React.CSSProperties; style?: React.CSSProperties;
animated?: boolean; animated?: boolean;
checkLinkCredibility?: boolean;
}) { }) {
const components: ReactMarkdownOptions["components"] = useMemo(() => {
return {
a: ({ href, children }) => (
<Link href={href} checkLinkCredibility={checkLinkCredibility}>
{children}
</Link>
),
img: ({ src, alt }) => (
<a href={src as string} target="_blank" rel="noopener noreferrer">
<Image className="rounded" src={src as string} alt={alt ?? ""} />
</a>
),
};
}, [checkLinkCredibility]);
const rehypePlugins = useMemo(() => { const rehypePlugins = useMemo(() => {
if (animated) { if (animated) {
return [rehypeKatex, rehypeSplitWordsIntoSpans]; return [rehypeKatex, rehypeSplitWordsIntoSpans];
@@ -57,7 +52,13 @@ export function Markdown({
return [rehypeKatex]; return [rehypeKatex];
}, [animated]); }, [animated]);
return ( return (
<div className={cn(className, "prose dark:prose-invert")} style={style}> <div
className={cn(
className,
"prose dark:prose-invert prose-p:my-0 prose-img:mt-0 flex flex-col gap-4",
)}
style={style}
>
<ReactMarkdown <ReactMarkdown
remarkPlugins={[remarkGfm, remarkMath]} remarkPlugins={[remarkGfm, remarkMath]}
rehypePlugins={rehypePlugins} rehypePlugins={rehypePlugins}
+1 -3
View File
@@ -19,7 +19,6 @@ export function Tooltip({
open, open,
side, side,
sideOffset, sideOffset,
delayDuration = 750,
}: { }: {
className?: string; className?: string;
style?: CSSProperties; style?: CSSProperties;
@@ -28,11 +27,10 @@ export function Tooltip({
open?: boolean; open?: boolean;
side?: "left" | "right" | "top" | "bottom"; side?: "left" | "right" | "top" | "bottom";
sideOffset?: number; sideOffset?: number;
delayDuration?: number;
}) { }) {
return ( return (
<TooltipProvider> <TooltipProvider>
<ShadcnTooltip delayDuration={delayDuration} open={open}> <ShadcnTooltip delayDuration={750} open={open}>
<TooltipTrigger asChild>{children}</TooltipTrigger> <TooltipTrigger asChild>{children}</TooltipTrigger>
<TooltipContent <TooltipContent
className={cn(className)} className={cn(className)}
+10 -3
View File
@@ -78,12 +78,16 @@ const taskItem = TaskItem.configure({
}); });
const horizontalRule = HorizontalRule.configure({ const horizontalRule = HorizontalRule.configure({
HTMLAttributes: {}, HTMLAttributes: {
class: cx("mt-4 mb-6 border-t border-muted-foreground"),
},
}); });
const starterKit = StarterKit.configure({ const starterKit = StarterKit.configure({
bulletList: { bulletList: {
HTMLAttributes: {}, HTMLAttributes: {
class: cx("list-disc list-outside leading-3 -mt-2"),
},
}, },
orderedList: { orderedList: {
HTMLAttributes: { HTMLAttributes: {
@@ -91,7 +95,9 @@ const starterKit = StarterKit.configure({
}, },
}, },
listItem: { listItem: {
HTMLAttributes: {}, HTMLAttributes: {
class: cx("leading-normal -mb-2"),
},
}, },
blockquote: { blockquote: {
HTMLAttributes: { HTMLAttributes: {
@@ -101,6 +107,7 @@ const starterKit = StarterKit.configure({
codeBlock: false, codeBlock: false,
code: { code: {
HTMLAttributes: { HTMLAttributes: {
class: cx("rounded-md bg-muted px-1.5 py-1 font-mono font-medium"),
spellcheck: "false", spellcheck: "false",
}, },
}, },
-11
View File
@@ -377,14 +377,3 @@ export function useLastFeedbackMessageId() {
); );
return waitingForFeedbackMessageId; return waitingForFeedbackMessageId;
} }
export function useToolCalls() {
return useStore(
useShallow((state) => {
return state.messageIds
?.map((id) => getMessage(id)?.toolCalls)
.filter((toolCalls) => toolCalls != null)
.flat();
}),
);
}
-4
View File
@@ -4,10 +4,6 @@
color: inherit; color: inherit;
} }
.ProseMirror {
line-height: 1.75;
}
.ProseMirror .is-editor-empty:first-child::before { .ProseMirror .is-editor-empty:first-child::before {
content: attr(data-placeholder); content: attr(data-placeholder);
float: left; float: left;