mikasa-cli 1.0.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/package.json +39 -0
- package/prisma/migrations/20260122185342_trst_migration/migration.sql +7 -0
- package/prisma/migrations/20260122192114/migration.sql +78 -0
- package/prisma/migrations/20260123074430_device_flow/migration.sql +15 -0
- package/prisma/migrations/migration_lock.toml +3 -0
- package/prisma/schema.prisma +125 -0
- package/src/cli/ai/google-service.js +235 -0
- package/src/cli/chat/chat-with-ai-agent.js +219 -0
- package/src/cli/chat/chat-with-ai-tool.js +377 -0
- package/src/cli/chat/chat-with-ai.js +275 -0
- package/src/cli/commands/ai/wakeUp.js +81 -0
- package/src/cli/commands/auth/login.js +607 -0
- package/src/cli/main.js +56 -0
- package/src/config/agent.config.js +166 -0
- package/src/config/google.config.js +8 -0
- package/src/config/tool.config.js +112 -0
- package/src/index.js +119 -0
- package/src/lib/auth-client.js +9 -0
- package/src/lib/auth.js +52 -0
- package/src/lib/db.js +9 -0
- package/src/services/chat.services.js +152 -0
|
@@ -0,0 +1,166 @@
|
|
|
1
|
+
import { promises as fs } from 'fs';
|
|
2
|
+
import path from 'path';
|
|
3
|
+
import chalk from 'chalk';
|
|
4
|
+
import { generateObject } from 'ai';
|
|
5
|
+
import { z } from 'zod';
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* Zod schema for structured application generation
|
|
9
|
+
*/
|
|
10
|
+
const ApplicationSchema = z.object({
|
|
11
|
+
folderName: z.string().describe('Kebab-case folder name for the application'),
|
|
12
|
+
description: z.string().describe('Brief description of what was created'),
|
|
13
|
+
files: z.array(
|
|
14
|
+
z.object({
|
|
15
|
+
path: z.string().describe('Relative file path (e.g., src/App.jsx)'),
|
|
16
|
+
content: z.string().describe('Complete file content'),
|
|
17
|
+
})
|
|
18
|
+
).describe('All files needed for the application'),
|
|
19
|
+
setupCommands: z.array(z.string()).describe('Bash commands to setup and run (e.g., npm install, npm run dev)'),
|
|
20
|
+
});
|
|
21
|
+
|
|
22
|
+
/**
|
|
23
|
+
* Console logging helpers
|
|
24
|
+
*/
|
|
25
|
+
function printSystem(message) {
|
|
26
|
+
console.log(message);
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
/**
|
|
30
|
+
* Display file tree structure
|
|
31
|
+
*/
|
|
32
|
+
function displayFileTree(files, folderName) {
|
|
33
|
+
printSystem(chalk.cyan('\nš Project Structure:'));
|
|
34
|
+
printSystem(chalk.white(`${folderName}/`));
|
|
35
|
+
|
|
36
|
+
const filesByDir = {};
|
|
37
|
+
files.forEach(file => {
|
|
38
|
+
const parts = file.path.split('/');
|
|
39
|
+
const dir = parts.length > 1 ? parts.slice(0, -1).join('/') : '';
|
|
40
|
+
|
|
41
|
+
if (!filesByDir[dir]) {
|
|
42
|
+
filesByDir[dir] = [];
|
|
43
|
+
}
|
|
44
|
+
filesByDir[dir].push(parts[parts.length - 1]);
|
|
45
|
+
});
|
|
46
|
+
|
|
47
|
+
Object.keys(filesByDir).sort().forEach(dir => {
|
|
48
|
+
if (dir) {
|
|
49
|
+
printSystem(chalk.white(`āāā ${dir}/`));
|
|
50
|
+
filesByDir[dir].forEach(file => {
|
|
51
|
+
printSystem(chalk.white(`ā āāā ${file}`));
|
|
52
|
+
});
|
|
53
|
+
} else {
|
|
54
|
+
filesByDir[dir].forEach(file => {
|
|
55
|
+
printSystem(chalk.white(`āāā ${file}`));
|
|
56
|
+
});
|
|
57
|
+
}
|
|
58
|
+
});
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
/**
|
|
62
|
+
* Create application files
|
|
63
|
+
*/
|
|
64
|
+
async function createApplicationFiles(baseDir, folderName, files) {
|
|
65
|
+
const appDir = path.join(baseDir, folderName);
|
|
66
|
+
|
|
67
|
+
await fs.mkdir(appDir, { recursive: true });
|
|
68
|
+
printSystem(chalk.cyan(`\nš Created directory: ${folderName}/`));
|
|
69
|
+
|
|
70
|
+
for (const file of files) {
|
|
71
|
+
const filePath = path.join(appDir, file.path);
|
|
72
|
+
const fileDir = path.dirname(filePath);
|
|
73
|
+
|
|
74
|
+
await fs.mkdir(fileDir, { recursive: true });
|
|
75
|
+
await fs.writeFile(filePath, file.content, 'utf8');
|
|
76
|
+
printSystem(chalk.green(` ā ${file.path}`));
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
return appDir;
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
/**
|
|
83
|
+
* Generate application using structured output
|
|
84
|
+
*/
|
|
85
|
+
export async function generateApplication(description, aiService, cwd = process.cwd()) {
|
|
86
|
+
try {
|
|
87
|
+
printSystem(chalk.cyan('\nš¤ Agent Mode: Generating your application...\n'));
|
|
88
|
+
printSystem(chalk.gray(`Request: ${description}\n`));
|
|
89
|
+
|
|
90
|
+
printSystem(chalk.magenta('š¤ Generating structured output...\n'));
|
|
91
|
+
|
|
92
|
+
|
|
93
|
+
const result = await generateObject({
|
|
94
|
+
model: aiService.model,
|
|
95
|
+
schema: ApplicationSchema,
|
|
96
|
+
prompt: `Create a complete, production-ready application for: ${description}
|
|
97
|
+
|
|
98
|
+
CRITICAL REQUIREMENTS:
|
|
99
|
+
1. Generate ALL files needed for the application to run
|
|
100
|
+
2. Include package.json with ALL dependencies and correct versions (if needed)
|
|
101
|
+
3. Include README.md with setup instructions
|
|
102
|
+
4. Include configuration files (.gitignore, etc.) if needed
|
|
103
|
+
5. Write clean, well-commented, production-ready code
|
|
104
|
+
6. Include error handling and input validation
|
|
105
|
+
7. Use modern JavaScript/TypeScript best practices
|
|
106
|
+
8. Make sure all imports and paths are correct
|
|
107
|
+
9. NO PLACEHOLDERS - everything must be complete and working
|
|
108
|
+
10. For simple HTML/CSS/JS projects, you can skip package.json if not needed
|
|
109
|
+
|
|
110
|
+
Provide:
|
|
111
|
+
- A meaningful kebab-case folder name
|
|
112
|
+
- All necessary files with complete content
|
|
113
|
+
- Setup commands (for example: cd folder, npm install, npm run dev OR just open index.html)
|
|
114
|
+
- Make it visually appealing and functional`,
|
|
115
|
+
});
|
|
116
|
+
|
|
117
|
+
const application = result.object;
|
|
118
|
+
|
|
119
|
+
printSystem(chalk.green(`\nā
Generated: ${application.folderName}\n`));
|
|
120
|
+
printSystem(chalk.gray(`Description: ${application.description}\n`));
|
|
121
|
+
|
|
122
|
+
if (!application.files || application.files.length === 0) {
|
|
123
|
+
throw new Error('No files were generated');
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
printSystem(chalk.green(`Files: ${application.files.length}\n`));
|
|
127
|
+
|
|
128
|
+
// Display file tree
|
|
129
|
+
displayFileTree(application.files, application.folderName);
|
|
130
|
+
|
|
131
|
+
// Create application directory and files
|
|
132
|
+
printSystem(chalk.cyan('\nš Creating files...\n'));
|
|
133
|
+
const appDir = await createApplicationFiles(cwd, application.folderName, application.files);
|
|
134
|
+
|
|
135
|
+
// Display results
|
|
136
|
+
printSystem(chalk.green.bold(`\n⨠Application created successfully!\n`));
|
|
137
|
+
printSystem(chalk.cyan(`š Location: ${chalk.bold(appDir)}\n`));
|
|
138
|
+
|
|
139
|
+
// Display setup commands
|
|
140
|
+
if (application.setupCommands && application.setupCommands.length > 0) {
|
|
141
|
+
printSystem(chalk.cyan('š Next Steps:\n'));
|
|
142
|
+
printSystem(chalk.white('```bash'));
|
|
143
|
+
application.setupCommands.forEach(cmd => {
|
|
144
|
+
printSystem(chalk.white(cmd));
|
|
145
|
+
});
|
|
146
|
+
printSystem(chalk.white('```\n'));
|
|
147
|
+
} else {
|
|
148
|
+
printSystem(chalk.yellow('ā¹ļø No setup commands provided\n'));
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
return {
|
|
152
|
+
folderName: application.folderName,
|
|
153
|
+
appDir,
|
|
154
|
+
files: application.files.map(f => f.path),
|
|
155
|
+
commands: application.setupCommands || [],
|
|
156
|
+
success: true,
|
|
157
|
+
};
|
|
158
|
+
|
|
159
|
+
} catch (err) {
|
|
160
|
+
printSystem(chalk.red(`\nā Error generating application: ${err.message}\n`));
|
|
161
|
+
if (err.stack) {
|
|
162
|
+
printSystem(chalk.dim(err.stack + '\n'));
|
|
163
|
+
}
|
|
164
|
+
throw err;
|
|
165
|
+
}
|
|
166
|
+
}
|
|
@@ -0,0 +1,112 @@
|
|
|
1
|
+
import { google } from '@ai-sdk/google';
|
|
2
|
+
import chalk from 'chalk';
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* Available Google Generative AI tools configuration
|
|
6
|
+
* Note: Tools are instantiated lazily to avoid initialization errors
|
|
7
|
+
*/
|
|
8
|
+
export const availableTools = [
|
|
9
|
+
{
|
|
10
|
+
id: 'google_search',
|
|
11
|
+
name: 'Google Search',
|
|
12
|
+
description: 'Access the latest information using Google search. Useful for current events, news, and real-time information.',
|
|
13
|
+
getTool: () => google.tools.googleSearch({}),
|
|
14
|
+
enabled: false,
|
|
15
|
+
},
|
|
16
|
+
{
|
|
17
|
+
id: 'code_execution',
|
|
18
|
+
name: 'Code Execution',
|
|
19
|
+
description: 'Generate and execute Python code to perform calculations, solve problems, or provide accurate information.',
|
|
20
|
+
getTool: () => google.tools.codeExecution({}),
|
|
21
|
+
enabled: false,
|
|
22
|
+
},
|
|
23
|
+
{
|
|
24
|
+
id: 'url_context',
|
|
25
|
+
name: 'URL Context',
|
|
26
|
+
description: 'Provide specific URLs that you want the model to analyze directly from the prompt. Supports up to 20 URLs per request.',
|
|
27
|
+
getTool: () => google.tools.urlContext({}),
|
|
28
|
+
enabled: false,
|
|
29
|
+
},
|
|
30
|
+
];
|
|
31
|
+
|
|
32
|
+
/**
|
|
33
|
+
* Get enabled tools as a tools object for AI SDK
|
|
34
|
+
*/
|
|
35
|
+
export function getEnabledTools() {
|
|
36
|
+
const tools = {};
|
|
37
|
+
|
|
38
|
+
try {
|
|
39
|
+
for (const toolConfig of availableTools) {
|
|
40
|
+
if (toolConfig.enabled) {
|
|
41
|
+
// Instantiate the tool when needed
|
|
42
|
+
tools[toolConfig.id] = toolConfig.getTool();
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
// Debug logging
|
|
47
|
+
if (Object.keys(tools).length > 0) {
|
|
48
|
+
console.log(chalk.gray(`[DEBUG] Enabled tools: ${Object.keys(tools).join(', ')}`));
|
|
49
|
+
} else {
|
|
50
|
+
console.log(chalk.yellow('[DEBUG] No tools enabled'));
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
return Object.keys(tools).length > 0 ? tools : undefined;
|
|
54
|
+
} catch (error) {
|
|
55
|
+
console.error(chalk.red('[ERROR] Failed to initialize tools:'), error.message);
|
|
56
|
+
console.error(chalk.yellow('Make sure you have @ai-sdk/google version 2.0+ installed'));
|
|
57
|
+
console.error(chalk.yellow('Run: npm install @ai-sdk/google@latest'));
|
|
58
|
+
return undefined;
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
/**
|
|
63
|
+
* Toggle a tool's enabled state
|
|
64
|
+
*/
|
|
65
|
+
export function toggleTool(toolId) {
|
|
66
|
+
const tool = availableTools.find(t => t.id === toolId);
|
|
67
|
+
if (tool) {
|
|
68
|
+
tool.enabled = !tool.enabled;
|
|
69
|
+
console.log(chalk.gray(`[DEBUG] Tool ${toolId} toggled to ${tool.enabled}`));
|
|
70
|
+
return tool.enabled;
|
|
71
|
+
}
|
|
72
|
+
console.log(chalk.red(`[DEBUG] Tool ${toolId} not found`));
|
|
73
|
+
return false;
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
/**
|
|
77
|
+
* Enable specific tools
|
|
78
|
+
*/
|
|
79
|
+
export function enableTools(toolIds) {
|
|
80
|
+
console.log(chalk.gray('[DEBUG] enableTools called with:'), toolIds);
|
|
81
|
+
|
|
82
|
+
availableTools.forEach(tool => {
|
|
83
|
+
const wasEnabled = tool.enabled;
|
|
84
|
+
tool.enabled = toolIds.includes(tool.id);
|
|
85
|
+
|
|
86
|
+
if (tool.enabled !== wasEnabled) {
|
|
87
|
+
console.log(chalk.gray(`[DEBUG] ${tool.id}: ${wasEnabled} ā ${tool.enabled}`));
|
|
88
|
+
}
|
|
89
|
+
});
|
|
90
|
+
|
|
91
|
+
const enabledCount = availableTools.filter(t => t.enabled).length;
|
|
92
|
+
console.log(chalk.gray(`[DEBUG] Total tools enabled: ${enabledCount}/${availableTools.length}`));
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
/**
|
|
96
|
+
* Get all enabled tool names
|
|
97
|
+
*/
|
|
98
|
+
export function getEnabledToolNames() {
|
|
99
|
+
const names = availableTools.filter(t => t.enabled).map(t => t.name);
|
|
100
|
+
console.log(chalk.gray('[DEBUG] getEnabledToolNames returning:'), names);
|
|
101
|
+
return names;
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
/**
|
|
105
|
+
* Reset all tools (disable all)
|
|
106
|
+
*/
|
|
107
|
+
export function resetTools() {
|
|
108
|
+
availableTools.forEach(tool => {
|
|
109
|
+
tool.enabled = false;
|
|
110
|
+
});
|
|
111
|
+
console.log(chalk.gray('[DEBUG] All tools have been reset (disabled)'));
|
|
112
|
+
}
|
package/src/index.js
ADDED
|
@@ -0,0 +1,119 @@
|
|
|
1
|
+
// import express from 'express';
|
|
2
|
+
// import dotenv from 'dotenv';
|
|
3
|
+
// import { fromNodeHeaders, toNodeHandler } from "better-auth/node";
|
|
4
|
+
// import cors from 'cors';
|
|
5
|
+
// import { auth } from './lib/auth.js';
|
|
6
|
+
|
|
7
|
+
// dotenv.config();
|
|
8
|
+
|
|
9
|
+
// const app = express();
|
|
10
|
+
// app.use(cors({
|
|
11
|
+
// origin: 'http://localhost:3000',
|
|
12
|
+
// methods: ['GET', 'POST', 'PUT', 'DELETE'],
|
|
13
|
+
// credentials: true,
|
|
14
|
+
// }));
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
// app.all("/api/auth/*spllat", toNodeHandler(auth));
|
|
18
|
+
// app.use(express.json());
|
|
19
|
+
// const PORT = process.env.PORT || 5000;
|
|
20
|
+
|
|
21
|
+
// app.get('/api/me', async (req, res) => {
|
|
22
|
+
// const session = await auth.getSession({
|
|
23
|
+
// headers: fromNodeHeaders(req.headers),
|
|
24
|
+
// });
|
|
25
|
+
// });
|
|
26
|
+
|
|
27
|
+
// // app.get("/", (req, res) => {
|
|
28
|
+
// // res.redirect("http://localhost:3000/");
|
|
29
|
+
// // });
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
// app.listen(PORT, () => {
|
|
33
|
+
// console.log(`Server is running on http://localhost:${PORT}`);
|
|
34
|
+
// });
|
|
35
|
+
|
|
36
|
+
|
|
37
|
+
|
|
38
|
+
|
|
39
|
+
import express from "express";
|
|
40
|
+
import dotenv from "dotenv";
|
|
41
|
+
import cors from "cors";
|
|
42
|
+
import { fromNodeHeaders, toNodeHandler } from "better-auth/node";
|
|
43
|
+
import { auth } from "./lib/auth.js";
|
|
44
|
+
|
|
45
|
+
dotenv.config();
|
|
46
|
+
|
|
47
|
+
const app = express();
|
|
48
|
+
|
|
49
|
+
app.use(cors({
|
|
50
|
+
origin: "https://mikasa-cli-xwcm.vercel.app",
|
|
51
|
+
credentials: true,
|
|
52
|
+
}));
|
|
53
|
+
|
|
54
|
+
app.use(express.json());
|
|
55
|
+
|
|
56
|
+
// ā
REGEX ROUTE (WORKS WITH path-to-regexp v8)
|
|
57
|
+
app.all(/^\/api\/auth\/.*$/, toNodeHandler(auth));
|
|
58
|
+
|
|
59
|
+
// app.get("/api/me", async (req, res) => {
|
|
60
|
+
// const session = await auth.getSession({
|
|
61
|
+
// headers: fromNodeHeaders(req.headers),
|
|
62
|
+
// });
|
|
63
|
+
// res.json(session);
|
|
64
|
+
// });
|
|
65
|
+
|
|
66
|
+
// Fixed: This endpoint now properly handles Bearer token authentication
|
|
67
|
+
app.get("/api/me", async (req, res) => {
|
|
68
|
+
try {
|
|
69
|
+
const session = await auth.api.getSession({
|
|
70
|
+
headers: fromNodeHeaders(req.headers),
|
|
71
|
+
});
|
|
72
|
+
|
|
73
|
+
if (!session) {
|
|
74
|
+
return res.status(401).json({ error: "No active session" });
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
return res.json(session);
|
|
78
|
+
} catch (error) {
|
|
79
|
+
console.error("Session error:", error);
|
|
80
|
+
return res.status(500).json({ error: "Failed to get session", details: error.message });
|
|
81
|
+
}
|
|
82
|
+
});
|
|
83
|
+
|
|
84
|
+
|
|
85
|
+
// You can remove this endpoint if you're using the Bearer token approach above
|
|
86
|
+
app.get("/api/me/:access_token", async (req, res) => {
|
|
87
|
+
const { access_token } = req.params;
|
|
88
|
+
console.log(access_token);
|
|
89
|
+
|
|
90
|
+
try {
|
|
91
|
+
const session = await auth.api.getSession({
|
|
92
|
+
headers: {
|
|
93
|
+
authorization: `Bearer ${access_token}`
|
|
94
|
+
}
|
|
95
|
+
});
|
|
96
|
+
|
|
97
|
+
if (!session) {
|
|
98
|
+
return res.status(401).json({ error: "Invalid token" });
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
return res.json(session);
|
|
102
|
+
} catch (error) {
|
|
103
|
+
console.error("Token validation error:", error);
|
|
104
|
+
return res.status(401).json({ error: "Unauthorized", details: error.message });
|
|
105
|
+
}
|
|
106
|
+
});
|
|
107
|
+
|
|
108
|
+
|
|
109
|
+
app.get("/device", (req, res) => {
|
|
110
|
+
const { user_code } = req.query;
|
|
111
|
+
res.redirect(`https://mikasa-cli-xwcm.vercel.app/device?user_code=${user_code}`);
|
|
112
|
+
});
|
|
113
|
+
|
|
114
|
+
const PORT = process.env.PORT || 5000;
|
|
115
|
+
|
|
116
|
+
app.listen(PORT, () => {
|
|
117
|
+
console.log(`Server running on http://localhost:${PORT}`);
|
|
118
|
+
});
|
|
119
|
+
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import { deviceAuthorizationClient } from "better-auth/client/plugins"
|
|
2
|
+
import { createAuthClient } from "better-auth/client"
|
|
3
|
+
|
|
4
|
+
export const authClient = createAuthClient({
|
|
5
|
+
baseURL: "https://mikasa-cli-2.onrender.com",
|
|
6
|
+
plugins: [
|
|
7
|
+
deviceAuthorizationClient(),
|
|
8
|
+
],
|
|
9
|
+
})
|
package/src/lib/auth.js
ADDED
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
import { betterAuth } from 'better-auth';
|
|
2
|
+
import { prismaAdapter } from 'better-auth/adapters/prisma';
|
|
3
|
+
import prisma from './db.js';
|
|
4
|
+
import { deviceAuthorization } from "better-auth/plugins";
|
|
5
|
+
|
|
6
|
+
export const auth = betterAuth({
|
|
7
|
+
database: prismaAdapter(prisma, {
|
|
8
|
+
provider: 'postgresql', // or 'postgresql', 'mysql', etc.
|
|
9
|
+
}),
|
|
10
|
+
basePath: '/api/auth',
|
|
11
|
+
trustedOrigins: ['https://mikasa-cli-xwcm.vercel.app'],
|
|
12
|
+
plugins: [
|
|
13
|
+
deviceAuthorization({
|
|
14
|
+
// verificationUri: "/device",
|
|
15
|
+
expiresIn: "30m", // default is 15 minutes
|
|
16
|
+
interval: "5s", // default is 5 seconds
|
|
17
|
+
}),
|
|
18
|
+
],
|
|
19
|
+
socialProviders: {
|
|
20
|
+
github: {
|
|
21
|
+
clientId: process.env.GITHUB_CLIENT_ID || "Ov23lir2byjlHCgWLvEf",
|
|
22
|
+
clientSecret: process.env.GITHUB_CLIENT_SECRET || "1508f24efb3a1969480348b36b35302b6ad07952",
|
|
23
|
+
}
|
|
24
|
+
},
|
|
25
|
+
// logger: {
|
|
26
|
+
// level: "debug"
|
|
27
|
+
// }
|
|
28
|
+
});
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
// import { betterAuth } from "better-auth";
|
|
32
|
+
// import { prismaAdapter } from "better-auth/adapters/prisma";
|
|
33
|
+
// import prisma from "./db.js";
|
|
34
|
+
|
|
35
|
+
// export const auth = betterAuth({
|
|
36
|
+
// basePath: "/api/auth",
|
|
37
|
+
// origin: "http://localhost:30005",
|
|
38
|
+
// trustedOrigins: ["http://localhost:3000"],
|
|
39
|
+
|
|
40
|
+
// database: prismaAdapter(prisma, {
|
|
41
|
+
// provider: "postgresql",
|
|
42
|
+
// }),
|
|
43
|
+
|
|
44
|
+
// socialProviders: {
|
|
45
|
+
// github: {
|
|
46
|
+
// clientId: process.env.GITHUB_CLIENT_ID,
|
|
47
|
+
// clientSecret: process.env.GITHUB_CLIENT_SECRET,
|
|
48
|
+
// },
|
|
49
|
+
// },
|
|
50
|
+
// });
|
|
51
|
+
|
|
52
|
+
|
package/src/lib/db.js
ADDED
|
@@ -0,0 +1,152 @@
|
|
|
1
|
+
|
|
2
|
+
import { auth } from "../lib/auth.js";
|
|
3
|
+
import prisma from "../lib/db.js";
|
|
4
|
+
|
|
5
|
+
export class ChatService {
|
|
6
|
+
/**
|
|
7
|
+
* Create a new conversation
|
|
8
|
+
* @param {string} userId - User ID
|
|
9
|
+
* @param {string} mode - chat, tool, or agent
|
|
10
|
+
* @param {string} title - Optional conversation title
|
|
11
|
+
*/
|
|
12
|
+
async createConversation(userId, mode = "chat", title = null) {
|
|
13
|
+
|
|
14
|
+
return await prisma.conversation.create({
|
|
15
|
+
data: {
|
|
16
|
+
userId,
|
|
17
|
+
mode,
|
|
18
|
+
title: title || `New ${mode} conversation`,
|
|
19
|
+
},
|
|
20
|
+
});
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
/**
|
|
24
|
+
* Get or create a conversation for user
|
|
25
|
+
* @param {string} userId - User ID
|
|
26
|
+
* @param {string} conversationId - Optional conversation ID
|
|
27
|
+
* @param {string} mode - chat, tool, or agent
|
|
28
|
+
*/
|
|
29
|
+
async getOrCreateConversation(userId, conversationId = null, mode = "chat") {
|
|
30
|
+
if (conversationId) {
|
|
31
|
+
const conversation = await prisma.conversation.findFirst({
|
|
32
|
+
where: {
|
|
33
|
+
id: conversationId,
|
|
34
|
+
userId,
|
|
35
|
+
},
|
|
36
|
+
include: {
|
|
37
|
+
messages: {
|
|
38
|
+
orderBy: { createdAt: "asc" },
|
|
39
|
+
},
|
|
40
|
+
},
|
|
41
|
+
});
|
|
42
|
+
|
|
43
|
+
if (conversation) return conversation;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
// Create new conversation if not found or not provided
|
|
47
|
+
return await this.createConversation(userId, mode);
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
/**
|
|
51
|
+
* Add a message to conversation
|
|
52
|
+
* @param {string} conversationId - Conversation ID
|
|
53
|
+
* @param {string} role - user, assistant, system, tool
|
|
54
|
+
* @param {string|object} content - Message content
|
|
55
|
+
*/
|
|
56
|
+
async addMessage(conversationId, role, content) {
|
|
57
|
+
// Convert content to JSON string if it's an object
|
|
58
|
+
const contentStr = typeof content === "string"
|
|
59
|
+
? content
|
|
60
|
+
: JSON.stringify(content);
|
|
61
|
+
|
|
62
|
+
return await prisma.message.create({
|
|
63
|
+
data: {
|
|
64
|
+
conversationId,
|
|
65
|
+
role,
|
|
66
|
+
content: contentStr,
|
|
67
|
+
},
|
|
68
|
+
});
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
/**
|
|
72
|
+
* Get conversation messages
|
|
73
|
+
* @param {string} conversationId - Conversation ID
|
|
74
|
+
*/
|
|
75
|
+
async getMessages(conversationId) {
|
|
76
|
+
const messages = await prisma.message.findMany({
|
|
77
|
+
where: { conversationId },
|
|
78
|
+
orderBy: { createdAt: "asc" },
|
|
79
|
+
});
|
|
80
|
+
|
|
81
|
+
// Parse JSON content back to objects if needed
|
|
82
|
+
return messages.map((msg) => ({
|
|
83
|
+
...msg,
|
|
84
|
+
content: this.parseContent(msg.content),
|
|
85
|
+
}));
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
/**
|
|
89
|
+
* Get all conversations for a user
|
|
90
|
+
* @param {string} userId - User ID
|
|
91
|
+
*/
|
|
92
|
+
async getUserConversations(userId) {
|
|
93
|
+
return await prisma.conversation.findMany({
|
|
94
|
+
where: { userId },
|
|
95
|
+
orderBy: { updatedAt: "desc" },
|
|
96
|
+
include: {
|
|
97
|
+
messages: {
|
|
98
|
+
take: 1,
|
|
99
|
+
orderBy: { createdAt: "desc" },
|
|
100
|
+
},
|
|
101
|
+
},
|
|
102
|
+
});
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
/**
|
|
106
|
+
* Delete a conversation
|
|
107
|
+
* @param {string} conversationId - Conversation ID
|
|
108
|
+
* @param {string} userId - User ID (for security)
|
|
109
|
+
*/
|
|
110
|
+
async deleteConversation(conversationId, userId) {
|
|
111
|
+
return await prisma.conversation.deleteMany({
|
|
112
|
+
where: {
|
|
113
|
+
id: conversationId,
|
|
114
|
+
userId,
|
|
115
|
+
},
|
|
116
|
+
});
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
/**
|
|
120
|
+
* Update conversation title
|
|
121
|
+
* @param {string} conversationId - Conversation ID
|
|
122
|
+
* @param {string} title - New title
|
|
123
|
+
*/
|
|
124
|
+
async updateTitle(conversationId, title) {
|
|
125
|
+
return await prisma.conversation.update({
|
|
126
|
+
where: { id: conversationId },
|
|
127
|
+
data: { title },
|
|
128
|
+
});
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
/**
|
|
132
|
+
* Helper to parse content (JSON or string)
|
|
133
|
+
*/
|
|
134
|
+
parseContent(content) {
|
|
135
|
+
try {
|
|
136
|
+
return JSON.parse(content);
|
|
137
|
+
} catch {
|
|
138
|
+
return content;
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
/**
|
|
143
|
+
* Format messages for AI SDK
|
|
144
|
+
* @param {Array} messages - Database messages
|
|
145
|
+
*/
|
|
146
|
+
formatMessagesForAI(messages) {
|
|
147
|
+
return messages.map((msg) => ({
|
|
148
|
+
role: msg.role,
|
|
149
|
+
content: typeof msg.content === "string" ? msg.content : JSON.stringify(msg.content),
|
|
150
|
+
}));
|
|
151
|
+
}
|
|
152
|
+
}
|