newpr 0.1.3 → 0.2.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/package.json +11 -1
- package/src/analyzer/pipeline.ts +22 -5
- package/src/github/fetch-pr.ts +43 -1
- package/src/history/store.ts +106 -1
- package/src/llm/client.ts +197 -0
- package/src/llm/prompts.ts +33 -8
- package/src/tui/Shell.tsx +7 -2
- package/src/types/github.ts +11 -0
- package/src/types/output.ts +44 -0
- package/src/web/client/App.tsx +29 -3
- package/src/web/client/components/AppShell.tsx +94 -47
- package/src/web/client/components/ChatSection.tsx +427 -0
- package/src/web/client/components/DetailPane.tsx +163 -75
- package/src/web/client/components/DiffViewer.tsx +679 -0
- package/src/web/client/components/InputScreen.tsx +110 -26
- package/src/web/client/components/Markdown.tsx +169 -43
- package/src/web/client/components/ResultsScreen.tsx +66 -71
- package/src/web/client/components/TipTapEditor.tsx +405 -0
- package/src/web/client/hooks/useAnalysis.ts +8 -1
- package/src/web/client/lib/shiki.ts +63 -0
- package/src/web/client/panels/CartoonPanel.tsx +94 -37
- package/src/web/client/panels/DiscussionPanel.tsx +158 -0
- package/src/web/client/panels/FilesPanel.tsx +435 -54
- package/src/web/client/panels/GroupsPanel.tsx +49 -40
- package/src/web/client/panels/StoryPanel.tsx +42 -22
- package/src/web/components/ui/tabs.tsx +3 -3
- package/src/web/server/routes.ts +716 -14
- package/src/web/server/session-manager.ts +11 -2
- package/src/web/server.ts +33 -0
- package/src/web/styles/built.css +1 -1
- package/src/web/styles/globals.css +117 -1
- package/src/web/client/panels/NarrativePanel.tsx +0 -9
- package/src/web/client/panels/SummaryPanel.tsx +0 -20
|
@@ -1,15 +1,15 @@
|
|
|
1
1
|
import { useState } from "react";
|
|
2
|
-
import {
|
|
2
|
+
import { ChevronRight } from "lucide-react";
|
|
3
3
|
import type { FileGroup } from "../../../types/output.ts";
|
|
4
4
|
|
|
5
|
-
const
|
|
6
|
-
feature: "bg-blue-500
|
|
7
|
-
refactor: "bg-purple-500
|
|
8
|
-
bugfix: "bg-red-500
|
|
9
|
-
chore: "bg-
|
|
10
|
-
docs: "bg-teal-500
|
|
11
|
-
test: "bg-yellow-500
|
|
12
|
-
config: "bg-orange-500
|
|
5
|
+
const TYPE_DOT: Record<string, string> = {
|
|
6
|
+
feature: "bg-blue-500",
|
|
7
|
+
refactor: "bg-purple-500",
|
|
8
|
+
bugfix: "bg-red-500",
|
|
9
|
+
chore: "bg-neutral-400",
|
|
10
|
+
docs: "bg-teal-500",
|
|
11
|
+
test: "bg-yellow-500",
|
|
12
|
+
config: "bg-orange-500",
|
|
13
13
|
};
|
|
14
14
|
|
|
15
15
|
export function GroupsPanel({ groups }: { groups: FileGroup[] }) {
|
|
@@ -24,39 +24,48 @@ export function GroupsPanel({ groups }: { groups: FileGroup[] }) {
|
|
|
24
24
|
}
|
|
25
25
|
|
|
26
26
|
return (
|
|
27
|
-
<div className="pt-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
{
|
|
36
|
-
<
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
<
|
|
52
|
-
|
|
27
|
+
<div className="pt-5">
|
|
28
|
+
<div className="text-[10px] font-medium text-muted-foreground/40 uppercase tracking-wider mb-3">
|
|
29
|
+
{groups.length} groups
|
|
30
|
+
</div>
|
|
31
|
+
<div className="space-y-px">
|
|
32
|
+
{groups.map((group, i) => {
|
|
33
|
+
const isOpen = expanded.has(i);
|
|
34
|
+
return (
|
|
35
|
+
<div key={group.name}>
|
|
36
|
+
<button
|
|
37
|
+
type="button"
|
|
38
|
+
className={`w-full flex items-center gap-2.5 py-2.5 px-2.5 -mx-1 text-left rounded-lg transition-colors ${
|
|
39
|
+
isOpen ? "bg-accent/50" : "hover:bg-accent/30"
|
|
40
|
+
}`}
|
|
41
|
+
onClick={() => toggle(i)}
|
|
42
|
+
>
|
|
43
|
+
<ChevronRight className={`h-3 w-3 text-muted-foreground/40 shrink-0 transition-transform ${isOpen ? "rotate-90" : ""}`} />
|
|
44
|
+
<span className={`h-1.5 w-1.5 rounded-full shrink-0 ${TYPE_DOT[group.type] ?? TYPE_DOT.chore}`} />
|
|
45
|
+
<span className="text-xs font-medium flex-1 min-w-0 truncate">{group.name}</span>
|
|
46
|
+
<span className="text-[10px] text-muted-foreground/30 shrink-0">{group.type}</span>
|
|
47
|
+
<span className="text-[10px] text-muted-foreground/30 shrink-0 tabular-nums">{group.files.length}</span>
|
|
48
|
+
</button>
|
|
49
|
+
{isOpen && (
|
|
50
|
+
<div className="pl-[34px] pr-2 pb-3 pt-1">
|
|
51
|
+
<p className="text-[11px] text-muted-foreground/60 leading-relaxed mb-2.5">{group.description}</p>
|
|
52
|
+
<div className="space-y-0.5">
|
|
53
|
+
{group.files.map((f) => (
|
|
54
|
+
<div
|
|
55
|
+
key={f}
|
|
56
|
+
className="text-[11px] font-mono text-muted-foreground/50 truncate py-0.5"
|
|
57
|
+
title={f}
|
|
58
|
+
>
|
|
59
|
+
{f}
|
|
60
|
+
</div>
|
|
61
|
+
))}
|
|
53
62
|
</div>
|
|
54
|
-
|
|
55
|
-
|
|
63
|
+
</div>
|
|
64
|
+
)}
|
|
56
65
|
</div>
|
|
57
|
-
)
|
|
58
|
-
|
|
59
|
-
|
|
66
|
+
);
|
|
67
|
+
})}
|
|
68
|
+
</div>
|
|
60
69
|
</div>
|
|
61
70
|
);
|
|
62
71
|
}
|
|
@@ -1,5 +1,16 @@
|
|
|
1
1
|
import type { NewprOutput } from "../../../types/output.ts";
|
|
2
2
|
import { Markdown } from "../components/Markdown.tsx";
|
|
3
|
+
import { ChatMessages } from "../components/ChatSection.tsx";
|
|
4
|
+
|
|
5
|
+
const TYPE_DOT: Record<string, string> = {
|
|
6
|
+
feature: "bg-blue-500",
|
|
7
|
+
refactor: "bg-purple-500",
|
|
8
|
+
bugfix: "bg-red-500",
|
|
9
|
+
chore: "bg-neutral-400",
|
|
10
|
+
docs: "bg-teal-500",
|
|
11
|
+
test: "bg-yellow-500",
|
|
12
|
+
config: "bg-orange-500",
|
|
13
|
+
};
|
|
3
14
|
|
|
4
15
|
export function StoryPanel({
|
|
5
16
|
data,
|
|
@@ -13,42 +24,51 @@ export function StoryPanel({
|
|
|
13
24
|
const { summary, groups, narrative } = data;
|
|
14
25
|
|
|
15
26
|
return (
|
|
16
|
-
<div className="pt-
|
|
17
|
-
<div className="space-y-
|
|
18
|
-
<p className="text-xs text-
|
|
19
|
-
|
|
27
|
+
<div className="pt-5 space-y-6">
|
|
28
|
+
<div className="space-y-4">
|
|
29
|
+
<p className="text-xs text-foreground/80 leading-relaxed">{summary.purpose}</p>
|
|
30
|
+
|
|
31
|
+
<div className="grid grid-cols-2 gap-x-6 gap-y-3">
|
|
20
32
|
<div>
|
|
21
|
-
<
|
|
22
|
-
<p className="text-
|
|
33
|
+
<div className="text-[10px] font-medium text-muted-foreground/40 uppercase tracking-wider mb-1">Scope</div>
|
|
34
|
+
<p className="text-[11px] text-muted-foreground/70 leading-relaxed">{summary.scope}</p>
|
|
23
35
|
</div>
|
|
24
36
|
<div>
|
|
25
|
-
<
|
|
26
|
-
<p className="text-
|
|
37
|
+
<div className="text-[10px] font-medium text-muted-foreground/40 uppercase tracking-wider mb-1">Impact</div>
|
|
38
|
+
<p className="text-[11px] text-muted-foreground/70 leading-relaxed">{summary.impact}</p>
|
|
27
39
|
</div>
|
|
28
40
|
</div>
|
|
41
|
+
|
|
29
42
|
<div className="flex flex-wrap gap-1.5">
|
|
30
|
-
{groups.map((g) =>
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
43
|
+
{groups.map((g) => {
|
|
44
|
+
const isActive = activeId === `group:${g.name}`;
|
|
45
|
+
return (
|
|
46
|
+
<button
|
|
47
|
+
key={g.name}
|
|
48
|
+
type="button"
|
|
49
|
+
onClick={() => onAnchorClick("group", g.name)}
|
|
50
|
+
className={`inline-flex items-center gap-1.5 text-[11px] px-2 py-0.5 rounded-md transition-colors ${
|
|
51
|
+
isActive
|
|
52
|
+
? "bg-accent text-foreground font-medium"
|
|
53
|
+
: "text-muted-foreground/60 hover:text-foreground hover:bg-accent/40"
|
|
54
|
+
}`}
|
|
55
|
+
>
|
|
56
|
+
<span className={`h-1.5 w-1.5 rounded-full shrink-0 ${TYPE_DOT[g.type] ?? TYPE_DOT.chore}`} />
|
|
57
|
+
{g.name}
|
|
58
|
+
</button>
|
|
59
|
+
);
|
|
60
|
+
})}
|
|
44
61
|
</div>
|
|
45
62
|
</div>
|
|
46
63
|
|
|
47
64
|
<div className="border-t pt-5">
|
|
65
|
+
<div className="text-[10px] font-medium text-muted-foreground/40 uppercase tracking-wider mb-4">Walkthrough</div>
|
|
48
66
|
<Markdown onAnchorClick={onAnchorClick} activeId={activeId}>
|
|
49
67
|
{narrative}
|
|
50
68
|
</Markdown>
|
|
51
69
|
</div>
|
|
70
|
+
|
|
71
|
+
<ChatMessages onAnchorClick={onAnchorClick} activeId={activeId} />
|
|
52
72
|
</div>
|
|
53
73
|
);
|
|
54
74
|
}
|
|
@@ -11,7 +11,7 @@ const TabsList = React.forwardRef<
|
|
|
11
11
|
<TabsPrimitive.List
|
|
12
12
|
ref={ref}
|
|
13
13
|
className={cn(
|
|
14
|
-
"inline-flex
|
|
14
|
+
"inline-flex items-center gap-0 border-b border-border -mb-px",
|
|
15
15
|
className,
|
|
16
16
|
)}
|
|
17
17
|
{...props}
|
|
@@ -26,7 +26,7 @@ const TabsTrigger = React.forwardRef<
|
|
|
26
26
|
<TabsPrimitive.Trigger
|
|
27
27
|
ref={ref}
|
|
28
28
|
className={cn(
|
|
29
|
-
"inline-flex items-center justify-center whitespace-nowrap
|
|
29
|
+
"inline-flex items-center justify-center gap-1.5 whitespace-nowrap px-3 pb-2.5 pt-1 text-xs font-medium text-muted-foreground/60 transition-colors hover:text-foreground/80 border-b-2 border-transparent -mb-px data-[state=active]:text-foreground data-[state=active]:border-foreground focus-visible:outline-none",
|
|
30
30
|
className,
|
|
31
31
|
)}
|
|
32
32
|
{...props}
|
|
@@ -41,7 +41,7 @@ const TabsContent = React.forwardRef<
|
|
|
41
41
|
<TabsPrimitive.Content
|
|
42
42
|
ref={ref}
|
|
43
43
|
className={cn(
|
|
44
|
-
"mt-
|
|
44
|
+
"mt-0 focus-visible:outline-none",
|
|
45
45
|
className,
|
|
46
46
|
)}
|
|
47
47
|
{...props}
|