Skip to content

Commit ebc4e83

Browse files
authored
feat: resetChatHistory function on a LlamaChatSession (#327)
* feat: `resetChatHistory` function on a `LlamaChatSession` * feat: copy model response in the example Electron app template
1 parent cf791f1 commit ebc4e83

File tree

10 files changed

+211
-8
lines changed

10 files changed

+211
-8
lines changed

.vitepress/utils/parseCmakeListsTxtOptions.ts

Lines changed: 12 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,18 +1,28 @@
1+
const maxLinesSpan = 10;
2+
13
export function parseCmakeListsTxtOptions(cmakeListsTxtString: string) {
24
const lines = cmakeListsTxtString.split("\n");
35

46
return lines
57
.map((line, index) => {
6-
const match = line.match(/^option\([\s\t\n\r]*(?<key>\S+)[\s\t\n\r]+"(?<description>(?:\\"|[^"])*)"[\s\t\n\r]+(?<defaultValue>\S+)[\s\t\n\r]*\)/);
7-
if (match == null || match.groups == null)
8+
const match = lines
9+
.slice(index, index + maxLinesSpan)
10+
.join("\n")
11+
.match(
12+
/^option\([\s\t\n\r]*(?<key>\S+)[\s\t\n\r]+"(?<description>(?:\\"|[^"])*)"[\s\t\n\r]+(?<defaultValue>\S+)[\s\t\n\r]*\)/
13+
);
14+
if (match == null || match.groups == null || match?.index !== 0)
815
return null;
916

17+
const totalLines = match[0]?.split("\n")?.length ?? 1;
18+
1019
const {key, description, defaultValue} = match.groups;
1120
if (key == null)
1221
return null;
1322

1423
return {
1524
lineNumber: index + 1,
25+
totalLines,
1626
key,
1727
description: description != null
1828
? description.replaceAll('\\"', '"')

docs/guide/cmakeOptions.data.ts

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -45,7 +45,10 @@ function renderCmakeOptionsTable(cmakeOptions: ReturnType<typeof parseCmakeOptio
4545
"Default value"
4646
].map(htmlEscape),
4747
cmakeOptions.map((option) => {
48-
const url = githubFileUrl + "#L" + option.lineNumber;
48+
let url = githubFileUrl + "#L" + option.lineNumber;
49+
50+
if (option.totalLines > 1)
51+
url += "-L" + (option.lineNumber + option.totalLines - 1);
4952

5053
return [
5154
`<a href=${JSON.stringify(url)}>` +

src/cli/recommendedModels.ts

Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -259,6 +259,45 @@ export const recommendedModels: ModelRecommendation[] = [{
259259
}
260260
}]
261261
}, */ {
262+
name: "OLMoE 1b 7B MoE",
263+
abilities: ["chat"],
264+
description: "OLMoE models were created by AllenAI, and are fully open source models that utilize a Mixture of Experts architecture" +
265+
"Mixtures of Experts (MoE) is a technique where different models, each skilled in solving a particular kind of problem, work together to the improve the overall performance on complex tasks.\n" +
266+
"This model includes 64 expert models, with a total of 7 billion parameters.\n" +
267+
"This model generates output extremely fast.",
268+
269+
fileOptions: [{
270+
huggingFace: {
271+
model: "allenai/OLMoE-1B-7B-0924-Instruct-GGUF",
272+
branch: "main",
273+
file: "olmoe-1b-7b-0924-instruct-q8_0.gguf"
274+
}
275+
}, {
276+
huggingFace: {
277+
model: "allenai/OLMoE-1B-7B-0924-Instruct-GGUF",
278+
branch: "main",
279+
file: "olmoe-1b-7b-0924-instruct-q6_k.gguf"
280+
}
281+
}, {
282+
huggingFace: {
283+
model: "allenai/OLMoE-1B-7B-0924-Instruct-GGUF",
284+
branch: "main",
285+
file: "olmoe-1b-7b-0924-instruct-q5_k_m.gguf"
286+
}
287+
}, {
288+
huggingFace: {
289+
model: "allenai/OLMoE-1B-7B-0924-Instruct-GGUF",
290+
branch: "main",
291+
file: "olmoe-1b-7b-0924-instruct-q4_k_s.gguf"
292+
}
293+
}, {
294+
huggingFace: {
295+
model: "allenai/OLMoE-1B-7B-0924-Instruct-GGUF",
296+
branch: "main",
297+
file: "olmoe-1b-7b-0924-instruct-q4_k_m.gguf"
298+
}
299+
}]
300+
}, {
262301
name: "Gemma 2 9B",
263302
abilities: ["chat", "complete"],
264303
description: "Gemma models were created by Google and are optimized suited for variety of text generation tasks, " +

src/evaluator/LlamaChatSession/LlamaChatSession.ts

Lines changed: 20 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -289,6 +289,8 @@ export class LlamaChatSession {
289289
/** @internal */ private readonly _disposeAggregator = new DisposeAggregator();
290290
/** @internal */ private readonly _autoDisposeSequence: boolean;
291291
/** @internal */ private readonly _contextShift?: LlamaChatSessionContextShiftOptions;
292+
/** @internal */ private readonly _forceAddSystemPrompt: boolean;
293+
/** @internal */ private readonly _systemPrompt?: string;
292294
/** @internal */ private readonly _chatLock = {};
293295
/** @internal */ private _chatHistory: ChatHistoryItem[];
294296
/** @internal */ private _lastEvaluation?: LlamaChatResponse["lastEvaluation"];
@@ -315,6 +317,8 @@ export class LlamaChatSession {
315317
throw new DisposedError();
316318

317319
this._contextShift = contextShift;
320+
this._forceAddSystemPrompt = forceAddSystemPrompt;
321+
this._systemPrompt = systemPrompt;
318322

319323
this._chat = new LlamaChat({
320324
autoDisposeSequence,
@@ -323,8 +327,8 @@ export class LlamaChatSession {
323327
});
324328

325329
const chatWrapperSupportsSystemMessages = this._chat.chatWrapper.settings.supportsSystemMessages;
326-
if (chatWrapperSupportsSystemMessages == null || chatWrapperSupportsSystemMessages || forceAddSystemPrompt)
327-
this._chatHistory = this._chat.chatWrapper.generateInitialChatHistory({systemPrompt});
330+
if (chatWrapperSupportsSystemMessages == null || chatWrapperSupportsSystemMessages || this._forceAddSystemPrompt)
331+
this._chatHistory = this._chat.chatWrapper.generateInitialChatHistory({systemPrompt: this._systemPrompt});
328332
else
329333
this._chatHistory = [];
330334

@@ -815,6 +819,20 @@ export class LlamaChatSession {
815819
this._lastEvaluation = undefined;
816820
}
817821

822+
/** Clear the chat history and reset it to the initial state. */
823+
public resetChatHistory() {
824+
if (this._chat == null || this.disposed)
825+
throw new DisposedError();
826+
827+
const chatWrapperSupportsSystemMessages = this._chat.chatWrapper.settings.supportsSystemMessages;
828+
if (chatWrapperSupportsSystemMessages == null || chatWrapperSupportsSystemMessages || this._forceAddSystemPrompt)
829+
this.setChatHistory(
830+
this._chat.chatWrapper.generateInitialChatHistory({systemPrompt: this._systemPrompt})
831+
);
832+
else
833+
this.setChatHistory([]);
834+
}
835+
818836
/** @internal */
819837
private _stopAllPreloadAndPromptCompletions() {
820838
for (const abortController of this._preloadAndCompleteAbortControllers)

templates/electron-typescript-react/src/App/components/ChatHistory/ChatHistory.css

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,8 @@
1717
margin-inline-end: 12px;
1818
color: var(--user-message-text-color);
1919
word-break: break-word;
20+
max-width: calc(100% - 48px - 12px);
21+
box-sizing: border-box;
2022

2123
&:not(:first-child) {
2224
margin-top: 36px;
@@ -28,6 +30,12 @@
2830
margin-inline-end: 48px;
2931
padding-inline-start: 24px;
3032
word-break: break-word;
33+
max-width: calc(100% - 48px);
34+
box-sizing: border-box;
35+
36+
&:hover + .buttons {
37+
opacity: 1;
38+
}
3139

3240
&.active {
3341
&:empty:after,
@@ -58,6 +66,15 @@
5866
margin-bottom: 0px;
5967
}
6068

69+
h2 {
70+
margin: 16px 0px;
71+
padding-top: 24px;
72+
}
73+
74+
h3 {
75+
margin: 32px 0px 0px 0px;
76+
}
77+
6178
table {
6279
display: block;
6380
border-style: hidden;
@@ -94,6 +111,20 @@
94111
}
95112
}
96113
}
114+
115+
> .buttons {
116+
display: flex;
117+
flex-direction: row;
118+
padding: 8px 18px;
119+
opacity: 0;
120+
121+
transition: opacity 0.1s ease-in-out;
122+
123+
&:hover,
124+
&:focus-visible {
125+
opacity: 1;
126+
}
127+
}
97128
}
98129

99130
@keyframes activeModelMessageIndicator {

templates/electron-typescript-react/src/App/components/ChatHistory/ChatHistory.tsx

Lines changed: 11 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
import classNames from "classnames";
22
import {LlmState} from "../../../../electron/state/llmState.ts";
33
import {MarkdownContent} from "../MarkdownContent/MarkdownContent.js";
4+
import {MessageCopyButton} from "./components/MessageCopyButton/MessageCopyButton.js";
45

56
import "./ChatHistory.css";
67

@@ -11,9 +12,16 @@ export function ChatHistory({simplifiedChat, generatingResult}: ChatHistoryProps
1112
simplifiedChat.map((item, index) => {
1213
if (item.type === "model") {
1314
const isActive = index === simplifiedChat.length - 1 && generatingResult;
14-
return <MarkdownContent key={index} className={classNames("message", "model", isActive && "active")}>
15-
{item.message}
16-
</MarkdownContent>;
15+
return <>
16+
<MarkdownContent key={index} className={classNames("message", "model", isActive && "active")}>
17+
{item.message}
18+
</MarkdownContent>
19+
{
20+
!isActive && <div className="buttons">
21+
<MessageCopyButton text={item.message} />
22+
</div>
23+
}
24+
</>;
1725

1826
} else if (item.type === "user")
1927
return <MarkdownContent key={index} className="message user">
Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
.appChatHistory > .buttons {
2+
> .copyButton {
3+
display: grid;
4+
grid-template-areas: "icon";
5+
padding: 6px;
6+
border: none;
7+
8+
transition: background-color 0.1s ease-in-out;
9+
10+
&:not(:hover, :focus-visible) {
11+
background-color: transparent;
12+
}
13+
14+
&.copied {
15+
> .icon.copy {
16+
opacity: 0;
17+
transition-delay: 0s;
18+
}
19+
20+
> .icon.check {
21+
opacity: 1;
22+
transition-delay: 0.1s;
23+
}
24+
}
25+
26+
> .icon {
27+
grid-area: icon;
28+
width: 18px;
29+
height: 18px;
30+
31+
transition: opacity 0.3s ease-in-out;
32+
33+
&.copy {
34+
opacity: 1;
35+
transition-delay: 0.1s;
36+
}
37+
&.check {
38+
opacity: 0;
39+
}
40+
}
41+
}
42+
}
Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
import classNames from "classnames";
2+
import {useCallback, useState} from "react";
3+
import {CopyIconSVG} from "../../../../../icons/CopyIconSVG.js";
4+
import {CheckIconSVG} from "../../../../../icons/CheckIconSVG.js";
5+
6+
import "./MessageCopyButton.css";
7+
8+
const showCopiedTime = 1000 * 2;
9+
10+
export function MessageCopyButton({text}: MessageCopyButtonProps) {
11+
const [copies, setCopies] = useState(0);
12+
13+
const onClick = useCallback(() => {
14+
navigator.clipboard.writeText(text)
15+
.then(() => {
16+
setCopies(copies + 1);
17+
18+
setTimeout(() => {
19+
setCopies(copies - 1);
20+
}, showCopiedTime);
21+
})
22+
.catch((error) => {
23+
console.error("Failed to copy text to clipboard", error);
24+
});
25+
}, [text]);
26+
27+
return <button
28+
onClick={onClick}
29+
className={classNames("copyButton", copies > 0 && "copied")}
30+
>
31+
<CopyIconSVG className="icon copy" />
32+
<CheckIconSVG className="icon check" />
33+
</button>;
34+
}
35+
36+
type MessageCopyButtonProps = {
37+
text: string
38+
};
Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
import {SVGProps} from "react";
2+
3+
export function CheckIconSVG(props: SVGProps<SVGSVGElement>) {
4+
return <svg height="24px" viewBox="0 -960 960 960" width="24px" {...props}>
5+
<path d="M382-240 154-468l57-57 171 171 367-367 57 57-424 424Z"/>
6+
</svg>;
7+
}
Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
import {SVGProps} from "react";
2+
3+
export function CopyIconSVG(props: SVGProps<SVGSVGElement>) {
4+
return <svg height="24px" viewBox="0 -960 960 960" width="24px" {...props}>
5+
<path d="M360-240q-33 0-56.5-23.5T280-320v-480q0-33 23.5-56.5T360-880h360q33 0 56.5 23.5T800-800v480q0 33-23.5 56.5T720-240H360Zm0-80h360v-480H360v480ZM200-80q-33 0-56.5-23.5T120-160v-560h80v560h440v80H200Zm160-240v-480 480Z"/>
6+
</svg>;
7+
}

0 commit comments

Comments
 (0)