Skip to content

Commit 4df672a

Browse files
authored
Merge pull request #12 from yorkeccak/main
ui
2 parents e58c288 + c207d4f commit 4df672a

10 files changed

Lines changed: 379 additions & 48 deletions

File tree

app/api/countries/conflicts/route.ts

Lines changed: 13 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,14 @@
11
import { NextResponse } from "next/server";
22
import { getCountryConflicts, streamCountryConflicts } from "@/lib/valyu";
3+
import { isSelfHostedMode } from "@/lib/app-mode";
34

45
export const dynamic = "force-dynamic";
56

67
export async function GET(request: Request) {
78
const { searchParams } = new URL(request.url);
89
const country = searchParams.get("country");
910
const stream = searchParams.get("stream") === "true";
11+
const accessToken = searchParams.get("accessToken");
1012

1113
if (!country) {
1214
return NextResponse.json(
@@ -15,14 +17,23 @@ export async function GET(request: Request) {
1517
);
1618
}
1719

20+
// In valyu mode, require user token
21+
const selfHosted = isSelfHostedMode();
22+
if (!selfHosted && !accessToken) {
23+
return NextResponse.json(
24+
{ error: "Authentication required", requiresReauth: true },
25+
{ status: 401 }
26+
);
27+
}
28+
1829
// Streaming mode - use Server-Sent Events
1930
if (stream) {
2031
const encoder = new TextEncoder();
2132

2233
const readable = new ReadableStream({
2334
async start(controller) {
2435
try {
25-
for await (const chunk of streamCountryConflicts(country)) {
36+
for await (const chunk of streamCountryConflicts(country, { accessToken: accessToken || undefined })) {
2637
const data = `data: ${JSON.stringify(chunk)}\n\n`;
2738
controller.enqueue(encoder.encode(data));
2839
}
@@ -49,7 +60,7 @@ export async function GET(request: Request) {
4960

5061
// Non-streaming mode - return full response
5162
try {
52-
const result = await getCountryConflicts(country);
63+
const result = await getCountryConflicts(country, { accessToken: accessToken || undefined });
5364

5465
return NextResponse.json({
5566
country,

app/api/deepresearch/[taskId]/route.ts

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -71,7 +71,15 @@ export async function GET(
7171
const selfHosted = isSelfHostedMode();
7272
let statusData: any;
7373

74-
// Use OAuth proxy if user is signed in and not self-hosted
74+
// Valyu mode requires auth
75+
if (!selfHosted && !accessToken) {
76+
return NextResponse.json(
77+
{ error: "Authentication required", requiresReauth: true },
78+
{ status: 401 }
79+
);
80+
}
81+
82+
// Use OAuth proxy in valyu mode
7583
if (!selfHosted && accessToken) {
7684
statusData = await getStatusViaProxy(taskId, accessToken);
7785
if (statusData.error) {

app/api/deepresearch/route.ts

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -102,7 +102,15 @@ export async function POST(request: Request) {
102102

103103
const selfHosted = isSelfHostedMode();
104104

105-
// Use OAuth proxy if user is signed in and not self-hosted
105+
// Valyu mode requires auth
106+
if (!selfHosted && !accessToken) {
107+
return NextResponse.json(
108+
{ error: "Authentication required", requiresReauth: true },
109+
{ status: 401 }
110+
);
111+
}
112+
113+
// Use OAuth proxy in valyu mode
106114
if (!selfHosted && accessToken) {
107115
const result = await createTaskViaProxy(topic, accessToken);
108116
if (result.error) {

app/api/entities/route.ts

Lines changed: 27 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
import { NextResponse } from "next/server";
22
import { getEntityResearch, deepResearch, searchEntityLocations, streamEntityResearch } from "@/lib/valyu";
3+
import { isSelfHostedMode } from "@/lib/app-mode";
34

45
export const dynamic = "force-dynamic";
56
import { geocodeLocationsFromText } from "@/lib/geocoding";
@@ -9,6 +10,7 @@ export async function GET(request: Request) {
910
const { searchParams } = new URL(request.url);
1011
const name = searchParams.get("name");
1112
const stream = searchParams.get("stream") === "true";
13+
const accessToken = searchParams.get("accessToken");
1214

1315
if (!name) {
1416
return NextResponse.json(
@@ -17,14 +19,23 @@ export async function GET(request: Request) {
1719
);
1820
}
1921

22+
// In valyu mode, require authentication
23+
const selfHosted = isSelfHostedMode();
24+
if (!selfHosted && !accessToken) {
25+
return NextResponse.json(
26+
{ error: "Authentication required", requiresReauth: true },
27+
{ status: 401 }
28+
);
29+
}
30+
2031
// Streaming mode - use Server-Sent Events
2132
if (stream) {
2233
const encoder = new TextEncoder();
2334

2435
const readable = new ReadableStream({
2536
async start(controller) {
2637
try {
27-
for await (const chunk of streamEntityResearch(name)) {
38+
for await (const chunk of streamEntityResearch(name, { accessToken: accessToken || undefined })) {
2839
const data = `data: ${JSON.stringify(chunk)}\n\n`;
2940
controller.enqueue(encoder.encode(data));
3041
}
@@ -53,7 +64,7 @@ export async function GET(request: Request) {
5364
const deep = searchParams.get("deep") === "true";
5465

5566
try {
56-
const entityData = await getEntityResearch(name);
67+
const entityData = await getEntityResearch(name, { accessToken: accessToken || undefined });
5768

5869
if (!entityData) {
5970
return NextResponse.json(
@@ -73,7 +84,7 @@ export async function GET(request: Request) {
7384
};
7485

7586
if (deep) {
76-
const research = await deepResearch(name);
87+
const research = await deepResearch(name, { accessToken: accessToken || undefined });
7788
profile.researchSummary = research.summary;
7889
}
7990

@@ -90,7 +101,7 @@ export async function GET(request: Request) {
90101
export async function POST(request: Request) {
91102
try {
92103
const body = await request.json();
93-
const { name, includeDeepResearch } = body;
104+
const { name, includeDeepResearch, accessToken } = body;
94105

95106
if (!name) {
96107
return NextResponse.json(
@@ -99,9 +110,18 @@ export async function POST(request: Request) {
99110
);
100111
}
101112

113+
// In valyu mode, require authentication
114+
const selfHosted = isSelfHostedMode();
115+
if (!selfHosted && !accessToken) {
116+
return NextResponse.json(
117+
{ error: "Authentication required", requiresReauth: true },
118+
{ status: 401 }
119+
);
120+
}
121+
102122
const [entityData, locationContent] = await Promise.all([
103-
getEntityResearch(name),
104-
searchEntityLocations(name),
123+
getEntityResearch(name, { accessToken: accessToken || undefined }),
124+
searchEntityLocations(name, { accessToken: accessToken || undefined }),
105125
]);
106126

107127
if (!entityData) {
@@ -137,7 +157,7 @@ export async function POST(request: Request) {
137157
let pdfUrl = undefined;
138158

139159
if (includeDeepResearch) {
140-
const research = await deepResearch(name);
160+
const research = await deepResearch(name, { accessToken: accessToken || undefined });
141161
profile.researchSummary = research.summary;
142162
deliverables = research.deliverables;
143163
pdfUrl = research.pdfUrl;

app/api/events/route.ts

Lines changed: 29 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -161,14 +161,33 @@ async function processSearchResults(
161161
export async function GET(request: Request) {
162162
const { searchParams } = new URL(request.url);
163163
const query = searchParams.get("q");
164+
const accessToken = searchParams.get("accessToken");
165+
166+
// In valyu mode, require authentication
167+
const selfHosted = isSelfHostedMode();
168+
if (!selfHosted && !accessToken) {
169+
return NextResponse.json(
170+
{ error: "Authentication required", requiresReauth: true },
171+
{ status: 401 }
172+
);
173+
}
164174

165175
try {
166176
const searchQueries = query ? [query] : THREAT_QUERIES;
177+
const tokenToUse = selfHosted ? undefined : accessToken;
167178

168179
const searchResultsArrays = await Promise.all(
169-
searchQueries.map((q) => searchEvents(q, { maxResults: 20 }))
180+
searchQueries.map((q) => searchEvents(q, { maxResults: 20, accessToken: tokenToUse || undefined }))
170181
);
171182

183+
const requiresReauth = searchResultsArrays.some((r) => r.requiresReauth);
184+
if (requiresReauth) {
185+
return NextResponse.json(
186+
{ error: "auth_error", message: "Session expired. Please sign in again.", requiresReauth: true },
187+
{ status: 401 }
188+
);
189+
}
190+
172191
const allResults = searchResultsArrays.flatMap((r) => r.results);
173192
const sortedEvents = await processSearchResults(allResults);
174193

@@ -192,6 +211,15 @@ export async function POST(request: Request) {
192211
const { queries, accessToken } = body;
193212

194213
const selfHosted = isSelfHostedMode();
214+
215+
// In valyu mode, require authentication
216+
if (!selfHosted && !accessToken) {
217+
return NextResponse.json(
218+
{ error: "Authentication required", requiresReauth: true },
219+
{ status: 401 }
220+
);
221+
}
222+
195223
const tokenToUse = selfHosted ? undefined : accessToken;
196224

197225
const searchQueries = queries && Array.isArray(queries) && queries.length > 0

app/api/reports/route.ts

Lines changed: 12 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,13 @@
11
import { NextResponse } from "next/server";
22
import { deepResearch } from "@/lib/valyu";
3+
import { isSelfHostedMode } from "@/lib/app-mode";
34

45
export const dynamic = "force-dynamic";
56

67
export async function POST(request: Request) {
78
try {
89
const body = await request.json();
9-
const { topic, type } = body;
10+
const { topic, type, accessToken } = body;
1011

1112
if (!topic) {
1213
return NextResponse.json(
@@ -15,6 +16,15 @@ export async function POST(request: Request) {
1516
);
1617
}
1718

19+
// In valyu mode, require authentication
20+
const selfHosted = isSelfHostedMode();
21+
if (!selfHosted && !accessToken) {
22+
return NextResponse.json(
23+
{ error: "Authentication required", requiresReauth: true },
24+
{ status: 401 }
25+
);
26+
}
27+
1828
let enhancedQuery = topic;
1929

2030
switch (type) {
@@ -34,7 +44,7 @@ export async function POST(request: Request) {
3444
enhancedQuery = `comprehensive analysis ${topic}`;
3545
}
3646

37-
const research = await deepResearch(enhancedQuery);
47+
const research = await deepResearch(enhancedQuery, { accessToken: accessToken || undefined });
3848

3949
return NextResponse.json({
4050
report: {

app/page.tsx

Lines changed: 2 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -8,14 +8,13 @@ import { ThreatMap } from "@/components/map/threat-map";
88
import { TimelineScrubber } from "@/components/map/timeline-scrubber";
99
import { MapControls } from "@/components/map/map-controls";
1010
import { WelcomeModal } from "@/components/welcome-modal";
11-
import { SignInPanel, SignInModal } from "@/components/auth";
11+
import { SignInPanel } from "@/components/auth";
1212

1313
const WELCOME_DISMISSED_KEY = "globalthreatmap_welcome_dismissed";
1414

1515
export default function Home() {
1616
const [showWelcome, setShowWelcome] = useState(false);
17-
const [showSignInModal, setShowSignInModal] = useState(false);
18-
const { isLoading, refresh, requiresSignIn } = useEvents({
17+
const { isLoading, refresh } = useEvents({
1918
autoRefresh: true,
2019
refreshInterval: 300000, // 5 minutes
2120
});
@@ -27,12 +26,6 @@ export default function Home() {
2726
}
2827
}, []);
2928

30-
useEffect(() => {
31-
if (requiresSignIn) {
32-
setShowSignInModal(true);
33-
}
34-
}, [requiresSignIn]);
35-
3629
return (
3730
<div className="flex h-screen flex-col">
3831
<Header
@@ -50,7 +43,6 @@ export default function Home() {
5043
</div>
5144
<WelcomeModal open={showWelcome} onOpenChange={setShowWelcome} />
5245
<SignInPanel />
53-
<SignInModal open={showSignInModal} onOpenChange={setShowSignInModal} />
5446
</div>
5547
);
5648
}

components/feed/event-feed.tsx

Lines changed: 14 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,13 @@
11
"use client";
22

3+
import { useState } from "react";
34
import { useEventsStore } from "@/stores/events-store";
45
import { useAuthStore } from "@/stores/auth-store";
56
import { ScrollArea } from "@/components/ui/scroll-area";
7+
import { Button } from "@/components/ui/button";
68
import { EventCard } from "./event-card";
79
import { FeedFilters } from "./feed-filters";
10+
import { SignInModal } from "@/components/auth";
811
import { Loader2, Lock } from "lucide-react";
912

1013
const APP_MODE = process.env.NEXT_PUBLIC_APP_MODE || "self-hosted";
@@ -13,6 +16,7 @@ export function EventFeed() {
1316
const { filteredEvents, isLoading, error, selectedEvent, selectEvent } =
1417
useEventsStore();
1518
const { isAuthenticated } = useAuthStore();
19+
const [showSignInModal, setShowSignInModal] = useState(false);
1620

1721
const requiresAuth = APP_MODE === "valyu";
1822
const showSignInPrompt = requiresAuth && !isAuthenticated && !isLoading && filteredEvents.length === 0;
@@ -50,9 +54,16 @@ export function EventFeed() {
5054
<p className="text-sm font-medium text-foreground mb-1">
5155
Sign in to view events
5256
</p>
53-
<p className="text-xs text-muted-foreground">
57+
<p className="text-xs text-muted-foreground mb-4">
5458
Events require authentication
5559
</p>
60+
<Button
61+
variant="outline"
62+
size="sm"
63+
onClick={() => setShowSignInModal(true)}
64+
>
65+
Sign in
66+
</Button>
5667
</div>
5768
)}
5869

@@ -76,6 +87,8 @@ export function EventFeed() {
7687
))}
7788
</div>
7889
</ScrollArea>
90+
91+
<SignInModal open={showSignInModal} onOpenChange={setShowSignInModal} />
7992
</div>
8093
);
8194
}

0 commit comments

Comments
 (0)