feat: improve markdown note styles and layout logic
This commit is contained in:
parent
b80796a7cd
commit
dad78ae0ce
10 changed files with 157 additions and 42 deletions
|
|
@ -19,16 +19,24 @@ function MarkdownPreviewImpl({
|
|||
className,
|
||||
plain = false,
|
||||
}: MarkdownPreviewProps): ReactElement {
|
||||
const markdownClassName =
|
||||
"w-full max-w-none min-w-0 space-y-2 [overflow-wrap:anywhere] [&_*]:max-w-none [&_p]:w-full [&_ul]:w-full [&_ol]:w-full [&_li]:w-full [&_h1]:w-full [&_h2]:w-full [&_h3]:w-full [&_h4]:w-full [&_h5]:w-full [&_h6]:w-full [&_pre]:w-full [&_table]:w-full [&_p]:break-words [&_li]:break-words [&_code]:break-words [&_pre]:whitespace-pre-wrap [&_pre]:break-words";
|
||||
|
||||
return (
|
||||
<div
|
||||
className={cn(
|
||||
plain
|
||||
? "h-full w-full overflow-auto p-2 text-xs leading-relaxed pointer-events-none select-none"
|
||||
: "nodrag max-h-56 overflow-auto rounded-md border border-border/60 bg-muted/20 p-2 text-xs leading-relaxed",
|
||||
? "h-full w-full min-w-0 overflow-auto p-2 text-xs leading-relaxed pointer-events-none select-none"
|
||||
: "nodrag max-h-56 w-full min-w-0 overflow-auto rounded-md border border-border/60 bg-muted/20 p-2 text-xs leading-relaxed",
|
||||
className,
|
||||
)}
|
||||
>
|
||||
<Streamdown mode="static" plugins={MARKDOWN_PLUGINS} controls={false}>
|
||||
<Streamdown
|
||||
mode="static"
|
||||
plugins={MARKDOWN_PLUGINS}
|
||||
controls={false}
|
||||
className={markdownClassName}
|
||||
>
|
||||
{markdown.trim() ? markdown : "_Empty note_"}
|
||||
</Streamdown>
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -137,6 +137,18 @@
|
|||
},
|
||||
"ui": {
|
||||
"nodes": [
|
||||
{
|
||||
"id": "provider_1",
|
||||
"x": -1056.848383841495,
|
||||
"y": 519.6373927070263,
|
||||
"width": 400
|
||||
},
|
||||
{
|
||||
"id": "model_1",
|
||||
"x": -543.7221365246206,
|
||||
"y": 488.2975724283656,
|
||||
"width": 400
|
||||
},
|
||||
{
|
||||
"id": "domain",
|
||||
"x": 0,
|
||||
|
|
@ -168,16 +180,48 @@
|
|||
"width": 400
|
||||
},
|
||||
{
|
||||
"id": "provider_1",
|
||||
"x": -1056.848383841495,
|
||||
"y": 519.6373927070263,
|
||||
"width": 400
|
||||
"id": "note_1",
|
||||
"x": 210.01377182764494,
|
||||
"y": -262.9440547613487,
|
||||
"width": 400,
|
||||
"node_type": "markdown_note",
|
||||
"name": "note_1",
|
||||
"markdown": "### Start with controlled chat context\nThis recipe uses sampler columns to shape each conversation:\n\n- `domain`\n- `topic`\n- `conversation_length` (4 or 6 messages)\n\n**Why this helps**:\n\n- You get varied conversations without manual writing\n- Each row stays grounded in a clear scenario\n- You can scale quickly while keeping data quality consistent",
|
||||
"note_color": "#FFE4E6",
|
||||
"note_opacity": "35"
|
||||
},
|
||||
{
|
||||
"id": "model_1",
|
||||
"x": -543.7221365246206,
|
||||
"y": 488.2975724283656,
|
||||
"width": 400
|
||||
"id": "note_2",
|
||||
"x": 515.9369583007435,
|
||||
"y": 454.3936030274385,
|
||||
"width": 400,
|
||||
"node_type": "markdown_note",
|
||||
"name": "note_2",
|
||||
"markdown": "The **LLM Text** block (`user_goal`) creates one realistic user intent from sampler context.\n\n**It should be**:\n\n- **specific**\n- **practical**\n- **short**\n\nThis goal becomes the anchor for the full multi-turn conversation.",
|
||||
"note_color": "#FFE4E6",
|
||||
"note_opacity": "35"
|
||||
},
|
||||
{
|
||||
"id": "note_3",
|
||||
"x": -12.952616065779239,
|
||||
"y": 912.1316336111515,
|
||||
"width": 400,
|
||||
"node_type": "markdown_note",
|
||||
"name": "note_3",
|
||||
"markdown": "The **LLM Structured** block (`output_format`) generates the conversation as strict JSON.\n\nIn this recipe, schema enforces:\n\n- `conversation` array\n- message objects with `role` + `content`\n- role enum: `user` / `assistant`\n- no extra keys\n\nPrompt constraints also enforce:\n\n- exact length (`{{ conversation_length }}`)\n- alternating roles\n- first user message, last assistant message\n- natural ending\n\nThis is key for training data: same shape, less cleanup.",
|
||||
"note_color": "#FFE4E6",
|
||||
"note_opacity": "35"
|
||||
},
|
||||
{
|
||||
"id": "note_4",
|
||||
"x": -519.9585237323188,
|
||||
"y": 81.84144119564277,
|
||||
"width": 400,
|
||||
"node_type": "markdown_note",
|
||||
"name": "note_4",
|
||||
"markdown": "Sampler columns are useful during generation but usually noisy in final export.\n\nSet helper columns to `drop=true`, keep only core outputs such as:\n\n- `user_goal`\n- `output_format`\n\nTip: Keep final schema close to your training format, not your generation scaffolding.\n",
|
||||
"note_color": "#FFE4E6",
|
||||
"note_opacity": "35"
|
||||
}
|
||||
],
|
||||
"edges": [
|
||||
|
|
@ -233,4 +277,4 @@
|
|||
],
|
||||
"layout_direction": "LR"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -64,7 +64,7 @@
|
|||
"node_type": "markdown_note",
|
||||
"name": "note_1",
|
||||
"markdown": "#### Hugginface seed block\nThis recipe uses a ** HuggingFace dataset ** as seed data.\nYou provide dataset identity, load columns, then generate new fields from seed columns.\n\n##### Setup:\n\n1. Paste dataset id as `org/repo` (example: `unsloth/alpaca-cleaned`)\n2. Add token only if dataset is gated/private\n3. Load columns + preview rows so variables are available in prompts\n\n##### Why this matters:\n- Seed columns can drive generation quality\n- You can reference seed values directly in prompts (for example `{{ output }}`)",
|
||||
"note_color": "#d6fff8",
|
||||
"note_color": "#DCFCE7",
|
||||
"note_opacity": "35"
|
||||
},
|
||||
{
|
||||
|
|
@ -75,7 +75,7 @@
|
|||
"node_type": "markdown_note",
|
||||
"name": "note_2",
|
||||
"markdown": "##### Drop columns behavior:\n\n- You can mark specific seed columns to **drop from final output**\n- Those columns are still used during generation\n- They are removed only from exported final dataset\n\n##### Example:\n- Keep `generated_instruction` from llm-text block\n- Drop original `instruction`, `input`, `output` from the hugginface dataset from final artifact\n- Result: clean training output while still using source columns as generation context\n",
|
||||
"note_color": "#d6fff8",
|
||||
"note_color": "#DCFCE7",
|
||||
"note_opacity": "35"
|
||||
},
|
||||
{
|
||||
|
|
@ -191,4 +191,4 @@
|
|||
"unstructured_chunk_size": "1200",
|
||||
"unstructured_chunk_overlap": "200"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -78,8 +78,8 @@
|
|||
"node_type": "markdown_note",
|
||||
"name": "note_1",
|
||||
"markdown": "This recipe uses **seed data** from external documents.\nInstead of starting from empty generation, we load real source text first.\n\nIn this flow, the seed source is **Unstructured Documents**:\n\n- Upload: `.pdf`, `.docx`, `.txt`\n- Text is extracted and split on client into chunks\n- Each chunk becomes a row-like seed record (`chunk_text`) that you can reference in prompts with `{{ chunk_text }} `",
|
||||
"note_color": "#30f9fd",
|
||||
"note_opacity": "10"
|
||||
"note_color": "#F3E8FF",
|
||||
"note_opacity": "35"
|
||||
},
|
||||
{
|
||||
"id": "note_2",
|
||||
|
|
@ -89,8 +89,8 @@
|
|||
"node_type": "markdown_note",
|
||||
"name": "note_2",
|
||||
"markdown": "##### Chunking settings:\n\n- **Chunk size**: how much text per chunk\n- **Chunk overlap**: shared text between neighboring chunks to preserve context\n\n##### Sampling settings:\n\n- **Ordered**: keep original document order\n- **Shuffle**: randomize chunk order\n- **Selection index / selection settings**: choose which part/subset of seed data to use",
|
||||
"note_color": "#2bf9fd",
|
||||
"note_opacity": "10"
|
||||
"note_color": "#F3E8FF",
|
||||
"note_opacity": "35"
|
||||
},
|
||||
{
|
||||
"id": "note_3",
|
||||
|
|
@ -100,8 +100,8 @@
|
|||
"node_type": "markdown_note",
|
||||
"name": "note_3",
|
||||
"markdown": "- LLM prompt: `{{ chunk_text }}`\n- Expression block: combine/format values using `{{ chunk_text }}`\n- Processor templates: use `{{ chunk_text }}` during transforms\n\nTip:\n- Start with medium chunk size + small overlap.\n- Increase overlap only if answers lose context between chunks.",
|
||||
"note_color": "#8bf0fd",
|
||||
"note_opacity": "20"
|
||||
"note_color": "#F3E8FF",
|
||||
"note_opacity": "35"
|
||||
},
|
||||
{
|
||||
"id": "seed",
|
||||
|
|
@ -194,4 +194,4 @@
|
|||
"unstructured_chunk_size": "1200",
|
||||
"unstructured_chunk_overlap": "200"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -174,7 +174,7 @@
|
|||
"node_type": "markdown_note",
|
||||
"name": "note_1",
|
||||
"markdown": "## Expression columns \nAre like lightweight spreadsheet formulas.\nUse them when you want to transform existing columns quickly, without calling an LLM.\n\n### What you can do:\n\n- Use values from other columns: `{{ first_name }} {{ last_name }}`\n- Clean/format text: `{{ city | upper }}`, `{{ product_name | trim }}`\n- Conditional logic:\n - `{% if order_total >= 100 %}VIP{% elif order_total >= 50 %}Standard{% else %}Starter{% endif %}`\n- Simple math:\n - `{{ quantity * unit_price }}`\n - `{{ (subtotal - discount) | round(2) }}`\n\n### Good rule:\n- If the value can be computed from existing data, use Expression first.\n- Use LLM only when you need true language generation.",
|
||||
"note_color": "#fffbeb",
|
||||
"note_color": "#CFFAFE",
|
||||
"note_opacity": "35"
|
||||
},
|
||||
{
|
||||
|
|
@ -185,7 +185,7 @@
|
|||
"node_type": "markdown_note",
|
||||
"name": "note_2",
|
||||
"markdown": "### LLM Structured block\nGenerates JSON that matches your Output Format schema.\nThink of Output Format as a contract for what the model must return.\n\n#### Prompt tips:\n\n- Reference existing columns with Jinja: `{{ column_name }}`\n- You can reference nested values too: `{{ customer.first_name }}`\n- Be explicit about what each field should contain.\n\n#### Example prompt pattern:\n\n```text\nCreate a support ticket summary.\nCustomer: {{ customer_name }}\nIssue text: {{ issue_text }}\n\nReturn data for:\n- priority\n- short_title\n- resolution_steps\n```",
|
||||
"note_color": "#f4efdc",
|
||||
"note_color": "#CFFAFE",
|
||||
"note_opacity": "35"
|
||||
},
|
||||
{
|
||||
|
|
@ -196,7 +196,7 @@
|
|||
"node_type": "markdown_note",
|
||||
"name": "note_3",
|
||||
"markdown": "## Example output format shape (concept):\n\n```json\n{\n \"type\": \"object\",\n \"properties\": {\n \"priority\": { \"type\": \"string\" },\n \"short_title\": { \"type\": \"string\" },\n \"resolution_steps\": { \"type\": \"array\", \"items\": { \"type\": \"string\" } }\n },\n \"required\": [\"priority\", \"short_title\", \"resolution_steps\"]\n}\n```",
|
||||
"note_color": "#fbf6e5",
|
||||
"note_color": "#CFFAFE",
|
||||
"note_opacity": "35"
|
||||
},
|
||||
{
|
||||
|
|
@ -207,7 +207,7 @@
|
|||
"node_type": "markdown_note",
|
||||
"name": "note_4",
|
||||
"markdown": "### Model provider & Config\nEvery LLM block needs a model alias.\nThat alias comes from a Model Config.\nModel Config points to a Model Provider.\n\n#### Minimum setup:\n\n1. Create **Model Provider**\n - Set endpoint/provider type\n - Prefer env var auth (`api_key_env`) over hardcoded keys\n\n2. Create **Model Config**\n - Set alias (example: `model_1`)\n - Set model id\n - Link to provider\n - Tune params (temperature, max_tokens, etc.)\n\n3. In each LLM block\n - Set `model_alias` to that alias\n\nIf alias/provider link is missing, validation/run will fail.",
|
||||
"note_color": "#f6f4ef",
|
||||
"note_color": "#CFFAFE",
|
||||
"note_opacity": "35"
|
||||
},
|
||||
{
|
||||
|
|
@ -359,4 +359,4 @@
|
|||
],
|
||||
"layout_direction": "LR"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -159,7 +159,7 @@
|
|||
"node_type": "markdown_note",
|
||||
"name": "note_1",
|
||||
"markdown": "The **LLM Code** block is where Python code is generated from your instruction/prompt.\n\n##### How it works in this recipe:\n\n- You provide a clear prompt (often using Jinja references from earlier columns)\n- The model returns a response\n- The block extracts code content directly for the output column\n\n##### Current status:\n\n- We are **not** running Python lint/syntax validation in this recipe yet (Soon)\n- Validation support is planned and will be added\n\n##### What this means:\n\n- You may get mostly correct code, but some rows can still have syntax/style issues\n- Keep prompts specific and constrained to reduce bad outputs\n\n##### Tip:\n\n- Ask for one self-contained function/script\n- Ask for required imports\n- Ask for no markdown fences if you want cleaner extraction\n",
|
||||
"note_color": "#FDE68A",
|
||||
"note_color": "#FEF3C7",
|
||||
"note_opacity": "35"
|
||||
},
|
||||
{
|
||||
|
|
@ -170,7 +170,7 @@
|
|||
"node_type": "markdown_note",
|
||||
"name": "note_2",
|
||||
"markdown": "The **LLM Judge** block evaluates generated outputs with rubric-style scores.\n\n##### Important:\n\n- A judge can have **one or many scores**\n- Each score has:\n - a name (for example: `Correctness`)\n - a description\n - options (value + meaning)\n\n##### Example multi-score setup:\n\n- Correctness\n- Readability\n- Efficiency\n\n##### Why use multiple scores:\n\n- You get richer quality signals than a single pass/fail\n- Easier filtering and weighting later in training data prep\n\n##### Practical pattern:\n\n1. Generate code with LLM Code\n2. Judge with 2-4 focused scores\n3. Keep high-quality rows based on score thresholds\n",
|
||||
"note_color": "#FDE68A",
|
||||
"note_color": "#FEF3C7",
|
||||
"note_opacity": "35"
|
||||
}
|
||||
],
|
||||
|
|
@ -234,4 +234,4 @@
|
|||
],
|
||||
"layout_direction": "LR"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -119,6 +119,18 @@
|
|||
},
|
||||
"ui": {
|
||||
"nodes": [
|
||||
{
|
||||
"id": "provider_1",
|
||||
"x": -1092.2003193114556,
|
||||
"y": 715.157165665104,
|
||||
"width": 400
|
||||
},
|
||||
{
|
||||
"id": "model_1",
|
||||
"x": -546.1001596557278,
|
||||
"y": 681.8114012018752,
|
||||
"width": 400
|
||||
},
|
||||
{
|
||||
"id": "domain",
|
||||
"x": -18.379173679952572,
|
||||
|
|
@ -156,16 +168,48 @@
|
|||
"width": 400
|
||||
},
|
||||
{
|
||||
"id": "model_1",
|
||||
"x": -546.1001596557278,
|
||||
"y": 681.8114012018752,
|
||||
"width": 400
|
||||
"id": "note_1",
|
||||
"x": -103.00586025666547,
|
||||
"y": -332.088439142397,
|
||||
"width": 600,
|
||||
"node_type": "markdown_note",
|
||||
"name": "note_1",
|
||||
"markdown": "##### This recipe starts with **sampler columns** to create controlled SQL task context:\n\n- `domain`\n- `topic` (subcategory from `domain`)\n- `sql_task_type`\n- `instruction_phrase`\n\n##### Why this is useful:\n\n- You get diverse tasks without writing every prompt by hand\n- You can steer business context + task pattern in a predictable way\n- LLM prompts become cleaner because context is already structured",
|
||||
"note_color": "#DBEAFE",
|
||||
"note_opacity": "35"
|
||||
},
|
||||
{
|
||||
"id": "provider_1",
|
||||
"x": -1092.2003193114556,
|
||||
"y": 715.157165665104,
|
||||
"width": 400
|
||||
"id": "note_2",
|
||||
"x": 517.0372102151987,
|
||||
"y": 600.4949327304814,
|
||||
"width": 400,
|
||||
"node_type": "markdown_note",
|
||||
"name": "note_2",
|
||||
"markdown": "The **LLM Text** block (`sql_prompt`) turns sampler context into one clean natural-language SQL task.\n\n##### Prompt pattern in this recipe:\n\n- references prior columns with Jinja (`{{ domain }}`, `{{ topic }}`, etc.)\n- enforces start phrase with `{{ instruction_phrase }}`\n- returns instruction text only (no SQL yet)\n\n##### Tip:\n\n- Keep this instruction block concise and specific\n- Save implementation details for the next SQL generation block",
|
||||
"note_color": "#DBEAFE",
|
||||
"note_opacity": "35"
|
||||
},
|
||||
{
|
||||
"id": "note_3",
|
||||
"x": 12.635681904967385,
|
||||
"y": 1224.7626182706356,
|
||||
"width": 400,
|
||||
"node_type": "markdown_note",
|
||||
"name": "note_3",
|
||||
"markdown": "The **LLM Code** block (`sql`) generates SQL script from `{{ sql_prompt }}`.\n\n##### In this recipe it returns:\n\n- schema section (`CREATE TABLE`)\n- sample seed rows (`INSERT`)\n- final query (`SELECT`)\n\n##### Current status:\n\n- SQL validation block is **not** included yet in this learning recipe\n- We will add SQL validation later",
|
||||
"note_color": "#DBEAFE",
|
||||
"note_opacity": "35"
|
||||
},
|
||||
{
|
||||
"id": "note_4",
|
||||
"x": -1044,
|
||||
"y": 108.64730935525904,
|
||||
"width": 400,
|
||||
"node_type": "markdown_note",
|
||||
"name": "note_4",
|
||||
"markdown": "Sampler columns are useful during generation, but often noisy in final output.\n\nSet helper columns to **drop=true** (like in this recipe), keep only output columns you want to export.\n\n#### Final keep we have set here:\n\n- `sql_prompt`\n- `sql`\n\n",
|
||||
"note_color": "#DBEAFE",
|
||||
"note_opacity": "35"
|
||||
}
|
||||
],
|
||||
"edges": [
|
||||
|
|
@ -228,4 +272,4 @@
|
|||
],
|
||||
"layout_direction": "LR"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -27,7 +27,7 @@ import {
|
|||
type NodeProps,
|
||||
} from "@xyflow/react";
|
||||
import { memo, type ReactElement, useEffect } from "react";
|
||||
import { MAX_NODE_WIDTH, MIN_NODE_WIDTH } from "../constants";
|
||||
import { MAX_NODE_WIDTH, MAX_NOTE_NODE_WIDTH, MIN_NODE_WIDTH } from "../constants";
|
||||
import { useRecipeStudioStore } from "../stores/recipe-studio";
|
||||
import type {
|
||||
RecipeNode as RecipeGraphNodeType,
|
||||
|
|
@ -393,7 +393,7 @@ function RecipeGraphNodeBase({
|
|||
isVisible={selected}
|
||||
minWidth={MIN_NODE_WIDTH}
|
||||
minHeight={80}
|
||||
maxWidth={MAX_NODE_WIDTH}
|
||||
maxWidth={MAX_NOTE_NODE_WIDTH}
|
||||
maxHeight={520}
|
||||
color="var(--primary)"
|
||||
lineClassName="!border-transparent !shadow-none"
|
||||
|
|
|
|||
|
|
@ -2,3 +2,4 @@ export const DEFAULT_NODE_WIDTH = 400;
|
|||
export const DEFAULT_NODE_HEIGHT = 120;
|
||||
export const MIN_NODE_WIDTH = 260;
|
||||
export const MAX_NODE_WIDTH = 900;
|
||||
export const MAX_NOTE_NODE_WIDTH = 600;
|
||||
|
|
|
|||
|
|
@ -298,6 +298,24 @@
|
|||
.dark .shadow-border {
|
||||
--tw-shadow: 0 4px 16px rgba(0, 0, 0, 0.3);
|
||||
}
|
||||
|
||||
[data-streamdown="unordered-list"] {
|
||||
list-style-type: disc;
|
||||
list-style-position: outside;
|
||||
padding-left: 1.25rem;
|
||||
margin-block: 0.5rem;
|
||||
}
|
||||
|
||||
[data-streamdown="ordered-list"] {
|
||||
list-style-type: decimal;
|
||||
list-style-position: outside;
|
||||
padding-left: 1.25rem;
|
||||
margin-block: 0.5rem;
|
||||
}
|
||||
|
||||
[data-streamdown="list-item"] {
|
||||
display: list-item;
|
||||
}
|
||||
}
|
||||
|
||||
/* Minimal scrollbar — thumb only, no track */
|
||||
|
|
@ -340,4 +358,4 @@
|
|||
body {
|
||||
@apply bg-background text-foreground;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue