feat: Deep Research section
This commit is contained in:
@ -1,345 +1,162 @@
|
|||||||
<template>
|
|
||||||
<div class="h-screen flex">
|
|
||||||
<!-- 左侧树形图 -->
|
|
||||||
<div class="w-1/2 h-full bg-transparent" ref="treeContainer">
|
|
||||||
<div v-if="!modelValue.root" class="h-full flex items-center justify-center text-gray-500 dark:text-gray-400">
|
|
||||||
<div class="text-center">
|
|
||||||
<div class="text-lg mb-2">No research data</div>
|
|
||||||
<div class="text-sm">Please answer and submit the questions to start research</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<svg
|
|
||||||
v-else
|
|
||||||
width="100%"
|
|
||||||
height="100%"
|
|
||||||
@click="handleSvgClick"
|
|
||||||
class="[&_.link]:stroke-2 [&_.link]:fill-none [&_.link]:stroke-gray-400 dark:[&_.link]:stroke-gray-600 [&_.link.processing]:stroke-blue-500 [&_.link.complete]:stroke-green-500 [&_.link.error]:stroke-red-500"
|
|
||||||
>
|
|
||||||
<g :transform="`translate(${margin.left}, ${margin.top})`">
|
|
||||||
<!-- 连接线 -->
|
|
||||||
<g class="links">
|
|
||||||
<path v-for="link in treeData.links" :key="link.id" :d="link.path" class="link" :class="link.status" />
|
|
||||||
</g>
|
|
||||||
<!-- 节点 -->
|
|
||||||
<g class="nodes">
|
|
||||||
<g
|
|
||||||
v-for="node in treeData.nodes"
|
|
||||||
:key="node.id"
|
|
||||||
class="node cursor-pointer transition-all"
|
|
||||||
:class="[node.status, { active: selectedNode?.id === node.id }]"
|
|
||||||
:transform="`translate(${node.x}, ${node.y})`"
|
|
||||||
@click.stop="handleNodeClick(node)"
|
|
||||||
@mouseover="handleNodeHover(node)"
|
|
||||||
>
|
|
||||||
<circle
|
|
||||||
r="20"
|
|
||||||
class="fill-white dark:fill-gray-700 stroke-2 stroke-gray-400 dark:stroke-gray-500 [.processing_&]:stroke-blue-500 [.complete_&]:stroke-green-500 [.error_&]:stroke-red-500 [.active_&]:stroke-[3px] [.active_&]:fill-gray-100 dark:[.active_&]:fill-gray-800"
|
|
||||||
/>
|
|
||||||
<text dy=".35em" text-anchor="middle" class="fill-gray-900 dark:fill-gray-100 text-sm select-none">
|
|
||||||
{{ node.depth }}
|
|
||||||
</text>
|
|
||||||
</g>
|
|
||||||
</g>
|
|
||||||
</g>
|
|
||||||
</svg>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<!-- 右侧内容区 -->
|
|
||||||
<div class="w-1/2 h-full p-4 border-l border-gray-200 dark:border-gray-700 overflow-y-auto">
|
|
||||||
<div v-if="selectedNode" class="sticky top-0">
|
|
||||||
<h3 class="text-lg font-bold mb-2 dark:text-gray-200">Search Detail</h3>
|
|
||||||
<div class="mb-4">
|
|
||||||
<div class="font-medium dark:text-gray-300">Query:</div>
|
|
||||||
<div class="text-gray-600 dark:text-gray-400">{{ selectedNode.query }}</div>
|
|
||||||
</div>
|
|
||||||
<div v-if="selectedNode.result">
|
|
||||||
<div class="font-medium mb-2 dark:text-gray-300">Learning Content:</div>
|
|
||||||
<ul class="list-disc pl-5 mb-4">
|
|
||||||
<li v-for="(learning, i) in selectedNode.result.learnings" :key="i" class="text-gray-600 dark:text-gray-400">
|
|
||||||
{{ learning }}
|
|
||||||
</li>
|
|
||||||
</ul>
|
|
||||||
<div class="font-medium mb-2 dark:text-gray-300">Follow-up Questions:</div>
|
|
||||||
<ul class="list-disc pl-5">
|
|
||||||
<li v-for="(question, i) in selectedNode.result.followUpQuestions" :key="i" class="text-gray-600 dark:text-gray-400">
|
|
||||||
{{ question }}
|
|
||||||
</li>
|
|
||||||
</ul>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div v-else class="h-full flex items-center justify-center text-gray-500 dark:text-gray-400 text-center">
|
|
||||||
Select a node to view details
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import * as d3 from 'd3'
|
import { deepResearch, type PartialSearchResult, type ResearchStep } from '~/lib/deep-research'
|
||||||
import { deepResearch, type ResearchStep } from '~/lib/deep-research'
|
import type { TreeNode } from './Tree.vue'
|
||||||
import type { ResearchFeedbackResult } from './ResearchFeedback.vue'
|
|
||||||
|
|
||||||
export interface SearchNode {
|
const tree = ref<TreeNode>({
|
||||||
id: string
|
id: 'root',
|
||||||
query: string
|
label: 'Start',
|
||||||
depth: number
|
children: [],
|
||||||
status: 'pending' | 'processing' | 'complete' | 'error'
|
|
||||||
children: SearchNode[]
|
|
||||||
result?: {
|
|
||||||
learnings: string[]
|
|
||||||
followUpQuestions: string[]
|
|
||||||
}
|
|
||||||
// 布局相关属性
|
|
||||||
x?: number
|
|
||||||
y?: number
|
|
||||||
parent?: SearchNode
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface SearchTree {
|
|
||||||
root: SearchNode | null
|
|
||||||
currentDepth: number
|
|
||||||
maxDepth: number
|
|
||||||
maxBreadth: number
|
|
||||||
}
|
|
||||||
|
|
||||||
const modelValue = defineModel<SearchTree>({
|
|
||||||
default: () => ({
|
|
||||||
root: null,
|
|
||||||
currentDepth: 0,
|
|
||||||
maxDepth: 0,
|
|
||||||
maxBreadth: 0,
|
|
||||||
}),
|
|
||||||
})
|
|
||||||
|
|
||||||
// 树形图布局配置
|
|
||||||
const margin = { top: 40, right: 40, bottom: 40, left: 40 }
|
|
||||||
const treeContainer = ref<HTMLElement>()
|
|
||||||
const width = ref(800)
|
|
||||||
const height = ref(600)
|
|
||||||
|
|
||||||
// 节点状态管理
|
|
||||||
const selectedNode = ref<SearchNode>()
|
|
||||||
|
|
||||||
// 计算节点和连接线
|
|
||||||
const treeData = computed(() => {
|
|
||||||
if (!modelValue.value.root) return { nodes: [], links: [] }
|
|
||||||
|
|
||||||
// 计算合适的树大小
|
|
||||||
const levels = getTreeDepth(modelValue.value.root)
|
|
||||||
const estimatedHeight = Math.max(levels * 20, 300) // 每层至少 20px
|
|
||||||
height.value = Math.min(estimatedHeight, window.innerHeight - 100) // 限制最大高度
|
|
||||||
|
|
||||||
const treeLayout = d3
|
|
||||||
.tree<SearchNode>()
|
|
||||||
.size([width.value - margin.left - margin.right, height.value - margin.top - margin.bottom])
|
|
||||||
.separation((a, b) => (a.parent === b.parent ? 1.5 : 2))
|
|
||||||
|
|
||||||
const root = d3.hierarchy(modelValue.value.root)
|
|
||||||
const layout = treeLayout(root)
|
|
||||||
|
|
||||||
const nodes = layout.descendants().map((d) => ({
|
|
||||||
...d.data,
|
|
||||||
x: d.x,
|
|
||||||
y: d.y,
|
|
||||||
}))
|
|
||||||
|
|
||||||
const links = layout.links().map((d, i) => ({
|
|
||||||
id: `link-${i}`,
|
|
||||||
path: d3.linkVertical()({
|
|
||||||
source: [d.source.x, d.source.y],
|
|
||||||
target: [d.target.x, d.target.y],
|
|
||||||
}) as string,
|
|
||||||
status: d.target.data.status,
|
|
||||||
}))
|
|
||||||
|
|
||||||
return { nodes, links }
|
|
||||||
})
|
|
||||||
|
|
||||||
// 辅助函数:获取树的深度
|
|
||||||
function getTreeDepth(node: SearchNode): number {
|
|
||||||
if (!node) return 0
|
|
||||||
return 1 + Math.max(0, ...(node.children?.map(getTreeDepth) || []))
|
|
||||||
}
|
|
||||||
|
|
||||||
// 监听节点状态变化
|
|
||||||
watch(
|
|
||||||
() => modelValue.value.root,
|
|
||||||
(newRoot) => {
|
|
||||||
if (newRoot) {
|
|
||||||
// 找到最新更新的节点
|
|
||||||
const currentNode = findCurrentNode(newRoot)
|
|
||||||
if (currentNode) {
|
|
||||||
selectedNode.value = currentNode
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{ deep: true },
|
|
||||||
)
|
|
||||||
|
|
||||||
// 事件处理
|
|
||||||
function handleNodeClick(node: SearchNode) {
|
|
||||||
selectedNode.value = node
|
|
||||||
}
|
|
||||||
|
|
||||||
function handleNodeHover(node: SearchNode) {
|
|
||||||
selectedNode.value = node
|
|
||||||
}
|
|
||||||
|
|
||||||
function handleSvgClick() {
|
|
||||||
selectedNode.value = undefined
|
|
||||||
}
|
|
||||||
|
|
||||||
// 辅助函数:查找指定深度的节点
|
|
||||||
function findNodeAtDepth(node: SearchNode | null, targetDepth: number): SearchNode | null {
|
|
||||||
if (!node) return null
|
|
||||||
if (node.depth === targetDepth) return node
|
|
||||||
if (!node.children?.length) return null
|
|
||||||
|
|
||||||
for (const child of node.children) {
|
|
||||||
const found = findNodeAtDepth(child, targetDepth)
|
|
||||||
if (found) return found
|
|
||||||
}
|
|
||||||
return null
|
|
||||||
}
|
|
||||||
|
|
||||||
// 辅助函数:查找当前正在处理的节点
|
|
||||||
function findCurrentNode(node: SearchNode): SearchNode | null {
|
|
||||||
if (node.status === 'processing') {
|
|
||||||
return node
|
|
||||||
}
|
|
||||||
if (node.children) {
|
|
||||||
for (const child of node.children) {
|
|
||||||
const found = findCurrentNode(child)
|
|
||||||
if (found) return found
|
|
||||||
}
|
|
||||||
}
|
|
||||||
// 如果没有正在处理的节点,返回最后一个完成的节点
|
|
||||||
if (node.status === 'complete' && (!node.children || node.children.length === 0)) {
|
|
||||||
return node
|
|
||||||
}
|
|
||||||
return null
|
|
||||||
}
|
|
||||||
|
|
||||||
// 辅助函数:在树中更新节点
|
|
||||||
function updateNodeInTree(root: SearchNode, nodeId: string, updates: Partial<SearchNode>): SearchNode {
|
|
||||||
if (root.id === nodeId) {
|
|
||||||
return { ...root, ...updates }
|
|
||||||
}
|
|
||||||
return {
|
|
||||||
...root,
|
|
||||||
children: root.children.map((child) => updateNodeInTree(child, nodeId, updates)),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// 监听容器大小变化
|
|
||||||
onMounted(() => {
|
|
||||||
if (treeContainer.value) {
|
|
||||||
const resizeObserver = new ResizeObserver((entries) => {
|
|
||||||
for (const entry of entries) {
|
|
||||||
width.value = entry.contentRect.width
|
|
||||||
height.value = entry.contentRect.height
|
|
||||||
}
|
|
||||||
})
|
|
||||||
resizeObserver.observe(treeContainer.value)
|
|
||||||
}
|
|
||||||
})
|
|
||||||
|
|
||||||
// 处理研究进度
|
|
||||||
function handleResearchProgress(step: ResearchStep) {
|
|
||||||
console.log(step)
|
|
||||||
if (step.type === 'start') {
|
|
||||||
// 初始化搜索树
|
|
||||||
modelValue.value = {
|
|
||||||
root: null,
|
|
||||||
currentDepth: 0,
|
|
||||||
maxDepth: step.depth || 0,
|
|
||||||
maxBreadth: step.breadth || 0,
|
|
||||||
}
|
|
||||||
} else if (step.type === 'generating_queries' && step.result) {
|
|
||||||
// 添加新的查询节点
|
|
||||||
if (step.depth === 1) {
|
|
||||||
// 第一层查询作为根节点
|
|
||||||
modelValue.value = {
|
|
||||||
...modelValue.value,
|
|
||||||
root: {
|
|
||||||
id: '0-0',
|
|
||||||
query: step.result[0].query,
|
|
||||||
depth: 0,
|
depth: 0,
|
||||||
status: 'processing',
|
breadth: 0,
|
||||||
children: step.result.slice(1).map((item, index) => ({
|
})
|
||||||
id: `1-${index}`,
|
const hoveredNode = ref<TreeNode | null>(null)
|
||||||
query: item.query,
|
const searchResults = ref<Record<string, PartialSearchResult>>({})
|
||||||
depth: 1,
|
|
||||||
status: 'pending',
|
const modelValue = computed(() => Object.values(searchResults.value))
|
||||||
children: [],
|
|
||||||
})),
|
function handleResearchProgress(step: ResearchStep) {
|
||||||
},
|
let node: TreeNode | null = null
|
||||||
|
let nodeId = ''
|
||||||
|
|
||||||
|
if (step.type !== 'complete') {
|
||||||
|
nodeId = step.nodeId
|
||||||
|
node = findNode(tree.value, step.nodeId)
|
||||||
|
if (node) {
|
||||||
|
node.status = step.type
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
console.log('step', step)
|
||||||
|
|
||||||
|
switch (step.type) {
|
||||||
|
case 'generating_query': {
|
||||||
|
if (!node) {
|
||||||
|
console.error('Creating new node', {
|
||||||
|
nodeId,
|
||||||
|
depth: step.depth,
|
||||||
|
breadth: step.breadth,
|
||||||
|
index: step.nodeIndex,
|
||||||
|
})
|
||||||
|
// 创建新节点
|
||||||
|
node = {
|
||||||
|
id: nodeId,
|
||||||
|
label: 'Generating...',
|
||||||
|
researchGoal: 'Generating research goal...',
|
||||||
|
learnings: [],
|
||||||
|
children: [],
|
||||||
|
depth: step.depth,
|
||||||
|
breadth: step.breadth,
|
||||||
|
index: step.nodeIndex,
|
||||||
|
}
|
||||||
|
// 如果是根节点的直接子节点
|
||||||
|
if (step.depth === 1) {
|
||||||
|
tree.value.children.push(node)
|
||||||
} else {
|
} else {
|
||||||
const parentNode = findNodeAtDepth(modelValue.value.root!, step.depth! - 1)
|
// 找到父节点并添加
|
||||||
|
const parentNode = findNode(tree.value, getParentNodeId(step.nodeId))
|
||||||
if (parentNode) {
|
if (parentNode) {
|
||||||
const updatedRoot = updateNodeInTree(modelValue.value.root!, parentNode.id, {
|
parentNode.children.push(node)
|
||||||
children: step.result.map((query: any, index: number) => ({
|
|
||||||
id: `${step.depth}-${index}`,
|
|
||||||
query: query.query,
|
|
||||||
depth: step.depth!,
|
|
||||||
status: 'pending',
|
|
||||||
children: [],
|
|
||||||
})),
|
|
||||||
})
|
|
||||||
modelValue.value = {
|
|
||||||
...modelValue.value,
|
|
||||||
root: updatedRoot,
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else if (step.type === 'processing_serach_result' && step.result) {
|
// 更新节点的查询内容
|
||||||
// 更新节点状态和结果
|
if (step.result) {
|
||||||
const nodeId = `${step.depth}-${step.queryIndex}`
|
node.label = step.result.query ?? 'Generating...'
|
||||||
const updatedRoot = updateNodeInTree(modelValue.value.root!, nodeId, {
|
node.researchGoal = step.result.researchGoal
|
||||||
status: 'complete',
|
|
||||||
result: {
|
|
||||||
learnings: step.result.learnings || [],
|
|
||||||
followUpQuestions: step.result.followUpQuestions || [],
|
|
||||||
},
|
|
||||||
})
|
|
||||||
modelValue.value = {
|
|
||||||
...modelValue.value,
|
|
||||||
root: updatedRoot,
|
|
||||||
}
|
}
|
||||||
} else if (step.type === 'error') {
|
break
|
||||||
// 处理错误状态
|
|
||||||
const currentNode = findCurrentNode(modelValue.value.root!)
|
|
||||||
if (currentNode) {
|
|
||||||
const updatedRoot = updateNodeInTree(modelValue.value.root!, currentNode.id, {
|
|
||||||
status: 'error',
|
|
||||||
})
|
|
||||||
modelValue.value = {
|
|
||||||
...modelValue.value,
|
|
||||||
root: updatedRoot,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
case 'generated_query': {
|
||||||
|
if (node) {
|
||||||
}
|
}
|
||||||
|
break
|
||||||
|
}
|
||||||
|
|
||||||
|
case 'searching': {
|
||||||
|
if (node) {
|
||||||
|
// node.label = `Searching: ${node.query}`
|
||||||
|
}
|
||||||
|
break
|
||||||
|
}
|
||||||
|
|
||||||
|
case 'search_complete': {
|
||||||
|
if (node) {
|
||||||
|
// node.label = `Found ${step.urls.length} results for: ${node.query}`
|
||||||
|
}
|
||||||
|
break
|
||||||
|
}
|
||||||
|
|
||||||
|
case 'processing_serach_result': {
|
||||||
|
if (node) {
|
||||||
|
node.learnings = step.result.learnings || []
|
||||||
|
node.followUpQuestions = step.result.followUpQuestions || []
|
||||||
|
// node.label = `Processing results: ${node.query}`
|
||||||
|
}
|
||||||
|
break
|
||||||
|
}
|
||||||
|
|
||||||
|
case 'processed_search_result': {
|
||||||
|
if (node) {
|
||||||
|
node.learnings = step.result.learnings
|
||||||
|
// node.label = `Completed: ${node.query}`
|
||||||
|
searchResults.value[nodeId] = step.result
|
||||||
|
}
|
||||||
|
break
|
||||||
|
}
|
||||||
|
|
||||||
|
case 'error':
|
||||||
|
console.error(`Research error on node ${nodeId}:`, step.message)
|
||||||
|
break
|
||||||
|
|
||||||
|
case 'complete':
|
||||||
|
console.log('Research complete')
|
||||||
|
break
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// 开始研究
|
// 辅助函数:根据节点ID查找节点
|
||||||
async function startResearch(query: string, depth: number, breadth: number, feedback: ResearchFeedbackResult[]) {
|
function findNode(root: TreeNode, targetId: string): TreeNode | null {
|
||||||
modelValue.value = {
|
if (!targetId) return null
|
||||||
root: null,
|
if (root.id === targetId) {
|
||||||
currentDepth: 0,
|
return root
|
||||||
maxDepth: 0,
|
|
||||||
maxBreadth: 0,
|
|
||||||
}
|
}
|
||||||
|
for (const child of root.children) {
|
||||||
|
const found = findNode(child, targetId)
|
||||||
|
if (found) {
|
||||||
|
return found
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
|
||||||
|
// 辅助函数:获取节点的父节点 ID
|
||||||
|
function getParentNodeId(nodeId: string): string {
|
||||||
|
const parts = nodeId.split('-')
|
||||||
|
parts.pop()
|
||||||
|
return parts.join('-')
|
||||||
|
}
|
||||||
|
|
||||||
|
async function startResearch(
|
||||||
|
query: string,
|
||||||
|
depth: number,
|
||||||
|
breadth: number,
|
||||||
|
feedback: { assistantQuestion: string; userAnswer: string }[],
|
||||||
|
) {
|
||||||
|
console.log('startResearch', query, depth, breadth, feedback)
|
||||||
|
tree.value.children = []
|
||||||
|
tree.value.depth = depth
|
||||||
|
tree.value.breadth = breadth
|
||||||
|
hoveredNode.value = null
|
||||||
|
searchResults.value = {}
|
||||||
try {
|
try {
|
||||||
const combinedQuery = `
|
const combinedQuery = `
|
||||||
Initial Query: ${query}
|
Initial Query: ${query}
|
||||||
Follow-up Questions and Answers:
|
Follow-up Questions and Answers:
|
||||||
${feedback.map((qa) => `Q: ${qa.assistantQuestion}\nA: ${qa.userAnswer}`).join('\n')}
|
${feedback.map((qa) => `Q: ${qa.assistantQuestion}\nA: ${qa.userAnswer}`).join('\n')}
|
||||||
`
|
`
|
||||||
|
|
||||||
await deepResearch({
|
await deepResearch({
|
||||||
query: combinedQuery,
|
query: combinedQuery,
|
||||||
depth,
|
maxDepth: depth,
|
||||||
breadth,
|
breadth,
|
||||||
onProgress: handleResearchProgress,
|
onProgress: handleResearchProgress,
|
||||||
})
|
})
|
||||||
@ -352,3 +169,28 @@ ${feedback.map((qa) => `Q: ${qa.assistantQuestion}\nA: ${qa.userAnswer}`).join('
|
|||||||
startResearch,
|
startResearch,
|
||||||
})
|
})
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<UCard>
|
||||||
|
<template #header>
|
||||||
|
<h2 class="font-bold">Deep Research</h2>
|
||||||
|
<p class="text-sm text-gray-500">Click a child node to view details</p>
|
||||||
|
</template>
|
||||||
|
<div class="flex flex-col">
|
||||||
|
<div class="overflow-y-auto">
|
||||||
|
<Tree :node="tree" @select="hoveredNode = $event" />
|
||||||
|
</div>
|
||||||
|
<div v-if="hoveredNode" class="p-4">
|
||||||
|
<h2 class="text-xl font-bold">{{ hoveredNode.label }}</h2>
|
||||||
|
<h3 class="text-lg font-semibold mt-2">Research Goal:</h3>
|
||||||
|
<p>{{ hoveredNode.researchGoal }}</p>
|
||||||
|
<div v-if="hoveredNode.learnings">
|
||||||
|
<h3 class="text-lg font-semibold mt-2">Learnings:</h3>
|
||||||
|
<ul>
|
||||||
|
<li v-for="(learning, index) in hoveredNode.learnings" :key="index">{{ learning }}</li>
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</UCard>
|
||||||
|
</template>
|
||||||
|
@ -63,7 +63,6 @@
|
|||||||
// Write the questions into modelValue
|
// Write the questions into modelValue
|
||||||
const parseResult = parsePartialJson(content)
|
const parseResult = parsePartialJson(content)
|
||||||
|
|
||||||
console.log(parseResult)
|
|
||||||
if (parseResult.state === 'repaired-parse' || parseResult.state === 'successful-parse') {
|
if (parseResult.state === 'repaired-parse' || parseResult.state === 'successful-parse') {
|
||||||
if (!isObject(parseResult.value) || Array.isArray(parseResult.value)) {
|
if (!isObject(parseResult.value) || Array.isArray(parseResult.value)) {
|
||||||
return (modelValue.value = [])
|
return (modelValue.value = [])
|
||||||
|
@ -58,8 +58,8 @@
|
|||||||
}>()
|
}>()
|
||||||
|
|
||||||
const input = ref('天空为什么是蓝的?')
|
const input = ref('天空为什么是蓝的?')
|
||||||
const breadth = ref(6)
|
const breadth = ref(3)
|
||||||
const depth = ref(3)
|
const depth = ref(2)
|
||||||
const numQuestions = ref(1)
|
const numQuestions = ref(1)
|
||||||
const isLoading = ref(false)
|
const isLoading = ref(false)
|
||||||
const result = ref<any>(null)
|
const result = ref<any>(null)
|
||||||
@ -91,13 +91,13 @@
|
|||||||
toast.add({
|
toast.add({
|
||||||
title: '复制成功',
|
title: '复制成功',
|
||||||
description: '研究报告已复制到剪贴板',
|
description: '研究报告已复制到剪贴板',
|
||||||
icon: 'i-heroicons-check-circle',
|
icon: 'i-lucide-badge-check',
|
||||||
})
|
})
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
toast.add({
|
toast.add({
|
||||||
title: '复制失败',
|
title: '复制失败',
|
||||||
description: '无法复制到剪贴板',
|
description: '无法复制到剪贴板',
|
||||||
icon: 'i-heroicons-x-circle',
|
icon: 'i-lucide-x',
|
||||||
color: 'error',
|
color: 'error',
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
76
components/Tree.vue
Normal file
76
components/Tree.vue
Normal file
@ -0,0 +1,76 @@
|
|||||||
|
<script setup lang="ts">
|
||||||
|
import type { ResearchStep } from '~/lib/deep-research'
|
||||||
|
|
||||||
|
export type TreeNodeStatus = Exclude<ResearchStep['type'], 'complete'>
|
||||||
|
|
||||||
|
export type TreeNode = {
|
||||||
|
id: string
|
||||||
|
/** Label, represents the search query */
|
||||||
|
label: string
|
||||||
|
researchGoal?: string
|
||||||
|
learnings?: string[]
|
||||||
|
followUpQuestions?: string[]
|
||||||
|
status?: TreeNodeStatus
|
||||||
|
children: TreeNode[]
|
||||||
|
/** Current depth of the node */
|
||||||
|
depth: number
|
||||||
|
/** Maximum breadth at the current depth */
|
||||||
|
breadth: number
|
||||||
|
/** Index of the node among its siblings */
|
||||||
|
index?: number
|
||||||
|
}
|
||||||
|
|
||||||
|
const props = defineProps<{
|
||||||
|
node: TreeNode
|
||||||
|
}>()
|
||||||
|
|
||||||
|
const emit = defineEmits<{
|
||||||
|
(e: 'select', value: TreeNode): void
|
||||||
|
}>()
|
||||||
|
|
||||||
|
const icon = computed(() => {
|
||||||
|
const result = { name: '', pulse: false }
|
||||||
|
if (!props.node.status) return result
|
||||||
|
switch (props.node.status) {
|
||||||
|
case 'generating_query':
|
||||||
|
result.name = 'i-lucide-clipboard-list'
|
||||||
|
result.pulse = true
|
||||||
|
break
|
||||||
|
case 'generated_query':
|
||||||
|
result.name = 'i-lucide-pause'
|
||||||
|
break
|
||||||
|
case 'searching':
|
||||||
|
result.name = 'i-lucide-search'
|
||||||
|
result.pulse = true
|
||||||
|
break
|
||||||
|
case 'search_complete':
|
||||||
|
result.name = 'i-lucide-search-check'
|
||||||
|
break
|
||||||
|
case 'processing_serach_result':
|
||||||
|
result.name = 'i-lucide-brain'
|
||||||
|
result.pulse = true
|
||||||
|
break
|
||||||
|
case 'processed_search_result':
|
||||||
|
result.name = 'i-lucide-circle-check-big'
|
||||||
|
break
|
||||||
|
case 'error':
|
||||||
|
result.name = 'i-lucide-octagon-x'
|
||||||
|
break
|
||||||
|
}
|
||||||
|
return result
|
||||||
|
})
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<div class="flex items-center gap-1">
|
||||||
|
<UIcon name="i-lucide-circle-dot" />
|
||||||
|
<UButton :class="icon.pulse && 'animate-pulse'" :icon="icon.name" size="sm" color="info" @click="emit('select', node)">{{
|
||||||
|
node.label
|
||||||
|
}}</UButton>
|
||||||
|
<ol v-if="node.children.length > 0" class="space-y-2">
|
||||||
|
<li v-for="node in node.children" :key="node.id">
|
||||||
|
<Tree class="ml-2" :node="node" @select="emit('select', $event)" />
|
||||||
|
</li>
|
||||||
|
</ol>
|
||||||
|
</div>
|
||||||
|
</template>
|
@ -8,20 +8,21 @@ import { o3MiniModel, trimPrompt } from './ai/providers';
|
|||||||
import { systemPrompt } from './prompt';
|
import { systemPrompt } from './prompt';
|
||||||
import zodToJsonSchema from 'zod-to-json-schema';
|
import zodToJsonSchema from 'zod-to-json-schema';
|
||||||
import { tavily, type TavilySearchResponse } from '@tavily/core';
|
import { tavily, type TavilySearchResponse } from '@tavily/core';
|
||||||
// import 'dotenv/config';
|
|
||||||
|
|
||||||
// Used for streaming response
|
// Used for streaming response
|
||||||
type PartialSerpQueries = DeepPartial<z.infer<typeof serpQueriesTypeSchema>['queries']>;
|
export type SearchQuery = z.infer<typeof searchQueriesTypeSchema>['queries'][0];
|
||||||
type PartialSearchResult = DeepPartial<z.infer<typeof serpResultTypeSchema>>;
|
export type PartialSearchQuery = DeepPartial<SearchQuery>;
|
||||||
|
export type SearchResult = z.infer<typeof searchResultTypeSchema>;
|
||||||
|
export type PartialSearchResult = DeepPartial<SearchResult>;
|
||||||
|
|
||||||
export type ResearchStep =
|
export type ResearchStep =
|
||||||
| { type: 'start'; message: string; depth: number; breadth: number }
|
| { type: 'generating_query'; result: PartialSearchQuery; depth: number; breadth: number; nodeIndex: number; nodeId: string }
|
||||||
| { type: 'generating_queries'; result: PartialSerpQueries; depth: number; breadth: number }
|
| { type: 'generated_query'; query: string; result: PartialSearchQuery; depth: number; breadth: number; nodeIndex: number; nodeId: string }
|
||||||
| { type: 'query_generated'; query: string; researchGoal: string; depth: number; breadth: number; queryIndex: number }
|
| { type: 'searching'; query: string; depth: number; breadth: number; nodeIndex: number; nodeId: string }
|
||||||
| { type: 'searching'; query: string; depth: number; breadth: number; queryIndex: number }
|
| { type: 'search_complete'; query: string; urls: string[]; depth: number; breadth: number; nodeIndex: number; nodeId: string }
|
||||||
| { type: 'search_complete'; query: string; urls: string[]; depth: number; breadth: number; queryIndex: number }
|
| { type: 'processing_serach_result'; query: string; result: PartialSearchResult; depth: number; breadth: number; nodeIndex: number; nodeId: string }
|
||||||
| { type: 'processing_serach_result'; query: string; result: PartialSearchResult; depth: number; breadth: number; queryIndex: number }
|
| { type: 'processed_search_result'; query: string; result: SearchResult; depth: number; breadth: number; nodeIndex: number; nodeId: string }
|
||||||
| { type: 'error'; message: string }
|
| { type: 'error'; message: string; depth: number; nodeId: string }
|
||||||
| { type: 'complete' };
|
| { type: 'complete' };
|
||||||
|
|
||||||
// increase this if you have higher API rate limits
|
// increase this if you have higher API rate limits
|
||||||
@ -38,9 +39,9 @@ const tvly = tavily({
|
|||||||
})
|
})
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Schema for {@link generateSerpQueries} without dynamic descriptions
|
* Schema for {@link generateSearchQueries} without dynamic descriptions
|
||||||
*/
|
*/
|
||||||
export const serpQueriesTypeSchema = z.object({
|
export const searchQueriesTypeSchema = z.object({
|
||||||
queries: z.array(
|
queries: z.array(
|
||||||
z.object({
|
z.object({
|
||||||
query: z.string(),
|
query: z.string(),
|
||||||
@ -50,7 +51,7 @@ export const serpQueriesTypeSchema = z.object({
|
|||||||
});
|
});
|
||||||
|
|
||||||
// take en user query, return a list of SERP queries
|
// take en user query, return a list of SERP queries
|
||||||
export function generateSerpQueries({
|
export function generateSearchQueries({
|
||||||
query,
|
query,
|
||||||
numQueries = 3,
|
numQueries = 3,
|
||||||
learnings,
|
learnings,
|
||||||
@ -92,11 +93,11 @@ export function generateSerpQueries({
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
export const serpResultTypeSchema = z.object({
|
export const searchResultTypeSchema = z.object({
|
||||||
learnings: z.array(z.string()),
|
learnings: z.array(z.string()),
|
||||||
followUpQuestions: z.array(z.string()),
|
followUpQuestions: z.array(z.string()),
|
||||||
});
|
});
|
||||||
function processSerpResult({
|
function processSearchResult({
|
||||||
query,
|
query,
|
||||||
result,
|
result,
|
||||||
numLearnings = 3,
|
numLearnings = 3,
|
||||||
@ -170,112 +171,159 @@ export async function writeFinalReport({
|
|||||||
return res.object.reportMarkdown + urlsSection;
|
return res.object.reportMarkdown + urlsSection;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function childNodeId(parentNodeId: string, currentIndex: number) {
|
||||||
|
return `${parentNodeId}-${currentIndex}`;
|
||||||
|
}
|
||||||
|
|
||||||
export async function deepResearch({
|
export async function deepResearch({
|
||||||
query,
|
query,
|
||||||
breadth,
|
breadth,
|
||||||
depth,
|
maxDepth,
|
||||||
learnings = [],
|
learnings = [],
|
||||||
visitedUrls = [],
|
visitedUrls = [],
|
||||||
onProgress,
|
onProgress,
|
||||||
|
currentDepth = 1,
|
||||||
|
nodeId = '0'
|
||||||
}: {
|
}: {
|
||||||
query: string;
|
query: string;
|
||||||
breadth: number;
|
breadth: number;
|
||||||
depth: number;
|
maxDepth: number;
|
||||||
learnings?: string[];
|
learnings?: string[];
|
||||||
visitedUrls?: string[];
|
visitedUrls?: string[];
|
||||||
onProgress: (step: ResearchStep) => void;
|
onProgress: (step: ResearchStep) => void;
|
||||||
|
currentDepth?: number;
|
||||||
|
nodeId?: string
|
||||||
}): Promise<void> {
|
}): Promise<void> {
|
||||||
onProgress({ type: 'start', message: `开始深度研究,深度:${depth},广度:${breadth}`, depth, breadth });
|
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const serpQueriesResult = generateSerpQueries({
|
const searchQueriesResult = generateSearchQueries({
|
||||||
query,
|
query,
|
||||||
learnings,
|
learnings,
|
||||||
numQueries: breadth,
|
numQueries: breadth,
|
||||||
});
|
});
|
||||||
const limit = pLimit(ConcurrencyLimit);
|
const limit = pLimit(ConcurrencyLimit);
|
||||||
|
|
||||||
let serpQueries: PartialSerpQueries = [];
|
let searchQueries: PartialSearchQuery[] = [];
|
||||||
|
|
||||||
for await (const parsedQueries of parseStreamingJson(
|
for await (const parsedQueries of parseStreamingJson(
|
||||||
serpQueriesResult.textStream,
|
searchQueriesResult.textStream,
|
||||||
serpQueriesTypeSchema,
|
searchQueriesTypeSchema,
|
||||||
(value) => !!value.queries?.length && !!value.queries[0]?.query
|
(value) => !!value.queries?.length && !!value.queries[0]?.query
|
||||||
)) {
|
)) {
|
||||||
if (parsedQueries.queries) {
|
if (parsedQueries.queries) {
|
||||||
serpQueries = parsedQueries.queries;
|
for (let i = 0; i < searchQueries.length; i++) {
|
||||||
onProgress({
|
onProgress({
|
||||||
type: 'generating_queries',
|
type: 'generating_query',
|
||||||
result: serpQueries,
|
result: searchQueries[i],
|
||||||
depth,
|
depth: currentDepth,
|
||||||
breadth
|
breadth,
|
||||||
|
nodeIndex: i,
|
||||||
|
nodeId: childNodeId(nodeId, i)
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
searchQueries = parsedQueries.queries;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
for (let i = 0; i < searchQueries.length; i++) {
|
||||||
|
onProgress({
|
||||||
|
type: 'generated_query',
|
||||||
|
query,
|
||||||
|
result: searchQueries[i],
|
||||||
|
depth: currentDepth,
|
||||||
|
breadth,
|
||||||
|
nodeIndex: i,
|
||||||
|
nodeId: childNodeId(nodeId, i)
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
await Promise.all(
|
await Promise.all(
|
||||||
serpQueries.map(serpQuery =>
|
searchQueries.map((searchQuery, nodeIndex) =>
|
||||||
limit(async () => {
|
limit(async () => {
|
||||||
if (!serpQuery?.query) return
|
if (!searchQuery?.query) return
|
||||||
|
onProgress({
|
||||||
|
type: 'searching',
|
||||||
|
query: searchQuery.query,
|
||||||
|
depth: currentDepth,
|
||||||
|
breadth,
|
||||||
|
nodeIndex,
|
||||||
|
nodeId: childNodeId(nodeId, nodeIndex)
|
||||||
|
})
|
||||||
try {
|
try {
|
||||||
// const result = await firecrawl.search(serpQuery.query, {
|
// const result = await firecrawl.search(searchQuery.query, {
|
||||||
// timeout: 15000,
|
// timeout: 15000,
|
||||||
// limit: 5,
|
// limit: 5,
|
||||||
// scrapeOptions: { formats: ['markdown'] },
|
// scrapeOptions: { formats: ['markdown'] },
|
||||||
// });
|
// });
|
||||||
const result = await tvly.search(serpQuery.query, {
|
const result = await tvly.search(searchQuery.query, {
|
||||||
maxResults: 5,
|
maxResults: 5,
|
||||||
})
|
})
|
||||||
console.log(`Ran ${serpQuery.query}, found ${result.results.length} contents`);
|
console.log(`Ran ${searchQuery.query}, found ${result.results.length} contents`);
|
||||||
|
|
||||||
// Collect URLs from this search
|
// Collect URLs from this search
|
||||||
const newUrls = compact(result.results.map(item => item.url));
|
const newUrls = compact(result.results.map(item => item.url));
|
||||||
const newBreadth = Math.ceil(breadth / 2);
|
// Breadth for the next search is half of the current breadth
|
||||||
const newDepth = depth - 1;
|
const nextBreadth = Math.ceil(breadth / 2);
|
||||||
|
|
||||||
const serpResultGenerator = processSerpResult({
|
const searchResultGenerator = processSearchResult({
|
||||||
query: serpQuery.query,
|
query: searchQuery.query,
|
||||||
result,
|
result,
|
||||||
numFollowUpQuestions: newBreadth,
|
numFollowUpQuestions: nextBreadth,
|
||||||
});
|
});
|
||||||
let serpResult: PartialSearchResult = {};
|
let searchResult: PartialSearchResult = {};
|
||||||
|
|
||||||
for await (const parsedLearnings of parseStreamingJson(
|
for await (const parsedLearnings of parseStreamingJson(
|
||||||
serpResultGenerator.textStream,
|
searchResultGenerator.textStream,
|
||||||
serpResultTypeSchema,
|
searchResultTypeSchema,
|
||||||
(value) => !!value.learnings?.length
|
(value) => !!value.learnings?.length
|
||||||
)) {
|
)) {
|
||||||
serpResult = parsedLearnings;
|
searchResult = parsedLearnings;
|
||||||
onProgress({
|
onProgress({
|
||||||
type: 'processing_serach_result',
|
type: 'processing_serach_result',
|
||||||
result: parsedLearnings,
|
result: parsedLearnings,
|
||||||
depth,
|
depth: currentDepth,
|
||||||
breadth,
|
breadth: breadth,
|
||||||
query: serpQuery.query,
|
query: searchQuery.query,
|
||||||
queryIndex: serpQueries.indexOf(serpQuery),
|
nodeIndex: nodeIndex,
|
||||||
|
nodeId: childNodeId(nodeId, nodeIndex)
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
console.log(`Processed serp result for ${serpQuery.query}`, serpResult);
|
console.log(`Processed search result for ${searchQuery.query}`, searchResult);
|
||||||
const allLearnings = [...learnings, ...(serpResult.learnings ?? [])];
|
const allLearnings = [...learnings, ...(searchResult.learnings ?? [])];
|
||||||
const allUrls = [...visitedUrls, ...newUrls];
|
const allUrls = [...visitedUrls, ...newUrls];
|
||||||
|
const nextDepth = currentDepth + 1;
|
||||||
|
|
||||||
if (newDepth > 0 && serpResult.followUpQuestions?.length) {
|
onProgress({
|
||||||
console.log(
|
type: 'processed_search_result',
|
||||||
`Researching deeper, breadth: ${newBreadth}, depth: ${newDepth}`,
|
result: {
|
||||||
|
learnings: allLearnings,
|
||||||
|
followUpQuestions: searchResult.followUpQuestions ?? [],
|
||||||
|
},
|
||||||
|
depth: currentDepth,
|
||||||
|
breadth,
|
||||||
|
query: searchQuery.query,
|
||||||
|
nodeIndex: nodeIndex,
|
||||||
|
nodeId: childNodeId(nodeId, nodeIndex)
|
||||||
|
})
|
||||||
|
|
||||||
|
if (nextDepth < maxDepth && searchResult.followUpQuestions?.length) {
|
||||||
|
console.warn(
|
||||||
|
`Researching deeper, breadth: ${nextBreadth}, depth: ${nextDepth}`,
|
||||||
);
|
);
|
||||||
|
|
||||||
const nextQuery = `
|
const nextQuery = `
|
||||||
Previous research goal: ${serpQuery.researchGoal}
|
Previous research goal: ${searchQuery.researchGoal}
|
||||||
Follow-up research directions: ${serpResult.followUpQuestions.map(q => `\n${q}`).join('')}
|
Follow-up research directions: ${searchResult.followUpQuestions.map(q => `\n${q}`).join('')}
|
||||||
`.trim();
|
`.trim();
|
||||||
|
|
||||||
return deepResearch({
|
return deepResearch({
|
||||||
query: nextQuery,
|
query: nextQuery,
|
||||||
breadth: newBreadth,
|
breadth: nextBreadth,
|
||||||
depth: newDepth,
|
maxDepth,
|
||||||
learnings: allLearnings,
|
learnings: allLearnings,
|
||||||
visitedUrls: allUrls,
|
visitedUrls: allUrls,
|
||||||
onProgress,
|
onProgress,
|
||||||
|
currentDepth: nextDepth,
|
||||||
|
nodeId: childNodeId(nodeId, nodeIndex),
|
||||||
});
|
});
|
||||||
} else {
|
} else {
|
||||||
return {
|
return {
|
||||||
@ -284,7 +332,7 @@ export async function deepResearch({
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
} catch (e: any) {
|
} catch (e: any) {
|
||||||
throw new Error(`Error searching for ${serpQuery.query}, depth ${depth}\nMessage: ${e.message}`)
|
throw new Error(`Error searching for ${searchQuery.query}, depth ${currentDepth}\nMessage: ${e.message}`)
|
||||||
}
|
}
|
||||||
}),
|
}),
|
||||||
),
|
),
|
||||||
@ -294,6 +342,8 @@ export async function deepResearch({
|
|||||||
onProgress({
|
onProgress({
|
||||||
type: 'error',
|
type: 'error',
|
||||||
message: error?.message ?? 'Something went wrong',
|
message: error?.message ?? 'Something went wrong',
|
||||||
|
depth: currentDepth,
|
||||||
|
nodeId,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -11,17 +11,15 @@
|
|||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@ai-sdk/openai": "^1.1.9",
|
"@ai-sdk/openai": "^1.1.9",
|
||||||
"@ai-sdk/openai-compatible": "^0.1.8",
|
|
||||||
"@ai-sdk/provider-utils": "^2.1.6",
|
|
||||||
"@ai-sdk/ui-utils": "^1.1.11",
|
"@ai-sdk/ui-utils": "^1.1.11",
|
||||||
"@ai-sdk/vue": "^1.1.11",
|
"@ai-sdk/vue": "^1.1.11",
|
||||||
|
"@iconify-json/lucide": "^1.2.26",
|
||||||
"@mendable/firecrawl-js": "^1.16.0",
|
"@mendable/firecrawl-js": "^1.16.0",
|
||||||
"@nuxt/ui": "3.0.0-alpha.12",
|
"@nuxt/ui": "3.0.0-alpha.12",
|
||||||
"@tailwindcss/typography": "^0.5.16",
|
"@tailwindcss/typography": "^0.5.16",
|
||||||
"@tavily/core": "^0.0.3",
|
"@tavily/core": "^0.0.3",
|
||||||
"@types/lodash-es": "^4.17.12",
|
"@types/lodash-es": "^4.17.12",
|
||||||
"ai": "^4.1.28",
|
"ai": "^4.1.28",
|
||||||
"d3": "^7.9.0",
|
|
||||||
"js-tiktoken": "^1.0.18",
|
"js-tiktoken": "^1.0.18",
|
||||||
"lodash-es": "^4.17.21",
|
"lodash-es": "^4.17.21",
|
||||||
"marked": "^15.0.7",
|
"marked": "^15.0.7",
|
||||||
|
@ -1,14 +1,12 @@
|
|||||||
<template>
|
<template>
|
||||||
<div>
|
<div>
|
||||||
<UContainer>
|
<UContainer>
|
||||||
<div class="py-8">
|
<div class="max-w-4xl mx-auto py-8 space-y-4">
|
||||||
<div class="max-w-4xl mx-auto">
|
|
||||||
<h1 class="text-3xl font-bold text-center mb-2"> Deep Research Assistant </h1>
|
<h1 class="text-3xl font-bold text-center mb-2"> Deep Research Assistant </h1>
|
||||||
<ColorModeButton></ColorModeButton>
|
<ColorModeButton></ColorModeButton>
|
||||||
<ResearchForm @submit="generateFeedback" />
|
<ResearchForm @submit="generateFeedback" />
|
||||||
<ResearchFeedback v-model="result.feedback" ref="feedbackRef" @submit="startDeepSearch" />
|
<ResearchFeedback v-model="result.feedback" ref="feedbackRef" @submit="startDeepSearch" />
|
||||||
<DeepResearch v-model="searchTree" ref="deepResearchRef" class="mb-8" />
|
<DeepResearch ref="deepResearchRef" class="mb-8" />
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
</UContainer>
|
</UContainer>
|
||||||
</div>
|
</div>
|
||||||
@ -18,7 +16,6 @@
|
|||||||
import type ResearchFeedback from '~/components/ResearchFeedback.vue'
|
import type ResearchFeedback from '~/components/ResearchFeedback.vue'
|
||||||
import type DeepResearch from '~/components/DeepResearch.vue'
|
import type DeepResearch from '~/components/DeepResearch.vue'
|
||||||
import type { ResearchInputData } from '~/components/ResearchForm.vue'
|
import type { ResearchInputData } from '~/components/ResearchForm.vue'
|
||||||
import type { SearchTree } from '~/components/DeepResearch.vue'
|
|
||||||
import type { ResearchFeedbackResult } from '~/components/ResearchFeedback.vue'
|
import type { ResearchFeedbackResult } from '~/components/ResearchFeedback.vue'
|
||||||
|
|
||||||
interface DeepResearchResult {
|
interface DeepResearchResult {
|
||||||
@ -39,7 +36,7 @@
|
|||||||
const result = ref<DeepResearchResult>({
|
const result = ref<DeepResearchResult>({
|
||||||
feedback: [],
|
feedback: [],
|
||||||
})
|
})
|
||||||
const searchTree = ref<SearchTree>({
|
const searchTree = ref({
|
||||||
root: null,
|
root: null,
|
||||||
currentDepth: 0,
|
currentDepth: 0,
|
||||||
maxDepth: 0,
|
maxDepth: 0,
|
||||||
|
346
pnpm-lock.yaml
generated
346
pnpm-lock.yaml
generated
@ -11,18 +11,15 @@ importers:
|
|||||||
'@ai-sdk/openai':
|
'@ai-sdk/openai':
|
||||||
specifier: ^1.1.9
|
specifier: ^1.1.9
|
||||||
version: 1.1.9(zod@3.24.1)
|
version: 1.1.9(zod@3.24.1)
|
||||||
'@ai-sdk/openai-compatible':
|
|
||||||
specifier: ^0.1.8
|
|
||||||
version: 0.1.8(zod@3.24.1)
|
|
||||||
'@ai-sdk/provider-utils':
|
|
||||||
specifier: ^2.1.6
|
|
||||||
version: 2.1.6(zod@3.24.1)
|
|
||||||
'@ai-sdk/ui-utils':
|
'@ai-sdk/ui-utils':
|
||||||
specifier: ^1.1.11
|
specifier: ^1.1.11
|
||||||
version: 1.1.11(zod@3.24.1)
|
version: 1.1.11(zod@3.24.1)
|
||||||
'@ai-sdk/vue':
|
'@ai-sdk/vue':
|
||||||
specifier: ^1.1.11
|
specifier: ^1.1.11
|
||||||
version: 1.1.11(vue@3.5.13(typescript@5.7.3))(zod@3.24.1)
|
version: 1.1.11(vue@3.5.13(typescript@5.7.3))(zod@3.24.1)
|
||||||
|
'@iconify-json/lucide':
|
||||||
|
specifier: ^1.2.26
|
||||||
|
version: 1.2.26
|
||||||
'@mendable/firecrawl-js':
|
'@mendable/firecrawl-js':
|
||||||
specifier: ^1.16.0
|
specifier: ^1.16.0
|
||||||
version: 1.16.0(ws@8.18.0)
|
version: 1.16.0(ws@8.18.0)
|
||||||
@ -41,9 +38,6 @@ importers:
|
|||||||
ai:
|
ai:
|
||||||
specifier: ^4.1.28
|
specifier: ^4.1.28
|
||||||
version: 4.1.28(react@19.0.0)(zod@3.24.1)
|
version: 4.1.28(react@19.0.0)(zod@3.24.1)
|
||||||
d3:
|
|
||||||
specifier: ^7.9.0
|
|
||||||
version: 7.9.0
|
|
||||||
js-tiktoken:
|
js-tiktoken:
|
||||||
specifier: ^1.0.18
|
specifier: ^1.0.18
|
||||||
version: 1.0.18
|
version: 1.0.18
|
||||||
@ -84,12 +78,6 @@ importers:
|
|||||||
|
|
||||||
packages:
|
packages:
|
||||||
|
|
||||||
'@ai-sdk/openai-compatible@0.1.8':
|
|
||||||
resolution: {integrity: sha512-o2WeZmkOgaaEHAIZfAdlAASotNemhWBzupUp7ql/vBKIrPDctbQS4K7XOvG7EZ1dshn0TxB+Ur7Q8HoWSeWPmA==}
|
|
||||||
engines: {node: '>=18'}
|
|
||||||
peerDependencies:
|
|
||||||
zod: ^3.0.0
|
|
||||||
|
|
||||||
'@ai-sdk/openai@1.1.9':
|
'@ai-sdk/openai@1.1.9':
|
||||||
resolution: {integrity: sha512-t/CpC4TLipdbgBJTMX/otzzqzCMBSPQwUOkYPGbT/jyuC86F+YO9o+LS0Ty2pGUE1kyT+B3WmJ318B16ZCg4hw==}
|
resolution: {integrity: sha512-t/CpC4TLipdbgBJTMX/otzzqzCMBSPQwUOkYPGbT/jyuC86F+YO9o+LS0Ty2pGUE1kyT+B3WmJ318B16ZCg4hw==}
|
||||||
engines: {node: '>=18'}
|
engines: {node: '>=18'}
|
||||||
@ -468,6 +456,9 @@ packages:
|
|||||||
'@floating-ui/vue@1.1.6':
|
'@floating-ui/vue@1.1.6':
|
||||||
resolution: {integrity: sha512-XFlUzGHGv12zbgHNk5FN2mUB7ROul3oG2ENdTpWdE+qMFxyNxWSRmsoyhiEnpmabNm6WnUvR1OvJfUfN4ojC1A==}
|
resolution: {integrity: sha512-XFlUzGHGv12zbgHNk5FN2mUB7ROul3oG2ENdTpWdE+qMFxyNxWSRmsoyhiEnpmabNm6WnUvR1OvJfUfN4ojC1A==}
|
||||||
|
|
||||||
|
'@iconify-json/lucide@1.2.26':
|
||||||
|
resolution: {integrity: sha512-arD/8mK0lRxFY2LgLf345NhWVWiOtV8sOxJuLnq4QRz3frMiOwVwGxEgp5Xe/bRGzxO2CxxCBok0bPRpCkYZQQ==}
|
||||||
|
|
||||||
'@iconify/collections@1.0.515':
|
'@iconify/collections@1.0.515':
|
||||||
resolution: {integrity: sha512-4aJAgfv31fST8LYjuGy14ZQN8zIDQpKuHRQXZgilQMDh6SW2YFiIWMWKd7MSEmRXwRQdygwPrAydelBNHW+qqw==}
|
resolution: {integrity: sha512-4aJAgfv31fST8LYjuGy14ZQN8zIDQpKuHRQXZgilQMDh6SW2YFiIWMWKd7MSEmRXwRQdygwPrAydelBNHW+qqw==}
|
||||||
|
|
||||||
@ -1581,133 +1572,6 @@ packages:
|
|||||||
csstype@3.1.3:
|
csstype@3.1.3:
|
||||||
resolution: {integrity: sha512-M1uQkMl8rQK/szD0LNhtqxIPLpimGm8sOBwU7lLnCpSbTyY3yeU1Vc7l4KT5zT4s/yOxHH5O7tIuuLOCnLADRw==}
|
resolution: {integrity: sha512-M1uQkMl8rQK/szD0LNhtqxIPLpimGm8sOBwU7lLnCpSbTyY3yeU1Vc7l4KT5zT4s/yOxHH5O7tIuuLOCnLADRw==}
|
||||||
|
|
||||||
d3-array@3.2.4:
|
|
||||||
resolution: {integrity: sha512-tdQAmyA18i4J7wprpYq8ClcxZy3SC31QMeByyCFyRt7BVHdREQZ5lpzoe5mFEYZUWe+oq8HBvk9JjpibyEV4Jg==}
|
|
||||||
engines: {node: '>=12'}
|
|
||||||
|
|
||||||
d3-axis@3.0.0:
|
|
||||||
resolution: {integrity: sha512-IH5tgjV4jE/GhHkRV0HiVYPDtvfjHQlQfJHs0usq7M30XcSBvOotpmH1IgkcXsO/5gEQZD43B//fc7SRT5S+xw==}
|
|
||||||
engines: {node: '>=12'}
|
|
||||||
|
|
||||||
d3-brush@3.0.0:
|
|
||||||
resolution: {integrity: sha512-ALnjWlVYkXsVIGlOsuWH1+3udkYFI48Ljihfnh8FZPF2QS9o+PzGLBslO0PjzVoHLZ2KCVgAM8NVkXPJB2aNnQ==}
|
|
||||||
engines: {node: '>=12'}
|
|
||||||
|
|
||||||
d3-chord@3.0.1:
|
|
||||||
resolution: {integrity: sha512-VE5S6TNa+j8msksl7HwjxMHDM2yNK3XCkusIlpX5kwauBfXuyLAtNg9jCp/iHH61tgI4sb6R/EIMWCqEIdjT/g==}
|
|
||||||
engines: {node: '>=12'}
|
|
||||||
|
|
||||||
d3-color@3.1.0:
|
|
||||||
resolution: {integrity: sha512-zg/chbXyeBtMQ1LbD/WSoW2DpC3I0mpmPdW+ynRTj/x2DAWYrIY7qeZIHidozwV24m4iavr15lNwIwLxRmOxhA==}
|
|
||||||
engines: {node: '>=12'}
|
|
||||||
|
|
||||||
d3-contour@4.0.2:
|
|
||||||
resolution: {integrity: sha512-4EzFTRIikzs47RGmdxbeUvLWtGedDUNkTcmzoeyg4sP/dvCexO47AaQL7VKy/gul85TOxw+IBgA8US2xwbToNA==}
|
|
||||||
engines: {node: '>=12'}
|
|
||||||
|
|
||||||
d3-delaunay@6.0.4:
|
|
||||||
resolution: {integrity: sha512-mdjtIZ1XLAM8bm/hx3WwjfHt6Sggek7qH043O8KEjDXN40xi3vx/6pYSVTwLjEgiXQTbvaouWKynLBiUZ6SK6A==}
|
|
||||||
engines: {node: '>=12'}
|
|
||||||
|
|
||||||
d3-dispatch@3.0.1:
|
|
||||||
resolution: {integrity: sha512-rzUyPU/S7rwUflMyLc1ETDeBj0NRuHKKAcvukozwhshr6g6c5d8zh4c2gQjY2bZ0dXeGLWc1PF174P2tVvKhfg==}
|
|
||||||
engines: {node: '>=12'}
|
|
||||||
|
|
||||||
d3-drag@3.0.0:
|
|
||||||
resolution: {integrity: sha512-pWbUJLdETVA8lQNJecMxoXfH6x+mO2UQo8rSmZ+QqxcbyA3hfeprFgIT//HW2nlHChWeIIMwS2Fq+gEARkhTkg==}
|
|
||||||
engines: {node: '>=12'}
|
|
||||||
|
|
||||||
d3-dsv@3.0.1:
|
|
||||||
resolution: {integrity: sha512-UG6OvdI5afDIFP9w4G0mNq50dSOsXHJaRE8arAS5o9ApWnIElp8GZw1Dun8vP8OyHOZ/QJUKUJwxiiCCnUwm+Q==}
|
|
||||||
engines: {node: '>=12'}
|
|
||||||
hasBin: true
|
|
||||||
|
|
||||||
d3-ease@3.0.1:
|
|
||||||
resolution: {integrity: sha512-wR/XK3D3XcLIZwpbvQwQ5fK+8Ykds1ip7A2Txe0yxncXSdq1L9skcG7blcedkOX+ZcgxGAmLX1FrRGbADwzi0w==}
|
|
||||||
engines: {node: '>=12'}
|
|
||||||
|
|
||||||
d3-fetch@3.0.1:
|
|
||||||
resolution: {integrity: sha512-kpkQIM20n3oLVBKGg6oHrUchHM3xODkTzjMoj7aWQFq5QEM+R6E4WkzT5+tojDY7yjez8KgCBRoj4aEr99Fdqw==}
|
|
||||||
engines: {node: '>=12'}
|
|
||||||
|
|
||||||
d3-force@3.0.0:
|
|
||||||
resolution: {integrity: sha512-zxV/SsA+U4yte8051P4ECydjD/S+qeYtnaIyAs9tgHCqfguma/aAQDjo85A9Z6EKhBirHRJHXIgJUlffT4wdLg==}
|
|
||||||
engines: {node: '>=12'}
|
|
||||||
|
|
||||||
d3-format@3.1.0:
|
|
||||||
resolution: {integrity: sha512-YyUI6AEuY/Wpt8KWLgZHsIU86atmikuoOmCfommt0LYHiQSPjvX2AcFc38PX0CBpr2RCyZhjex+NS/LPOv6YqA==}
|
|
||||||
engines: {node: '>=12'}
|
|
||||||
|
|
||||||
d3-geo@3.1.1:
|
|
||||||
resolution: {integrity: sha512-637ln3gXKXOwhalDzinUgY83KzNWZRKbYubaG+fGVuc/dxO64RRljtCTnf5ecMyE1RIdtqpkVcq0IbtU2S8j2Q==}
|
|
||||||
engines: {node: '>=12'}
|
|
||||||
|
|
||||||
d3-hierarchy@3.1.2:
|
|
||||||
resolution: {integrity: sha512-FX/9frcub54beBdugHjDCdikxThEqjnR93Qt7PvQTOHxyiNCAlvMrHhclk3cD5VeAaq9fxmfRp+CnWw9rEMBuA==}
|
|
||||||
engines: {node: '>=12'}
|
|
||||||
|
|
||||||
d3-interpolate@3.0.1:
|
|
||||||
resolution: {integrity: sha512-3bYs1rOD33uo8aqJfKP3JWPAibgw8Zm2+L9vBKEHJ2Rg+viTR7o5Mmv5mZcieN+FRYaAOWX5SJATX6k1PWz72g==}
|
|
||||||
engines: {node: '>=12'}
|
|
||||||
|
|
||||||
d3-path@3.1.0:
|
|
||||||
resolution: {integrity: sha512-p3KP5HCf/bvjBSSKuXid6Zqijx7wIfNW+J/maPs+iwR35at5JCbLUT0LzF1cnjbCHWhqzQTIN2Jpe8pRebIEFQ==}
|
|
||||||
engines: {node: '>=12'}
|
|
||||||
|
|
||||||
d3-polygon@3.0.1:
|
|
||||||
resolution: {integrity: sha512-3vbA7vXYwfe1SYhED++fPUQlWSYTTGmFmQiany/gdbiWgU/iEyQzyymwL9SkJjFFuCS4902BSzewVGsHHmHtXg==}
|
|
||||||
engines: {node: '>=12'}
|
|
||||||
|
|
||||||
d3-quadtree@3.0.1:
|
|
||||||
resolution: {integrity: sha512-04xDrxQTDTCFwP5H6hRhsRcb9xxv2RzkcsygFzmkSIOJy3PeRJP7sNk3VRIbKXcog561P9oU0/rVH6vDROAgUw==}
|
|
||||||
engines: {node: '>=12'}
|
|
||||||
|
|
||||||
d3-random@3.0.1:
|
|
||||||
resolution: {integrity: sha512-FXMe9GfxTxqd5D6jFsQ+DJ8BJS4E/fT5mqqdjovykEB2oFbTMDVdg1MGFxfQW+FBOGoB++k8swBrgwSHT1cUXQ==}
|
|
||||||
engines: {node: '>=12'}
|
|
||||||
|
|
||||||
d3-scale-chromatic@3.1.0:
|
|
||||||
resolution: {integrity: sha512-A3s5PWiZ9YCXFye1o246KoscMWqf8BsD9eRiJ3He7C9OBaxKhAd5TFCdEx/7VbKtxxTsu//1mMJFrEt572cEyQ==}
|
|
||||||
engines: {node: '>=12'}
|
|
||||||
|
|
||||||
d3-scale@4.0.2:
|
|
||||||
resolution: {integrity: sha512-GZW464g1SH7ag3Y7hXjf8RoUuAFIqklOAq3MRl4OaWabTFJY9PN/E1YklhXLh+OQ3fM9yS2nOkCoS+WLZ6kvxQ==}
|
|
||||||
engines: {node: '>=12'}
|
|
||||||
|
|
||||||
d3-selection@3.0.0:
|
|
||||||
resolution: {integrity: sha512-fmTRWbNMmsmWq6xJV8D19U/gw/bwrHfNXxrIN+HfZgnzqTHp9jOmKMhsTUjXOJnZOdZY9Q28y4yebKzqDKlxlQ==}
|
|
||||||
engines: {node: '>=12'}
|
|
||||||
|
|
||||||
d3-shape@3.2.0:
|
|
||||||
resolution: {integrity: sha512-SaLBuwGm3MOViRq2ABk3eLoxwZELpH6zhl3FbAoJ7Vm1gofKx6El1Ib5z23NUEhF9AsGl7y+dzLe5Cw2AArGTA==}
|
|
||||||
engines: {node: '>=12'}
|
|
||||||
|
|
||||||
d3-time-format@4.1.0:
|
|
||||||
resolution: {integrity: sha512-dJxPBlzC7NugB2PDLwo9Q8JiTR3M3e4/XANkreKSUxF8vvXKqm1Yfq4Q5dl8budlunRVlUUaDUgFt7eA8D6NLg==}
|
|
||||||
engines: {node: '>=12'}
|
|
||||||
|
|
||||||
d3-time@3.1.0:
|
|
||||||
resolution: {integrity: sha512-VqKjzBLejbSMT4IgbmVgDjpkYrNWUYJnbCGo874u7MMKIWsILRX+OpX/gTk8MqjpT1A/c6HY2dCA77ZN0lkQ2Q==}
|
|
||||||
engines: {node: '>=12'}
|
|
||||||
|
|
||||||
d3-timer@3.0.1:
|
|
||||||
resolution: {integrity: sha512-ndfJ/JxxMd3nw31uyKoY2naivF+r29V+Lc0svZxe1JvvIRmi8hUsrMvdOwgS1o6uBHmiz91geQ0ylPP0aj1VUA==}
|
|
||||||
engines: {node: '>=12'}
|
|
||||||
|
|
||||||
d3-transition@3.0.1:
|
|
||||||
resolution: {integrity: sha512-ApKvfjsSR6tg06xrL434C0WydLr7JewBB3V+/39RMHsaXTOG0zmt/OAXeng5M5LBm0ojmxJrpomQVZ1aPvBL4w==}
|
|
||||||
engines: {node: '>=12'}
|
|
||||||
peerDependencies:
|
|
||||||
d3-selection: 2 - 3
|
|
||||||
|
|
||||||
d3-zoom@3.0.0:
|
|
||||||
resolution: {integrity: sha512-b8AmV3kfQaqWAuacbPuNbL6vahnOJflOhexLzMMNLga62+/nh0JzvJ0aO/5a5MVgUFGS7Hu1P9P03o3fJkDCyw==}
|
|
||||||
engines: {node: '>=12'}
|
|
||||||
|
|
||||||
d3@7.9.0:
|
|
||||||
resolution: {integrity: sha512-e1U46jVP+w7Iut8Jt8ri1YsPOvFpg46k+K8TpCb0P+zjCkjkPnV7WzfDJzMHy1LnA+wj5pLT1wjO901gLXeEhA==}
|
|
||||||
engines: {node: '>=12'}
|
|
||||||
|
|
||||||
db0@0.2.3:
|
db0@0.2.3:
|
||||||
resolution: {integrity: sha512-PunuHESDNefmwVy1LDpY663uWwKt2ogLGoB6NOz2sflGREWqDreMwDgF8gfkXxgNXW+dqviyiJGm924H1BaGiw==}
|
resolution: {integrity: sha512-PunuHESDNefmwVy1LDpY663uWwKt2ogLGoB6NOz2sflGREWqDreMwDgF8gfkXxgNXW+dqviyiJGm924H1BaGiw==}
|
||||||
peerDependencies:
|
peerDependencies:
|
||||||
@ -1771,9 +1635,6 @@ packages:
|
|||||||
defu@6.1.4:
|
defu@6.1.4:
|
||||||
resolution: {integrity: sha512-mEQCMmwJu317oSz8CwdIOdwf3xMif1ttiM8LTufzc3g6kR+9Pe236twL8j3IYT1F7GfRgGcW6MWxzZjLIkuHIg==}
|
resolution: {integrity: sha512-mEQCMmwJu317oSz8CwdIOdwf3xMif1ttiM8LTufzc3g6kR+9Pe236twL8j3IYT1F7GfRgGcW6MWxzZjLIkuHIg==}
|
||||||
|
|
||||||
delaunator@5.0.1:
|
|
||||||
resolution: {integrity: sha512-8nvh+XBe96aCESrGOqMp/84b13H9cdKbG5P2ejQCh4d4sK9RL4371qou9drQjMhvnPmhWl5hnmqbEE0fXr9Xnw==}
|
|
||||||
|
|
||||||
delayed-stream@1.0.0:
|
delayed-stream@1.0.0:
|
||||||
resolution: {integrity: sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==}
|
resolution: {integrity: sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==}
|
||||||
engines: {node: '>=0.4.0'}
|
engines: {node: '>=0.4.0'}
|
||||||
@ -2172,10 +2033,6 @@ packages:
|
|||||||
resolution: {integrity: sha512-AXcZb6vzzrFAUE61HnN4mpLqd/cSIwNQjtNWR0euPm6y0iqx3G4gOXaIDdtdDwZmhwe82LA6+zinmW4UBWVePQ==}
|
resolution: {integrity: sha512-AXcZb6vzzrFAUE61HnN4mpLqd/cSIwNQjtNWR0euPm6y0iqx3G4gOXaIDdtdDwZmhwe82LA6+zinmW4UBWVePQ==}
|
||||||
engines: {node: '>=16.17.0'}
|
engines: {node: '>=16.17.0'}
|
||||||
|
|
||||||
iconv-lite@0.6.3:
|
|
||||||
resolution: {integrity: sha512-4fCk79wshMdzMp2rH06qWrJE4iolqLhCUH+OiuIgU++RB0+94NlDL81atO7GX55uUKueo0txHNtvEyI6D7WdMw==}
|
|
||||||
engines: {node: '>=0.10.0'}
|
|
||||||
|
|
||||||
ieee754@1.2.1:
|
ieee754@1.2.1:
|
||||||
resolution: {integrity: sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA==}
|
resolution: {integrity: sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA==}
|
||||||
|
|
||||||
@ -2207,10 +2064,6 @@ packages:
|
|||||||
resolution: {integrity: sha512-QQnnxNyfvmHFIsj7gkPcYymR8Jdw/o7mp5ZFihxn6h8Ci6fh3Dx4E1gPjpQEpIuPo9XVNY/ZUwh4BPMjGyL01g==}
|
resolution: {integrity: sha512-QQnnxNyfvmHFIsj7gkPcYymR8Jdw/o7mp5ZFihxn6h8Ci6fh3Dx4E1gPjpQEpIuPo9XVNY/ZUwh4BPMjGyL01g==}
|
||||||
engines: {node: ^14.17.0 || ^16.13.0 || >=18.0.0}
|
engines: {node: ^14.17.0 || ^16.13.0 || >=18.0.0}
|
||||||
|
|
||||||
internmap@2.0.3:
|
|
||||||
resolution: {integrity: sha512-5Hh7Y1wQbvY5ooGgPbDaL5iYLAPzMTUrjMulskHLH6wnv/A+1q5rgEaiuqEjB+oxGXIVZs1FF+R/KPN3ZSQYYg==}
|
|
||||||
engines: {node: '>=12'}
|
|
||||||
|
|
||||||
ioredis@5.5.0:
|
ioredis@5.5.0:
|
||||||
resolution: {integrity: sha512-7CutT89g23FfSa8MDoIFs2GYYa0PaNiW/OrT+nRyjRXHDZd17HmIgy+reOQ/yhh72NznNjGuS8kbCAcA4Ro4mw==}
|
resolution: {integrity: sha512-7CutT89g23FfSa8MDoIFs2GYYa0PaNiW/OrT+nRyjRXHDZd17HmIgy+reOQ/yhh72NznNjGuS8kbCAcA4Ro4mw==}
|
||||||
engines: {node: '>=12.22.0'}
|
engines: {node: '>=12.22.0'}
|
||||||
@ -3121,9 +2974,6 @@ packages:
|
|||||||
resolution: {integrity: sha512-l0OE8wL34P4nJH/H2ffoaniAokM2qSmrtXHmlpvYr5AVVX8msAyW0l8NVJFDxlSK4u3Uh/f41cQheDVdnYijwQ==}
|
resolution: {integrity: sha512-l0OE8wL34P4nJH/H2ffoaniAokM2qSmrtXHmlpvYr5AVVX8msAyW0l8NVJFDxlSK4u3Uh/f41cQheDVdnYijwQ==}
|
||||||
hasBin: true
|
hasBin: true
|
||||||
|
|
||||||
robust-predicates@3.0.2:
|
|
||||||
resolution: {integrity: sha512-IXgzBWvWQwE6PrDI05OvmXUIruQTcoMDzRsOd5CDvHCVLcLHMTSYvOK5Cm46kWqlV3yAbuSpBZdJ5oP5OUoStg==}
|
|
||||||
|
|
||||||
rollup-plugin-visualizer@5.14.0:
|
rollup-plugin-visualizer@5.14.0:
|
||||||
resolution: {integrity: sha512-VlDXneTDaKsHIw8yzJAFWtrzguoJ/LnQ+lMpoVfYJ3jJF4Ihe5oYLAqLklIK/35lgUY+1yEzCkHyZ1j4A5w5fA==}
|
resolution: {integrity: sha512-VlDXneTDaKsHIw8yzJAFWtrzguoJ/LnQ+lMpoVfYJ3jJF4Ihe5oYLAqLklIK/35lgUY+1yEzCkHyZ1j4A5w5fA==}
|
||||||
engines: {node: '>=18'}
|
engines: {node: '>=18'}
|
||||||
@ -3149,18 +2999,12 @@ packages:
|
|||||||
run-parallel@1.2.0:
|
run-parallel@1.2.0:
|
||||||
resolution: {integrity: sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA==}
|
resolution: {integrity: sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA==}
|
||||||
|
|
||||||
rw@1.3.3:
|
|
||||||
resolution: {integrity: sha512-PdhdWy89SiZogBLaw42zdeqtRJ//zFd2PgQavcICDUgJT5oW10QCRKbJ6bg4r0/UY2M6BWd5tkxuGFRvCkgfHQ==}
|
|
||||||
|
|
||||||
safe-buffer@5.1.2:
|
safe-buffer@5.1.2:
|
||||||
resolution: {integrity: sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==}
|
resolution: {integrity: sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==}
|
||||||
|
|
||||||
safe-buffer@5.2.1:
|
safe-buffer@5.2.1:
|
||||||
resolution: {integrity: sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==}
|
resolution: {integrity: sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==}
|
||||||
|
|
||||||
safer-buffer@2.1.2:
|
|
||||||
resolution: {integrity: sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==}
|
|
||||||
|
|
||||||
scule@1.3.0:
|
scule@1.3.0:
|
||||||
resolution: {integrity: sha512-6FtHJEvt+pVMIB9IBY+IcCJ6Z5f1iQnytgyfKMhDKgmzYG+TeH/wx1y3l27rshSbLiSanrR9ffZDrEsmjlQF2g==}
|
resolution: {integrity: sha512-6FtHJEvt+pVMIB9IBY+IcCJ6Z5f1iQnytgyfKMhDKgmzYG+TeH/wx1y3l27rshSbLiSanrR9ffZDrEsmjlQF2g==}
|
||||||
|
|
||||||
@ -3868,12 +3712,6 @@ packages:
|
|||||||
|
|
||||||
snapshots:
|
snapshots:
|
||||||
|
|
||||||
'@ai-sdk/openai-compatible@0.1.8(zod@3.24.1)':
|
|
||||||
dependencies:
|
|
||||||
'@ai-sdk/provider': 1.0.7
|
|
||||||
'@ai-sdk/provider-utils': 2.1.6(zod@3.24.1)
|
|
||||||
zod: 3.24.1
|
|
||||||
|
|
||||||
'@ai-sdk/openai@1.1.9(zod@3.24.1)':
|
'@ai-sdk/openai@1.1.9(zod@3.24.1)':
|
||||||
dependencies:
|
dependencies:
|
||||||
'@ai-sdk/provider': 1.0.7
|
'@ai-sdk/provider': 1.0.7
|
||||||
@ -4238,6 +4076,10 @@ snapshots:
|
|||||||
- '@vue/composition-api'
|
- '@vue/composition-api'
|
||||||
- vue
|
- vue
|
||||||
|
|
||||||
|
'@iconify-json/lucide@1.2.26':
|
||||||
|
dependencies:
|
||||||
|
'@iconify/types': 2.0.0
|
||||||
|
|
||||||
'@iconify/collections@1.0.515':
|
'@iconify/collections@1.0.515':
|
||||||
dependencies:
|
dependencies:
|
||||||
'@iconify/types': 2.0.0
|
'@iconify/types': 2.0.0
|
||||||
@ -5730,158 +5572,6 @@ snapshots:
|
|||||||
|
|
||||||
csstype@3.1.3: {}
|
csstype@3.1.3: {}
|
||||||
|
|
||||||
d3-array@3.2.4:
|
|
||||||
dependencies:
|
|
||||||
internmap: 2.0.3
|
|
||||||
|
|
||||||
d3-axis@3.0.0: {}
|
|
||||||
|
|
||||||
d3-brush@3.0.0:
|
|
||||||
dependencies:
|
|
||||||
d3-dispatch: 3.0.1
|
|
||||||
d3-drag: 3.0.0
|
|
||||||
d3-interpolate: 3.0.1
|
|
||||||
d3-selection: 3.0.0
|
|
||||||
d3-transition: 3.0.1(d3-selection@3.0.0)
|
|
||||||
|
|
||||||
d3-chord@3.0.1:
|
|
||||||
dependencies:
|
|
||||||
d3-path: 3.1.0
|
|
||||||
|
|
||||||
d3-color@3.1.0: {}
|
|
||||||
|
|
||||||
d3-contour@4.0.2:
|
|
||||||
dependencies:
|
|
||||||
d3-array: 3.2.4
|
|
||||||
|
|
||||||
d3-delaunay@6.0.4:
|
|
||||||
dependencies:
|
|
||||||
delaunator: 5.0.1
|
|
||||||
|
|
||||||
d3-dispatch@3.0.1: {}
|
|
||||||
|
|
||||||
d3-drag@3.0.0:
|
|
||||||
dependencies:
|
|
||||||
d3-dispatch: 3.0.1
|
|
||||||
d3-selection: 3.0.0
|
|
||||||
|
|
||||||
d3-dsv@3.0.1:
|
|
||||||
dependencies:
|
|
||||||
commander: 7.2.0
|
|
||||||
iconv-lite: 0.6.3
|
|
||||||
rw: 1.3.3
|
|
||||||
|
|
||||||
d3-ease@3.0.1: {}
|
|
||||||
|
|
||||||
d3-fetch@3.0.1:
|
|
||||||
dependencies:
|
|
||||||
d3-dsv: 3.0.1
|
|
||||||
|
|
||||||
d3-force@3.0.0:
|
|
||||||
dependencies:
|
|
||||||
d3-dispatch: 3.0.1
|
|
||||||
d3-quadtree: 3.0.1
|
|
||||||
d3-timer: 3.0.1
|
|
||||||
|
|
||||||
d3-format@3.1.0: {}
|
|
||||||
|
|
||||||
d3-geo@3.1.1:
|
|
||||||
dependencies:
|
|
||||||
d3-array: 3.2.4
|
|
||||||
|
|
||||||
d3-hierarchy@3.1.2: {}
|
|
||||||
|
|
||||||
d3-interpolate@3.0.1:
|
|
||||||
dependencies:
|
|
||||||
d3-color: 3.1.0
|
|
||||||
|
|
||||||
d3-path@3.1.0: {}
|
|
||||||
|
|
||||||
d3-polygon@3.0.1: {}
|
|
||||||
|
|
||||||
d3-quadtree@3.0.1: {}
|
|
||||||
|
|
||||||
d3-random@3.0.1: {}
|
|
||||||
|
|
||||||
d3-scale-chromatic@3.1.0:
|
|
||||||
dependencies:
|
|
||||||
d3-color: 3.1.0
|
|
||||||
d3-interpolate: 3.0.1
|
|
||||||
|
|
||||||
d3-scale@4.0.2:
|
|
||||||
dependencies:
|
|
||||||
d3-array: 3.2.4
|
|
||||||
d3-format: 3.1.0
|
|
||||||
d3-interpolate: 3.0.1
|
|
||||||
d3-time: 3.1.0
|
|
||||||
d3-time-format: 4.1.0
|
|
||||||
|
|
||||||
d3-selection@3.0.0: {}
|
|
||||||
|
|
||||||
d3-shape@3.2.0:
|
|
||||||
dependencies:
|
|
||||||
d3-path: 3.1.0
|
|
||||||
|
|
||||||
d3-time-format@4.1.0:
|
|
||||||
dependencies:
|
|
||||||
d3-time: 3.1.0
|
|
||||||
|
|
||||||
d3-time@3.1.0:
|
|
||||||
dependencies:
|
|
||||||
d3-array: 3.2.4
|
|
||||||
|
|
||||||
d3-timer@3.0.1: {}
|
|
||||||
|
|
||||||
d3-transition@3.0.1(d3-selection@3.0.0):
|
|
||||||
dependencies:
|
|
||||||
d3-color: 3.1.0
|
|
||||||
d3-dispatch: 3.0.1
|
|
||||||
d3-ease: 3.0.1
|
|
||||||
d3-interpolate: 3.0.1
|
|
||||||
d3-selection: 3.0.0
|
|
||||||
d3-timer: 3.0.1
|
|
||||||
|
|
||||||
d3-zoom@3.0.0:
|
|
||||||
dependencies:
|
|
||||||
d3-dispatch: 3.0.1
|
|
||||||
d3-drag: 3.0.0
|
|
||||||
d3-interpolate: 3.0.1
|
|
||||||
d3-selection: 3.0.0
|
|
||||||
d3-transition: 3.0.1(d3-selection@3.0.0)
|
|
||||||
|
|
||||||
d3@7.9.0:
|
|
||||||
dependencies:
|
|
||||||
d3-array: 3.2.4
|
|
||||||
d3-axis: 3.0.0
|
|
||||||
d3-brush: 3.0.0
|
|
||||||
d3-chord: 3.0.1
|
|
||||||
d3-color: 3.1.0
|
|
||||||
d3-contour: 4.0.2
|
|
||||||
d3-delaunay: 6.0.4
|
|
||||||
d3-dispatch: 3.0.1
|
|
||||||
d3-drag: 3.0.0
|
|
||||||
d3-dsv: 3.0.1
|
|
||||||
d3-ease: 3.0.1
|
|
||||||
d3-fetch: 3.0.1
|
|
||||||
d3-force: 3.0.0
|
|
||||||
d3-format: 3.1.0
|
|
||||||
d3-geo: 3.1.1
|
|
||||||
d3-hierarchy: 3.1.2
|
|
||||||
d3-interpolate: 3.0.1
|
|
||||||
d3-path: 3.1.0
|
|
||||||
d3-polygon: 3.0.1
|
|
||||||
d3-quadtree: 3.0.1
|
|
||||||
d3-random: 3.0.1
|
|
||||||
d3-scale: 4.0.2
|
|
||||||
d3-scale-chromatic: 3.1.0
|
|
||||||
d3-selection: 3.0.0
|
|
||||||
d3-shape: 3.2.0
|
|
||||||
d3-time: 3.1.0
|
|
||||||
d3-time-format: 4.1.0
|
|
||||||
d3-timer: 3.0.1
|
|
||||||
d3-transition: 3.0.1(d3-selection@3.0.0)
|
|
||||||
d3-zoom: 3.0.0
|
|
||||||
|
|
||||||
db0@0.2.3: {}
|
db0@0.2.3: {}
|
||||||
|
|
||||||
debug@2.6.9:
|
debug@2.6.9:
|
||||||
@ -5909,10 +5599,6 @@ snapshots:
|
|||||||
|
|
||||||
defu@6.1.4: {}
|
defu@6.1.4: {}
|
||||||
|
|
||||||
delaunator@5.0.1:
|
|
||||||
dependencies:
|
|
||||||
robust-predicates: 3.0.2
|
|
||||||
|
|
||||||
delayed-stream@1.0.0: {}
|
delayed-stream@1.0.0: {}
|
||||||
|
|
||||||
denque@2.1.0: {}
|
denque@2.1.0: {}
|
||||||
@ -6319,10 +6005,6 @@ snapshots:
|
|||||||
|
|
||||||
human-signals@5.0.0: {}
|
human-signals@5.0.0: {}
|
||||||
|
|
||||||
iconv-lite@0.6.3:
|
|
||||||
dependencies:
|
|
||||||
safer-buffer: 2.1.2
|
|
||||||
|
|
||||||
ieee754@1.2.1: {}
|
ieee754@1.2.1: {}
|
||||||
|
|
||||||
ignore@7.0.3: {}
|
ignore@7.0.3: {}
|
||||||
@ -6352,8 +6034,6 @@ snapshots:
|
|||||||
|
|
||||||
ini@4.1.1: {}
|
ini@4.1.1: {}
|
||||||
|
|
||||||
internmap@2.0.3: {}
|
|
||||||
|
|
||||||
ioredis@5.5.0:
|
ioredis@5.5.0:
|
||||||
dependencies:
|
dependencies:
|
||||||
'@ioredis/commands': 1.2.0
|
'@ioredis/commands': 1.2.0
|
||||||
@ -7392,8 +7072,6 @@ snapshots:
|
|||||||
dependencies:
|
dependencies:
|
||||||
glob: 10.4.5
|
glob: 10.4.5
|
||||||
|
|
||||||
robust-predicates@3.0.2: {}
|
|
||||||
|
|
||||||
rollup-plugin-visualizer@5.14.0(rollup@4.34.6):
|
rollup-plugin-visualizer@5.14.0(rollup@4.34.6):
|
||||||
dependencies:
|
dependencies:
|
||||||
open: 8.4.2
|
open: 8.4.2
|
||||||
@ -7434,14 +7112,10 @@ snapshots:
|
|||||||
dependencies:
|
dependencies:
|
||||||
queue-microtask: 1.2.3
|
queue-microtask: 1.2.3
|
||||||
|
|
||||||
rw@1.3.3: {}
|
|
||||||
|
|
||||||
safe-buffer@5.1.2: {}
|
safe-buffer@5.1.2: {}
|
||||||
|
|
||||||
safe-buffer@5.2.1: {}
|
safe-buffer@5.2.1: {}
|
||||||
|
|
||||||
safer-buffer@2.1.2: {}
|
|
||||||
|
|
||||||
scule@1.3.0: {}
|
scule@1.3.0: {}
|
||||||
|
|
||||||
secure-json-parse@2.7.0: {}
|
secure-json-parse@2.7.0: {}
|
||||||
|
@ -1,3 +1,4 @@
|
|||||||
|
// This file is currently unused
|
||||||
import { deepResearch, ResearchStep } from "~/lib/deep-research";
|
import { deepResearch, ResearchStep } from "~/lib/deep-research";
|
||||||
|
|
||||||
export default defineEventHandler(async event => {
|
export default defineEventHandler(async event => {
|
||||||
|
Reference in New Issue
Block a user