54 lines
2 KiB
TypeScript
54 lines
2 KiB
TypeScript
import type { Part } from "@opencode-ai/sdk/v2"
|
|
import { Locale } from "../../util/locale"
|
|
|
|
export function relativeTime(timestamp: number): string {
|
|
const diff = Date.now() - timestamp
|
|
if (diff < 0) return "just now"
|
|
const seconds = Math.floor(diff / 1000)
|
|
if (seconds < 60) return "just now"
|
|
const minutes = Math.floor(seconds / 60)
|
|
if (minutes < 60) return `${minutes}m ago`
|
|
const hours = Math.floor(minutes / 60)
|
|
if (hours < 24) return `${hours}h ago`
|
|
const days = Math.floor(hours / 24)
|
|
if (days < 7) return `${days}d ago`
|
|
const d = new Date(timestamp)
|
|
return d.toLocaleDateString(undefined, { month: "short", day: "numeric" })
|
|
}
|
|
|
|
export function extractMessageText(parts: readonly Part[], maxLength: number): string {
|
|
const joined = collectTextParts(parts).join(" ").replace(/\s+/g, " ").trim()
|
|
return Locale.truncate(joined, maxLength)
|
|
}
|
|
|
|
export function extractMessageMarkdown(parts: readonly Part[], maxLines: number, maxChars: number): string {
|
|
const joined = collectTextParts(parts).join("\n\n").trim()
|
|
if (!joined) return joined
|
|
|
|
let truncated = joined
|
|
const lines = truncated.split("\n")
|
|
if (lines.length > maxLines) {
|
|
truncated = lines.slice(0, maxLines).join("\n")
|
|
}
|
|
if (truncated.length > maxChars) {
|
|
truncated = truncated.slice(0, maxChars).trimEnd()
|
|
}
|
|
if (truncated.length === joined.length) return joined
|
|
// Close any unterminated fenced code block so the renderer doesn't keep
|
|
// the rest of the panel in "code mode".
|
|
const fences = (truncated.match(/^```/gm) ?? []).length
|
|
if (fences % 2 === 1) truncated += "\n```"
|
|
return truncated + "\n\n…"
|
|
}
|
|
|
|
function collectTextParts(parts: readonly Part[]): string[] {
|
|
const chunks: string[] = []
|
|
for (const part of parts) {
|
|
if (part.type !== "text") continue
|
|
const p = part as Part & { type: "text"; text: string; synthetic?: boolean; ignored?: boolean }
|
|
if (p.synthetic || p.ignored) continue
|
|
if (!p.text) continue
|
|
chunks.push(p.text)
|
|
}
|
|
return chunks
|
|
}
|