Skip to content

Commit 056d47c

Browse files
committed
feat: add similar issues endpoint
1 parent bccaf40 commit 056d47c

File tree

4 files changed

+84
-5
lines changed

4 files changed

+84
-5
lines changed

server/api/clusters/[owner]/[repo].get.ts

+3-3
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
1-
import { clusterEmbeddings } from '../../../utils/cluster'
2-
import { getEmbeddingsForIssue } from '../../../utils/embeddings'
1+
import { clusterEmbeddings } from '~~/server/utils/cluster'
2+
import { getEmbeddingsForIssue } from '~~/server/utils/embeddings'
33

44
import { isAllowedRepo, type AllowedRepo } from '#shared/repos'
55

@@ -62,7 +62,7 @@ export default defineCachedEventHandler(async (event) => {
6262
}, {
6363
swr: true,
6464
getKey(event) {
65-
const [owner, repo] = getRouterParam(event, 'repo')?.split('/') || []
65+
const { owner, repo } = getRouterParams(event)
6666
return `clusters:${owner}:${repo}`.toLowerCase()
6767
},
6868
maxAge: 60 * 60 * 1000,

server/api/issues/[owner]/[repo].get.ts

+2-2
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
import { Octokit } from '@octokit/rest'
22

3-
import { getLabels, type Issue } from '../../../utils/github'
3+
import { getLabels, type Issue } from '~~/server/utils/github'
44
import { isAllowedRepo } from '#shared/repos'
55

66
const labelsToExclude = ['documentation', 'invalid', 'enhancement']
@@ -41,7 +41,7 @@ export default defineCachedEventHandler(async (event) => {
4141
}, {
4242
swr: true,
4343
getKey(event) {
44-
const [owner, repo] = getRouterParam(event, 'repo')?.split('/') || []
44+
const { owner, repo } = getRouterParams(event)
4545
return `issues:${owner}:${repo}`.toLowerCase()
4646
},
4747
maxAge: 60 * 60 * 1000,
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,72 @@
1+
import { Octokit } from '@octokit/rest'
2+
3+
import { isAllowedRepo } from '#shared/repos'
4+
import { getEmbeddingsForIssue, getStoredEmbeddingsForIssue } from '~~/server/utils/embeddings'
5+
6+
export default defineCachedEventHandler(async (event) => {
7+
const { owner, repo, number } = getRouterParams(event)
8+
9+
if (!owner || !repo || !number) {
10+
throw createError({
11+
status: 400,
12+
message: 'Invalid repository',
13+
})
14+
}
15+
16+
const source = `${owner}/${repo}`
17+
18+
if (!isAllowedRepo(source)) {
19+
throw createError({
20+
status: 400,
21+
message: 'Repository not allowed',
22+
})
23+
}
24+
25+
const vectorize = typeof hubVectorize !== 'undefined' ? hubVectorize('issues') : null
26+
27+
let issueEmbeddings = await getStoredEmbeddingsForIssue(event, owner, repo, number)
28+
if (!issueEmbeddings) {
29+
const options = owner !== 'unjs' ? { auth: useRuntimeConfig(event).github.token } : {}
30+
const octokit = new Octokit(options)
31+
32+
const { data: issue } = await octokit.issues.get({
33+
owner,
34+
repo,
35+
issue_number: parseInt(number),
36+
})
37+
38+
issueEmbeddings = await getEmbeddingsForIssue(event, issue)
39+
}
40+
41+
const results = await vectorize?.query(issueEmbeddings, {
42+
// TODO: support similar repos
43+
// filter: {
44+
// owner,
45+
// repository: repo,
46+
// },
47+
})
48+
49+
return results?.matches.map((m) => {
50+
const groups = m.id.match(/^issue:(?<owner>[^:]+):(?<repo>[^:]+):(?<number>\d+)$/)?.groups
51+
if (!groups) {
52+
console.error('Invalid match', m.id)
53+
return
54+
}
55+
return {
56+
owner: groups.owner,
57+
repo: groups.repo,
58+
number: parseInt(groups.number!),
59+
score: m.score,
60+
}
61+
}).filter(Boolean)
62+
}, {
63+
swr: true,
64+
getKey(event) {
65+
const { owner, repo, number } = getRouterParams(event)
66+
return `issue:${owner}:${repo}:${number}`.toLowerCase()
67+
},
68+
maxAge: 60 * 60 * 1000,
69+
staleMaxAge: 60 * 60 * 1000,
70+
shouldBypassCache: event => getHeader(event, 'force') === 'true',
71+
shouldInvalidateCache: event => getHeader(event, 'force') === 'true',
72+
})

server/utils/embeddings.ts

+7
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,13 @@ import { hash } from 'ohash'
33

44
import { getLabels, type Issue } from './github'
55

6+
export async function getStoredEmbeddingsForIssue(event: H3Event, owner: string, repo: string, number: number | string) {
7+
const storage = hubKV()
8+
const storageKey = `issue:${owner}:${repo}:${number}`
9+
const res = await storage.getItem<StoredEmbeddings>(storageKey)
10+
return res?.embeddings
11+
}
12+
613
export async function getEmbeddingsForIssue(event: H3Event, issue: Issue) {
714
const storage = hubKV()
815
const vectorize = typeof hubVectorize !== 'undefined' ? hubVectorize('issues') : null

0 commit comments

Comments
 (0)