commit
a01e04ad81
111
src/util/APIUtil.ts
Normal file
111
src/util/APIUtil.ts
Normal file
@ -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();
|
@ -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",
|
||||
|
@ -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);
|
||||
});
|
||||
|
@ -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{" "}
|
||||
</Text>
|
||||
) : (
|
||||
<a href={envMap[env].link} target="_blank">
|
||||
<a href={links[env]} target="_blank">
|
||||
web.devchat.ai{" "}
|
||||
</a>
|
||||
)}
|
||||
|
@ -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 }) => (
|
||||
<Tooltip sx={{ padding: '3px', fontSize: 'var(--vscode-editor-font-size)' }} label={label} withArrow position="left" color="gray">
|
||||
@ -31,10 +32,14 @@ const CommitButton = ({ code }) => {
|
||||
};
|
||||
|
||||
const CodeCopyButton = ({ code }) => {
|
||||
const {config} = useMst();
|
||||
return (
|
||||
<CopyButton value={code} timeout={2000}>
|
||||
{({ copied, copy }) => (
|
||||
<IconButton label={copied ? 'Copied' : 'Copy'} color={copied ? 'teal' : 'gray'} onClick={copy}>
|
||||
<IconButton label={copied ? 'Copied' : 'Copy'} color={copied ? 'teal' : 'gray'} onClick={() => {
|
||||
copy();
|
||||
APIUtil.createEvent({name: 'copy', value: 'copy'})
|
||||
}}>
|
||||
{copied ? <IconCheck size="1rem" /> : <IconCopy size="1rem" />}
|
||||
</IconButton>
|
||||
)}
|
||||
@ -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 (
|
||||
<IconButton label='View Diff' onClick={handleClick}>
|
||||
@ -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 (
|
||||
<IconButton label='Insert Code' onClick={handleClick}>
|
||||
@ -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 (
|
||||
<IconButton label='Replace File' onClick={handleClick}>
|
||||
@ -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 (
|
||||
<IconButton label='Create New File' onClick={handleClick}>
|
||||
|
@ -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<Step[]>([]);
|
||||
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) => (
|
||||
|
@ -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<RootInstance>(self).config.getDefaultModel();
|
||||
const config = getParent<RootInstance>(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 === "") {
|
||||
|
@ -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 = [
|
||||
|
Loading…
x
Reference in New Issue
Block a user