olly-molly 0.1.0
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/LICENSE +21 -0
- package/README.md +182 -0
- package/app/api/agent/execute/route.ts +157 -0
- package/app/api/agent/status/route.ts +38 -0
- package/app/api/check-api-key/route.ts +12 -0
- package/app/api/conversations/[id]/route.ts +35 -0
- package/app/api/conversations/route.ts +24 -0
- package/app/api/members/[id]/route.ts +37 -0
- package/app/api/members/route.ts +12 -0
- package/app/api/pm/breakdown/route.ts +142 -0
- package/app/api/pm/tickets/route.ts +147 -0
- package/app/api/projects/[id]/route.ts +56 -0
- package/app/api/projects/active/route.ts +15 -0
- package/app/api/projects/route.ts +53 -0
- package/app/api/tickets/[id]/logs/route.ts +16 -0
- package/app/api/tickets/[id]/route.ts +60 -0
- package/app/api/tickets/[id]/work-logs/route.ts +16 -0
- package/app/api/tickets/route.ts +37 -0
- package/app/design-system/page.tsx +242 -0
- package/app/favicon.ico +0 -0
- package/app/globals.css +318 -0
- package/app/layout.tsx +37 -0
- package/app/page.tsx +331 -0
- package/bin/cli.js +66 -0
- package/components/ThemeProvider.tsx +56 -0
- package/components/ThemeToggle.tsx +31 -0
- package/components/activity/ActivityLog.tsx +96 -0
- package/components/activity/index.ts +1 -0
- package/components/kanban/ConversationList.tsx +75 -0
- package/components/kanban/ConversationView.tsx +132 -0
- package/components/kanban/KanbanBoard.tsx +179 -0
- package/components/kanban/KanbanColumn.tsx +80 -0
- package/components/kanban/SortableTicket.tsx +58 -0
- package/components/kanban/TicketCard.tsx +98 -0
- package/components/kanban/TicketModal.tsx +510 -0
- package/components/kanban/TicketSidebar.tsx +448 -0
- package/components/kanban/index.ts +8 -0
- package/components/pm/PMRequestModal.tsx +196 -0
- package/components/pm/index.ts +1 -0
- package/components/project/ProjectSelector.tsx +211 -0
- package/components/project/index.ts +1 -0
- package/components/team/MemberCard.tsx +147 -0
- package/components/team/TeamPanel.tsx +57 -0
- package/components/team/index.ts +2 -0
- package/components/ui/ApiKeyModal.tsx +101 -0
- package/components/ui/Avatar.tsx +95 -0
- package/components/ui/Badge.tsx +59 -0
- package/components/ui/Button.tsx +60 -0
- package/components/ui/Card.tsx +64 -0
- package/components/ui/Input.tsx +41 -0
- package/components/ui/Modal.tsx +76 -0
- package/components/ui/ResizablePane.tsx +97 -0
- package/components/ui/Select.tsx +45 -0
- package/components/ui/Textarea.tsx +41 -0
- package/components/ui/index.ts +8 -0
- package/db/dev.sqlite +0 -0
- package/db/dev.sqlite-shm +0 -0
- package/db/dev.sqlite-wal +0 -0
- package/db/schema-conversations.sql +26 -0
- package/db/schema-projects.sql +29 -0
- package/db/schema.sql +94 -0
- package/lib/agent-jobs.ts +232 -0
- package/lib/db.ts +564 -0
- package/next.config.ts +10 -0
- package/package.json +80 -0
- package/postcss.config.mjs +7 -0
- package/public/app-icon.png +0 -0
- package/public/file.svg +1 -0
- package/public/globe.svg +1 -0
- package/public/next.svg +1 -0
- package/public/profiles/designer.png +0 -0
- package/public/profiles/dev-backend.png +0 -0
- package/public/profiles/dev-frontend.png +0 -0
- package/public/profiles/pm.png +0 -0
- package/public/profiles/qa.png +0 -0
- package/public/vercel.svg +1 -0
- package/public/window.svg +1 -0
- package/tsconfig.json +34 -0
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
'use client';
|
|
2
|
+
|
|
3
|
+
import { useDroppable } from '@dnd-kit/core';
|
|
4
|
+
import { SortableContext, verticalListSortingStrategy } from '@dnd-kit/sortable';
|
|
5
|
+
import { SortableTicket } from './SortableTicket';
|
|
6
|
+
|
|
7
|
+
interface Ticket {
|
|
8
|
+
id: string;
|
|
9
|
+
title: string;
|
|
10
|
+
description?: string | null;
|
|
11
|
+
status: string;
|
|
12
|
+
priority: string;
|
|
13
|
+
assignee?: {
|
|
14
|
+
id: string;
|
|
15
|
+
name: string;
|
|
16
|
+
avatar?: string | null;
|
|
17
|
+
role: string;
|
|
18
|
+
system_prompt: string;
|
|
19
|
+
} | null;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
interface KanbanColumnProps {
|
|
23
|
+
id: string;
|
|
24
|
+
title: string;
|
|
25
|
+
tickets: Ticket[];
|
|
26
|
+
color: string;
|
|
27
|
+
icon: string;
|
|
28
|
+
onTicketClick: (ticket: Ticket) => void;
|
|
29
|
+
runningTicketIds?: string[];
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
export function KanbanColumn({ id, title, tickets, color, icon, onTicketClick, runningTicketIds = [] }: KanbanColumnProps) {
|
|
33
|
+
const { setNodeRef, isOver } = useDroppable({ id });
|
|
34
|
+
|
|
35
|
+
return (
|
|
36
|
+
<div
|
|
37
|
+
ref={setNodeRef}
|
|
38
|
+
className={`
|
|
39
|
+
flex-1 min-w-[260px]
|
|
40
|
+
flex flex-col max-h-[calc(100vh-180px)]
|
|
41
|
+
border-r border-[var(--border-primary)] last:border-r-0
|
|
42
|
+
transition-colors duration-150
|
|
43
|
+
${isOver ? 'bg-[var(--bg-secondary)]' : 'bg-transparent'}
|
|
44
|
+
`}
|
|
45
|
+
>
|
|
46
|
+
{/* Column Header */}
|
|
47
|
+
<div className="px-4 py-3 border-b border-[var(--border-primary)]">
|
|
48
|
+
<div className="flex items-center gap-2">
|
|
49
|
+
<span className="text-sm">{icon}</span>
|
|
50
|
+
<h3 className={`text-xs font-medium uppercase tracking-wider ${color}`}>
|
|
51
|
+
{title}
|
|
52
|
+
</h3>
|
|
53
|
+
<span className="ml-auto text-xs text-[var(--text-muted)]">
|
|
54
|
+
{tickets.length}
|
|
55
|
+
</span>
|
|
56
|
+
</div>
|
|
57
|
+
</div>
|
|
58
|
+
|
|
59
|
+
{/* Tickets */}
|
|
60
|
+
<div className="flex-1 overflow-y-auto">
|
|
61
|
+
<SortableContext items={tickets.map(t => t.id)} strategy={verticalListSortingStrategy}>
|
|
62
|
+
{tickets.map((ticket) => (
|
|
63
|
+
<SortableTicket
|
|
64
|
+
key={ticket.id}
|
|
65
|
+
ticket={ticket}
|
|
66
|
+
onTicketClick={onTicketClick}
|
|
67
|
+
isRunning={runningTicketIds.includes(ticket.id)}
|
|
68
|
+
/>
|
|
69
|
+
))}
|
|
70
|
+
</SortableContext>
|
|
71
|
+
|
|
72
|
+
{tickets.length === 0 && (
|
|
73
|
+
<div className="flex items-center justify-center py-12 text-[var(--text-muted)] text-xs">
|
|
74
|
+
No tickets
|
|
75
|
+
</div>
|
|
76
|
+
)}
|
|
77
|
+
</div>
|
|
78
|
+
</div>
|
|
79
|
+
);
|
|
80
|
+
}
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
'use client';
|
|
2
|
+
|
|
3
|
+
import { useSortable } from '@dnd-kit/sortable';
|
|
4
|
+
import { CSS } from '@dnd-kit/utilities';
|
|
5
|
+
import { TicketCard } from './TicketCard';
|
|
6
|
+
|
|
7
|
+
interface Ticket {
|
|
8
|
+
id: string;
|
|
9
|
+
title: string;
|
|
10
|
+
description?: string | null;
|
|
11
|
+
status: string;
|
|
12
|
+
priority: string;
|
|
13
|
+
assignee?: {
|
|
14
|
+
id: string;
|
|
15
|
+
name: string;
|
|
16
|
+
avatar?: string | null;
|
|
17
|
+
role: string;
|
|
18
|
+
system_prompt: string;
|
|
19
|
+
} | null;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
interface SortableTicketProps {
|
|
23
|
+
ticket: Ticket;
|
|
24
|
+
onTicketClick: (ticket: Ticket) => void;
|
|
25
|
+
isRunning?: boolean;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
export function SortableTicket({ ticket, onTicketClick, isRunning }: SortableTicketProps) {
|
|
29
|
+
const {
|
|
30
|
+
attributes,
|
|
31
|
+
listeners,
|
|
32
|
+
setNodeRef,
|
|
33
|
+
transform,
|
|
34
|
+
transition,
|
|
35
|
+
isDragging,
|
|
36
|
+
} = useSortable({ id: ticket.id });
|
|
37
|
+
|
|
38
|
+
const style = {
|
|
39
|
+
transform: CSS.Transform.toString(transform),
|
|
40
|
+
transition,
|
|
41
|
+
};
|
|
42
|
+
|
|
43
|
+
return (
|
|
44
|
+
<div
|
|
45
|
+
ref={setNodeRef}
|
|
46
|
+
style={style}
|
|
47
|
+
{...attributes}
|
|
48
|
+
{...listeners}
|
|
49
|
+
>
|
|
50
|
+
<TicketCard
|
|
51
|
+
ticket={ticket}
|
|
52
|
+
onClick={() => !isDragging && onTicketClick(ticket)}
|
|
53
|
+
isDragging={isDragging}
|
|
54
|
+
isRunning={isRunning}
|
|
55
|
+
/>
|
|
56
|
+
</div>
|
|
57
|
+
);
|
|
58
|
+
}
|
|
@@ -0,0 +1,98 @@
|
|
|
1
|
+
'use client';
|
|
2
|
+
|
|
3
|
+
import { Avatar } from '@/components/ui/Avatar';
|
|
4
|
+
import { PriorityBadge } from '@/components/ui/Badge';
|
|
5
|
+
|
|
6
|
+
interface Ticket {
|
|
7
|
+
id: string;
|
|
8
|
+
title: string;
|
|
9
|
+
description?: string | null;
|
|
10
|
+
status: string;
|
|
11
|
+
priority: string;
|
|
12
|
+
assignee?: {
|
|
13
|
+
id: string;
|
|
14
|
+
name: string;
|
|
15
|
+
avatar?: string | null;
|
|
16
|
+
role: string;
|
|
17
|
+
system_prompt: string;
|
|
18
|
+
} | null;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
interface TicketCardProps {
|
|
22
|
+
ticket: Ticket;
|
|
23
|
+
onClick: () => void;
|
|
24
|
+
isDragging?: boolean;
|
|
25
|
+
isRunning?: boolean;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
export function TicketCard({ ticket, onClick, isDragging, isRunning }: TicketCardProps) {
|
|
29
|
+
const roleImages: Record<string, string> = {
|
|
30
|
+
PM: '/profiles/pm.png',
|
|
31
|
+
FE_DEV: '/profiles/dev-frontend.png',
|
|
32
|
+
BACKEND_DEV: '/profiles/dev-backend.png',
|
|
33
|
+
QA: '/profiles/qa.png',
|
|
34
|
+
};
|
|
35
|
+
|
|
36
|
+
const profileImage = ticket.assignee ? roleImages[ticket.assignee.role] : undefined;
|
|
37
|
+
|
|
38
|
+
return (
|
|
39
|
+
<div
|
|
40
|
+
onClick={onClick}
|
|
41
|
+
className={`
|
|
42
|
+
px-4 py-3 border-b border-[var(--border-primary)] cursor-pointer
|
|
43
|
+
transition-colors duration-150
|
|
44
|
+
hover:bg-[var(--bg-secondary)]
|
|
45
|
+
${isDragging ? 'opacity-50 bg-[var(--bg-secondary)]' : ''}
|
|
46
|
+
${isRunning ? 'bg-[var(--status-progress)]/30' : ''}
|
|
47
|
+
`}
|
|
48
|
+
>
|
|
49
|
+
<div className="flex items-start gap-3">
|
|
50
|
+
{/* Left: Assignee Avatar */}
|
|
51
|
+
<div className="flex-shrink-0 pt-0.5">
|
|
52
|
+
{ticket.assignee ? (
|
|
53
|
+
<Avatar
|
|
54
|
+
name={ticket.assignee.name}
|
|
55
|
+
src={profileImage}
|
|
56
|
+
emoji={!profileImage ? ticket.assignee.avatar : undefined}
|
|
57
|
+
badge={profileImage ? ticket.assignee.avatar : undefined}
|
|
58
|
+
size="sm"
|
|
59
|
+
/>
|
|
60
|
+
) : (
|
|
61
|
+
<div className="w-6 h-6 border border-dashed border-[var(--border-secondary)] flex items-center justify-center text-[var(--text-muted)] text-xs">
|
|
62
|
+
?
|
|
63
|
+
</div>
|
|
64
|
+
)}
|
|
65
|
+
</div>
|
|
66
|
+
|
|
67
|
+
{/* Right: Content */}
|
|
68
|
+
<div className="flex-1 min-w-0">
|
|
69
|
+
<div className="flex items-start justify-between gap-2">
|
|
70
|
+
<h4 className="text-sm text-[var(--text-primary)] line-clamp-2 leading-snug">
|
|
71
|
+
{isRunning && (
|
|
72
|
+
<span className="inline-block w-1.5 h-1.5 bg-[var(--status-progress-text)] rounded-full gentle-pulse mr-1.5 align-middle" />
|
|
73
|
+
)}
|
|
74
|
+
{ticket.title}
|
|
75
|
+
</h4>
|
|
76
|
+
<PriorityBadge priority={ticket.priority} />
|
|
77
|
+
</div>
|
|
78
|
+
|
|
79
|
+
{ticket.description && (
|
|
80
|
+
<p className="text-xs text-[var(--text-muted)] mt-1 line-clamp-1">
|
|
81
|
+
{ticket.description}
|
|
82
|
+
</p>
|
|
83
|
+
)}
|
|
84
|
+
|
|
85
|
+
{(ticket.assignee || isRunning) && (
|
|
86
|
+
<p className="text-xs text-[var(--text-muted)] mt-1.5">
|
|
87
|
+
{isRunning ? (
|
|
88
|
+
<span className="text-[var(--status-progress-text)]">Working...</span>
|
|
89
|
+
) : (
|
|
90
|
+
ticket.assignee?.name
|
|
91
|
+
)}
|
|
92
|
+
</p>
|
|
93
|
+
)}
|
|
94
|
+
</div>
|
|
95
|
+
</div>
|
|
96
|
+
</div>
|
|
97
|
+
);
|
|
98
|
+
}
|