mirror of
https://github.com/katanemo/plano.git
synced 2026-04-25 00:36:34 +02:00
* feat: redesign archgw -> plano + website * feat(www): refactor landing page sections, add new diagrams and UI improvements * feat(www): sections enhanced for clarify & diagrams added * feat(www): improvements to mobile design, layout of diagrams * feat(www): clean + typecheck * feat(www): feedback loop changes * feat(www): fix type error * fix lib/utils error * feat(www): ran biome formatting * feat(www): graphic changes * feat(www): web analytics * fea(www): changes * feat(www): introduce monorepo This change brings Turborepo monorepo to independently handle the marketing website, the docs website and any other future use cases for mutli-platform support. They are using internal @katanemo package handlers for the design system and logic. * fix(www): transpiler failure * fix(www): tsconfig issue * fix(www): next.config issue * feat(docs): hold off on docs * Delete next.config.ts * feat(www): content fix * feat(www): introduce blog * feat(www): content changes * Update package-lock.json * feat: update text * Update IntroSection.tsx * feat: Turbopack issue * fix * Update IntroSection.tsx * feat: updated Research page * refactor(www): text clarity, padding adj. * format(www) * fix: add missing lib/ files to git - fixes Vercel GitHub deployment - Updated .gitignore to properly exclude Python lib/ but include Next.js lib/ directories - Added packages/ui/src/lib/utils.ts (cn utility function) - Added apps/www/src/lib/sanity.ts (Sanity client configuration) - Fixes module resolution errors in Vercel GitHub deployments (case-sensitive filesystem) * Update .gitignore * style(www): favicon + metadata * fix(www): links * fix(www): add analytics * fix(www): add * fix(www): fix links + image * fix(www): fix links + image * fix(www): fix links * fix(www): remove from tools testing.md
424 lines
12 KiB
TypeScript
424 lines
12 KiB
TypeScript
/**
|
|
* ASCII Diagram Builder - Auto-spacing and formatting utilities
|
|
*
|
|
* This module provides utilities to ensure consistent spacing across ASCII diagrams
|
|
* similar to the intent detection diagram pattern.
|
|
*/
|
|
|
|
interface BoxDimensions {
|
|
label: string;
|
|
width: number;
|
|
height: number;
|
|
}
|
|
|
|
/**
|
|
* Calculates proper padding to center content within a container width
|
|
*/
|
|
export function calculateCenterPadding(
|
|
contentWidth: number,
|
|
containerWidth: number,
|
|
): number {
|
|
return Math.floor((containerWidth - contentWidth) / 2);
|
|
}
|
|
|
|
/**
|
|
* Creates a horizontal arrow between two positions
|
|
*/
|
|
export function createArrow(
|
|
length: number,
|
|
direction: "→" | "↓" | "↑" | "←" = "→",
|
|
): string {
|
|
return direction.repeat(length);
|
|
}
|
|
|
|
/**
|
|
* Builds a box with specified dimensions, label, and box type
|
|
*/
|
|
export function buildBox(
|
|
label: string,
|
|
type: "container" | "inner" | "regular" = "regular",
|
|
shadow: boolean = true,
|
|
width?: number,
|
|
): string[] {
|
|
const actualWidth = width || Math.max(label.length + 4, 12);
|
|
const paddedLabel = label
|
|
.padStart(Math.floor((actualWidth - 2 + label.length) / 2), " ")
|
|
.padEnd(actualWidth - 2, " ");
|
|
|
|
const symbols = {
|
|
container: { tl: "╔", tr: "╗", bl: "╚", br: "╝", h: "═", v: "║" },
|
|
inner: { tl: "┏", tr: "┓", bl: "┗", br: "┛", h: "━", v: "┃" },
|
|
regular: { tl: "┌", tr: "┐", bl: "└", br: "┘", h: "─", v: "│" },
|
|
};
|
|
|
|
const s = symbols[type];
|
|
const shadowChar = "░";
|
|
|
|
const lines = [
|
|
s.tl + s.h.repeat(actualWidth - 2) + s.tr + (shadow ? shadowChar : ""),
|
|
s.v + paddedLabel + s.v + (shadow ? shadowChar : ""),
|
|
s.bl + s.h.repeat(actualWidth - 2) + s.br + (shadow ? shadowChar : ""),
|
|
];
|
|
|
|
if (shadow) {
|
|
lines.push(" " + shadowChar.repeat(actualWidth));
|
|
}
|
|
|
|
return lines;
|
|
}
|
|
|
|
/**
|
|
* Fixes spacing in an existing diagram by analyzing and adjusting alignment
|
|
*/
|
|
export function fixDiagramSpacing(diagram: string): string {
|
|
const lines = diagram.split("\n");
|
|
if (lines.length === 0) return diagram;
|
|
|
|
// Find the container boundaries (look for ╔ and ╚ markers)
|
|
let containerStart = -1;
|
|
let containerEnd = -1;
|
|
let containerWidth = 0;
|
|
|
|
for (let i = 0; i < lines.length; i++) {
|
|
const line = lines[i];
|
|
if (line.includes("╔═") && line.includes("╗")) {
|
|
containerStart = i;
|
|
containerWidth = line.length;
|
|
}
|
|
if (line.includes("╚") && line.includes("╝")) {
|
|
containerEnd = i;
|
|
}
|
|
}
|
|
|
|
if (containerStart === -1 || containerEnd === -1) {
|
|
return diagram; // Can't fix if no container found
|
|
}
|
|
|
|
// The intent detection pattern shows:
|
|
// Line 2: title line with ╔═ {title} ═{fill}╗
|
|
// Lines 3-19: content with ║ on sides
|
|
// Line 20: bottom ╚══════╝
|
|
// Line 21: shadow line
|
|
|
|
// For intent detection, the container content width is about 60 chars
|
|
// Total line width including borders is about 68-70
|
|
// Content starts around position 26
|
|
|
|
// Detect pattern by looking at first content line
|
|
const firstContentLine = lines[containerStart + 1];
|
|
if (!firstContentLine) return diagram;
|
|
|
|
const leftPadding = firstContentLine.indexOf("║");
|
|
const rightPadding = containerWidth - firstContentLine.lastIndexOf("║") - 1;
|
|
|
|
// Now standardize all internal lines
|
|
const fixedLines = [...lines];
|
|
|
|
for (let i = containerStart + 1; i < containerEnd; i++) {
|
|
const line = lines[i];
|
|
const shadowIndex = line.indexOf("░");
|
|
|
|
if (line.trim().startsWith("║")) {
|
|
// This is a content line inside the container
|
|
// Standardize the padding
|
|
const content = extractContainerContent(line);
|
|
fixedLines[i] = padContainerLine(content, containerWidth, leftPadding);
|
|
}
|
|
}
|
|
|
|
return fixedLines.join("\n");
|
|
}
|
|
|
|
function extractContainerContent(line: string): string {
|
|
// Extract content between ║ characters
|
|
const startIdx = line.indexOf("║");
|
|
const endIdx = line.lastIndexOf("║");
|
|
if (startIdx === -1 || endIdx === -1 || startIdx === endIdx) return line;
|
|
return line.substring(startIdx + 1, endIdx);
|
|
}
|
|
|
|
function padContainerLine(
|
|
content: string,
|
|
containerWidth: number,
|
|
targetLeftPad: number,
|
|
): string {
|
|
const padding = " ".repeat(targetLeftPad);
|
|
const contentLength = content.length;
|
|
const rightPadding = containerWidth - targetLeftPad - contentLength - 2; // -2 for two ║
|
|
const rightPad = rightPadding > 0 ? " ".repeat(rightPadding) : "";
|
|
|
|
return padding + "║" + content + "║" + rightPad + "░";
|
|
}
|
|
|
|
/**
|
|
* Creates a simple flow diagram programmatically
|
|
* Usage:
|
|
* ```ts
|
|
* const diagram = createFlowDiagram({
|
|
* title: "My Process",
|
|
* width: 60,
|
|
* steps: [
|
|
* { label: "Step 1", type: "regular" },
|
|
* { label: "Step 2", type: "inner" },
|
|
* { label: "Step 3", type: "regular" }
|
|
* ]
|
|
* });
|
|
* ```
|
|
*/
|
|
export interface FlowStep {
|
|
label: string;
|
|
type?: "container" | "inner" | "regular";
|
|
shadow?: boolean;
|
|
}
|
|
|
|
export interface FlowDiagramConfig {
|
|
title: string;
|
|
width?: number;
|
|
steps: FlowStep[];
|
|
layout?: "vertical" | "horizontal";
|
|
externalElements?: FlowStep[]; // Elements outside the container (like "agent")
|
|
}
|
|
|
|
export function createFlowDiagram(config: FlowDiagramConfig): string {
|
|
const layout = config.layout || "vertical";
|
|
|
|
if (layout === "horizontal") {
|
|
return createHorizontalFlow(config);
|
|
} else {
|
|
return createVerticalFlow(config);
|
|
}
|
|
}
|
|
|
|
function createVerticalFlow(config: FlowDiagramConfig): string {
|
|
const width = config.width || 60;
|
|
const hasExternal =
|
|
config.externalElements && config.externalElements.length > 0;
|
|
|
|
// Build external elements first
|
|
let externalBoxes: string[] = [];
|
|
let externalWidth = 0;
|
|
|
|
if (hasExternal) {
|
|
externalWidth = 20;
|
|
for (const extEl of config.externalElements!) {
|
|
const extWidth = Math.max(extEl.label.length + 4, 12);
|
|
const extBoxLines = buildBox(
|
|
extEl.label,
|
|
extEl.type || "regular",
|
|
extEl.shadow !== false,
|
|
extWidth,
|
|
);
|
|
|
|
for (const extLine of extBoxLines) {
|
|
externalBoxes.push(" ".repeat(2) + extLine);
|
|
}
|
|
|
|
// Add vertical arrow if not last
|
|
if (
|
|
extEl !== config.externalElements![config.externalElements!.length - 1]
|
|
) {
|
|
const arrowPad = 2 + Math.floor(extWidth / 2);
|
|
externalBoxes.push(" ".repeat(arrowPad) + "▼");
|
|
}
|
|
}
|
|
}
|
|
|
|
const titleLine = hasExternal
|
|
? ` ╔═ ${config.title} ${"═".repeat(Math.max(0, width - config.title.length - 5))}╗`
|
|
: `╔═ ${config.title} ${"═".repeat(Math.max(0, width - config.title.length - 5))}╗`;
|
|
|
|
const lines: string[] = [];
|
|
lines.push(titleLine);
|
|
|
|
// Find max step width
|
|
const maxStepWidth = Math.max(...config.steps.map((s) => s.label.length), 20);
|
|
const stepWidth = maxStepWidth + 4;
|
|
|
|
// Build internal steps
|
|
const internalLines: string[] = [];
|
|
|
|
for (let i = 0; i < config.steps.length; i++) {
|
|
const step = config.steps[i];
|
|
const boxLines = buildBox(
|
|
step.label,
|
|
step.type || "regular",
|
|
step.shadow !== false,
|
|
stepWidth,
|
|
);
|
|
|
|
// Center each box
|
|
const leftPadding = calculateCenterPadding(stepWidth, width);
|
|
|
|
for (const boxLine of boxLines) {
|
|
internalLines.push(" ".repeat(leftPadding) + boxLine);
|
|
}
|
|
|
|
// Add vertical arrow between steps (except last)
|
|
if (i < config.steps.length - 1) {
|
|
const arrowPad = calculateCenterPadding(1, width);
|
|
internalLines.push(" ".repeat(arrowPad) + "│");
|
|
internalLines.push(" ".repeat(arrowPad) + "▼");
|
|
}
|
|
}
|
|
|
|
// Combine external and internal elements
|
|
const maxHeight = Math.max(externalBoxes.length, internalLines.length);
|
|
|
|
for (let row = 0; row < maxHeight; row++) {
|
|
let line = "";
|
|
|
|
// External part
|
|
if (row < externalBoxes.length) {
|
|
line += externalBoxes[row];
|
|
|
|
// Add connecting arrow on middle row
|
|
if (row === Math.floor(externalBoxes.length / 2)) {
|
|
line += "░".repeat(6) + "─".repeat(10) + "─▶║─";
|
|
} else {
|
|
line += "░".repeat(6) + " ".repeat(10) + " ║ ";
|
|
}
|
|
} else if (hasExternal) {
|
|
line += " ".repeat(externalWidth);
|
|
if (row < internalLines.length) {
|
|
line += "░".repeat(6) + " ".repeat(10) + " ║ ";
|
|
}
|
|
} else {
|
|
line += " ".repeat(externalWidth);
|
|
}
|
|
|
|
// Internal container part
|
|
if (row < internalLines.length) {
|
|
line += internalLines[row];
|
|
} else {
|
|
line += " ".repeat(width);
|
|
}
|
|
|
|
line += "║░";
|
|
lines.push(line);
|
|
}
|
|
|
|
// Close container
|
|
const bottomPadding = hasExternal ? " ".repeat(externalWidth + 16) : "";
|
|
const bottomLine = bottomPadding + "╚" + "═".repeat(width - 1) + "╝░";
|
|
lines.push(bottomLine);
|
|
|
|
const shadowLine =
|
|
(hasExternal ? " ".repeat(externalWidth + 17) : " ") + "░".repeat(width);
|
|
lines.push(shadowLine);
|
|
|
|
return lines.join("\n");
|
|
}
|
|
|
|
function createHorizontalFlow(config: FlowDiagramConfig): string {
|
|
const width = config.width || 70;
|
|
const hasExternal =
|
|
config.externalElements && config.externalElements.length > 0;
|
|
const lines: string[] = [];
|
|
|
|
// Calculate step widths
|
|
const maxStepWidth = Math.max(...config.steps.map((s) => s.label.length), 16);
|
|
const stepWidth = maxStepWidth + 4;
|
|
const arrowGap = 12;
|
|
const totalStepWidth =
|
|
config.steps.length * stepWidth + (config.steps.length - 1) * arrowGap;
|
|
const containerPadding = Math.max(
|
|
4,
|
|
Math.floor((width - totalStepWidth) / 2),
|
|
);
|
|
|
|
// Build internal boxes matrix
|
|
const boxMatrix: string[][] = [];
|
|
let maxHeight = 0;
|
|
for (const step of config.steps) {
|
|
const boxLines = buildBox(
|
|
step.label,
|
|
step.type || "regular",
|
|
step.shadow !== false,
|
|
stepWidth,
|
|
);
|
|
boxMatrix.push(boxLines);
|
|
maxHeight = Math.max(maxHeight, boxLines.length);
|
|
}
|
|
|
|
// Title line - position based on external elements
|
|
const titleLeftPad = hasExternal ? 26 : 26;
|
|
const titleRepeatCount = Math.max(0, width - config.title.length - 5);
|
|
const titleLine =
|
|
" ".repeat(titleLeftPad) +
|
|
`╔═ ${config.title} ${"═".repeat(titleRepeatCount)}╗`;
|
|
lines.push(titleLine);
|
|
|
|
// Build external box for rendering
|
|
let externalBoxLines: string[] = [];
|
|
if (hasExternal) {
|
|
const extEl = config.externalElements![0];
|
|
const extWidth = Math.max(extEl.label.length + 4, 16);
|
|
externalBoxLines = buildBox(
|
|
extEl.label,
|
|
extEl.type || "regular",
|
|
extEl.shadow !== false,
|
|
extWidth,
|
|
);
|
|
}
|
|
|
|
// Render content rows
|
|
for (let row = 0; row < maxHeight; row++) {
|
|
let line = "";
|
|
|
|
// External elements on left (if present)
|
|
if (hasExternal) {
|
|
const extRow = row < externalBoxLines.length ? row : -1;
|
|
|
|
if (extRow >= 0) {
|
|
line += " " + externalBoxLines[extRow];
|
|
|
|
// Add connecting arrow on middle row
|
|
if (row === Math.floor(externalBoxLines.length / 2)) {
|
|
line += "░".repeat(5) + "─".repeat(8) + "─▶║─";
|
|
} else {
|
|
line += "░".repeat(5) + " ".repeat(8) + " ║ ";
|
|
}
|
|
} else {
|
|
line += " ".repeat(26) + "║ ";
|
|
}
|
|
} else {
|
|
line += " ".repeat(26) + "║ ";
|
|
}
|
|
|
|
// Internal container boxes with proper padding
|
|
line += " ".repeat(containerPadding);
|
|
|
|
for (let i = 0; i < boxMatrix.length; i++) {
|
|
const boxLines = boxMatrix[i];
|
|
const boxLine =
|
|
row < boxLines.length
|
|
? boxLines[row]
|
|
: " ".repeat(stepWidth + (config.steps[i].shadow !== false ? 1 : 0));
|
|
line += boxLine;
|
|
|
|
// Add horizontal arrow between boxes
|
|
if (i < boxMatrix.length - 1) {
|
|
if (row === Math.floor(maxHeight / 2)) {
|
|
line += "─".repeat(arrowGap) + "►";
|
|
} else {
|
|
line += " ".repeat(arrowGap + 1);
|
|
}
|
|
}
|
|
}
|
|
|
|
// Right padding and border
|
|
const usedWidth = containerPadding + totalStepWidth;
|
|
const rightPad = Math.max(0, width - usedWidth);
|
|
line += " ".repeat(rightPad);
|
|
line += "║░";
|
|
lines.push(line);
|
|
}
|
|
|
|
// Close container
|
|
const bottomLine = " ".repeat(26) + "╚" + "═".repeat(width - 1) + "╝░";
|
|
lines.push(bottomLine);
|
|
const shadowLine = " ".repeat(27) + "░".repeat(width);
|
|
lines.push(shadowLine);
|
|
|
|
return lines.join("\n");
|
|
}
|