Merge pull request #5 from devchat-ai/feat/commit

Feat/commit
This commit is contained in:
小石头 2023-12-27 15:01:18 +08:00 committed by GitHub
commit 00b2b4a44e
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
3 changed files with 333 additions and 275 deletions

View File

@ -240,6 +240,19 @@ const JStoIdea = {
}, },
}; };
window.JSJavaBridge.callJava(JSON.stringify(params));
},
userInput: (message) => {
const params = {
action: "input/request",
metadata: {
callback: "IdeaToJSMessage",
},
payload: {
data: message?.text || "",
},
};
window.JSJavaBridge.callJava(JSON.stringify(params)); window.JSJavaBridge.callJava(JSON.stringify(params));
}, },
}; };
@ -437,6 +450,7 @@ class IdeaBridge {
} }
sendMessage(message: any) { sendMessage(message: any) {
console.log("sendMessage message: ", message);
// 根据 command 分发到不同的方法· // 根据 command 分发到不同的方法·
switch (message.command) { switch (message.command) {
// 发送消息 // 发送消息
@ -498,6 +512,9 @@ class IdeaBridge {
case "openLink": case "openLink":
JStoIdea.openLink(message); JStoIdea.openLink(message);
break; break;
case "userInput":
JStoIdea.userInput(message);
break;
default: default:
break; break;
} }

View File

@ -1,70 +1,79 @@
import React, { useEffect, useState } from 'react'; import React, { useEffect, useState } from "react";
import { Box, Button, Checkbox, Text, Radio, Textarea, createStyles } from '@mantine/core'; import {
import { useListState, useSetState } from '@mantine/hooks'; Box,
import { useMst } from '@/views/stores/RootStore'; Button,
import yaml from 'js-yaml'; Checkbox,
Text,
Radio,
Textarea,
createStyles,
} from "@mantine/core";
import { useListState, useSetState } from "@mantine/hooks";
import { useMst } from "@/views/stores/RootStore";
import yaml from "js-yaml";
const useStyles = createStyles((theme) => ({ const useStyles = createStyles((theme) => ({
container:{ container: {
padding:0, padding: 0,
margin:0, margin: 0,
}, },
submit:{ submit: {
marginTop:theme.spacing.xs, marginTop: theme.spacing.xs,
marginRight:theme.spacing.xs, marginRight: theme.spacing.xs,
marginBottom:theme.spacing.xs, marginBottom: theme.spacing.xs,
}, },
cancel:{ cancel: {},
button: {
marginTop: theme.spacing.xs,
marginRight: theme.spacing.xs,
marginBottom: theme.spacing.xs,
}, },
button:{ checkbox: {
marginTop:theme.spacing.xs, marginTop: theme.spacing.xs,
marginRight:theme.spacing.xs, marginBottom: theme.spacing.xs,
marginBottom:theme.spacing.xs,
}, },
checkbox:{ label: {
marginTop:theme.spacing.xs, color: "var(--vscode-editor-foreground)",
marginBottom:theme.spacing.xs,
}, },
label:{ radio: {
color:'var(--vscode-editor-foreground)', marginTop: theme.spacing.xs,
marginBottom: theme.spacing.xs,
}, },
radio:{ editor: {
marginTop:theme.spacing.xs, backgroundColor: "var(--vscode-input-background)",
marginBottom:theme.spacing.xs, borderColor: "var(--vscode-input-border)",
color: "var(--vscode-input-foreground)",
}, },
editor:{ editorWrapper: {
backgroundColor: 'var(--vscode-input-background)', marginTop: theme.spacing.xs,
borderColor: 'var(--vscode-input-border)', marginBottom: theme.spacing.xs,
color: 'var(--vscode-input-foreground)',
}, },
editorWrapper:{ }));
marginTop:theme.spacing.xs,
marginBottom:theme.spacing.xs,
}
}));
interface Wdiget{ interface Wdiget {
id:string, id: string;
value:string, value: string;
title?:string, title?: string;
type:'editor'|'checkbox'|'radio'|'button'|'text' type: "editor" | "checkbox" | "radio" | "button" | "text";
} }
const ChatMark = ({ children,value,messageDone }) => { const ChatMark = ({ children, value, messageDone }) => {
const {classes} = useStyles(); const { classes } = useStyles();
const [widgets,widgetsHandlers] = useListState<Wdiget>(); const [widgets, widgetsHandlers] = useListState<Wdiget>();
const {chat} = useMst(); const { chat } = useMst();
const [autoForm,setAutoForm] = useState(false); // if any widget is checkbox,radio or editor wdiget, the form is auto around them const [autoForm, setAutoForm] = useState(false); // if any widget is checkbox,radio or editor wdiget, the form is auto around them
const values = value?yaml.load(value):{}; const values = value ? yaml.load(value) : {};
const [disabled,setDisabled] = useState(messageDone||!!value); const [disabled, setDisabled] = useState(messageDone || !!value);
const handleSubmit = () => { const handleSubmit = () => {
let formData = {}; let formData = {};
widgets.forEach((widget)=>{ widgets.forEach((widget) => {
if(widget.type === 'text' if (
|| widget.type === 'button' widget.type === "text" ||
|| (widget.type === 'radio' && widget.value === 'unchecked') widget.type === "button" ||
|| (widget.type === 'checkbox' && widget.value === 'unchecked')){ (widget.type === "radio" && widget.value === "unchecked") ||
(widget.type === "checkbox" && widget.value === "unchecked")
) {
// ignore // ignore
return; return;
} }
@ -75,47 +84,46 @@ const ChatMark = ({ children,value,messageDone }) => {
const handleCancel = () => { const handleCancel = () => {
chat.userInput({ chat.userInput({
'form':'canceled' form: "canceled",
}); });
}; };
const handleButtonClick = ({event,index}) => { const handleButtonClick = ({ event, index }) => {
const widget = widgets[index]; const widget = widgets[index];
widget['value'] = event.currentTarget.value;; widget["value"] = event.currentTarget.value;
widgetsHandlers.setItem(index,widget); widgetsHandlers.setItem(index, widget);
chat.userInput({ chat.userInput({
[widget['id']]:'clicked' [widget["id"]]: "clicked",
}); });
}; };
const handleCheckboxChange = ({event,index})=>{ const handleCheckboxChange = ({ event, index }) => {
const widget = widgets[index]; const widget = widgets[index];
widget['value'] = event.currentTarget.checked?'checked':'unchecked'; widget["value"] = event.currentTarget.checked ? "checked" : "unchecked";
widgetsHandlers.setItem(index,widget); widgetsHandlers.setItem(index, widget);
}; };
const handleRadioChange = ({event,allValues})=>{ const handleRadioChange = ({ event, allValues }) => {
widgetsHandlers.apply((item, index) => { widgetsHandlers.apply((item, index) => {
if(allValues.includes(item.id)){ if (allValues.includes(item.id)) {
if(item.id === event){ if (item.id === event) {
item.value = 'checked'; item.value = "checked";
}else{ } else {
item.value = 'unchecked'; item.value = "unchecked";
} }
} }
return item; return item;
}); });
}; };
const handleEditorChange = ({event,index})=>{ const handleEditorChange = ({ event, index }) => {
const widget = widgets[index]; const widget = widgets[index];
widget['value'] = event.currentTarget.value; widget["value"] = event.currentTarget.value;
widgetsHandlers.setItem(index,widget); widgetsHandlers.setItem(index, widget);
}; };
useEffect(()=>{ useEffect(() => {
const lines = children.split("\n");
const lines = children.split('\n'); let detectEditorId = "";
let detectEditorId = ''; let editorContentRecorder = "";
let editorContentRecorder = '';
const textRegex = /^([^>].*)/; // Text widget const textRegex = /^([^>].*)/; // Text widget
const buttonRegex = /^>\s*\((.*?)\)\s*(.*)/; // Button widget const buttonRegex = /^>\s*\((.*?)\)\s*(.*)/; // Button widget
@ -125,146 +133,174 @@ const ChatMark = ({ children,value,messageDone }) => {
const editorContentRegex = /^>\s*(.*)/; // Editor widget const editorContentRegex = /^>\s*(.*)/; // Editor widget
lines.forEach((line, index) => { lines.forEach((line, index) => {
let match; let match;
if (match = line.match(textRegex)) { if ((match = line.match(textRegex))) {
widgetsHandlers.append({ widgetsHandlers.append({
id:`text${index}`, id: `text${index}`,
type:'text', type: "text",
value:line, value: line,
}); });
} else if (match = line.match(buttonRegex)) { } else if ((match = line.match(buttonRegex))) {
const [id, title] = match.slice(1); const [id, title] = match.slice(1);
widgetsHandlers.append({ widgetsHandlers.append({
id, id,
title, title,
type:'button', type: "button",
value: id value: id,
}); });
} else if (match = line.match(checkboxRegex)) { } else if ((match = line.match(checkboxRegex))) {
const [status, id, title] = match.slice(1); const [status, id, title] = match.slice(1);
widgetsHandlers.append({ widgetsHandlers.append({
id, id,
title, title,
type:'checkbox', type: "checkbox",
value: value?'unchecked':status === 'x'?'checked':'unchecked', value: value ? "unchecked" : status === "x" ? "checked" : "unchecked",
}); });
setAutoForm(true); setAutoForm(true);
} else if (match = line.match(radioRegex)) { } else if ((match = line.match(radioRegex))) {
const [id, title] = match.slice(1); const [id, title] = match.slice(1);
widgetsHandlers.append({ widgetsHandlers.append({
id, id,
title, title,
type:'radio', type: "radio",
value:'unchecked', value: "unchecked",
}); });
setAutoForm(true); setAutoForm(true);
} else if (match = line.match(editorRegex)) { } else if ((match = line.match(editorRegex))) {
const [id] = match.slice(1); const [id] = match.slice(1);
detectEditorId = id; detectEditorId = id;
widgetsHandlers.append({ widgetsHandlers.append({
id, id,
type:'editor', type: "editor",
value: '', value: "",
}); });
setAutoForm(true); setAutoForm(true);
} else if(match = line.match(editorContentRegex)){ } else if ((match = line.match(editorContentRegex))) {
const [content] = match.slice(1); const [content] = match.slice(1);
editorContentRecorder += content + '\n'; editorContentRecorder += content + "\n";
} }
// if next line is not editor, then end current editor // if next line is not editor, then end current editor
const nextLine = index + 1 < lines.length? lines[index + 1]:null; const nextLine = index + 1 < lines.length ? lines[index + 1] : null;
if (detectEditorId && (!nextLine || !nextLine.startsWith('>'))) { if (detectEditorId && (!nextLine || !nextLine.startsWith(">"))) {
// remove last \n // remove last \n
editorContentRecorder = editorContentRecorder.substring(0, editorContentRecorder.length - 1); editorContentRecorder = editorContentRecorder.substring(
0,
editorContentRecorder.length - 1
);
// apply editor content to widget // apply editor content to widget
((editorId,editorContent) => widgetsHandlers.apply((item)=>{ ((editorId, editorContent) =>
if(item.id === editorId && !(item.id in values)){ widgetsHandlers.apply((item) => {
if (item.id === editorId && !(item.id in values)) {
item.value = editorContent; item.value = editorContent;
} }
return item; return item;
}))(detectEditorId,editorContentRecorder); }))(detectEditorId, editorContentRecorder);
// reset editor // reset editor
detectEditorId = ''; detectEditorId = "";
editorContentRecorder = ''; editorContentRecorder = "";
} }
}); });
for (const key in values) { for (const key in values) {
widgetsHandlers.apply((item)=>{ widgetsHandlers.apply((item) => {
if(item.id === key){ if (item.id === key) {
item.value = values[key]; item.value = values[key];
} }
return item; return item;
}); });
} }
},[]); }, []);
// Render markdown widgets // Render markdown widgets
const renderWidgets = (widgets) => { const renderWidgets = (widgets) => {
let radioGroupTemp:any = []; let radioGroupTemp: any = [];
let radioValuesTemp:any = []; let radioValuesTemp: any = [];
let wdigetsTemp:any = []; let wdigetsTemp: any = [];
widgets.map((widget, index) => { widgets.map((widget, index) => {
if (widget.type === 'text') { if (widget.type === "text") {
wdigetsTemp.push(<Text key={index}>{widget.value}</Text>); wdigetsTemp.push(<Text key={index}>{widget.value}</Text>);
} else if (widget.type === 'button') { } else if (widget.type === "button") {
wdigetsTemp.push(<Button wdigetsTemp.push(
<Button
className={classes.button} className={classes.button}
disabled={disabled} disabled={disabled}
key={'widget'+index} key={"widget" + index}
size='xs' size="xs"
value={widget.value} value={widget.value}
onClick={event => handleButtonClick({event,index})}> onClick={(event) => handleButtonClick({ event, index })}
{values[widget.id] === 'clicked' ? '[x] ' + widget.title:widget.title} >
</Button>); {values[widget.id] === "clicked"
} else if (widget.type === 'checkbox') { ? "[x] " + widget.title
wdigetsTemp.push(<Checkbox : widget.title}
classNames={{root:classes.checkbox,label:classes.label}} </Button>
);
} else if (widget.type === "checkbox") {
wdigetsTemp.push(
<Checkbox
classNames={{ root: classes.checkbox, label: classes.label }}
disabled={disabled} disabled={disabled}
key={'widget'+index} key={"widget" + index}
label={widget.title} label={widget.title}
checked={widget.value==='checked'} checked={widget.value === "checked"}
size='xs' size="xs"
onChange={event => handleCheckboxChange({event,index})}/>); onChange={(event) => handleCheckboxChange({ event, index })}
} else if (widget.type === 'radio') { />
);
} else if (widget.type === "radio") {
radioValuesTemp.push(widget.id); radioValuesTemp.push(widget.id);
radioGroupTemp.push(<Radio radioGroupTemp.push(
classNames={{root:classes.radio,label:classes.label}} <Radio
classNames={{ root: classes.radio, label: classes.label }}
disabled={disabled} disabled={disabled}
key={'widget'+index} key={"widget" + index}
label={widget.title} label={widget.title}
value={widget.id} value={widget.id}
size='xs' />); size="xs"
/>
);
// if next widget is not radio, then end current group // if next widget is not radio, then end current group
const nextWidget = index + 1 < widgets.length? widgets[index + 1]:null; const nextWidget =
if (!nextWidget || nextWidget.type !== 'radio') { index + 1 < widgets.length ? widgets[index + 1] : null;
const radioGroup = ((radios,allValues)=>{ if (!nextWidget || nextWidget.type !== "radio") {
const filteredValues = allValues.filter((value) => values[value] === 'checked'); const radioGroup = ((radios, allValues) => {
return <Radio.Group const filteredValues = allValues.filter(
(value) => values[value] === "checked"
);
return (
<Radio.Group
key={`radio-group-${index}`} key={`radio-group-${index}`}
value={filteredValues.length > 0 ? filteredValues[0] : undefined} value={
onChange={ filteredValues.length > 0 ? filteredValues[0] : undefined
event => handleRadioChange({ }
onChange={(event) =>
handleRadioChange({
event, event,
allValues allValues,
}) })
}> }
>
{radios} {radios}
</Radio.Group>; </Radio.Group>
})(radioGroupTemp,radioValuesTemp); );
})(radioGroupTemp, radioValuesTemp);
radioGroupTemp = []; radioGroupTemp = [];
radioValuesTemp = []; radioValuesTemp = [];
wdigetsTemp.push(radioGroup); wdigetsTemp.push(radioGroup);
} }
} else if (widget.type === 'editor') { } else if (widget.type === "editor") {
wdigetsTemp.push(<Textarea wdigetsTemp.push(
<Textarea
disabled={disabled} disabled={disabled}
autosize autosize
classNames={{wrapper:classes.editorWrapper,input:classes.editor}} classNames={{
key={'widget'+index} wrapper: classes.editorWrapper,
input: classes.editor,
}}
key={"widget" + index}
defaultValue={widget.value} defaultValue={widget.value}
maxRows={10} maxRows={10}
onChange={event => handleEditorChange({event,index})}/>); onChange={(event) => handleEditorChange({ event, index })}
/>
);
} }
}); });
return wdigetsTemp; return wdigetsTemp;
@ -272,16 +308,21 @@ const ChatMark = ({ children,value,messageDone }) => {
return ( return (
<Box className={classes.container}> <Box className={classes.container}>
{autoForm && !disabled {autoForm && !disabled ? (
?<form> <form>
{renderWidgets(widgets)} {renderWidgets(widgets)}
<Box> <Box>
<Button className={classes.submit} size='xs' onClick={handleSubmit}>Submit</Button> <Button className={classes.submit} size="xs" onClick={handleSubmit}>
<Button className={classes.cancel} size='xs' onClick={handleCancel}>Cancel</Button> Submit
</Button>
<Button className={classes.cancel} size="xs" onClick={handleCancel}>
Cancel
</Button>
</Box> </Box>
</form> </form>
:renderWidgets(widgets) ) : (
} renderWidgets(widgets)
)}
</Box> </Box>
); );
}; };

View File

@ -253,12 +253,12 @@ ${yaml.dump(values)}
${self.currentMessage} ${self.currentMessage}
${inputStr} ${inputStr}
\`\`\`Step \`\`\`Step
Thinking...123 Thinking...
\`\`\` \`\`\`
`; `;
messageUtil.sendMessage({ messageUtil.sendMessage({
command: "userInput", command: "userInput",
text: inputStr text: inputStr,
}); });
// goto bottom // goto bottom
goScrollBottom(); goScrollBottom();