-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathstackOverflow.ts
125 lines (103 loc) · 3.18 KB
/
stackOverflow.ts
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
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
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
import { constants } from "../../deploy/constants.js";
import { ChatMessageRole, FetchFunction, SlashCommand } from "../../index.js";
import { pruneStringFromBottom, stripImages } from "../../llm/countTokens.js";
const PROMPT = (
input: string,
) => `The above sources are excerpts from related StackOverflow questions. Use them to help answer the below question from our user. Provide links to the sources in markdown whenever possible:
${input}
`;
async function getResults(q: string, fetch: FetchFunction): Promise<any> {
const payload = JSON.stringify({
q: `${q} site:stackoverflow.com`,
});
const resp = await fetch(new URL("/search", constants.a), {
method: "POST",
headers: {
"Content-Type": "application/json",
},
body: payload,
});
return await resp.json();
}
async function fetchData(
url: string,
fetch: FetchFunction,
): Promise<string | undefined> {
const response = await fetch(url, {
headers: {
Accept: "text/html",
},
});
const htmlString = await response.text();
const parser = new DOMParser();
const doc = parser.parseFromString(htmlString, "text/html");
const h1 = doc.querySelector("h1.fs-headline1");
const title = h1?.textContent?.trim() ?? "No Title";
const bodies = doc.querySelectorAll("div.js-post-body");
if (bodies.length < 2) {
return undefined;
}
const question = bodies[0].textContent ?? "";
const answer = bodies[1].textContent ?? "";
return `
# Question: [${title}](${url})
${question}
# Best Answer
${answer}
`;
}
const StackOverflowSlashCommand: SlashCommand = {
name: "so",
description: "Search Stack Overflow",
run: async function* ({ llm, input, addContextItem, history, fetch }) {
const contextLength = llm.contextLength;
const sources: string[] = [];
const results = await getResults(input, fetch);
const links = results.organic.map((result: any) => result.link);
let totalTokens = llm.countTokens(input) + 200;
for (const link of links) {
const contents = await fetchData(link, fetch);
if (!contents) {
continue;
}
sources.push(contents);
const newTokens = llm.countTokens(contents);
totalTokens += newTokens;
let shouldBreak = false;
if (totalTokens > contextLength) {
sources[sources.length - 1] = pruneStringFromBottom(
llm.model,
contextLength - (totalTokens - newTokens),
sources[sources.length - 1],
);
shouldBreak = true;
}
if (sources.length >= 3) {
shouldBreak = true;
}
addContextItem({
content: sources[sources.length - 1],
description: "StackOverflow Answer",
name: `StackOverflow ${sources.length}`,
id: {
providerTitle: "so",
itemId: links[sources.length - 1],
},
});
if (shouldBreak) {
break;
}
}
for await (const chunk of llm.streamChat([
...history,
...sources.map((source) => ({
role: "user" as ChatMessageRole,
content: source,
})),
{ role: "user", content: PROMPT(input) },
])) {
yield stripImages(chunk.content);
}
},
};
export default StackOverflowSlashCommand;