Skip to content

Commit 93ff7a7

Browse files
committed
paper graph
1 parent 9f0eaaf commit 93ff7a7

File tree

22 files changed

+3432
-698
lines changed

22 files changed

+3432
-698
lines changed

.env.production

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1 +1 @@
1-
API_ENDPOINT = https://api-chocomint-projects.vercel.app/
1+
NEXT_PUBLIC_API_ENDPOINT = https://api-chocomint-projects.vercel.app/

@types/notion.d.ts

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
// type Title = {
2+
// title: [{
3+
// type: 'text',
4+
// text: { content: value?.toString?.() ?? '' },
5+
// }],
6+
// }

@types/paper-graph.d.ts

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
type Paper = {
2+
citationCount: number,
3+
authors: string[],
4+
id: string,
5+
title: string,
6+
references: string[],
7+
abstract: string | null,
8+
citations: string[],
9+
year: number
10+
}

__tests__/test.ts

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
2+
function sum(a: number, b: number) {
3+
return a + b;
4+
}
5+
6+
test('adds 1 + 2 to equal 3', () => {
7+
expect(sum(1, 2)).toBe(4);
8+
});

app/tools/annotation/page.tsx

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
import Reading from "@components/pages/reading";
2+
3+
export default function Test() {
4+
return <Reading />
5+
}
Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
import { OpenIcon } from "@public/icons";
2+
import { getOpenUrl } from "./utils";
3+
4+
export default function PaperCard({ paper }: { paper: Paper }) {
5+
return (
6+
<div key={paper.id} className='shadow-sm p-2'>
7+
<div className='flex justify-between items-start gap-2'>
8+
<span className='font-semibold break-words w-[90%]'>
9+
{paper.title}
10+
</span>
11+
<OpenIcon
12+
size={18}
13+
className='cursor-pointer shrink-0'
14+
onClick={(e) => {
15+
e.stopPropagation();
16+
const url = getOpenUrl(paper);
17+
if (url) window.open(url, '_blank');
18+
}}
19+
/>
20+
</div>
21+
<div>
22+
<span className="mr-3">
23+
{paper.year}
24+
</span>
25+
<span className="">
26+
{paper.citationCount} citations
27+
</span>
28+
29+
</div>
30+
</div>
31+
)
32+
}

app/tools/paper-graph/Search.tsx

