olly-molly 0.1.0 → 0.1.2

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.
Files changed (77) hide show
  1. package/README.md +3 -3
  2. package/bin/cli.js +53 -14
  3. package/package.json +4 -15
  4. package/app/api/agent/execute/route.ts +0 -157
  5. package/app/api/agent/status/route.ts +0 -38
  6. package/app/api/check-api-key/route.ts +0 -12
  7. package/app/api/conversations/[id]/route.ts +0 -35
  8. package/app/api/conversations/route.ts +0 -24
  9. package/app/api/members/[id]/route.ts +0 -37
  10. package/app/api/members/route.ts +0 -12
  11. package/app/api/pm/breakdown/route.ts +0 -142
  12. package/app/api/pm/tickets/route.ts +0 -147
  13. package/app/api/projects/[id]/route.ts +0 -56
  14. package/app/api/projects/active/route.ts +0 -15
  15. package/app/api/projects/route.ts +0 -53
  16. package/app/api/tickets/[id]/logs/route.ts +0 -16
  17. package/app/api/tickets/[id]/route.ts +0 -60
  18. package/app/api/tickets/[id]/work-logs/route.ts +0 -16
  19. package/app/api/tickets/route.ts +0 -37
  20. package/app/design-system/page.tsx +0 -242
  21. package/app/favicon.ico +0 -0
  22. package/app/globals.css +0 -318
  23. package/app/layout.tsx +0 -37
  24. package/app/page.tsx +0 -331
  25. package/components/ThemeProvider.tsx +0 -56
  26. package/components/ThemeToggle.tsx +0 -31
  27. package/components/activity/ActivityLog.tsx +0 -96
  28. package/components/activity/index.ts +0 -1
  29. package/components/kanban/ConversationList.tsx +0 -75
  30. package/components/kanban/ConversationView.tsx +0 -132
  31. package/components/kanban/KanbanBoard.tsx +0 -179
  32. package/components/kanban/KanbanColumn.tsx +0 -80
  33. package/components/kanban/SortableTicket.tsx +0 -58
  34. package/components/kanban/TicketCard.tsx +0 -98
  35. package/components/kanban/TicketModal.tsx +0 -510
  36. package/components/kanban/TicketSidebar.tsx +0 -448
  37. package/components/kanban/index.ts +0 -8
  38. package/components/pm/PMRequestModal.tsx +0 -196
  39. package/components/pm/index.ts +0 -1
  40. package/components/project/ProjectSelector.tsx +0 -211
  41. package/components/project/index.ts +0 -1
  42. package/components/team/MemberCard.tsx +0 -147
  43. package/components/team/TeamPanel.tsx +0 -57
  44. package/components/team/index.ts +0 -2
  45. package/components/ui/ApiKeyModal.tsx +0 -101
  46. package/components/ui/Avatar.tsx +0 -95
  47. package/components/ui/Badge.tsx +0 -59
  48. package/components/ui/Button.tsx +0 -60
  49. package/components/ui/Card.tsx +0 -64
  50. package/components/ui/Input.tsx +0 -41
  51. package/components/ui/Modal.tsx +0 -76
  52. package/components/ui/ResizablePane.tsx +0 -97
  53. package/components/ui/Select.tsx +0 -45
  54. package/components/ui/Textarea.tsx +0 -41
  55. package/components/ui/index.ts +0 -8
  56. package/db/dev.sqlite +0 -0
  57. package/db/dev.sqlite-shm +0 -0
  58. package/db/dev.sqlite-wal +0 -0
  59. package/db/schema-conversations.sql +0 -26
  60. package/db/schema-projects.sql +0 -29
  61. package/db/schema.sql +0 -94
  62. package/lib/agent-jobs.ts +0 -232
  63. package/lib/db.ts +0 -564
  64. package/next.config.ts +0 -10
  65. package/postcss.config.mjs +0 -7
  66. package/public/app-icon.png +0 -0
  67. package/public/file.svg +0 -1
  68. package/public/globe.svg +0 -1
  69. package/public/next.svg +0 -1
  70. package/public/profiles/designer.png +0 -0
  71. package/public/profiles/dev-backend.png +0 -0
  72. package/public/profiles/dev-frontend.png +0 -0
  73. package/public/profiles/pm.png +0 -0
  74. package/public/profiles/qa.png +0 -0
  75. package/public/vercel.svg +0 -1
  76. package/public/window.svg +0 -1
  77. package/tsconfig.json +0 -34
