diff --git a/frontend/app/view/waveai.tsx b/frontend/app/view/waveai.tsx index e22ff372..d08589c5 100644 --- a/frontend/app/view/waveai.tsx +++ b/frontend/app/view/waveai.tsx @@ -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: `

Hello, how may I help you with this command?
-(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)

`, - 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; @@ -49,7 +50,7 @@ export class WaveAiModel implements ViewModel { messagesAtom: jotai.PrimitiveAtom>; addMessageAtom: jotai.WritableAtom; updateLastMessageAtom: jotai.WritableAtom; - simulateAssistantResponseAtom: jotai.WritableAtom; + simulateAssistantResponseAtom: jotai.WritableAtom>; 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 = [ - { - 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 = 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(); }; diff --git a/pkg/waveai/waveai.go b/pkg/waveai/waveai.go index 1a49e0c5..3800b9c8 100644 --- a/pkg/waveai/waveai.go +++ b/pkg/waveai/waveai.go @@ -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 diff --git a/pkg/wconfig/settingsconfig.go b/pkg/wconfig/settingsconfig.go index 5d7c4c9d..34315850 100644 --- a/pkg/wconfig/settingsconfig.go +++ b/pkg/wconfig/settingsconfig.go @@ -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: `

Hello, how may I help you?
+(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)

`, +} + 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}}, }, }, }