AI Port Context (#170)

This brings over the AI context from the previous app. In particular, it
makes it so each block has its own context that persists after the app
is reloaded.

Note that this does not provide the app with the cli-specific context
from the previous app.
This commit is contained in:
Sylvie Crowe
2024-07-29 13:21:44 -07:00
committed by GitHub
parent e70f0bb9ab
commit 4494dc25cb
3 changed files with 61 additions and 30 deletions
+42 -24
View File
@@ -3,7 +3,7 @@
import { Markdown } from "@/app/element/markdown";
import { TypingIndicator } from "@/app/element/typingindicator";
import { WOS, atoms } from "@/store/global";
import { WOS, atoms, globalStore } from "@/store/global";
import { WshServer } from "@/store/wshserver";
import * as jotai from "jotai";
import type { OverlayScrollbars } from "overlayscrollbars";
@@ -25,19 +25,20 @@ interface ChatMessageType {
const outline = "2px solid var(--accent-color)";
const defaultMessage: ChatMessageType = {
id: uuidv4(),
user: "assistant",
text: `<p>Hello, how may I help you with this command?<br>
(Cmd-Shift-Space: open/close, Ctrl+L: clear chat buffer, Up/Down: select code blocks, Enter: to copy a selected code block to the command input)</p>`,
isAssistant: true,
};
interface ChatItemProps {
chatItem: ChatMessageType;
itemCount: number;
}
function promptToMsg(prompt: OpenAIPromptMessageType): ChatMessageType {
return {
id: uuidv4(),
user: prompt.role,
text: prompt.content,
isAssistant: prompt.role == "assistant",
};
}
export class WaveAiModel implements ViewModel {
blockId: string;
blockAtom: jotai.Atom<Block>;
@@ -49,7 +50,7 @@ export class WaveAiModel implements ViewModel {
messagesAtom: jotai.PrimitiveAtom<Array<ChatMessageType>>;
addMessageAtom: jotai.WritableAtom<unknown, [message: ChatMessageType], void>;
updateLastMessageAtom: jotai.WritableAtom<unknown, [text: string, isUpdating: boolean], void>;
simulateAssistantResponseAtom: jotai.WritableAtom<unknown, [userMessage: ChatMessageType], void>;
simulateAssistantResponseAtom: jotai.WritableAtom<unknown, [userMessage: ChatMessageType], Promise<void>>;
constructor(blockId: string) {
this.blockId = blockId;
@@ -57,8 +58,12 @@ export class WaveAiModel implements ViewModel {
this.viewIcon = jotai.atom((get) => {
return "sparkles"; // should not be hardcoded
});
this.viewName = jotai.atom("Ai");
this.messagesAtom = jotai.atom([defaultMessage]);
this.viewName = jotai.atom("Wave Ai");
this.messagesAtom = jotai.atom(
globalStore
.get(this.blockAtom)
.meta?.history?.map((prompt: OpenAIPromptMessageType) => promptToMsg(prompt)) ?? []
);
this.addMessageAtom = jotai.atom(null, (get, set, message: ChatMessageType) => {
const messages = get(this.messagesAtom);
@@ -73,7 +78,7 @@ export class WaveAiModel implements ViewModel {
set(this.messagesAtom, [...messages.slice(0, -1), updatedMessage]);
}
});
this.simulateAssistantResponseAtom = jotai.atom(null, (get, set, userMessage: ChatMessageType) => {
this.simulateAssistantResponseAtom = jotai.atom(null, async (get, set, userMessage: ChatMessageType) => {
const typingMessage: ChatMessageType = {
id: uuidv4(),
user: "assistant",
@@ -106,8 +111,10 @@ export class WaveAiModel implements ViewModel {
const [messages] = jotai.useAtom(this.messagesAtom);
const [, addMessage] = jotai.useAtom(this.addMessageAtom);
const [, simulateResponse] = jotai.useAtom(this.simulateAssistantResponseAtom);
const metadata = jotai.useAtomValue(this.blockAtom).meta;
const block = jotai.useAtomValue(this.blockAtom);
const metadata = block.meta;
const clientId = jotai.useAtomValue(atoms.clientId);
const blockId = this.blockId;
const sendMessage = (text: string, user: string = "user") => {
const newMessage: ChatMessageType = {
@@ -119,23 +126,23 @@ export class WaveAiModel implements ViewModel {
addMessage(newMessage);
// send message to backend and get response
const opts: OpenAIOptsType = {
model: "gpt-3.5-turbo",
model: "gpt-4o-mini",
apitoken: metadata?.apitoken as string,
maxtokens: 1000,
timeout: 10,
baseurl: metadata?.baseurl as string,
};
const prompt: Array<OpenAIPromptMessageType> = [
{
role: "user",
content: text,
name: (metadata?.name as string) || "user",
},
];
const newPrompt: OpenAIPromptMessageType = {
role: "user",
content: text,
name: (metadata?.name as string) || "user",
};
const updatedHistory: Array<OpenAIPromptMessageType> = metadata?.history || [];
updatedHistory.push(newPrompt);
const beMsg: OpenAiStreamRequest = {
clientid: clientId,
opts: opts,
prompt: prompt,
prompt: updatedHistory,
};
const aiGen = WshServer.StreamWaveAiCommand(beMsg);
let temp = async () => {
@@ -149,7 +156,18 @@ export class WaveAiModel implements ViewModel {
text: fullMsg,
isAssistant: true,
};
simulateResponse(response);
const responsePrompt: OpenAIPromptMessageType = {
role: "assistant",
content: fullMsg,
};
updatedHistory.push(responsePrompt);
const writeToHistory = WshServer.SetMetaCommand({
oref: WOS.makeORef("block", blockId),
meta: { ...metadata, history: updatedHistory },
});
const typeResponse = simulateResponse(response);
Promise.all([writeToHistory, typeResponse]);
};
temp();
};
+11 -5
View File
@@ -51,6 +51,16 @@ type OpenAICloudReqPacketType struct {
MaxChoices int `json:"maxchoices,omitempty"`
}
type OpenAIOptsType struct {
Model string `json:"model"`
APIToken string `json:"apitoken"`
BaseURL string `json:"baseurl,omitempty"`
MaxTokens int `json:"maxtokens,omitempty"`
MaxChoices int `json:"maxchoices,omitempty"`
Timeout int `json:"timeout,omitempty"`
BlockId string `json:"blockid"`
}
func MakeOpenAICloudReqPacket() *OpenAICloudReqPacketType {
return &OpenAICloudReqPacketType{
Type: OpenAICloudReqStr,
@@ -71,7 +81,7 @@ func GetWSEndpoint() string {
}
const DefaultMaxTokens = 1000
const DefaultModel = "gpt-3.5-turbo"
const DefaultModel = "gpt-4o-mini"
const DefaultStreamChanSize = 10
const PCloudWSEndpoint = "wss://wsapi.waveterm.dev/"
const PCloudWSEndpointVarName = "PCLOUD_WS_ENDPOINT"
@@ -101,7 +111,6 @@ func ConvertPrompt(prompt []wshrpc.OpenAIPromptMessageType) []openaiapi.ChatComp
func RunCloudCompletionStream(ctx context.Context, request wshrpc.OpenAiStreamRequest) chan wshrpc.RespOrErrorUnion[wshrpc.OpenAIPacketType] {
rtn := make(chan wshrpc.RespOrErrorUnion[wshrpc.OpenAIPacketType])
go func() {
log.Printf("start: %v", request)
defer close(rtn)
if request.Opts == nil {
rtn <- wshrpc.RespOrErrorUnion[wshrpc.OpenAIPacketType]{Error: fmt.Errorf("no openai opts found")}
@@ -139,7 +148,6 @@ func RunCloudCompletionStream(ctx context.Context, request wshrpc.OpenAiStreamRe
return
}
for {
log.Printf("loop")
_, socketMessage, err := conn.ReadMessage()
if err == io.EOF {
break
@@ -173,7 +181,6 @@ func RunCloudCompletionStream(ctx context.Context, request wshrpc.OpenAiStreamRe
func RunLocalCompletionStream(ctx context.Context, request wshrpc.OpenAiStreamRequest) chan wshrpc.RespOrErrorUnion[wshrpc.OpenAIPacketType] {
rtn := make(chan wshrpc.RespOrErrorUnion[wshrpc.OpenAIPacketType])
go func() {
log.Printf("start2: %v", request)
defer close(rtn)
if request.Opts == nil {
rtn <- wshrpc.RespOrErrorUnion[wshrpc.OpenAIPacketType]{Error: fmt.Errorf("no openai opts found")}
@@ -208,7 +215,6 @@ func RunLocalCompletionStream(ctx context.Context, request wshrpc.OpenAiStreamRe
}
sentHeader := false
for {
log.Printf("loop2")
streamResp, err := apiResp.Recv()
if err == io.EOF {
break
+8 -1
View File
@@ -8,12 +8,19 @@ import (
"path/filepath"
"github.com/wavetermdev/thenextwave/pkg/wavebase"
"github.com/wavetermdev/thenextwave/pkg/wshrpc"
"github.com/wavetermdev/thenextwave/pkg/wstore"
)
const termThemesDir = "terminal-themes"
const settingsFile = "settings.json"
var defaultAiMessage = wshrpc.OpenAIPromptMessageType{
Role: "assistant",
Content: `<p>Hello, how may I help you?<br>
(Cmd-Shift-Space: open/close, Ctrl+L: clear chat buffer, Up/Down: select code blocks, Enter: to copy a selected code block to the command input)</p>`,
}
var settingsAbsPath = filepath.Join(configDirAbsPath, settingsFile)
type WidgetsConfigType struct {
@@ -187,7 +194,7 @@ func applyDefaultSettings(settings *SettingsConfigType) {
Label: "waveai",
BlockDef: wstore.BlockDef{
View: "waveai",
Meta: map[string]any{"name": userName, "baseurl": "", "apitoken": ""},
Meta: map[string]any{"name": userName, "baseurl": "", "apitoken": "", "history": []wshrpc.OpenAIPromptMessageType{defaultAiMessage}},
},
},
}