455 lines
13 KiB
TypeScript
Raw Normal View History

2023-12-12 15:13:17 +08:00
import { types, flow, Instance } from "mobx-state-tree";
2023-12-15 14:37:49 +08:00
import messageUtil from "@/util/MessageUtil";
import { ChatContext } from "@/views/stores/InputStore";
import yaml from "js-yaml";
2023-12-12 15:13:17 +08:00
interface Context {
2023-12-15 14:37:49 +08:00
content: string;
role: string;
2023-12-12 15:13:17 +08:00
}
interface Entry {
2023-12-15 14:37:49 +08:00
hash: string;
type: string;
user: string;
date: string;
request: string;
response: string;
context: Context[];
2023-12-12 15:13:17 +08:00
}
interface LoadHistoryMessage {
2023-12-15 14:37:49 +08:00
command: string;
entries: Entry[];
2023-12-12 15:13:17 +08:00
}
export const fetchHistoryMessages = async (params) => {
2023-12-15 14:37:49 +08:00
const { pageIndex } = params;
return new Promise<{ pageIndex: number; entries: Entry[] }>(
(resolve, reject) => {
try {
messageUtil.sendMessage({
command: "historyMessages",
page: pageIndex,
});
messageUtil.registerHandler(
"loadHistoryMessages",
(message: LoadHistoryMessage) => {
resolve({
pageIndex: pageIndex,
entries: message.entries,
2023-12-12 15:13:17 +08:00
});
2023-12-15 14:37:49 +08:00
}
);
} catch (e) {
reject(e);
}
}
);
2023-12-12 15:13:17 +08:00
};
interface DevChatInstalledMessage {
2023-12-15 14:37:49 +08:00
command: string;
result: boolean;
2023-12-12 15:13:17 +08:00
}
export const deleteMessage = async (messageHash: string) => {
2023-12-15 14:37:49 +08:00
return new Promise<{ hash: string }>((resolve, reject) => {
try {
messageUtil.sendMessage({
command: "deleteChatMessage",
hash: messageHash,
});
messageUtil.registerHandler("deletedChatMessage", (message) => {
resolve({
hash: message.hash,
});
});
} catch (e) {
reject(e);
}
});
2023-12-12 15:13:17 +08:00
};
export const Message = types.model({
2023-12-15 14:37:49 +08:00
index: types.maybe(types.number),
hash: types.maybe(types.string),
type: types.enumeration(["user", "bot", "system"]),
message: types.string,
contexts: types.maybe(types.array(ChatContext)),
2023-12-12 15:13:17 +08:00
});
2023-12-15 14:37:49 +08:00
export const ChatStore = types
.model("Chat", {
2023-12-12 15:13:17 +08:00
generating: false,
responsed: false,
2023-12-15 14:37:49 +08:00
currentMessage: "",
2023-12-12 15:13:17 +08:00
hasDone: false,
2023-12-15 14:37:49 +08:00
errorMessage: "",
2023-12-12 15:13:17 +08:00
messages: types.array(Message),
pageIndex: 0,
isLastPage: false,
isBottom: true,
isTop: false,
scrollBottom: 0,
2023-12-15 14:37:49 +08:00
chatModel: "GPT-3.5",
2023-12-12 15:13:17 +08:00
chatPanelWidth: 300,
disabled: false,
2023-12-15 14:37:49 +08:00
rechargeSite: "https://web.devchat.ai/pricing/",
2023-12-15 11:14:15 +08:00
features: types.optional(types.frozen(), {}),
2023-12-15 14:37:49 +08:00
key: types.optional(types.string, ""),
})
.actions((self) => {
const goScrollBottom = () => {
self.scrollBottom++;
};
2023-12-12 15:13:17 +08:00
2023-12-15 14:37:49 +08:00
const lastNonEmptyHash = () => {
let lastNonEmptyHash;
for (let i = self.messages.length - 1; i >= 0; i--) {
if (self.messages[i].hash) {
lastNonEmptyHash = self.messages[i].hash;
break;
}
}
return lastNonEmptyHash === "message" ? null : lastNonEmptyHash;
};
2023-12-12 15:13:17 +08:00
2023-12-15 14:37:49 +08:00
// Process and send the message to the extension
const contextInfo = (chatContexts) =>
chatContexts.map((item, index: number) => {
const { file, path, content, command } = item;
return {
file,
context: {
path: path,
command: command,
content: content,
},
2023-12-12 15:13:17 +08:00
};
2023-12-15 14:37:49 +08:00
});
2023-12-12 15:13:17 +08:00
2023-12-15 14:37:49 +08:00
const helpMessage = (originalMessage = false) => {
let helps = `
2023-12-12 15:13:17 +08:00
Do you want to write some code or have a question about the project? Simply right-click on your chosen files or code snippets and add them to DevChat. Feel free to ask me anything or let me help you with coding.
Don't forget to check out the "+" button on the left of the input to add more context. To see a list of workflows you can run in the context, just type "/". Happy prompting!
To get started, here are some of the things that I can do for you:
[/code: write code based on your prompt](#code)
[/release_note: draft a release note based on your latest commits](#release_note)
2023-12-15 14:37:49 +08:00
${
2023-12-15 21:42:56 +08:00
self.features["ask-code"]
? "[/ask-code: ask anything about your codebase and get answers from our AI agent](#ask_code)"
: ""
2023-12-15 14:37:49 +08:00
}
2023-12-12 15:13:17 +08:00
You can configure DevChat from [Settings](#settings).`;
2023-12-15 14:37:49 +08:00
const setKeyMessage = `
Devchat key is missing from your environment or settings. Kindly input your DevChat key, and I'll ensure DevChat is all set for you.
<button value="get_devchat_key" href="https://web.devchat.ai" component="a">Register DevChat key</button>
2023-12-15 14:37:49 +08:00
<button value="setting_devchat_key">Set DevChat key</button>
`;
2023-12-12 15:13:17 +08:00
2023-12-15 14:37:49 +08:00
const setKeyUser = `Is DevChat Access Key ready?`;
2023-12-12 15:13:17 +08:00
2023-12-15 14:51:12 +08:00
if (self.key === "") {
2023-12-15 14:37:49 +08:00
self.messages.push(
Message.create({
type: "user",
message: setKeyUser,
})
);
self.messages.push(
Message.create({
type: "bot",
message: setKeyMessage,
})
);
} else {
self.messages.push(
Message.create({
type: "user",
message: originalMessage ? "How do I use DevChat?" : "/help",
})
);
self.messages.push(
Message.create({
type: "bot",
message: helps,
})
);
}
2023-12-12 15:13:17 +08:00
2023-12-15 14:37:49 +08:00
// goto bottom
goScrollBottom();
};
const startGenerating = (text: string, chatContexts) => {
self.generating = true;
self.responsed = false;
self.hasDone = false;
self.errorMessage = "";
self.currentMessage = "";
messageUtil.sendMessage({
command: "sendMessage",
text: text,
contextInfo: contextInfo(chatContexts),
parent_hash: lastNonEmptyHash(),
model: self.chatModel,
});
};
const sendLastUserMessage = () => {
const lastUserMessage = self.messages[self.messages.length - 2];
const lastBotMessage = self.messages[self.messages.length - 1];
if (lastUserMessage && lastUserMessage.type === "user") {
startGenerating(lastUserMessage.message, lastUserMessage.contexts);
}
self.disabled = false;
};
const cancelDevchatAsk = () => {
const lastBotMessage = self.messages[self.messages.length - 1];
if (lastBotMessage && lastBotMessage.type === "bot") {
lastBotMessage.message =
"You've cancelled the question. Please let me know if you have any other questions or if there's anything else I can assist with.";
}
self.disabled = false;
};
const commonMessage = (text: string, chatContexts) => {
self.messages.push({
type: "user",
message: text,
contexts: chatContexts,
});
self.messages.push({
type: "bot",
message: "",
});
// start generating
startGenerating(text, chatContexts);
// goto bottom
goScrollBottom();
};
2023-12-12 15:13:17 +08:00
2023-12-15 14:37:49 +08:00
const userInput = (values: any) => {
const inputStr = `
2023-12-12 15:13:17 +08:00
\`\`\`yaml type=chatmark-values
${yaml.dump(values)}
\`\`\`
`;
self.currentMessage = `
${self.currentMessage}
${inputStr}
\`\`\`Step
Thinking...
\`\`\`
`;
2023-12-15 14:37:49 +08:00
messageUtil.sendMessage({
command: "userInput",
text: inputStr,
2023-12-15 14:37:49 +08:00
});
// goto bottom
goScrollBottom();
};
2023-12-12 15:13:17 +08:00
2023-12-15 14:37:49 +08:00
return {
helpMessage,
sendLastUserMessage,
cancelDevchatAsk,
goScrollBottom,
startGenerating,
commonMessage,
userInput,
devchatAsk: flow(function* (userMessage, chatContexts) {
self.messages.push({
type: "user",
contexts: chatContexts,
message: userMessage,
});
2023-12-12 15:13:17 +08:00
2023-12-22 04:53:30 +08:00
self.messages.push({
type: "bot",
message: "",
});
startGenerating(userMessage, chatContexts);
2023-12-12 15:13:17 +08:00
2023-12-15 14:37:49 +08:00
// goto bottom
goScrollBottom();
}),
updateChatPanelWidth: (width: number) => {
self.chatPanelWidth = width;
},
changeChatModel: (chatModel: string) => {
self.chatModel = chatModel;
},
updateFeatures: (features: any) => {
self.features = features;
},
startSystemMessage: () => {
self.generating = true;
self.responsed = false;
self.hasDone = false;
self.errorMessage = "";
self.currentMessage = "";
},
reGenerating: () => {
self.generating = true;
self.responsed = false;
self.hasDone = false;
self.errorMessage = "";
self.currentMessage = "";
messageUtil.sendMessage({
command: "regeneration",
});
},
stopGenerating: (
hasDone: boolean,
hash: string = "",
message: string = ""
) => {
self.generating = false;
self.responsed = false;
self.hasDone = hasDone;
2023-12-27 19:35:50 +08:00
self.currentMessage = message;
2023-12-15 14:37:49 +08:00
const messagesLength = self.messages.length;
if (hasDone) {
if (messagesLength > 1) {
self.messages[messagesLength - 2].hash = hash;
self.messages[messagesLength - 1].hash = hash;
} else if (messagesLength > 0) {
self.messages[messagesLength - 1].hash = hash;
}
} else {
self.messages[messagesLength - 1].message = message;
}
},
startResponsing: (message: string) => {
self.responsed = true;
self.currentMessage = message;
},
setKey: (key: string) => {
self.key = key;
},
newMessage: (message: IMessage) => {
self.messages.push(message);
},
addMessages: (messages: IMessage[]) => {
self.messages.push(...messages);
},
updateLastMessage: (message: string) => {
if (self.messages.length > 0) {
self.messages[self.messages.length - 1].message = message;
}
},
shiftMessage: () => {
self.messages.splice(0, 1);
},
popMessage: () => {
self.messages.pop();
},
clearMessages: () => {
self.messages.length = 0;
},
happendError: (errorMessage: string) => {
self.errorMessage = errorMessage;
},
onMessagesTop: () => {
self.isTop = true;
self.isBottom = false;
},
onMessagesBottom: () => {
self.isTop = false;
self.isBottom = true;
},
onMessagesMiddle: () => {
self.isTop = false;
self.isBottom = false;
},
reloadMessage: ({ entries, pageIndex }) => {
if (entries.length > 0) {
self.pageIndex = pageIndex;
const messages = entries
.map((entry, index) => {
const { hash, user, date, request, response, context } = entry;
const chatContexts = context?.map(({ content }) => {
return JSON.parse(content);
});
return [
{
type: "user",
message: request,
contexts: chatContexts,
date: date,
hash: hash,
},
{ type: "bot", message: response, date: date, hash: hash },
];
})
.flat();
if (self.pageIndex === 0) {
self.messages = messages;
} else if (self.pageIndex > 0) {
self.messages.concat(...messages);
}
} else {
self.isLastPage = true;
if (self.messages.length === 0) {
helpMessage(true);
}
}
},
fetchHistoryMessages: flow(function* (params: { pageIndex: number }) {
const { pageIndex, entries } = yield fetchHistoryMessages(params);
if (entries.length > 0) {
self.pageIndex = pageIndex;
const messages = entries
.map((entry, index) => {
const { hash, user, date, request, response, context } = entry;
const chatContexts = context?.map(({ content }) => {
return JSON.parse(content);
});
return [
{
type: "user",
message: request,
contexts: chatContexts,
date: date,
hash: hash,
},
{ type: "bot", message: response, date: date, hash: hash },
];
})
.flat();
if (self.pageIndex === 0) {
self.messages.push(...messages);
} else if (self.pageIndex > 0) {
self.messages.concat(...messages);
}
} else {
self.isLastPage = true;
if (self.messages.length === 0) {
helpMessage(true);
}
}
}),
deleteMessage: flow(function* (messageHash: string) {
const { hash } = yield deleteMessage(messageHash);
const index = self.messages.findIndex(
(item: any) => item.hash === hash
);
if (index > -1) {
self.messages.splice(index);
}
}),
};
});
2023-12-12 15:13:17 +08:00
export type IMessage = Instance<typeof Message>;