hololake-system-architecture/product-source/hololake-platform/src/lib/aiBrowserObservation.ts

87 lines
2.8 KiB
TypeScript

import type { AiAgentMessage } from './aiAgentConversation'
interface BrowserNodePreview {
nodeId: string
text: string
}
export interface BrowserObservation {
documentId?: string
url: string
totalCharacters?: number
totalNodes?: number
nodes: BrowserNodePreview[]
status: 'pending' | 'done' | 'error'
}
function parseObject(value?: string): Record<string, unknown> | null {
if (!value) return null
try {
const parsed: unknown = JSON.parse(value)
return typeof parsed === 'object' && parsed !== null && !Array.isArray(parsed)
? parsed as Record<string, unknown>
: null
} catch {
return null
}
}
function stringField(object: Record<string, unknown> | null, ...keys: string[]): string | undefined {
for (const key of keys) {
const value = object?.[key]
if (typeof value === 'string' && value.trim()) return value.trim()
}
return undefined
}
function numberField(object: Record<string, unknown> | null, ...keys: string[]): number | undefined {
for (const key of keys) {
const value = object?.[key]
if (typeof value === 'number' && Number.isFinite(value)) return value
}
return undefined
}
function browserAction(action: AiAgentMessage['actions'][number]): boolean {
if (action.tool === 'read_web_page' || action.tool === 'read_web_page_nodes') return true
const input = parseObject(action.input)
if (action.tool !== 'magic_brush') return false
const steps = input?.steps
return Array.isArray(steps) && steps.some((step) => (
typeof step === 'object'
&& step !== null
&& ['read_web_page', 'read_web_page_nodes'].includes(String((step as Record<string, unknown>).tool))
))
}
function previewNodes(output: Record<string, unknown> | null): BrowserNodePreview[] {
const raw = output?.node_previews ?? output?.nodes
if (!Array.isArray(raw)) return []
return raw.flatMap((item) => {
if (typeof item !== 'object' || item === null) return []
const node = item as Record<string, unknown>
const nodeId = stringField(node, 'node_id', 'nodeId')
const text = stringField(node, 'text')
return nodeId && text ? [{ nodeId, text }] : []
})
}
export function latestBrowserObservation(messages: AiAgentMessage[]): BrowserObservation | null {
const actions = messages.flatMap((message) => message.actions).filter(browserAction)
const action = actions.at(-1)
if (!action) return null
const input = parseObject(action.input)
const output = parseObject(action.output)
const url = stringField(output, 'url') ?? stringField(input, 'url')
if (!url) return null
return {
documentId: stringField(output, 'document_id', 'documentId'),
url,
totalCharacters: numberField(output, 'total_characters', 'totalCharacters'),
totalNodes: numberField(output, 'total_nodes', 'totalNodes'),
nodes: previewNodes(output),
status: action.status,
}
}