package/README.md CHANGED
@@ -89,7 +89,7 @@ olly-molly
89
89
  ### Development
90
90
 
91
91
  ```bash
92
- git clone https://github.com/YOUR_USERNAME/olly-molly.git
92
+ git clone https://github.com/ruucm/olly-molly.git
93
93
  cd olly-molly
94
94
  npm install
95
95
  npm run dev
@@ -128,7 +128,7 @@ We love contributions! Here's how you can help:
128
128
 
129
129
  ```bash
130
130
  # Clone the repo
131
- git clone https://github.com/YOUR_USERNAME/olly-molly.git
131
+ git clone https://github.com/ruucm/olly-molly.git
132
132
  cd olly-molly
133
133
 
134
134
  # Install dependencies
@@ -173,7 +173,7 @@ olly-molly/
173
173
 
174
174
  ## License
175
175
 
176
- MIT © [Your Name]
176
+ MIT © ruucm
177
177
 
178
178
  ---
179
179
 
package/bin/cli.js CHANGED
@@ -3,18 +3,54 @@
3
3
  const { spawn, execSync } = require('child_process');
4
4
  const path = require('path');
5
5
  const fs = require('fs');
6
+ const os = require('os');
6
7
 
7
- const packageDir = path.dirname(__dirname);
8
+ const APP_NAME = 'olly-molly';
9
+ const REPO_URL = 'https://github.com/ruucm/olly-molly.git';
10
+ const APP_DIR = path.join(os.homedir(), '.olly-molly');
8
11
 
9
12
  console.log('\n🐙 Olly Molly - Your AI Development Team\n');
10
13
 
