-
Notifications
You must be signed in to change notification settings - Fork 4.6k
Expand file tree
/
Copy pathutil.ts
More file actions
143 lines (124 loc) · 4.06 KB
/
util.ts
File metadata and controls
143 lines (124 loc) · 4.06 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
import { distance } from "fastest-levenshtein";
import { ChatMessage } from "../index.js";
import { renderChatMessageWithoutThinking } from "../util/messageContent.js";
export type LineStream = AsyncGenerator<string>;
export type MatchLineResult = {
/**
* -1 if it's a new line, otherwise the index of the first match
* in the old lines.
*/
matchIndex: number;
isPerfectMatch: boolean;
newLine: string;
};
function linesMatchPerfectly(lineA: string, lineB: string): boolean {
return lineA === lineB && lineA !== "";
}
const END_BRACKETS = ["}", "});", "})"];
function linesMatch(lineA: string, lineB: string, linesBetween = 0): boolean {
// Require a perfect (without padding) match for these lines
// Otherwise they are edit distance 1 from empty lines and other single char lines (e.g. each other)
if (["}", "*", "});", "})"].includes(lineA.trim())) {
return lineA.trim() === lineB.trim();
}
const d = distance(lineA, lineB);
return (
// Should be more unlikely for lines to fuzzy match if they are further away
(d / Math.max(lineA.length, lineB.length) <=
Math.max(0, 0.48 - linesBetween * 0.06) ||
lineA.trim() === lineB.trim()) &&
lineA.trim() !== ""
);
}
/**
* Used to find a match for a new line in an array of old lines.
*
* Return the index of the first match and whether it is a perfect match
* Also return a version of the line with correct indentation if needs fixing
*/
export function matchLine(
newLine: string,
oldLines: string[],
permissiveAboutIndentation = false,
): MatchLineResult {
// Only match empty lines if it's the next one:
if (newLine.trim() === "" && oldLines[0]?.trim() === "") {
return {
matchIndex: 0,
isPerfectMatch: true,
newLine: newLine.trim(),
};
}
const isEndBracket = END_BRACKETS.includes(newLine.trim());
for (let i = 0; i < oldLines.length; i++) {
// trims trailing whitespaces from the lines before comparison
//this ensures trailing spaces don't affect matching.
const oldLineTrimmed = oldLines[i].trimEnd();
const newLineTrimmed = newLine.trimEnd();
// Don't match end bracket lines if too far away
if (i > 4 && isEndBracket) {
return { matchIndex: -1, isPerfectMatch: false, newLine };
}
if (linesMatchPerfectly(newLineTrimmed, oldLineTrimmed)) {
return { matchIndex: i, isPerfectMatch: true, newLine };
}
if (linesMatch(newLineTrimmed, oldLineTrimmed, i)) {
// This is a way to fix indentation, but only for sufficiently long lines to avoid matching whitespace or short lines
if (
newLineTrimmed.trimStart() === oldLineTrimmed.trimStart() &&
(permissiveAboutIndentation || newLine.trim().length > 8)
) {
return {
matchIndex: i,
isPerfectMatch: true,
newLine: oldLines[i],
};
}
return { matchIndex: i, isPerfectMatch: false, newLine };
}
}
return { matchIndex: -1, isPerfectMatch: false, newLine };
}
/**
* Convert a stream of arbitrary chunks to a stream of lines
*/
export async function* streamLines(
streamCompletion: AsyncGenerator<string | ChatMessage>,
log: boolean = false,
): LineStream {
let allLines = [];
let buffer = "";
try {
for await (const update of streamCompletion) {
const chunk =
typeof update === "string"
? update
: renderChatMessageWithoutThinking(update);
buffer += chunk;
const lines = buffer.split("\n");
buffer = lines.pop() ?? "";
for (const line of lines) {
yield line;
allLines.push(line);
}
// if (buffer === "" && chunk.endsWith("\n")) {
// yield "";
// allLines.push("");
// }
}
if (buffer.length > 0) {
yield buffer;
allLines.push(buffer);
}
} finally {
if (log) {
console.log("Streamed lines: ", allLines.join("\n"));
}
}
}
export async function* generateLines<T>(lines: T[]): AsyncGenerator<T> {
for (const line of lines) {
yield line;
// await new Promise((resolve, reject) => setTimeout(() => resolve(null), 50));
}
}