Untitled
unknown
plain_text
9 months ago
21 kB
10
Indexable
/**
* 1) Convert an Arkham-style snippet {"component": "foo", ...} into an object.
*/
function parseArkhamComponent(segment: string): Record<string, string | number | null> {
// 1) Strip outer { } braces
const trimmed = segment.trim();
if (!trimmed.startsWith("{") || !trimmed.endsWith("}")) {
throw new Error("Segment not wrapped in { ... }");
}
const inside = trimmed.slice(1, -1).trim();
// Try parsing as JSON first
try {
const jsonObj = JSON.parse(trimmed);
if (jsonObj.component) {
return jsonObj;
}
} catch (e) {
// Not valid JSON, continue with custom format parsing
}
// 2) Split fields by comma
const fields = inside.split(",").map((p) => p.trim());
const obj: Record<string, string | number | null> = {};
// 3) For each field, split by the first colon
fields.forEach((field) => {
const [rawKey, ...rest] = field.split(":");
if (!rawKey || !rest.length) {
throw new Error("Invalid field: " + field);
}
const key = rawKey.trim();
let val: string | number | null = rest.join(":").trim();
// If it's wrapped in quotes, remove them
if ((val.startsWith('"') && val.endsWith('"')) || (val.startsWith("'") && val.endsWith("'"))) {
val = val.slice(1, -1);
}
// If it's purely numeric, parse as number
else if (/^-?\d+(\.\d+)?$/.test(val)) {
val = parseFloat(val);
}
// Otherwise, treat as a string
else {
val = val.trim();
}
obj[key] = val;
});
return obj;
}
/**
* 2) Break the big text string into {"component": ... } blocks + raw text,
* parse them, and return an array of React Nodes.
*/
export function ParseAiString(text: string, chain?: string, color = cssVar("gray")) {
// First, try to unescape any escaped JSON strings in the text
text = text.replace(/\\"/g, '"');
const newlineRegex = /\n+/g; // Match one or more newlines
const nodes: React.ReactNode[] = [];
// Parse components by finding {"component": ...} with proper brace counting for nested objects
const parts: (string | Record<string, any>)[] = [];
let lastIndex = 0;
for (let i = 0; i < text.length; i++) {
// Look for start of component: {"component"
if (text.substring(i, i + 1) === "{" && (text.substring(i + 1, i + 12) === '"component"' || text.substring(i + 1, i + 11) === "component:")) {
// Add text before this component
if (i > lastIndex) {
parts.push(text.slice(lastIndex, i));
}
// Count braces to find the complete JSON object
let braceCount = 0;
let jsonEnd = i;
for (let j = i; j < text.length; j++) {
if (text[j] === "{") braceCount++;
if (text[j] === "}") braceCount--;
if (braceCount === 0) {
jsonEnd = j + 1;
break;
}
}
const jsonString = text.slice(i, jsonEnd);
try {
const parsed = JSON.parse(jsonString);
parts.push(parsed);
lastIndex = jsonEnd;
i = jsonEnd - 1;
} catch (e) {
// If parsing fails, treat as regular text
parts.push(jsonString);
lastIndex = jsonEnd;
i = jsonEnd - 1;
}
}
}
// Add remaining text
if (lastIndex < text.length) {
parts.push(text.slice(lastIndex));
}
for (let i = 0; i < parts.length; i++) {
const segment = parts[i];
// Handle plain text segments
if (typeof segment === "string") {
if (segment) {
// Split plain text by newlines and add spacing for deliberate newlines
const lines = segment.split(newlineRegex);
lines.forEach((line, index) => {
if (line) {
// Check if line starts with "* " for bullet point
const isBulletPoint = line.trimStart().startsWith("* ");
const bulletContent = isBulletPoint ? line.trimStart().substring(2) : line;
const contentToRender = isBulletPoint ? bulletContent : line;
// Replace "⇌" with the swap icon
const swapReplaced = contentToRender.split("⇌").map((part, j) =>
j > 0 ? (
<span key={`swap-${i}-${j}`}>
{/* put on the baseline */}
<div className={styles.llmSwapIcon}>
<IoIosSwap />
</div>
{part}
</span>
) : (
part
)
);
if (isBulletPoint) {
nodes.push(
<div key={`text-${i}-${index}`} style={{ color: color, display: "flex", gap: "0.5rem", marginLeft: "1rem" }}>
<span style={{ flexShrink: 0 }}>•</span>
<span>{swapReplaced}</span>
</div>
);
} else {
nodes.push(
<span key={`text-${i}-${index}`} style={{ color: color }}>
{swapReplaced}
</span>
);
}
}
// Add spacing between deliberate newlines
if (index < lines.length - 1) {
nodes.push(<div key={`newline-${i}-${index}`} style={{ height: "1rem" }} />);
}
});
}
continue;
}
// Handle component objects (already parsed)
const parsed = segment;
try {
switch (parsed.component) {
case "address": {
const addressString = typeof parsed.address === "string" ? parsed.address : "";
nodes.push(
<span key={i} style={{ display: "inline-block", color: cssVar("blue"), lineHeight: "1em" }}>
<Address
address={{ address: addressString, chain: chain as Chain }}
shorthand={true}
ignoreChain={chain != "solana"}
baseline
addressTitle
llmTransactionAddress
copyToClipboard={false}
/>
</span>
);
break;
}
case "amount": {
const tokenAmount = typeof parsed.tokenAmount === "number" ? parsed.tokenAmount : undefined;
const usdValue = typeof parsed.usdValue === "number" ? parsed.usdValue : undefined;
// Determine if we should show USD value:
// - If only USD value is specified, show it even if 0
// - If both tokenAmount and usdValue exist, only show USD if it's not 0
const shouldShowUsd = usdValue != null && (tokenAmount == null || usdValue != 0);
nodes.push(
<span key={i} className={styles.llmTransactionAmount}>
{tokenAmount != null && (
<span style={{ color: cssVar("off-white") }}>
<Number value={tokenAmount} compact={true} />
</span>
)}
{tokenAmount != null && shouldShowUsd && " "}
{shouldShowUsd && (
<span>
{tokenAmount != null ? (
<span style={{ color: usdValue < 0 ? cssVar("red") : usdValue > 0 ? cssVar("green") : cssVar("gray") }}>
(
<Number
showSign={usdValue < 0}
colored={true}
value={usdValue}
usd
compact={true}
decimals={usdValue > 0.01 ? 2 : Math.max(2, -Math.floor(Math.log10(Math.abs(usdValue)))) + 2}
/>
)
</span>
) : (
<>
<Number
showSign={usdValue < 0}
colored={true}
value={usdValue}
compact={true}
usd
decimals={usdValue > 0.01 ? 2 : Math.max(2, -Math.floor(Math.log10(Math.abs(usdValue)))) + 2}
/>
</>
)}
</span>
)}
</span>
);
break;
}
case "token": {
const tokenSymbol = typeof parsed.tokenSymbol === "string" ? parsed.tokenSymbol : "";
const tokenId = typeof parsed.tokenId === "string" ? parsed.tokenId : undefined;
const address = typeof parsed.address === "string" ? parsed.address : undefined;
const href = tokenId && tokenId != "" ? `/explorer/token/${tokenId}` : `/explorer/token/${chain}/${address}`;
nodes.push(
<span key={i} style={{ display: "inline-flex" }}>
<Link className={styles.underline} href={href}>
<WithIcon type="token" id={tokenId} className={styles.llmTransactionToken} baseline={true}>
{tokenSymbol.toUpperCase()}
</WithIcon>
</Link>
</span>
);
break;
}
case "instrument": {
const instrumentSymbol = typeof parsed.instrumentSymbol === "string" ? parsed.instrumentSymbol : "";
const baseTokenId = typeof parsed.baseTokenId === "string" ? parsed.baseTokenId : "";
const quoteTokenId = typeof parsed.quoteTokenId === "string" ? parsed.quoteTokenId : "";
const singleIcon = baseTokenId && (!quoteTokenId || quoteTokenId === "");
const href = `/explorer/token/${baseTokenId}`;
nodes.push(
<span key={i} style={{ display: "inline-flex", alignItems: "center" }}>
{/* token icons stacked/overlapped */}
{!singleIcon ? (
<>
<span className={styles.instrumentIcons /* see CSS stub below */}>
<WithIcon type="token" id={baseTokenId} className={styles.instrumentIconBase} baseline />
<WithIcon type="token" id={quoteTokenId} className={styles.instrumentIconQuote} baseline />
</span>
<Link className={styles.underline} href={href}>
<span className={styles.instrumentSymbol /* optional style */}>{instrumentSymbol.toUpperCase()}</span>
</Link>
</>
) : (
<Link className={styles.underline} href={href}>
<WithIcon type="token" id={baseTokenId} className={styles.instrumentIconSingle} baseline={true}>
{instrumentSymbol.toUpperCase()}
</WithIcon>
</Link>
)}
</span>
);
break;
}
case "instrument": {
console.log("Parsing instrument component:", parsed);
const instrumentSymbol = typeof parsed.instrumentSymbol === "string" ? parsed.instrumentSymbol : "";
const baseTokenId = typeof parsed.baseTokenId === "string" ? parsed.baseTokenId : "";
const quoteTokenId = typeof parsed.quoteTokenId === "string" ? parsed.quoteTokenId : "";
const singleIcon = baseTokenId && (!quoteTokenId || quoteTokenId === "");
console.log("Parsed instrument:", parsed, baseTokenId, quoteTokenId, singleIcon);
const href = `/explorer/token/${baseTokenId}`;
nodes.push(
<>
{!singleIcon ? (
<span key={i} style={{ display: "inline-flex", alignItems: "center" }}>
{/* token icons */}
<Link className={styles.underline} href={href}>
<span className={styles.instrumentIcons}>
<WithIcon type="token" id={baseTokenId} className={styles.instrumentIconBase} baseline />
<WithIcon type="token" id={quoteTokenId} className={styles.instrumentIconQuote} baseline />
</span>
</Link>
<span className={styles.instrumentSymbol}>{instrumentSymbol.toUpperCase()}</span>
</span>
) : (
<span key={i} style={{ display: "inline-flex" }}>
<Link className={styles.underline} href={href}>
<WithIcon type="token" id={baseTokenId} className={styles.llmTransactionToken} baseline={true}>
{instrumentSymbol.toUpperCase()}
</WithIcon>
</Link>
</span>
)}
</>
);
break;
}
case "entity": {
const entityId = typeof parsed.entityId === "string" ? parsed.entityId : undefined;
const entityName = typeof parsed.entityName === "string" ? parsed.entityName : "";
nodes.push(
<span key={i} style={{ display: "inline-flex" }}>
<Link className={styles.underline} href={`/explorer/entity/${entityId}`}>
<WithIcon type="entity" id={entityId} className={styles.llmTransactionToken} baseline={true}>
{entityName}
</WithIcon>
</Link>
</span>
);
break;
}
case "dashboard-unit": {
const config = parsed.config;
if (config && typeof config === "object") {
// Render dashboard unit inline
const unitConfig = config as any; // Unit config from parsed JSON
nodes.push(
<div key={i} style={{ marginTop: "1rem", marginBottom: "1rem", maxHeight: "40em", overflow: "auto" }}>
<DashboardUnitComponent unit={{ ...unitConfig, id: "", width: 4, height: 4, top: 0, left: 0 }} updateUnit={() => {}} />
</div>
);
}
break;
}
case "instruction": {
const instruction = typeof parsed.instruction === "string" ? parsed.instruction : "";
nodes.push(
<span key={i} style={{ display: "inline-block" }}>
<div className={styles.solanaLlmInstruction}>{instruction}</div>
</span>
);
break;
}
case "tx": {
const txHash = typeof parsed.txHash === "string" ? parsed.txHash : "";
const order = typeof parsed.role === "number" ? parsed.role : 1;
const analyzedTxOrder = typeof parsed.isTarget === "number" ? parsed.isTarget : 0;
// Determine label and color based on `order`
let label = "";
let boxColorClass = ""; // CSS class controlling the box color
if (order === 1) {
label = "FRONTRUN:";
boxColorClass = styles.redBox; // e.g. background-color: var(--red);
} else if (order === 2) {
label = "VICTIM:";
boxColorClass = styles.greenBox; // e.g. background-color: var(--green);
} else {
label = "BACKRUN:";
boxColorClass = styles.redBox; // e.g. background-color: var(--red);
}
// Shorten the transaction hash
const shortHash = txHash.slice(0, 5);
// Add spacing before the component
nodes.push(<br key={`linebreak-before-${i}`} />);
nodes.push(<br key={`linebreak-before-${i}-2`} />);
// Push the styled component to the `nodes` array
nodes.push(
<span key={i} className={`$ ${boxColorClass}`}>
{label}
<span style={{ color: cssVar("gray"), fontWeight: "normal" }}>{" In "}</span>
{analyzedTxOrder ? (
// Render "(This TX)" if the order matches the analyzed tx order
<span className={styles.thisTx}>this TX</span>
) : (
<>
<span className={styles.thisTx}>TX:</span>
<Link href={`/explorer/tx/${txHash}`} className={styles.txLink} target="_blank" rel="noopener noreferrer">
<>{shortHash}</>
</Link>
</>
)}
<span className={styles.thisTx}>,</span>
</span>
);
break;
}
case "txHash": {
const txHash = typeof parsed.txHash === "string" ? parsed.txHash : "";
const datetime = typeof parsed.datetime === "string" ? parsed.datetime : "";
// Render the txHash component with the same styling as the time column in transfer panel
nodes.push(
<Link key={i} href={`/explorer/tx/${txHash}`} className={styles.txHashLink} target="_blank" rel="noopener noreferrer">
{datetime}
</Link>
);
break;
}
default:
// Unknown component type - render as JSON string
nodes.push(<span key={i}>{JSON.stringify(parsed)}</span>);
break;
}
} catch (err) {
console.error("Failed to render component:", parsed, err);
nodes.push(<span key={i}>{JSON.stringify(parsed)}</span>);
}
}
return nodes;
}
Editor is loading...
Leave a Comment