diff --git a/components/DeepResearch/DeepResearch.vue b/components/DeepResearch/DeepResearch.vue index 44e6471..35faf98 100644 --- a/components/DeepResearch/DeepResearch.vue +++ b/components/DeepResearch/DeepResearch.vue @@ -9,10 +9,11 @@ formInjectionKey, researchResultInjectionKey, } from '~/constants/injection-keys' - import Flow from './SearchFlow.vue' + import Flow, { type SearchNode, type SearchEdge } from './SearchFlow.vue' import SearchFlow from './SearchFlow.vue' import NodeDetail from './NodeDetail.vue' import { isChildNode, isParentNode, isRootNode } from '~/utils/tree-node' + import { UCard, UModal, UButton } from '#components' export type DeepResearchNodeStatus = Exclude @@ -39,7 +40,7 @@ const toast = useToast() const { t, locale } = useI18n() - const { config } = storeToRefs(useConfigStore()) + const isLargeScreen = useMediaQuery('(min-width: 768px)') const flowRef = ref>() const rootNode: DeepResearchNode = { id: '0', label: 'Start' } @@ -49,6 +50,12 @@ const selectedNodeId = ref() const searchResults = ref>({}) const isLoading = ref(false) + const isFullscreen = ref(false) + // The edges and nodes of SearchFlow.vue + // These are not managed inside SearchFlow, because here we need to switch between + // two SearchFlows in fullscreen and non-fullscreen mode + const flowNodes = ref([flowRootNode()]) + const flowEdges = ref([]) const selectedNode = computed(() => { if (selectedNodeId.value) { @@ -202,19 +209,35 @@ selectedNodeId.value = undefined } else { selectedNodeId.value = nodeId + flowRef.value?.layoutGraph(true) + } + } + + // The default root node for SearchFlow + function flowRootNode(): SearchNode { + return { + id: '0', + data: { title: 'Start' }, + position: { x: 0, y: 0 }, + type: 'search', // We only have this type } } async function startResearch(retryNode?: DeepResearchNode) { if (!form.value.query || !form.value.breadth || !form.value.depth) return - // 如果不是重试,清空所有节点 + // Clear all nodes if it's not a retry if (!retryNode) { nodes.value = [{ ...rootNode }] selectedNodeId.value = undefined searchResults.value = {} - flowRef.value?.clearNodes() + flowNodes.value = [flowRootNode()] + flowEdges.value = [] isLoading.value = true + // Wait for the nodes and edges to reflect to `SearchFlow.vue` + nextTick(() => { + flowRef.value?.reset() + }) } // Wait after the flow is cleared @@ -302,6 +325,21 @@ await startResearch(nodeCurrentData) } + let scrollY = 0 + + function toggleFullscreen() { + // Because changing `isFullscreen` causes the height of the page to change (UCard disappears and appears) + // so we should scroll back to the last position after exiting fullscreen mode. + if (!isFullscreen.value) { + scrollY = window.scrollY + } else { + requestAnimationFrame(() => { + window.scrollTo({ top: scrollY }) + }) + } + isFullscreen.value = !isFullscreen.value + } + defineExpose({ startResearch, isLoading, @@ -309,18 +347,75 @@