refactor: use Nuxt 4 directory structure

This commit is contained in:
AnotiaWang
2025-02-28 16:16:02 +08:00
parent 7a87ed5def
commit c45d75fad2
31 changed files with 33 additions and 28 deletions

View File

@ -0,0 +1,46 @@
import { createDeepSeek } from '@ai-sdk/deepseek'
import { createOpenRouter } from '@openrouter/ai-sdk-provider'
import { createOpenAI } from '@ai-sdk/openai'
import {
extractReasoningMiddleware,
wrapLanguageModel,
type LanguageModelV1,
} from 'ai'
export const useAiModel = () => {
const { config, aiApiBase } = useConfigStore()
let model: LanguageModelV1
if (config.ai.provider === 'openrouter') {
const openRouter = createOpenRouter({
apiKey: config.ai.apiKey,
baseURL: aiApiBase,
})
model = openRouter(config.ai.model, {
includeReasoning: true,
})
} else if (
config.ai.provider === 'deepseek' ||
config.ai.provider === 'siliconflow' ||
// Special case if model name includes 'deepseek'
// This ensures compatibilty with providers like Siliconflow
config.ai.model?.toLowerCase().includes('deepseek')
) {
const deepSeek = createDeepSeek({
apiKey: config.ai.apiKey,
baseURL: aiApiBase,
})
model = deepSeek(config.ai.model)
} else {
const openai = createOpenAI({
apiKey: config.ai.apiKey,
baseURL: aiApiBase,
})
model = openai(config.ai.model)
}
return wrapLanguageModel({
model,
middleware: extractReasoningMiddleware({ tagName: 'think' }),
})
}

View File

@ -0,0 +1,62 @@
import dagre from '@dagrejs/dagre'
import { Position, useVueFlow, type Edge, type Node } from '@vue-flow/core'
// Picked from https://vueflow.dev/examples/layout/animated.html
export function useNodeLayout() {
const { findNode } = useVueFlow()
function layout(nodes: Node[], edges: Edge[]) {
// we create a new graph instance, in case some nodes/edges were removed, otherwise dagre would act as if they were still there
const dagreGraph = new dagre.graphlib.Graph()
dagreGraph.setDefaultEdgeLabel(() => ({}))
const isHorizontal = true
dagreGraph.setGraph({
rankdir: 'LR',
// distance between nodes at the same level
nodesep: 25,
// distance between levels
ranksep: 30,
})
for (const node of nodes) {
// if you need width+height of nodes for your layout, you can use the dimensions property of the internal node (`GraphNode` type)
const graphNode = findNode(node.id)
if (!graphNode) {
console.error(`Node with id ${node.id} not found in the graph`)
continue
}
dagreGraph.setNode(node.id, {
width: graphNode.dimensions.width || 100,
height: graphNode.dimensions.height || 50,
})
}
for (const edge of edges) {
dagreGraph.setEdge(edge.source, edge.target)
}
dagre.layout(dagreGraph, {
rankdir: 'LR',
nodesep: 25,
ranksep: 30,
})
// set nodes with updated positions
return nodes.map((node) => {
const nodeWithPosition = dagreGraph.node(node.id)
return {
...node,
targetPosition: isHorizontal ? Position.Left : Position.Top,
sourcePosition: isHorizontal ? Position.Right : Position.Bottom,
position: { x: nodeWithPosition.x, y: nodeWithPosition.y },
}
})
}
return { layout }
}

View File

@ -0,0 +1,30 @@
import pLimit from 'p-limit'
/**
* The concurrency value used by the global limit.
* This represents the *actual* limit value.
* The value in `globalLimit` should not be used, because `deepResearch` uses recursive calls,
* and `globalLimit.concurrency` can be much higher than the actual one.
*/
let globalLimitConcurrency = 2
const globalLimit = pLimit(globalLimitConcurrency)
export function usePLimit() {
const { config } = useConfigStore()
if (
config.webSearch.concurrencyLimit &&
config.webSearch.concurrencyLimit >= 1 &&
globalLimitConcurrency !== config.webSearch.concurrencyLimit
) {
console.log(
`[usePLimit] Updating concurrency from ${globalLimitConcurrency} to ${config.webSearch.concurrencyLimit}. Current concurrency: ${globalLimit.concurrency}`,
)
let newLimit = config.webSearch.concurrencyLimit
let diff = newLimit - globalLimitConcurrency
globalLimitConcurrency = newLimit
globalLimit.concurrency += diff
}
return globalLimit
}

View File

@ -0,0 +1,72 @@
import { tavily } from '@tavily/core'
import Firecrawl from '@mendable/firecrawl-js'
type WebSearchOptions = {
maxResults?: number
/** The search language, e.g. `en`. Only works for Firecrawl. */
lang?: string
}
export type WebSearchResult = {
content: string
url: string
title?: string
}
type WebSearchFunction = (
query: string,
options: WebSearchOptions,
) => Promise<WebSearchResult[]>
export const useWebSearch = (): WebSearchFunction => {
const { config, webSearchApiBase } = useConfigStore()
switch (config.webSearch.provider) {
case 'firecrawl': {
const fc = new Firecrawl({
apiKey: config.webSearch.apiKey,
apiUrl: webSearchApiBase,
})
return async (q: string, o: WebSearchOptions) => {
const results = await fc.search(q, {
...o,
scrapeOptions: {
formats: ['markdown']
}
})
if (results.error) {
throw new Error(results.error)
}
return results.data
.filter((x) => !!x?.markdown && !!x.url)
.map((r) => ({
content: r.markdown!,
url: r.url!,
title: r.title,
}))
}
}
case 'tavily':
default: {
const tvly = tavily({
apiKey: config.webSearch.apiKey,
})
return async (q: string, o: WebSearchOptions) => {
const results = await tvly.search(q, {
...o,
searchDepth: config.webSearch.tavilyAdvancedSearch
? 'advanced'
: 'basic',
topic: config.webSearch.tavilySearchTopic,
})
return results.results
.filter((x) => !!x?.content && !!x.url)
.map((r) => ({
content: r.content,
url: r.url,
title: r.title,
}))
}
}
}
}