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

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();