457 lines
13 KiB
TypeScript
Raw Normal View History

2024-02-04 17:47:03 +08:00
import { types, flow, Instance, getParent } 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";
2024-02-02 19:40:58 +08:00
import { RootInstance } from "./RootStore";
import { get } from "http";
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,
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
2024-02-04 17:47:03 +08:00
const helpWorkflowCommands = () => {
2024-02-02 19:40:58 +08:00
const rootStore = getParent<RootInstance>(self);
const recommendCommands = rootStore.input.commandMenus
.filter((item) => item.recommend > -1)
.sort((a, b) => a.recommend - b.recommend);
if (recommendCommands.length > 0) {
return recommendCommands
.map((item) => {
if (item.name === "help") {
return "";
}
return `<a class="workflow_command" href="${item.pattern}">/${item.name}: <span style="color:var(--vscode-editor-foreground)"> ${item.description} </span></a>`;
})
.join("\n\n");
}
2024-02-04 17:47:03 +08:00
return rootStore.input.commandMenus
.map((item) => {
if (item.name === "help") {
return "";
}
2024-03-27 21:18:01 +08:00
return `<a class="workflow_command" href="${item.pattern}">/${item.name}: <span style="color:var(--vscode-editor-foreground)"> ${item.description} </span></a>`;
2024-02-04 17:47:03 +08:00
})
.join("\n\n");
2024-02-02 19:40:58 +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:
2024-02-02 19:40:58 +08:00
${helpWorkflowCommands()}`;
2023-12-12 15:13:17 +08:00
2023-12-15 14:37:49 +08:00
const setKeyMessage = `
Your DevChat Access Key is not detected in the current settings. Please set your Access Key below, and we'll have everything set up for you in no time.
2024-02-04 17:47:03 +08:00
<button value="get_devchat_key" ${
process.env.platform === "vscode"
? 'href="https://web.devchat.ai" component="a"'
: ""
}>Get 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
const accessKey = getParent<RootInstance>(self).config.getUserKey();
if (accessKey === "") {
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 = "";
const chatModel = getParent<RootInstance>(self).config.getDefaultModel();
2023-12-15 14:37:49 +08:00
messageUtil.sendMessage({
command: "sendMessage",
text: text,
contextInfo: contextInfo(chatContexts),
parent_hash: lastNonEmptyHash(),
model: chatModel,
2023-12-15 14:37:49 +08:00
});
};
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,
2024-02-02 19:40:58 +08:00
helpWorkflowCommands,
2023-12-15 14:37:49 +08:00
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) => {
const rootStore = getParent<RootInstance>(self);
rootStore.config.setConfigValue("default_model", chatModel);
2023-12-15 14:37:49 +08:00
},
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;
},
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, reset }) => {
2023-12-15 14:37:49 +08:00
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 (reset) {
self.messages = [] as any;
self.errorMessage = "";
}
2023-12-15 14:37:49 +08:00
if (self.messages.length === 0) {
helpMessage(true);
}
}
},
fetchHistoryMessages: () => {
self.isLastPage = true;
if (self.messages.length === 0) {
helpMessage(true);
2023-12-15 14:37:49 +08:00
}
},
2023-12-15 14:37:49 +08:00
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>;