import { existsSync } from 'node:fs' import { join } from 'pathe' import { addImports, addServerHandler, addComponent, createResolver, defineNuxtModule, logger } from '@nuxt/kit' import { hasAnyAiKey } from './keys' export interface AiChatModuleOptions { /** * 聊天 API 端点路径 * @default '/api/ai-chat' */ apiPath?: string /** * MCP 服务器连接路径 * @default '/mcp' */ mcpPath?: string /** * 使用的 AI 模型 */ model?: string /** * 可用模型列表 * - 格式为 "provider/model" 或 "model" */ models?: string[] } const log = logger.withTag('@movk/nuxt-docs') export default defineNuxtModule({ meta: { name: 'ai-chat', configKey: 'aiChat' }, defaults: { apiPath: '/api/ai-chat', mcpPath: '/mcp', model: '', models: [] }, setup(options, nuxt) { const hasApiKey = hasAnyAiKey() const { resolve } = createResolver(import.meta.url) nuxt.options.runtimeConfig.public.aiChat = { enabled: hasApiKey, apiPath: options.apiPath!, model: options.model!, models: options.models! } addImports([ { name: 'useAIChat', from: resolve('./runtime/composables/useAIChat') } ]) const components = [ 'AiChat', 'AiChatFloatingInput', 'AiChatModelSelect', 'AiChatPanel' ] components.forEach(name => addComponent({ name, filePath: hasApiKey ? resolve(`./runtime/components/${name}.vue`) : resolve('./runtime/components/AiChatDisabled.vue') }) ) if (hasApiKey) { addComponent({ name: 'AiMarkdown', filePath: resolve('./runtime/components/AiMarkdown.client.ts') }) } if (!hasApiKey) { log.warn('[movk-nuxt-docs] Ai Chat Module disabled: no API key found in environment variables.') return } nuxt.options.runtimeConfig.aiChat = { mcpPath: options.mcpPath! } /** * 检查用户项目中是否存在自定义 handler * '/api/ai-chat' -> 'api/ai-chat','/custom' -> 'custom' */ const routePath = options.apiPath!.replace(/^\//, '') const extensions = ['ts', 'js', 'mjs'] const possibleHandlerPaths = extensions.flatMap(ext => [ join(nuxt.options.serverDir, `${routePath}.${ext}`), join(nuxt.options.serverDir, routePath, `index.${ext}`) ]) const hasCustomHandler = possibleHandlerPaths.some(path => existsSync(path)) if (!hasCustomHandler) { addServerHandler({ route: options.apiPath!, handler: resolve('./runtime/server/api/ai-chat') }) } else { log.info(`[movk-nuxt-docs] Using custom handler, skipping default handler registration`) } } }) declare module 'nuxt/schema' { interface PublicRuntimeConfig { aiChat: { enabled: boolean apiPath: string model: string models: string[] } } interface RuntimeConfig { aiChat: { mcpPath: string } } }