492 lines
14 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";
2024-04-01 19:08:47 +08:00
import { useTranslation } from "react-i18next";
import i18next from "i18next";
2024-06-07 01:55:09 +08:00
import APIUtil from "@/util/APIUtil";
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 "";
}
2024-04-01 19:08:47 +08:00
return `<a class="workflow_command" href="${
item.pattern
}">${i18next.t(
`/${item.name}`
)}:<span style="color:var(--vscode-editor-foreground)">${i18next.t(
`${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
const startGenerating = (text: string, chatContexts) => {
self.generating = true;
self.responsed = false;
self.hasDone = false;
self.errorMessage = "";
self.currentMessage = "";
2024-06-07 01:55:09 +08:00
const config = getParent<RootInstance>(self).config
const chatModel = config.getDefaultModel();
2024-06-07 16:36:00 +08:00
messageUtil.registerHandler("codeDiffApply", (_: any) => {
const e = 'code_diff_apply'
APIUtil.createEvent({name: e, value: e})
})
messageUtil.sendMessage({
command: "sendMessage",
text: text,
contextInfo: contextInfo(chatContexts),
parent_hash: lastNonEmptyHash(),
model: chatModel,
});
2024-06-07 17:07:15 +08:00
APIUtil.createMessage({content: text, model: chatModel});
};
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.
To see a list of workflows you can run in the context, just type "/". Happy prompting!
2023-12-12 15:13:17 +08:00
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",
2024-04-02 16:34:33 +08:00
message: helps,
2023-12-15 14:37:49 +08:00
})
);
}
2023-12-12 15:13:17 +08:00
// const rootStore = getParent<RootInstance>(self);
// setTimeout(() => {
// rootStore.chat.startResponsing("form settimeout");
// setTimeout(() => {
// rootStore.chat.stopGenerating(true, "123123", "form settimeout stop");
// rootStore.chat.happendError("form settimeout error");
// }, 1000);
// }, 1000);
2023-12-15 14:37:49 +08:00
// goto bottom
goScrollBottom();
};
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
const startResponsing = (message: string) => {
self.responsed = true;
self.currentMessage = message;
};
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,
startResponsing,
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;
}
},
2023-12-15 14:37:49 +08:00
newMessage: (message: IMessage) => {
self.messages.push(message);
},
addMessages: (messages: IMessage[]) => {
self.messages.push(...messages);
},
updateLastMessage: (message: string) => {
2024-06-07 01:55:09 +08:00
// console.log("message: ", message);
2023-12-15 14:37:49 +08:00
if (self.messages.length > 0) {
self.messages[self.messages.length - 1].message = message;
// if (message === "") {
// self.messages[self.messages.length - 1].message = message;
// } else if (
// self.messages[self.messages.length - 1].message !== message
// ) {
// self.messages[self.messages.length - 1].message += message;
// }
2023-12-15 14:37:49 +08:00
}
},
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>;