diff --git a/src/util/APIUtil.ts b/src/util/APIUtil.ts
new file mode 100644
index 0000000..a0efc3e
--- /dev/null
+++ b/src/util/APIUtil.ts
@@ -0,0 +1,111 @@
+import axios from "axios";
+import { v4 as uuidv4 } from 'uuid';
+
+class APIUtil {
+ private static instance: APIUtil;
+ private baseUrl: string | undefined;
+ private accessKey: string | undefined;
+ private webappUrl: string | undefined;
+ private currentMessageId: string | undefined;
+
+
+ constructor() {
+ console.log("APIUtil ready");
+ }
+
+ public static getInstance(): APIUtil {
+ if (!APIUtil.instance) {
+ APIUtil.instance = new APIUtil();
+ }
+ return APIUtil.instance;
+ }
+ async fetchWebappUrl() {
+ try {
+ const res = await axios.get(
+ `${this.baseUrl}/addresses/webapp`,
+ { headers: { 'Authorization': `Bearer ${this.accessKey}` }}
+ )
+ const urlOrPath = res?.data;
+ if (!urlOrPath) {
+ throw new Error("No webapp url found");
+ }
+ let href = "";
+ if (urlOrPath.startsWith("http://") || urlOrPath.startsWith("https://")) {
+ href = urlOrPath;
+ } else {
+ href = new URL(urlOrPath, this.baseUrl).href
+ }
+ if (href.endsWith('/')) {
+ href = href.slice(0, -1);
+ }
+ if (href.endsWith('/api')) {
+ href = href.slice(0, -4);
+ }
+ console.log('Webapp url: ', href)
+ return href;
+ } catch (err) {
+ console.error("Error fetch webapp url:", err);
+ return "https://app.devchat.ai";
+ }
+ }
+
+ config(baseUrl: string, accessKey: string) {
+ this.baseUrl = baseUrl;
+ this.accessKey = accessKey;
+ if (!this.webappUrl) {
+ this.fetchWebappUrl().then(url => {
+ this.webappUrl = url;
+ })
+ }
+ }
+
+ async createMessage(message: object) {
+ this.currentMessageId = `msg-${uuidv4()}`;
+ try {
+ const res = await axios.post(
+ `${this.webappUrl}/api/v1/messages`,
+ {...message, message_id: this.currentMessageId},
+ { headers: {
+ Authorization: `Bearer ${this.accessKey}`,
+ 'Content-Type': 'application/json',
+ }}
+ )
+ console.log("Message created: ", res?.data);
+ } catch(err) {
+ console.error(err);
+ }
+ }
+
+ async createEvent(event: object) {
+ try {
+ const res = await axios.post(
+ `${this.webappUrl}/api/v1/messages/${this.currentMessageId}/events`,
+ event,
+ {headers: {
+ Authorization: `Bearer ${this.accessKey}`,
+ 'Content-Type': 'application/json',
+ }}
+ )
+ console.log("Event created: ", res?.data);
+ } catch(err) {
+ console.error(err);
+ }
+ }
+
+ async getBalance() {
+ try {
+ if (!this.webappUrl) this.webappUrl = await this.fetchWebappUrl();
+ const res = await axios.get(
+ `${this.webappUrl}/api/v1/users/profile`,
+ {headers: { Authorization: `Bearer ${this.accessKey}` }}
+ )
+ return res?.data?.organization
+ } catch(err) {
+ console.error(err);
+ return null;
+ }
+ }
+}
+
+
+export default APIUtil.getInstance();
diff --git a/src/util/ideaBridge.ts b/src/util/ideaBridge.ts
index 715a9b9..63d6225 100644
--- a/src/util/ideaBridge.ts
+++ b/src/util/ideaBridge.ts
@@ -329,6 +329,9 @@ class IdeaBridge {
case "updateSetting/response":
this.resviceUpdateSetting(res);
break;
+ case "codeDiffApply/response":
+ this.resviceCodeDiffApply(res);
+ break;
case "sendUserMessage/response":
this.resviceSendUserMessage(res);
break;
@@ -377,6 +380,10 @@ class IdeaBridge {
this.executeHandlers("updateSetting", res.payload);
}
+ resviceCodeDiffApply(res) {
+ this.executeHandlers("codeDiffApply", res.payload);
+ }
+
resviceSendUserMessage(res) {
this.executeHandlers("chatWithDevChat", {
command: "chatWithDevChat",
diff --git a/src/views/App.tsx b/src/views/App.tsx
index 812bb4f..315cd37 100644
--- a/src/views/App.tsx
+++ b/src/views/App.tsx
@@ -8,6 +8,7 @@ import { useMst } from "./stores/RootStore";
import MessageUtil from "@/util/MessageUtil";
import "./App.css";
import "./i18n";
+import APIUtil from "@/util/APIUtil";
export default function App() {
const [ready, setReady] = useState(false);
@@ -28,6 +29,7 @@ export default function App() {
MessageUtil.registerHandler("readConfig", (data: { value: any }) => {
console.log("readConfig registerHandler: ", data);
config.setConfig(data.value);
+ APIUtil.config(config.getAPIBase(), config.getUserKey())
config.refreshModelList();
setReady(true);
});
diff --git a/src/views/components/BalanceTip/index.tsx b/src/views/components/BalanceTip/index.tsx
index cfea49b..43fec7d 100644
--- a/src/views/components/BalanceTip/index.tsx
+++ b/src/views/components/BalanceTip/index.tsx
@@ -1,5 +1,4 @@
import React, { useEffect, useState } from "react";
-import axios from "axios";
import messageUtil from "@/util/MessageUtil";
import { IconWallet } from "@tabler/icons-react";
import {
@@ -11,6 +10,7 @@ import {
} from "@mantine/core";
import { Trans } from "react-i18next";
import { useMst } from "@/views/stores/RootStore";
+import APIUtil from "@/util/APIUtil";
const currencyMap = {
USD: "$",
@@ -28,15 +28,9 @@ function formatCurrency(balance: number | null, currency: string) {
return `${currencyMap[currency] || currency}${balance}`;
}
-const envMap = {
- dev: {
- requestUrl: "https://apptest.devchat.ai",
- link: "https://webtest.devchat.ai",
- },
- prod: {
- requestUrl: "https://app.devchat.ai",
- link: "https://web.devchat.ai",
- },
+const links = {
+ dev: "https://webtest.devchat.ai",
+ prod: "https://web.devchat.ai",
};
export default function WechatTip() {
@@ -50,23 +44,13 @@ export default function WechatTip() {
const platform = process.env.platform;
const getBalance = () => {
- if (!envMap[env].requestUrl || !accessKey) {
- return;
- }
- setLoading(true);
- axios
- .get(`${envMap[env].requestUrl}/api/v1/users/profile`, {
- headers: { Authorization: `Bearer ${accessKey}` },
- })
- .then((res) => {
- if (res?.data?.organization?.balance) {
- setBalance(formatBalance(res?.data?.organization?.balance));
- setCurrency(res?.data?.organization?.currency);
- }
- })
- .finally(() => {
- setLoading(false);
- });
+ APIUtil.getBalance().then(org => {
+ setLoading(true);
+ setBalance(formatBalance(org?.balance));
+ setCurrency(org?.currency);
+ }).finally(() => {
+ setLoading(false);
+ })
};
useEffect(() => {
@@ -93,7 +77,7 @@ export default function WechatTip() {
e.stopPropagation();
messageUtil.sendMessage({
command: "openLink",
- url: envMap[env].link,
+ url: links[env],
});
};
@@ -139,7 +123,7 @@ export default function WechatTip() {
web.devchat.ai{" "}
) : (
-
+
web.devchat.ai{" "}
)}
diff --git a/src/views/components/MessageMarkdown/CodeButtons.tsx b/src/views/components/MessageMarkdown/CodeButtons.tsx
index d03f80d..40a44c4 100644
--- a/src/views/components/MessageMarkdown/CodeButtons.tsx
+++ b/src/views/components/MessageMarkdown/CodeButtons.tsx
@@ -1,9 +1,10 @@
import { Tooltip, ActionIcon, CopyButton, Flex } from "@mantine/core";
import { IconCheck, IconGitCommit, IconFileDiff, IconColumnInsertRight, IconReplace, IconCopy,IconFile } from "@tabler/icons-react";
import React, { useState } from "react";
+import { useMst } from "@/views/stores/RootStore";
import messageUtil from '@/util/MessageUtil';
-import language from "react-syntax-highlighter/dist/esm/languages/hljs/1c";
+import APIUtil from "@/util/APIUtil";
const IconButton = ({ label, color = 'gray', onClick, children }) => (
@@ -31,10 +32,14 @@ const CommitButton = ({ code }) => {
};
const CodeCopyButton = ({ code }) => {
+ const {config} = useMst();
return (
{({ copied, copy }) => (
-
+ {
+ copy();
+ APIUtil.createEvent({name: 'copy', value: 'copy'})
+ }}>
{copied ? : }
)}
@@ -43,11 +48,14 @@ const CodeCopyButton = ({ code }) => {
};
const DiffButton = ({ code }) => {
+ const {config} = useMst();
const handleClick = () => {
+ const e = 'show_diff';
messageUtil.sendMessage({
- command: 'show_diff',
+ command: e,
content: code
});
+ APIUtil.createEvent({name: e, value: e})
};
return (
@@ -57,11 +65,14 @@ const DiffButton = ({ code }) => {
};
const CodeApplyButton = ({ code }) => {
+ const {config} = useMst();
const handleClick = () => {
+ const e = 'code_apply';
messageUtil.sendMessage({
- command: 'code_apply',
+ command: e,
content: code
});
+ APIUtil.createEvent({name: e, value: e})
};
return (
@@ -71,11 +82,14 @@ const CodeApplyButton = ({ code }) => {
};
const FileApplyButton = ({ code }) => {
+ const {config} = useMst();
const handleClick = () => {
+ const e = 'code_file_apply';
messageUtil.sendMessage({
- command: 'code_file_apply',
+ command: e,
content: code
});
+ APIUtil.createEvent({name: e, value: e})
};
return (
@@ -86,12 +100,15 @@ const FileApplyButton = ({ code }) => {
// Add a new button to create new file
const NewFileButton = ({ language,code }) => {
+ const {config} = useMst();
const handleClick = () => {
+ const e = 'code_new_file';
messageUtil.sendMessage({
- command: 'code_new_file',
+ command: e,
language: language,
content: code
});
+ APIUtil.createEvent({name: e, value: e})
};
return (
diff --git a/src/views/components/MessageMarkdown/index.tsx b/src/views/components/MessageMarkdown/index.tsx
index e76d460..bdfae4e 100644
--- a/src/views/components/MessageMarkdown/index.tsx
+++ b/src/views/components/MessageMarkdown/index.tsx
@@ -17,6 +17,7 @@ import { useSetState } from "@mantine/hooks";
import { useTranslation } from "react-i18next";
import { useRouter } from "@/views/router";
import remarkGfm from "remark-gfm";
+import APIUtil from "@/util/APIUtil";
(typeof global !== "undefined" ? global : window).Prism = Prism;
require("prismjs/components/prism-java");
@@ -70,7 +71,7 @@ function parseMetaData(string) {
const MessageMarkdown = observer((props: MessageMarkdownProps) => {
const { children, activeStep = false, messageDone } = props;
- const { chat } = useMst();
+ const { config, chat } = useMst();
const router = useRouter();
const [steps, setSteps] = useState([]);
const tree = fromMarkdown(children);
@@ -107,6 +108,13 @@ const MessageMarkdown = observer((props: MessageMarkdownProps) => {
});
};
+ const handleCodeCopy = (event) => {
+ const selection = window.getSelection()?.toString();
+ console.log("Copied: ", selection);
+ const e = 'manual_copy';
+ APIUtil.createEvent({name: e, value: selection})
+ }
+
useEffect(() => {
let previousNode: any = null;
let chatmarkCount = 0;
@@ -328,6 +336,7 @@ const MessageMarkdown = observer((props: MessageMarkdownProps) => {
whiteSpace: "pre",
...props.style,
}}
+ onCopy={handleCodeCopy}
{...props}
>
{tokens.map((line, i) => (
diff --git a/src/views/stores/ChatStore.ts b/src/views/stores/ChatStore.ts
index 325644a..43c8f36 100644
--- a/src/views/stores/ChatStore.ts
+++ b/src/views/stores/ChatStore.ts
@@ -5,6 +5,7 @@ import yaml from "js-yaml";
import { RootInstance } from "./RootStore";
import { useTranslation } from "react-i18next";
import i18next from "i18next";
+import APIUtil from "@/util/APIUtil";
interface Context {
content: string;
@@ -171,7 +172,12 @@ export const ChatStore = types
self.hasDone = false;
self.errorMessage = "";
self.currentMessage = "";
- const chatModel = getParent(self).config.getDefaultModel();
+ const config = getParent(self).config
+ const chatModel = config.getDefaultModel();
+ messageUtil.registerHandler("codeDiffApply", (_: any) => {
+ const e = 'code_diff_apply'
+ APIUtil.createEvent({name: e, value: e})
+ })
messageUtil.sendMessage({
command: "sendMessage",
text: text,
@@ -179,6 +185,7 @@ export const ChatStore = types
parent_hash: lastNonEmptyHash(),
model: chatModel,
});
+ APIUtil.createMessage({content: text, model: chatModel});
};
const helpMessage = (originalMessage = false) => {
@@ -390,7 +397,7 @@ Thinking...
self.messages.push(...messages);
},
updateLastMessage: (message: string) => {
- console.log("message: ", message);
+ // console.log("message: ", message);
if (self.messages.length > 0) {
self.messages[self.messages.length - 1].message = message;
// if (message === "") {
diff --git a/src/views/stores/ConfigStore.ts b/src/views/stores/ConfigStore.ts
index 9d0642d..f9c6c10 100644
--- a/src/views/stores/ConfigStore.ts
+++ b/src/views/stores/ConfigStore.ts
@@ -2,7 +2,6 @@ import MessageUtil from "@/util/MessageUtil";
import { types, Instance, flow } from "mobx-state-tree";
import modelsTemplate from "@/models";
import cloneDeep from "lodash.clonedeep";
-import { set } from "mobx";
import axios from "axios";
const defaultAPIBase = [