Files
tianshu-engine/vite.config.ts

160 lines
6.1 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

import { defineConfig } from 'vite'
import vue from '@vitejs/plugin-vue'
import { resolve } from 'path'
import fs from 'fs'
import { spawn } from 'child_process'
function apiSavePlugin() {
return {
name: 'api-save',
configureServer(server: any) {
server.middlewares.use('/api/save', (req: any, res: any) => {
if (req.method !== 'POST') { res.writeHead(405); res.end(); return }
let body = ''
req.on('data', (c: string) => body += c)
req.on('end', () => {
try {
const { path, data } = JSON.parse(body)
if (!path || typeof path !== 'string' || !path.startsWith('/scenes/')) {
res.writeHead(400)
res.end(JSON.stringify({ error: 'invalid path' }))
return
}
const safePath = resolve(__dirname, 'public', '.' + path)
fs.writeFileSync(safePath, JSON.stringify(data, null, 2))
res.writeHead(200, { 'Content-Type': 'application/json' })
res.end(JSON.stringify({ ok: true }))
} catch (e: any) {
res.writeHead(400)
res.end(JSON.stringify({ error: e.message }))
}
})
})
const dedupMap = new Map<string, number>()
server.middlewares.use('/api/ai', (req: any, res: any) => {
if (req.method !== 'POST') { res.writeHead(405); res.end(); return }
let body = ''
req.on('data', (c: string) => body += c)
req.on('end', () => {
try {
const { sessionId, userMessage, apiKey, mode } = JSON.parse(body)
if (!userMessage) { res.writeHead(400); res.end(JSON.stringify({ error: 'missing fields' })); return }
const dedupKey = `${mode}_${sessionId || ''}_${userMessage}`
const last = dedupMap.get(dedupKey) || 0
if (Date.now() - last < 3000) {
res.writeHead(429)
res.end(JSON.stringify({ error: 'duplicate request' }))
return
}
dedupMap.set(dedupKey, Date.now())
const modePrefix = mode === 'code'
? '代码模式:直接修改 src/ 下的源码文件并保存。需求:'
: 'JSON模式只返回修改后的 JSON 文本,不要写任何文件。需求:'
const fullMessage = modePrefix + userMessage
const args = ['run', '--model', 'deepseek/deepseek-v4-pro', '--format', 'json']
if (sessionId) args.push('--session', sessionId)
args.push(fullMessage)
const opencodeBin = resolve(__dirname, 'node_modules', '.bin', 'opencode')
const child = spawn(opencodeBin, args, {
env: { ...process.env, DEEPSEEK_API_KEY: apiKey || process.env.DEEPSEEK_API_KEY || '' },
timeout: 15000,
})
let stdout = ''
let stderr = ''
child.stdout.on('data', (d: Buffer) => stdout += d.toString())
child.stderr.on('data', (d: Buffer) => stderr += d.toString())
child.on('close', async (code) => {
if (code !== 0) {
res.writeHead(500)
res.end(JSON.stringify({ error: 'opencode exited with code ' + code, stderr }))
return
}
let resolvedSessionId = sessionId
if (!resolvedSessionId) {
try {
const listChild = spawn(opencodeBin, ['session', 'list', '--format', 'json', '--max-count', '1'], {
timeout: 5000,
env: { ...process.env, DEEPSEEK_API_KEY: apiKey || process.env.DEEPSEEK_API_KEY || '' },
})
let listOut = ''
listChild.stdout.on('data', (d: Buffer) => listOut += d.toString())
await new Promise<void>((resolveList) => listChild.on('close', () => {
try {
const sessions = JSON.parse(listOut)
if (Array.isArray(sessions) && sessions.length > 0) {
resolvedSessionId = sessions[0].id
}
} catch {}
resolveList()
}))
} catch {}
}
if (mode === 'json') {
const match = stdout.match(/```json\n?([\s\S]*?)\n?```|\{[\s\S]*\}/)
const jsonStr = match ? (match[1] || match[0]) : stdout
try { JSON.parse(jsonStr) } catch {
res.writeHead(500)
res.end(JSON.stringify({ error: 'invalid JSON returned', raw: stdout }))
return
}
res.writeHead(200, { 'Content-Type': 'application/json' })
res.end(JSON.stringify({ result: jsonStr, sessionId: resolvedSessionId || '' }))
} else {
res.writeHead(200, { 'Content-Type': 'application/json' })
res.end(JSON.stringify({ result: stdout || 'done', sessionId: resolvedSessionId || '' }))
}
})
} catch (e: any) {
res.writeHead(400)
res.end(JSON.stringify({ error: e.message }))
}
})
})
server.middlewares.use('/api/ai/sessions', (req: any, res: any) => {
try {
const opencodeBin = resolve(__dirname, 'node_modules', '.bin', 'opencode')
const child = spawn(opencodeBin, ['session', 'list', '--format', 'json'], { timeout: 5000 })
let stdout = ''
child.stdout.on('data', (d: Buffer) => stdout += d.toString())
child.on('close', () => {
res.writeHead(200, { 'Content-Type': 'application/json' })
res.end(stdout || '[]')
})
} catch {
res.writeHead(200, { 'Content-Type': 'application/json' })
res.end('[]')
}
})
},
}
}
export default defineConfig({
plugins: [vue(), apiSavePlugin()],
resolve: {
alias: {
'@': resolve(__dirname, 'src'),
'@engine': resolve(__dirname, 'engine'),
},
},
build: {
rollupOptions: {
input: {
main: resolve(__dirname, 'index.html'),
editor: resolve(__dirname, 'editor/index.html'),
},
},
},
})