-
Notifications
You must be signed in to change notification settings - Fork 1
Expand file tree
/
Copy patharticle.js
More file actions
192 lines (155 loc) · 5.11 KB
/
article.js
File metadata and controls
192 lines (155 loc) · 5.11 KB
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
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
const getUserMessage = async (str) =>
new Promise((resolve) => resolve(prompt(str)));
const waitForClick = (button) =>
new Promise((resolve) => {
const handler = () => {
button.removeEventListener("click", handler);
resolve();
};
button.addEventListener("click", handler);
});
async function waitForDirAccess() {
if (cachedDirHandle === null) {
const btn = document.querySelector("#fsAccess");
console.log(
"%ctool:%c You need to click the button to grant access to the directory",
"color: oklch(79.2% .209 151.711)",
"",
);
await waitForClick(btn);
}
return getDir();
}
async function readFile(path) {
const fileName = path.includes("/") ? path.split("/").reverse()[0] : path;
const dir = await waitForDirAccess();
const fileHandle = await dir.getFileHandle(fileName);
const file = await fileHandle.getFile();
const content = await file.text();
return content;
}
async function listFiles() {
const dirHandle = await waitForDirAccess();
const files = [];
for await (const [name, handle] of dirHandle.entries()) {
if (handle.kind === "file") {
files.push(name);
}
}
return files;
}
let cachedDirHandle = null;
async function getDir() {
if (
cachedDirHandle &&
(await cachedDirHandle.queryPermission({ mode: "read" })) === "granted"
) {
return cachedDirHandle;
}
const dirHandle = await window.showDirectoryPicker();
let permission = await dirHandle.queryPermission({ mode: "read" });
if (permission === "prompt") {
permission = await dirHandle.requestPermission({ mode: "read" });
}
if (permission === "granted") {
cachedDirHandle = dirHandle;
return dirHandle;
}
throw new Error("Permission not granted");
}
async function prepareToolCall(text) {
const output = text
.replace(/^\s*console\.log\(\s*([a-zA-Z_$][\w$]*)\s*\);?/m, "return $1;")
.replace(/^const\s+\w+\s*=\s*(await\s+\w+\([^)]*\));\s*$/m, "return $1;")
.replace(/^\s*(await\s+\w+\([^)]*\));\s*$/m, "return $1;");
const generatedFn = new Function(`return (async function(){ ${output} })();`);
const result = await generatedFn(readFile);
return "```tool_output\n" + result + "\n```";
}
async function extractToolCall(text) {
const match = text.match(/```tool_code\s*([\s\S]*?)\s*```/);
const code = match?.[1] || null;
if (code) {
console.log("%ctool:%c \n%s", "color: oklch(79.2% .209 151.711)", "", code);
const res = await prepareToolCall(code);
return res;
}
return false;
}
const params = await LanguageModel.params();
const client = await LanguageModel.create({
temperature: 0.1,
topK: params.defaultTopK,
initialPrompts: [
{
role: "system",
content: `At each turn, if you decide to invoke any of the function(s), it should be wrapped with \`\`\`tool_code\`\`\`. The Typescript methods described below are imported and available, you can only use defined methods. The generated code should be readable and efficient. The response to a method will be wrapped in \`\`\`tool_output\`\`\` use it to call more tools or generate a helpful, friendly response. When using a \`\`\`tool_code\`\`\` think step by step why and how it should be used.
The following Typescript methods are available:
\`\`\`js
/**
* Read the contents of a given relative file path. Use this when you want to see what's inside a file. Do not use this with directory names.
*
* @param path - The relative path of a file in the working directory.
* @returns {Promise<string>} A promise that resolves with the files content as a string.
*/
async function readFile(path: string): Promise<string> {}
\`\`\`
\`\`\`js
/**
* List files of a directory that the user will grant permission too. All you're concerned with is calling this to get access to a directory and its files.
*
* @returns {Promise<string[]>} A promise that resolves with the array of file names.
*/
async function listFiles(): Promise<string[]> {}
\`\`\`
User: `,
},
],
});
class Agent {
constructor(client, getUserMessage) {
this.client = client;
this.getUserMessage = getUserMessage;
}
async run() {
console.log("Chat with On-device LLM (cancel the prompt to quit)");
let readInput = true;
let userInput = "";
while (true) {
if (readInput) {
userInput = await this.getUserMessage();
if (userInput === null) break;
console.log(
"%cYou:%c %s",
"color: oklch(70.7% .165 254.624)",
"",
userInput,
);
}
const result = await this.runInference(userInput);
let toolRes = false;
const isToolCall = await extractToolCall(result);
if (isToolCall) {
toolRes = isToolCall;
}
if (!toolRes) {
readInput = true;
console.log(
"%cAgent:%c %s",
"color: oklch(90.5% .182 98.111)",
"",
result,
);
continue;
}
readInput = false;
userInput = toolRes;
console.log(userInput);
}
}
async runInference(message) {
return this.client.prompt(message);
}
}
const agent = new Agent(client, () => getUserMessage("You:"));
agent.run();