fa7bce0789
Single-scoped helpers/sub-files were sitting flat in shared/grab-bag dirs.
Fold each family into its own folder (index = the export, dir resolution keeps
public import paths intact), dropping the now-redundant filename prefix:
- session/hooks/use-prompt-actions.ts (+ -utils, + tests)
-> use-prompt-actions/{index,utils}.ts (+ tests)
- components/assistant-ui/thread* + assistant/system/user message renderers
-> assistant-ui/thread/{index,content,status,message-parts,timestamp,types,
list,timeline,timeline-data,assistant-message,system-message,user-message,
user-edit-composer,user-message-text} (+ tests)
- components/assistant-ui/tool-fallback(+model)/tool-approval
-> assistant-ui/tool/{fallback,fallback-model,approval} (+ tests)
Pure move + import rewrites; no behaviour change. App-wide shared primitives
(markdown-text, directive-text, tooltip-icon-button, clarify-tool, ansi-text,
message-render-boundary) stay flat. desktop-controller intentionally left in
app/ (route root; foldering would churn ~80 relative imports for no gain).
64 lines
1.7 KiB
TypeScript
64 lines
1.7 KiB
TypeScript
const EMPTY_ATTACHMENT_REFS: string[] = []
|
|
|
|
export function partText(part: unknown): string {
|
|
if (typeof part === 'string') {
|
|
return part
|
|
}
|
|
|
|
if (!part || typeof part !== 'object') {
|
|
return ''
|
|
}
|
|
|
|
const row = part as { text?: unknown; type?: unknown }
|
|
|
|
return (!row.type || row.type === 'text') && typeof row.text === 'string' ? row.text : ''
|
|
}
|
|
|
|
export function messageContentText(content: unknown): string {
|
|
if (typeof content === 'string') {
|
|
return content.trim()
|
|
}
|
|
|
|
return Array.isArray(content) ? content.map(partText).join('').trim() : ''
|
|
}
|
|
|
|
// Cheap streaming-stable "does this message have visible text" check: returns
|
|
// on the first non-whitespace text part without concatenating the whole
|
|
// message. Used as a useAuiState selector so its boolean output stays stable
|
|
// across token flushes (flips false→true once per turn).
|
|
export function contentHasVisibleText(content: unknown): boolean {
|
|
if (typeof content === 'string') {
|
|
return content.trim().length > 0
|
|
}
|
|
|
|
if (!Array.isArray(content)) {
|
|
return false
|
|
}
|
|
|
|
for (const part of content) {
|
|
if (partText(part).trim().length > 0) {
|
|
return true
|
|
}
|
|
}
|
|
|
|
return false
|
|
}
|
|
|
|
export function messageAttachmentRefs(value: unknown): string[] {
|
|
if (!Array.isArray(value)) {
|
|
return EMPTY_ATTACHMENT_REFS
|
|
}
|
|
|
|
return value.every(ref => typeof ref === 'string') ? value : EMPTY_ATTACHMENT_REFS
|
|
}
|
|
|
|
export function pickPrimaryPreviewTarget(targets: string[]): string[] {
|
|
if (targets.length <= 1) {
|
|
return targets
|
|
}
|
|
|
|
const localUrl = targets.find(value => /^https?:\/\/(?:localhost|127\.0\.0\.1|0\.0\.0\.0|\[::1\])/i.test(value))
|
|
|
|
return [localUrl || targets[targets.length - 1]]
|
|
}
|