11
- // Check if node_modules exists, if not install
12
- const nodeModulesPath = path.join(packageDir, 'node_modules');
14
+ // Check if app is installed locally
15
+ if (!fs.existsSync(APP_DIR)) {
16
+ console.log('📦 First run - downloading Olly Molly...\n');
17
+ try {
18
+ execSync(`git clone --depth 1 ${REPO_URL} "${APP_DIR}"`, {
19
+ stdio: 'inherit'
20
+ });
21
+ } catch (error) {
22
+ console.error('❌ Failed to download. Make sure git is installed.');
23
+ process.exit(1);
24
+ }
25
+ }
26
+
27
+ // Check for updates (pull latest)
28
+ const args = process.argv.slice(2);
29
+ if (args.includes('--update') || args.includes('-u')) {
30
+ console.log('🔄 Updating Olly Molly...\n');
31
+ try {
32
+ execSync('git pull origin main', {
33
+ cwd: APP_DIR,
34
+ stdio: 'inherit'
35
+ });
36
+ // Clear build cache after update
37
+ const nextDir = path.join(APP_DIR, '.next');
38
+ if (fs.existsSync(nextDir)) {
39
+ fs.rmSync(nextDir, { recursive: true, force: true });
40
+ }
41
+ console.log('✅ Updated! Rebuilding...\n');
42
+ } catch (error) {
43
+ console.error('⚠️ Update failed, continuing with current version');
44
+ }
45
+ }
46
+
47
+ // Install dependencies if needed
48
+ const nodeModulesPath = path.join(APP_DIR, 'node_modules');
13
49
  if (!fs.existsSync(nodeModulesPath)) {
14
50
  console.log('📦 Installing dependencies...\n');
15
51
  try {
16
52
  execSync('npm install', {
17
- cwd: packageDir,
53
+ cwd: APP_DIR,
18
54
  stdio: 'inherit'
19
55
  });
20
56
  } catch (error) {
@@ -23,32 +59,34 @@ if (!fs.existsSync(nodeModulesPath)) {
23
59
  }
24
60
  }
25
61
 
26
- // Check if .next build exists, if not build
27
- const nextPath = path.join(packageDir, '.next');
62
+ // Build if needed
63
+ const nextPath = path.join(APP_DIR, '.next');
28
64
  if (!fs.existsSync(nextPath)) {
29
- console.log('🔨 Building app...\n');
65
+ console.log('🔨 Building app (first time only)...\n');
30
66
  try {
31
67
  execSync('npm run build', {
32
- cwd: packageDir,
68
+ cwd: APP_DIR,
33
69
  stdio: 'inherit'
34
70
  });
35
71
  } catch (error) {
36
- console.error('❌ Failed to build app');
72
+ console.error('❌ Failed to build');
37
73
  process.exit(1);
38
74
  }
39
75
  }
40
76
 
41
- // Start the server
42
- console.log('🚀 Starting Olly Molly on http://localhost:1234\n');
77
+ console.log('🚀 Starting on http://localhost:1234\n');
78
+ console.log(' Press Ctrl+C to stop');
79
+ console.log(' Run "npx olly-molly --update" to get latest version\n');
43
80
 
81
+ // Start the server
44
82
  const server = spawn('npm', ['run', 'start', '--', '--port', '1234'], {
45
- cwd: packageDir,
83
+ cwd: APP_DIR,
46
84
  stdio: 'inherit',
47
85
  shell: true
48
86
  });
49
87
 
50
88
  server.on('error', (error) => {
51
- console.error('❌ Failed to start server:', error.message);
89
+ console.error('❌ Failed to start:', error.message);
52
90
  process.exit(1);
53
91
  });
54
92
 
@@ -56,8 +94,9 @@ server.on('close', (code) => {
56
94
  process.exit(code || 0);
57
95
  });
58
96
 
59
- // Handle graceful shutdown
97
+ // Graceful shutdown
60
98
  process.on('SIGINT', () => {
99
+ console.log('\n👋 Bye!');
61
100
  server.kill('SIGINT');
62
101
  });
63
102
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "olly-molly",
3
- "version": "0.1.0",
3
+ "version": "0.1.2",
4
4
  "description": "Your AI Development Team, Running Locally - Manage AI agents (PM, Frontend, Backend, QA) from a beautiful kanban board",
5
5
  "keywords": [
6
6
  "ai",
@@ -16,34 +16,23 @@
16
16
  "license": "MIT",
17
17
  "repository": {
18
18
  "type": "git",
19
- "url": "https://github.com/ruucm/olly-molly.git"
19
+ "url": "git+https://github.com/ruucm/olly-molly.git"
20
20
  },
21
21
  "homepage": "https://github.com/ruucm/olly-molly#readme",
22
22
  "bugs": {
23
23
  "url": "https://github.com/ruucm/olly-molly/issues"
24
24
  },
25
25
  "bin": {
26
- "olly-molly": "./bin/cli.js"
26
+ "olly-molly": "bin/cli.js"
27
27
  },
28
28
  "files": [
29
- "bin",
30
- "app",
31
- "components",
32
- "db",
33
- "lib",
34
- "public",
35
- "types",
36
- "next.config.ts",
37
- "postcss.config.mjs",
38
- "tsconfig.json",
39
- "package.json"
29
+ "bin"
40
30
  ],
41
31
  "scripts": {
42
32
  "dev": "next dev --port 1234",
43
33
  "build": "next build",
44
34
  "start": "next start",
45
35
  "lint": "eslint",
46
- "prepublishOnly": "npm run build",
47
36
  "tauri": "tauri",
48
37
  "tauri:dev": "tauri dev",
49
38
  "tauri:build": "tauri build",
@@ -1,157 +0,0 @@
1
- import { NextRequest, NextResponse } from 'next/server';
2
- import { ticketService, memberService, projectService, activityService, conversationService } from '@/lib/db';
3
- import { startBackgroundJob, AgentProvider } from '@/lib/agent-jobs';
4
- import { v4 as uuidv4 } from 'uuid';
5
-
6
- interface AgentExecuteRequest {
7
- ticket_id: string;
8
- feedback?: string;
9
- provider?: AgentProvider;
10
- }
11
-
12
- function buildAgentPrompt(ticket: {
13
- title: string;
14
- description?: string | null;
15
- }, agent: {
16
- name: string;
17
- role: string;
18
- system_prompt: string;
19
- }, project: {
20
- name: string;
21
- path: string;
22
- }, feedback?: string): string {
23
- // Check if role is QA to add specific port instructions
24
- const isQA = agent.role === 'QA';
25
- const qaInstruction = isQA
26
- ? `\nIMPORTANT:
27
- 1. PORT CONFIGURATION: When running tests or starting servers for the TARGET PROJECT, you MUST use port 3001 (or any port other than 1234) to avoid conflict with this dashboard app. Use "PORT=3001 npm run dev" or equivalent.
28
- 2. TOOL USAGE: You MUST use the **Playwright MCP** (https://github.com/microsoft/playwright-mcp) tools for automated testing. verify the available tools and use them for browser automation and testing. Do NOT rely solely on manual terminal commands.`
29
- : '';
30
-
31
- const feedbackSection = feedback
32
- ? `\n\nIMPORTANT FEEDBACK FROM USER:\n${feedback}\n\nPlease address this feedback specifically in your implementation.`
33
- : '';
34
-
35
- return `You are acting as ${agent.name} (${agent.role}) for the project "${project.name}".
36
-
37
- ${agent.system_prompt}
38
-
39
- ---
40
-
41
- TASK TO COMPLETE:
42
- Title: ${ticket.title}
43
- ${ticket.description ? `Description: ${ticket.description}` : ''}
44
- ${feedbackSection}
45
-
46
- ---
47
-
48
- INSTRUCTIONS:
49
- 1. Analyze the task requirements carefully
50
- 2. Make the necessary code changes to complete this task
51
- 3. Focus only on what's needed for this specific task
52
- 4. Write clean, well-documented code
53
- 5. After completing, provide a brief summary of changes made
54
- 6. If you make changes, commit them with a meaningful message
55
- 7. CRITICAL: You are working on the external project "${project.name}". When starting its server, ALWAYS use port 3001 (e.g. "PORT=3001 npm run dev"). NEVER use port 1234.${qaInstruction}
56
-
57
- Please complete this task now.`;
58
- }
59
-
60
- export async function POST(request: NextRequest) {
61
- try {
62
- const body: AgentExecuteRequest = await request.json();
63
-
64
- if (!body.ticket_id) {
65
- return NextResponse.json({ error: 'ticket_id is required' }, { status: 400 });
66
- }
67
-
68
- // Get ticket
69
- const ticket = ticketService.getById(body.ticket_id);
70
- if (!ticket) {
71
- return NextResponse.json({ error: 'Ticket not found' }, { status: 404 });
72
- }
73
-
74
- // Get assignee (agent)
75
- if (!ticket.assignee_id) {
76
- return NextResponse.json({ error: 'Ticket has no assignee' }, { status: 400 });
77
- }
78
-
79
- const agent = memberService.getById(ticket.assignee_id);
80
- if (!agent) {
81
- return NextResponse.json({ error: 'Agent not found' }, { status: 404 });
82
- }
83
-
84
- // Get active project
85
- const project = projectService.getActive();
86
- if (!project) {
87
- return NextResponse.json({ error: 'No active project selected. Please select a project first.' }, { status: 400 });
88
- }
89
-
90
- // Build prompt
91
- const prompt = buildAgentPrompt(ticket, agent, project, body.feedback);
92
-
93
- // Use provided provider or default to 'claude'
94
- const provider: AgentProvider = body.provider || 'claude';
95
-
96
- // Generate job ID
97
- const jobId = uuidv4();
98
-
99
- // Update ticket status to IN_PROGRESS
100
- ticketService.update(body.ticket_id, { status: 'IN_PROGRESS' }, agent.id);
101
-
102
- // Create conversation for this execution
103
- const conversation = conversationService.create({
104
- ticket_id: body.ticket_id,
105
- agent_id: agent.id,
106
- provider,
107
- prompt,
108
- feedback: body.feedback,
109
- });
110
-
111
- // Log activity
112
- activityService.log({
113
- ticket_id: body.ticket_id,
114
- member_id: agent.id,
115
- action: 'AGENT_WORK_STARTED',
116
- details: `${agent.name} started working on this task using ${provider === 'opencode' ? 'OpenCode' : 'Claude Code'}`,
117
- });
118
-
119
- // Start background job (non-blocking)
120
- startBackgroundJob({
121
- jobId,
122
- conversationId: conversation.id,
123
- ticketId: body.ticket_id,
124
- agentId: agent.id,
125
- agentName: agent.name,
126
- projectPath: project.path,
127
- prompt,
128
- provider,
129
- });
130
-
131
- // Return immediately with job info
132
- return NextResponse.json({
133
- success: true,
134
- job_id: jobId,
135
- conversation_id: conversation.id,
136
- message: `${agent.name} started working on the task. The job is running in the background.`,
137
- agent: {
138
- id: agent.id,
139
- name: agent.name,
140
- role: agent.role,
141
- avatar: agent.avatar,
142
- },
143
- project: {
144
- id: project.id,
145
- name: project.name,
146
- path: project.path,
147
- },
148
- ticket_status: 'IN_PROGRESS',
149
- });
150
- } catch (error) {
151
- console.error('Error executing agent:', error);
152
- return NextResponse.json({
153
- error: 'Failed to execute agent',
154
- details: String(error)
155
- }, { status: 500 });
156
- }
157
- }
@@ -1,38 +0,0 @@
1
- import { NextRequest, NextResponse } from 'next/server';
2
- import { getRunningJobs, getJobByTicketId, getJobOutput, cancelJob } from '@/lib/agent-jobs';
3
-
4
- // Get all running jobs
5
- export async function GET(request: NextRequest) {
6
- const url = new URL(request.url);
7
- const ticketId = url.searchParams.get('ticket_id');
8
- const jobId = url.searchParams.get('job_id');
9
-
10
- if (jobId) {
11
- // Get specific job output
12
- const output = getJobOutput(jobId);
13
- return NextResponse.json({ output });
14
- }
15
-
16
- if (ticketId) {
17
- // Get job for specific ticket
18
- const job = getJobByTicketId(ticketId);
19
- return NextResponse.json({ job });
20
- }
21
-
22
- // Get all running jobs
23
- const jobs = getRunningJobs();
24
- return NextResponse.json({ jobs });
25
- }
26
-
27
- // Cancel a job
28
- export async function DELETE(request: NextRequest) {
29
- const url = new URL(request.url);
30
- const jobId = url.searchParams.get('job_id');
31
-
32
- if (!jobId) {
33
- return NextResponse.json({ error: 'job_id is required' }, { status: 400 });
34
- }
35
-
36
- const cancelled = cancelJob(jobId);
37
- return NextResponse.json({ success: cancelled });
38
- }
@@ -1,12 +0,0 @@
1
- import { NextRequest, NextResponse } from 'next/server';
2
-
3
- export async function GET(request: NextRequest) {
4
- try {
5
- // Check if OPENAI_API_KEY exists in environment
6
- const hasKey = !!process.env.OPENAI_API_KEY;
7
-
8
- return NextResponse.json({ hasKey });
9
- } catch (error) {
10
- return NextResponse.json({ hasKey: false }, { status: 500 });
11
- }
12
- }
@@ -1,35 +0,0 @@
1
- import { NextRequest, NextResponse } from 'next/server';
2
- import { conversationService, conversationMessageService } from '@/lib/db';
3
-
4
- interface RouteParams {
5
- params: Promise<{
6
- id: string;
7
- }>;
8
- }
9
-
10
- // GET /api/conversations/[id]
11
- export async function GET(request: NextRequest, { params }: RouteParams) {
12
- try {
13
- // Await params for Next.js 15+
14
- const { id } = await params;
15
-
16
- const conversation = conversationService.getById(id);
17
-
18
- if (!conversation) {
19
- return NextResponse.json({ error: 'Conversation not found' }, { status: 404 });
20
- }
21
-
22
- const messages = conversationMessageService.getByConversationId(id);
23
-
24
- return NextResponse.json({
25
- conversation,
26
- messages
27
- });
28
- } catch (error) {
29
- console.error('Error fetching conversation:', error);
30
- return NextResponse.json({
31
- error: 'Failed to fetch conversation',
32
- details: String(error)
33
- }, { status: 500 });
34
- }
35
- }
@@ -1,24 +0,0 @@
1
- import { NextRequest, NextResponse } from 'next/server';
2
- import { conversationService, conversationMessageService } from '@/lib/db';
3
-
4
- // GET /api/conversations?ticket_id=xxx
5
- export async function GET(request: NextRequest) {
6
- try {
7
- const { searchParams } = new URL(request.url);
8
- const ticketId = searchParams.get('ticket_id');
9
-
10
- if (!ticketId) {
11
- return NextResponse.json({ error: 'ticket_id is required' }, { status: 400 });
12
- }
13
-
14
- const conversations = conversationService.getByTicketId(ticketId);
15
-
16
- return NextResponse.json({ conversations });
17
- } catch (error) {
18
- console.error('Error fetching conversations:', error);
19
- return NextResponse.json({
20
- error: 'Failed to fetch conversations',
21
- details: String(error)
22
- }, { status: 500 });
23
- }
24
- }
@@ -1,37 +0,0 @@
1
- import { NextRequest, NextResponse } from 'next/server';
2
- import { memberService } from '@/lib/db';
3
-
4
- export async function GET(
5
- request: NextRequest,
6
- { params }: { params: Promise<{ id: string }> }
7
- ) {
8
- try {
9
- const { id } = await params;
10
- const member = memberService.getById(id);
11
- if (!member) {
12
- return NextResponse.json({ error: 'Member not found' }, { status: 404 });
13
- }
14
- return NextResponse.json(member);
15
- } catch (error) {
16
- console.error('Error fetching member:', error);
17
- return NextResponse.json({ error: 'Failed to fetch member' }, { status: 500 });
18
- }
19
- }
20
-
21
- export async function PATCH(
22
- request: NextRequest,
23
- { params }: { params: Promise<{ id: string }> }
24
- ) {
25
- try {
26
- const { id } = await params;
27
- const body = await request.json();
28
- const member = memberService.update(id, body);
29
- if (!member) {
30
- return NextResponse.json({ error: 'Member not found' }, { status: 404 });
31
- }
32
- return NextResponse.json(member);
33
- } catch (error) {
34
- console.error('Error updating member:', error);
35
- return NextResponse.json({ error: 'Failed to update member' }, { status: 500 });
36
- }
37
- }
@@ -1,12 +0,0 @@
1
- import { NextResponse } from 'next/server';
2
- import { memberService } from '@/lib/db';
3
-
4
- export async function GET() {
5
- try {
6
- const members = memberService.getAll();
7
- return NextResponse.json(members);
8
- } catch (error) {
9
- console.error('Error fetching members:', error);
10
- return NextResponse.json({ error: 'Failed to fetch members' }, { status: 500 });
11
- }
12
- }
@@ -1,142 +0,0 @@
1
- import { NextRequest, NextResponse } from 'next/server';
2
- import { ticketService, memberService } from '@/lib/db';
3
- import OpenAI from 'openai';
4
-
5
- const openai = new OpenAI({
6
- apiKey: process.env.OPENAI_API_KEY,
7
- });
8
-
9
- /**
10
- * PM Agent - AI-powered feature breakdown
11
- *
12
- * Uses OpenAI to analyze feature requests and create appropriate tasks
13
- * with intelligent assignment to team members.
14
- */
15
-
16
- interface TaskFromAI {
17
- title: string;
18
- description: string;
19
- priority: 'LOW' | 'MEDIUM' | 'HIGH' | 'CRITICAL';
20
- assignee_role: 'FE_DEV' | 'BACKEND_DEV' | 'QA' | 'DEVOPS';
21
- }
22
-
23
- const SYSTEM_PROMPT = `You are a PM (Project Manager) AI agent for a software development team.
24
-
25
- Your team consists of:
26
- - FE_DEV (Frontend Developer): Handles UI/UX, React, Next.js, CSS, components, user interfaces
27
- - BACKEND_DEV (Backend Developer): Handles APIs, databases, server logic, authentication, business logic
28
- - QA (QA Engineer): Handles testing, quality assurance, bug verification, E2E tests
29
- - DEVOPS (DevOps Engineer): Handles deployment, CI/CD, infrastructure, monitoring
30
-
31
- When given a feature request, you must:
32
- 1. Break it down into specific, actionable tasks
33
- 2. Assign each task to the appropriate team member based on their expertise
34
- 3. Set priorities (CRITICAL > HIGH > MEDIUM > LOW)
35
-
36
- IMPORTANT RULES:
37
- - Create focused, single-responsibility tasks
38
- - Backend tasks should come before frontend integration tasks
39
- - Always include a QA task for testing
40
- - Be specific in task descriptions
41
- - Use Korean for titles and descriptions
42
-
43
- Respond in JSON format:
44
- {
45
- "tasks": [
46
- {
47
- "title": "Task title in Korean",
48
- "description": "Detailed task description in Korean",
49
- "priority": "HIGH",
50
- "assignee_role": "BACKEND_DEV"
51
- }
52
- ],
53
- "summary": "Brief summary of the breakdown in Korean"
54
- }`;
55
-
56
- async function breakdownWithAI(request: string): Promise<{ tasks: TaskFromAI[]; summary: string }> {
57
- const completion = await openai.chat.completions.create({
58
- model: 'gpt-4o-mini',
59
- messages: [
60
- { role: 'system', content: SYSTEM_PROMPT },
61
- { role: 'user', content: `Feature Request: ${request}` }
62
- ],
63
- response_format: { type: 'json_object' },
64
- temperature: 0.7,
65
- max_tokens: 2000,
66
- });
67
-
68
- const content = completion.choices[0].message.content;
69
- if (!content) {
70
- throw new Error('No response from AI');
71
- }
72
-
73
- return JSON.parse(content);
74
- }
75
-
76
- function getAssigneeByRole(role: string): string | null {
77
- const member = memberService.getByRole(role);
78
- return member?.id || null;
79
- }
80
-
81
- export async function POST(request: NextRequest) {
82
- try {
83
- const body = await request.json();
84
-
85
- if (!body.request) {
86
- return NextResponse.json(
87
- { error: 'Feature request is required' },
88
- { status: 400 }
89
- );
90
- }
91
-
92
- if (!process.env.OPENAI_API_KEY) {
93
- return NextResponse.json(
94
- { error: 'OpenAI API key not configured' },
95
- { status: 500 }
96
- );
97
- }
98
-
99
- const pmMember = memberService.getByRole('PM');
100
-
101
- // Use AI to break down the request
102
- const aiResponse = await breakdownWithAI(body.request);
103
-
104
- const createdTickets = [];
105
-
106
- for (const task of aiResponse.tasks) {
107
- const assigneeId = getAssigneeByRole(task.assignee_role);
108
-
109
- const ticket = ticketService.create({
110
- title: task.title,
111
- description: task.description,
112
- priority: task.priority,
113
- assignee_id: assigneeId || undefined,
114
- project_id: body.project_id,
115
- created_by: pmMember?.id,
116
- });
117
-
118
- const assignee = assigneeId ? memberService.getById(assigneeId) : null;
119
- createdTickets.push({
120
- ...ticket,
121
- assignee,
122
- assigned_role: task.assignee_role,
123
- });
124
- }
125
-
126
- return NextResponse.json({
127
- success: true,
128
- original_request: body.request,
129
- created_by: pmMember,
130
- tickets_created: createdTickets.length,
131
- tickets: createdTickets,
132
- ai_summary: aiResponse.summary,
133
- message: `PM이 AI를 사용해 "${body.request}" 요청을 분석하여 ${createdTickets.length}개의 태스크를 생성했습니다.`,
134
- }, { status: 201 });
135
- } catch (error) {
136
- console.error('Error in PM breakdown:', error);
137
- return NextResponse.json(
138
- { error: 'Failed to process feature request', details: String(error) },
139
- { status: 500 }
140
- );
141
- }
142
- }