Lines changed: 169 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,169 @@
1+
'use client';
2+
3+
import { useRef, useState } from 'react';
4+
import {
5+
sleep,
6+
searchFromSemanticScholar,
7+
fetchFromSemanticScholar,
8+
addToNotionDatabase,
9+
updateNotionPage,
10+
} from './utils';
11+
import { SearchIcon } from '@public/icons';
12+
import { Loading } from '@components/atoms';
13+
import useClickOutside from '@hooks/useClickOutside';
14+
15+
type Suggestion = {
16+
id: string;
17+
title: string;
18+
abstract: string;
19+
year: string;
20+
};
21+
22+
export default function Search({
23+
knownIds,
24+
onSelect,
25+
}: {
26+
knownIds: Set<string>;
27+
onSelect: (paper: any) => void;
28+
}) {
29+
const [modalOpen, setModalOpen] = useState(false);
30+
const [query, setQuery] = useState('');
31+
const [searched, setSearched] = useState(false)
32+
const [suggestions, setSuggestions] = useState<Suggestion[]>([]);
33+
const [loading, setLoading] = useState(false);
34+
const [status, setStatus] = useState('');
35+
36+
const ref = useRef<HTMLDivElement>(null);
37+
useClickOutside(ref, () => setModalOpen(false));
38+
39+
40+
async function handleSearch() {
41+
if (!query.trim()) {
42+
setSuggestions([]);
43+
return;
44+
}
45+
setSearched(true);
46+
setLoading(true);
47+
const data = await searchFromSemanticScholar(query);
48+
setSuggestions(data);
49+
setLoading(false);
50+
}
51+
52+
async function handleSelect(id: string) {
53+
if (knownIds.has(id)) return;
54+
55+
setQuery('');
56+
setSuggestions([]);
57+
setStatus('Fetching selected paper...');
58+
const paper = await fetchFromSemanticScholar(id);
59+
60+
setStatus('Adding selected paper to database...');
61+
const returnedPaper = await addToNotionDatabase({ ...paper, status: 'shown' });
62+
if (!returnedPaper) return;
63+
64+
knownIds.add(paper.paperId);
65+
66+
returnedPaper.authors = paper.authors.map((a: any) => a.name);
67+
onSelect(returnedPaper);
68+
69+
const referenceIds: string[] = [];
70+
const citationIds: string[] = [];
71+
72+
setStatus('Adding references...');
73+
for (const [index, ref] of (paper.references ?? []).entries()) {
74+
if (ref.paperId && !knownIds.has(ref.paperId)) {
75+
const returnedRef = await addToNotionDatabase({ ...ref, status: 'suggested' });
76+
if (!returnedRef) continue;
77+
knownIds.add(ref.paperId);
78+
referenceIds.push(returnedRef.id);
79+
await sleep(200);
80+
setStatus(`Added references ${index + 1}/${paper.references.length}...`);
81+
}
82+
}
83+
84+
setStatus('Adding citations...');
85+
for (const [index, cite] of (paper.citations ?? []).entries()) {
86+
if (cite.paperId && !knownIds.has(cite.paperId)) {
87+
const returnedCite = await addToNotionDatabase({ ...cite, status: 'suggested' });
88+
if (!returnedCite) continue;
89+
knownIds.add(cite.paperId);
90+
citationIds.push(returnedCite.id);
91+
await sleep(2000);
92+
setStatus(`Added citations ${index + 1}/${paper.citations.length}...`);
93+
}
94+
}
95+
96+
await updateNotionPage(returnedPaper.id, {
97+
references: referenceIds,
98+
citations: citationIds,
99+
});
100+
101+
setStatus('');
102+
setModalOpen(false);
103+
}
104+
105+
return (
106+
<>
107+
<div className='search flex justify-between'>
108+
<button
109+
onClick={() => setModalOpen(true)}
110+
className="p-2 rounded-full bg-gray-100 hover:bg-gray-200"
111+
>
112+
<SearchIcon className="w-5 h-5 text-gray-700" />
113+
</button>
114+
{status !== "" &&
115+
<span>
116+
{status}
117+
</span>}
118+
</div>
119+
120+
121+
{modalOpen && (
122+
<div className="fixed inset-0 z-50 bg-black/40 flex items-center justify-center">
123+
<div ref={ref} className="bg-white rounded-lg shadow-lg w-full max-w-md p-4 relative">
124+
<button
125+
onClick={() => setModalOpen(false)}
126+
className="absolute top-2 right-3 text-gray-400 hover:text-gray-600 text-xl"
127+
>
128+
×
129+
</button>
130+
131+
<h2 className="text-lg font-semibold mb-1">Search Paper</h2>
132+
<p className="text-sm text-gray-500 mb-3">{status}</p>
133+
134+
<input
135+
type="search"
136+
value={query}
137+
placeholder="Type paper title and press Enter..."
138+
onChange={(e) => setQuery(e.target.value)}
139+
onKeyDown={(e) => e.key === 'Enter' && handleSearch()}
140+
className="border p-2 w-full rounded mb-2"
141+
autoFocus
142+
/>
143+
144+
{loading
145+
? <Loading />
146+
: suggestions.length === 0 && query.trim() !== '' ? (
147+
searched && <p className="text-sm text-gray-500 mt-2">No results</p>
148+
) : (
149+
<ul className="border max-h-60 overflow-auto rounded bg-white">
150+
{suggestions.map((s) => (
151+
<li
152+
key={s.id}
153+
className={`p-2 ${knownIds.has(s.id)
154+
? 'bg-gray-100 text-gray-400 cursor-default'
155+
: 'hover:bg-blue-100 cursor-pointer'
156+
}`}
157+
onClick={() => !knownIds.has(s.id) && handleSelect(s.id)}
158+
>
159+
{s.title}
160+
</li>
161+
))}
162+
</ul>
163+
)}
164+
</div>
165+
</div>
166+
)}
167+
</>
168+
);
169+
}

0 commit comments

Comments
 (0)