feat: add spice (#12)
This commit is contained in:
@@ -78,12 +78,17 @@ type StaticItem =
|
||||
| {
|
||||
kind: "welcome";
|
||||
id: string;
|
||||
snapshot: { continueSession: boolean; agentId?: string };
|
||||
snapshot: {
|
||||
continueSession: boolean;
|
||||
agentState?: Letta.AgentState | null;
|
||||
terminalWidth: number;
|
||||
};
|
||||
}
|
||||
| Line;
|
||||
|
||||
export default function App({
|
||||
agentId,
|
||||
agentState,
|
||||
loadingState = "ready",
|
||||
continueSession = false,
|
||||
startupApproval = null,
|
||||
@@ -91,6 +96,7 @@ export default function App({
|
||||
tokenStreaming = true,
|
||||
}: {
|
||||
agentId: string;
|
||||
agentState?: Letta.AgentState | null;
|
||||
loadingState?:
|
||||
| "assembling"
|
||||
| "upserting"
|
||||
@@ -310,7 +316,8 @@ export default function App({
|
||||
id: `welcome-${Date.now().toString(36)}`,
|
||||
snapshot: {
|
||||
continueSession,
|
||||
agentId: agentId !== "loading" ? agentId : undefined,
|
||||
agentState,
|
||||
terminalWidth: columns,
|
||||
},
|
||||
},
|
||||
]);
|
||||
@@ -325,8 +332,9 @@ export default function App({
|
||||
messageHistory,
|
||||
refreshDerived,
|
||||
commitEligibleLines,
|
||||
agentId,
|
||||
continueSession,
|
||||
columns,
|
||||
agentState,
|
||||
]);
|
||||
|
||||
// Fetch llmConfig when agent is ready
|
||||
@@ -1119,12 +1127,19 @@ export default function App({
|
||||
id: `welcome-${Date.now().toString(36)}`,
|
||||
snapshot: {
|
||||
continueSession,
|
||||
agentId: agentId !== "loading" ? agentId : undefined,
|
||||
agentState,
|
||||
terminalWidth: columns,
|
||||
},
|
||||
},
|
||||
]);
|
||||
}
|
||||
}, [loadingState, continueSession, agentId, messageHistory.length]);
|
||||
}, [
|
||||
loadingState,
|
||||
continueSession,
|
||||
messageHistory.length,
|
||||
columns,
|
||||
agentState,
|
||||
]);
|
||||
|
||||
return (
|
||||
<Box flexDirection="column" gap={1}>
|
||||
@@ -1160,7 +1175,7 @@ export default function App({
|
||||
<WelcomeScreen
|
||||
loadingState={loadingState}
|
||||
continueSession={continueSession}
|
||||
agentId={agentId !== "loading" ? agentId : undefined}
|
||||
agentState={agentState}
|
||||
/>
|
||||
)}
|
||||
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
// Import useInput from vendored Ink for bracketed paste support
|
||||
import { Box, Text, useInput } from "ink";
|
||||
import { type ComponentType, memo, useMemo, useState } from "react";
|
||||
import { memo, useMemo, useState } from "react";
|
||||
import type { ApprovalContext } from "../../permissions/analyzer";
|
||||
import { type AdvancedDiffSuccess, computeAdvancedDiff } from "../helpers/diff";
|
||||
import { resolvePlaceholders } from "../helpers/pasteRegistry";
|
||||
|
||||
34
src/cli/components/AsciiArt.ts
Normal file
34
src/cli/components/AsciiArt.ts
Normal file
@@ -0,0 +1,34 @@
|
||||
export const shortAsciiLogo = `
|
||||
██████ ██╗ ███████╗████████╗████████╗ █████╗
|
||||
██ ██ ██║ ██╔════╝╚══██╔══╝╚══██╔══╝██╔══██╗
|
||||
██ ▇▇ ██ ██║ █████╗ ██║ ██║ ███████║
|
||||
██ ██ ██║ ██╔══╝ ██║ ██║ ██╔══██║
|
||||
██████ ███████╗███████╗ ██║ ██║ ██║ ██║
|
||||
╚═════╝ ╚══════╝╚══════╝ ╚═╝ ╚═╝ ╚═╝ ╚═╝
|
||||
`;
|
||||
|
||||
export const longAsciiLogo = `
|
||||
██████ ██╗ ███████╗████████╗████████╗ █████╗ ██████╗ ██████╗ ██████╗ ███████╗
|
||||
██ ██ ██║ ██╔════╝╚══██╔══╝╚══██╔══╝██╔══██╗ ██╔════╝██╔═══██╗██╔══██╗██╔════╝
|
||||
██ ▇▇ ██ ██║ █████╗ ██║ ██║ ███████║ ██║ ██║ ██║██║ ██║█████╗
|
||||
██ ██ ██║ ██╔══╝ ██║ ██║ ██╔══██║ ██║ ██║ ██║██║ ██║██╔══╝
|
||||
██████ ███████╗███████╗ ██║ ██║ ██║ ██║ ╚██████╗╚██████╔╝██████╔╝███████╗
|
||||
╚═════╝ ╚══════╝╚══════╝ ╚═╝ ╚═╝ ╚═╝ ╚═╝ ╚═════╝ ╚═════╝ ╚═════╝ ╚══════╝
|
||||
`;
|
||||
|
||||
export const tinyAsciiLogo = `
|
||||
██████ ██╗ ███████╗████████╗████████╗ █████╗
|
||||
██ ██ ██║ ██╔════╝╚══██╔══╝╚══██╔══╝██╔══██╗
|
||||
██ ▇▇ ██ ██║ █████╗ ██║ ██║ ███████║
|
||||
██ ██ ██║ ██╔══╝ ██║ ██║ ██╔══██║
|
||||
██████ ███████╗███████╗ ██║ ██║ ██║ ██║
|
||||
╚═════╝ ╚══════╝╚══════╝ ╚═╝ ╚═╝ ╚═╝ ╚═╝
|
||||
`;
|
||||
|
||||
export const asciiLogo = `
|
||||
██████
|
||||
██ ██
|
||||
██ ▇▇ ██
|
||||
██ ██
|
||||
██████
|
||||
`;
|
||||
@@ -1,5 +1,5 @@
|
||||
import { Box, Text, useInput } from "ink";
|
||||
import { type ComponentType, memo, useState } from "react";
|
||||
import { memo, useState } from "react";
|
||||
import { resolvePlaceholders } from "../helpers/pasteRegistry";
|
||||
import { colors } from "./colors";
|
||||
import { MarkdownDisplay } from "./MarkdownDisplay";
|
||||
|
||||
@@ -1,4 +1,9 @@
|
||||
import type { Letta } from "@letta-ai/letta-client";
|
||||
import { Box, Text } from "ink";
|
||||
import Link from "ink-link";
|
||||
import { getVersion } from "../../version";
|
||||
import { useTerminalWidth } from "../hooks/useTerminalWidth";
|
||||
import { asciiLogo } from "./AsciiArt";
|
||||
import { colors } from "./colors";
|
||||
|
||||
type LoadingState =
|
||||
@@ -11,43 +16,128 @@ type LoadingState =
|
||||
export function WelcomeScreen({
|
||||
loadingState,
|
||||
continueSession,
|
||||
agentId,
|
||||
agentState,
|
||||
terminalWidth: frozenWidth,
|
||||
}: {
|
||||
loadingState: LoadingState;
|
||||
continueSession?: boolean;
|
||||
agentId?: string;
|
||||
agentState?: Letta.AgentState | null;
|
||||
terminalWidth?: number;
|
||||
}) {
|
||||
const getInitializingMessage = () => {
|
||||
if (continueSession && agentId) {
|
||||
return `Resuming agent ${agentId}...`;
|
||||
const currentWidth = useTerminalWidth();
|
||||
const terminalWidth = frozenWidth ?? currentWidth;
|
||||
const cwd = process.cwd();
|
||||
const version = getVersion();
|
||||
const agentId = agentState?.id;
|
||||
|
||||
// Split logo into lines for side-by-side rendering
|
||||
const logoLines = asciiLogo.trim().split("\n");
|
||||
|
||||
// Determine verbosity level based on terminal width
|
||||
const isWide = terminalWidth >= 120;
|
||||
const isMedium = terminalWidth >= 80;
|
||||
|
||||
const getMemoryBlocksText = () => {
|
||||
if (!agentState?.memory?.blocks) {
|
||||
return null;
|
||||
}
|
||||
return "Creating agent...";
|
||||
|
||||
const blocks = agentState.memory.blocks;
|
||||
const count = blocks.length;
|
||||
const labels = blocks
|
||||
.map((b) => b.label)
|
||||
.filter(Boolean)
|
||||
.join(", ");
|
||||
|
||||
if (isWide && labels) {
|
||||
return `attached ${count} memory block${count !== 1 ? "s" : ""} (${labels})`;
|
||||
}
|
||||
if (isMedium) {
|
||||
return `attached ${count} memory block${count !== 1 ? "s" : ""}`;
|
||||
}
|
||||
return null;
|
||||
};
|
||||
|
||||
const getReadyMessage = () => {
|
||||
if (continueSession && agentId) {
|
||||
return `Resumed agent (${agentId}). Ready to go!`;
|
||||
const getAgentMessage = () => {
|
||||
if (loadingState === "ready") {
|
||||
const memoryText = getMemoryBlocksText();
|
||||
const baseText =
|
||||
continueSession && agentId
|
||||
? "Resumed agent"
|
||||
: agentId
|
||||
? "Created a new agent"
|
||||
: "Ready to go!";
|
||||
|
||||
if (memoryText) {
|
||||
return `${baseText}, ${memoryText}`;
|
||||
}
|
||||
return baseText;
|
||||
}
|
||||
if (agentId) {
|
||||
return `Created a new agent (${agentId}). Ready to go!`;
|
||||
if (loadingState === "initializing") {
|
||||
return continueSession ? "Resuming agent..." : "Creating agent...";
|
||||
}
|
||||
return "Ready to go!";
|
||||
if (loadingState === "assembling") {
|
||||
return "Assembling tools...";
|
||||
}
|
||||
if (loadingState === "upserting") {
|
||||
return "Upserting tools...";
|
||||
}
|
||||
if (loadingState === "checking") {
|
||||
return "Checking for pending approvals...";
|
||||
}
|
||||
return "";
|
||||
};
|
||||
|
||||
const stateMessages: Record<LoadingState, string> = {
|
||||
assembling: "Assembling tools...",
|
||||
upserting: "Upserting tools...",
|
||||
initializing: getInitializingMessage(),
|
||||
checking: "Checking for pending approvals...",
|
||||
ready: getReadyMessage(),
|
||||
const getPathLine = () => {
|
||||
if (isMedium) {
|
||||
return `Running in ${cwd}`;
|
||||
}
|
||||
return cwd;
|
||||
};
|
||||
|
||||
const getAgentLink = () => {
|
||||
if (loadingState === "ready" && agentId) {
|
||||
const url = `https://app.letta.com/projects/default-project/agents/${agentId}`;
|
||||
if (isWide) {
|
||||
return { text: url, url };
|
||||
}
|
||||
if (isMedium) {
|
||||
return { text: agentId, url };
|
||||
}
|
||||
return { text: "Click to view in ADE", url };
|
||||
}
|
||||
return null;
|
||||
};
|
||||
|
||||
const agentMessage = getAgentMessage();
|
||||
const pathLine = getPathLine();
|
||||
const agentLink = getAgentLink();
|
||||
|
||||
return (
|
||||
<Box flexDirection="column">
|
||||
<Text bold color={colors.welcome.accent}>
|
||||
Letta Code
|
||||
</Text>
|
||||
<Text dimColor>{stateMessages[loadingState]}</Text>
|
||||
<Box flexDirection="row" marginTop={1}>
|
||||
{/* Left column: Logo */}
|
||||
<Box flexDirection="column" paddingLeft={1} paddingRight={2}>
|
||||
{logoLines.map((line, idx) => (
|
||||
// biome-ignore lint/suspicious/noArrayIndexKey: Logo lines are static and never reorder
|
||||
<Text key={idx} bold color={colors.welcome.accent}>
|
||||
{idx === 0 ? ` ${line}` : line}
|
||||
</Text>
|
||||
))}
|
||||
</Box>
|
||||
|
||||
{/* Right column: Text info */}
|
||||
<Box flexDirection="column" marginTop={0}>
|
||||
<Text bold color={colors.welcome.accent}>
|
||||
Letta Code v{version}
|
||||
</Text>
|
||||
<Text dimColor>{pathLine}</Text>
|
||||
{agentMessage && <Text dimColor>{agentMessage}</Text>}
|
||||
{agentLink && (
|
||||
<Link url={agentLink.url}>
|
||||
<Text dimColor>{agentLink.text}</Text>
|
||||
</Link>
|
||||
)}
|
||||
</Box>
|
||||
</Box>
|
||||
);
|
||||
}
|
||||
|
||||
12
src/cli/helpers/asciiUtils.ts
Normal file
12
src/cli/helpers/asciiUtils.ts
Normal file
@@ -0,0 +1,12 @@
|
||||
/**
|
||||
* Calculates the maximum width of a multi-line ASCII art string.
|
||||
* @param asciiArt The ASCII art string.
|
||||
* @returns The length of the longest line in the ASCII art.
|
||||
*/
|
||||
export function getAsciiArtWidth(asciiArt: string): number {
|
||||
if (!asciiArt) {
|
||||
return 0;
|
||||
}
|
||||
const lines = asciiArt.split("\n");
|
||||
return Math.max(...lines.map((line) => line.length));
|
||||
}
|
||||
Reference in New Issue
Block a user