From 3f13a42fb12df3ab5e84a3b88a8d7a66c353a3cf Mon Sep 17 00:00:00 2001 From: Lxy Date: Sat, 6 Jun 2026 20:03:58 +0800 Subject: [PATCH] =?UTF-8?q?feat:=20=E5=AE=8C=E5=96=84=E5=AF=B9=E8=AF=9D?= =?UTF-8?q?=E5=BC=8F=E8=A7=84=E5=88=92=E3=80=81=E5=BF=AB=E9=80=9F=E8=A7=84?= =?UTF-8?q?=E5=88=92=E7=AD=89=E5=8A=9F=E8=83=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- package-lock.json | 27 + package.json | 1 + server.js | 71 ++- src/components/CustomPlanPanel.vue | 473 +++++++++++++++ src/components/QuickPlanPanel.vue | 936 +++++++++++++++++++++++++++++ src/components/Workbench.vue | 101 +++- src/services/aiService.js | 283 ++++++++- src/stores/itinerary.js | 43 +- src/views/HomePage.vue | 164 ++++- 9 files changed, 2064 insertions(+), 35 deletions(-) create mode 100644 src/components/CustomPlanPanel.vue create mode 100644 src/components/QuickPlanPanel.vue diff --git a/package-lock.json b/package-lock.json index 412bbae..b14e071 100644 --- a/package-lock.json +++ b/package-lock.json @@ -8,6 +8,7 @@ "name": "trip-planner", "version": "0.0.0", "dependencies": { + "cors": "^2.8.6", "express": "^4.22.2", "leaflet": "^1.9.4", "pinia": "^3.0.4", @@ -1108,6 +1109,23 @@ "url": "https://github.com/sponsors/mesqueeb" } }, + "node_modules/cors": { + "version": "2.8.6", + "resolved": "https://registry.npmmirror.com/cors/-/cors-2.8.6.tgz", + "integrity": "sha512-tJtZBBHA6vjIAaF6EnIaq6laBBP9aq/Y3ouVJjEfoHbRBcHBAHYcMh/w8LDrk2PvIMMq8gmopa5D4V8RmbrxGw==", + "license": "MIT", + "dependencies": { + "object-assign": "^4", + "vary": "^1" + }, + "engines": { + "node": ">= 0.10" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" + } + }, "node_modules/csstype": { "version": "3.2.3", "resolved": "https://registry.npmjs.org/csstype/-/csstype-3.2.3.tgz", @@ -1640,6 +1658,15 @@ "node": ">= 0.6" } }, + "node_modules/object-assign": { + "version": "4.1.1", + "resolved": "https://registry.npmmirror.com/object-assign/-/object-assign-4.1.1.tgz", + "integrity": "sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==", + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, "node_modules/object-inspect": { "version": "1.13.4", "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.13.4.tgz", diff --git a/package.json b/package.json index c5547e9..42e74e8 100644 --- a/package.json +++ b/package.json @@ -9,6 +9,7 @@ "preview": "vite preview" }, "dependencies": { + "cors": "^2.8.6", "express": "^4.22.2", "leaflet": "^1.9.4", "pinia": "^3.0.4", diff --git a/server.js b/server.js index 93f1e55..102f3df 100644 --- a/server.js +++ b/server.js @@ -1,5 +1,5 @@ import express from 'express' -import { createServer as createViteServer } from 'vite' +import { spawn } from 'child_process' import fs from 'fs' import path from 'path' import { fileURLToPath } from 'url' @@ -43,7 +43,15 @@ function saveConfig(config) { } } +import cors from 'cors' + const app = express() +app.use(cors({ + origin: 'http://localhost:5173', + methods: ['GET', 'POST', 'OPTIONS'], + allowedHeaders: ['Content-Type'], + exposedHeaders: ['Content-Type'] +})) app.use(express.json()) // API: Get config @@ -122,6 +130,7 @@ app.post('/api/config/test', async (req, res) => { // API: Proxy AI chat request (keeps API key on server) app.post('/api/chat', async (req, res) => { + console.log('[api/chat] POST received, body:', JSON.stringify(req.body).substring(0, 100)) const config = loadConfig() if (!config.apiKey) { @@ -130,7 +139,10 @@ app.post('/api/chat', async (req, res) => { // Handle client disconnect let clientDisconnected = false - req.on('close', () => { clientDisconnected = true }) + req.socket.on('close', () => { + clientDisconnected = true + console.log('[api/chat] Client socket closed') + }) try { const response = await fetch(`${config.baseUrl}/chat/completions`, { @@ -143,8 +155,7 @@ app.post('/api/chat', async (req, res) => { ...req.body, model: req.body.model || config.model, temperature: req.body.temperature ?? config.temperature, - max_tokens: req.body.max_tokens || config.maxTokens, - response_format: { type: 'json_object' } + max_tokens: req.body.max_tokens || config.maxTokens }) }) @@ -153,14 +164,19 @@ app.post('/api/chat', async (req, res) => { return res.status(response.status).json(error) } + console.log('[api/chat] AI response status:', response.status) + // Stream the response to client - res.setHeader('Content-Type', 'text/event-stream') + res.setHeader('Content-Type', 'application/x-ndjson') res.setHeader('Cache-Control', 'no-cache') res.setHeader('Connection', 'keep-alive') res.setHeader('X-Accel-Buffering', 'no') + console.log('[api/chat] Streaming started') const reader = response.body.getReader() const decoder = new TextDecoder() + let totalBytes = 0 + let chunkCount = 0 while (true) { if (clientDisconnected) { @@ -171,15 +187,17 @@ app.post('/api/chat', async (req, res) => { if (done) break const chunk = decoder.decode(value, { stream: true }) - try { - res.write(chunk) - } catch (e) { - // Client disconnected, stop writing - reader.cancel() - return + totalBytes += chunk.length + chunkCount++ + + res.write(chunk) + + if (chunkCount % 10 === 0) { + console.log('[api/chat] Chunk count:', chunkCount, 'bytes:', totalBytes) } } + console.log('[api/chat] Streaming done, total chunks:', chunkCount, 'bytes:', totalBytes) res.end() } catch (error) { if (!clientDisconnected) { @@ -188,22 +206,35 @@ app.post('/api/chat', async (req, res) => { } }) +// Catch-all for debugging +app.all('/api/*', (req, res) => { + console.log('[DEBUG CATCH-ALL]', req.method, req.path) + res.status(404).json({ error: 'Route not found' }) +}) + async function start() { const isDev = process.env.NODE_ENV !== 'production' if (isDev) { - // Development: use Vite middleware - const vite = await createViteServer({ - server: { middlewareMode: true }, - appType: 'spa' + console.log('[Server] Starting Vite dev server...') + // Run Vite dev server on port 5173 + // API routes are handled by Express on port 3001 + // We use a simple approach: start both servers + const viteProcess = spawn('npx', ['vite'], { + stdio: 'inherit', + shell: true, + env: { ...process.env, NODE_ENV: 'development' } }) - app.use(vite.middlewares) + viteProcess.on('error', (err) => { + console.error('[Server] Failed to start Vite:', err) + }) - const port = 5173 - app.listen(port, () => { - console.log(`Server running at http://localhost:${port}`) - console.log(`API Key stored in: ${CONFIG_FILE}`) + // Start Express API server on port 3001 + const apiPort = 3001 + app.listen(apiPort, () => { + console.log(`[Server] API server running at http://localhost:${apiPort}`) + console.log(`[Server] API Key stored in: ${CONFIG_FILE}`) }) } else { // Production: serve static files diff --git a/src/components/CustomPlanPanel.vue b/src/components/CustomPlanPanel.vue new file mode 100644 index 0000000..1137d38 --- /dev/null +++ b/src/components/CustomPlanPanel.vue @@ -0,0 +1,473 @@ + + + + + diff --git a/src/components/QuickPlanPanel.vue b/src/components/QuickPlanPanel.vue new file mode 100644 index 0000000..6add2a8 --- /dev/null +++ b/src/components/QuickPlanPanel.vue @@ -0,0 +1,936 @@ + + + + + diff --git a/src/components/Workbench.vue b/src/components/Workbench.vue index 852888f..3f8b912 100644 --- a/src/components/Workbench.vue +++ b/src/components/Workbench.vue @@ -15,6 +15,7 @@
+

🚗 {{ itineraryTitle }}

{{ itinerarySubtitle }}
@@ -26,6 +27,26 @@
+ +
+ + +
+ +
+ {{ getSchemeLabel(i) }} + {{ s.name }} +
+
+
+