mirror of
https://github.com/koodo-reader/koodo-reader.git
synced 2026-06-18 21:00:35 -04:00
fix: update font files and enhance popup assist functionality with new features
This commit is contained in:
@@ -40,7 +40,7 @@
|
||||
"jszip": "^3.10.1",
|
||||
"localforage": "^1.10.0",
|
||||
"mammoth": "^1.8.0",
|
||||
"marked": "^15.0.3",
|
||||
"marked": "^15.0.11",
|
||||
"megajs": "^1.3.7",
|
||||
"mhtml2html": "^3.0.0",
|
||||
"node-machine-id": "^1.1.12",
|
||||
|
||||
2
src/assets/lib/kookit-extra-browser.min.js
vendored
2
src/assets/lib/kookit-extra-browser.min.js
vendored
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
@@ -63,6 +63,8 @@
|
||||
"Default": "Default",
|
||||
"Small": "Small",
|
||||
"Medium": "Medium",
|
||||
"Reading Assistant": "Reading",
|
||||
"Chat Assistant": "Chat",
|
||||
"Large": "Large",
|
||||
"Cancellation successful": "Cancellation successful",
|
||||
"Only supported by desktop version": "Only supported with the desktop version",
|
||||
|
||||
@@ -284,6 +284,18 @@
|
||||
"Your trial period has expired": "您的试用已结束",
|
||||
"Exit Pro": "退出专业版",
|
||||
"I've paid": "我已支付",
|
||||
"Summarize this chapter for me": "帮我总结这一章的内容",
|
||||
"What are the key points of this chapter": "整理并输出格式清晰的内容要点",
|
||||
"Remove data source": "移除数据源",
|
||||
"Ask anything about this chapter": "询问关于这篇文章的任何内容",
|
||||
"Ask anything about reading or learning": "询问关于阅读和学习的任何问题",
|
||||
"Hi there! I'm happy to help with any questions about reading or learning": "你好,我可以解答你关于阅读和学习的任何问题。",
|
||||
"Hi there! What questions do you have about this chapter?": "你好,关于这章内容,您有什么想知道的吗?",
|
||||
"Thinking, please wait...": "思考中,请稍候...",
|
||||
"Chat Assistant": "AI 助手",
|
||||
"Reading Assistant": "AI 问书",
|
||||
"Recommend me some books from Colleen Hoover": "推荐几本刘慈欣的小说",
|
||||
"Explain Stoicism and its principles to me": "斯多葛学派的主要思想和观点是什么",
|
||||
"Need help": "需要帮助",
|
||||
"Upgrade": "升级",
|
||||
"You haven't upgraded to Pro yet": "您还不是会员哦",
|
||||
|
||||
Binary file not shown.
@@ -98,4 +98,6 @@
|
||||
<glyph unicode="" glyph-name="s3compatible" d="M620.757 789.333c-155.434 0-285.76-107.2-321.194-251.669-2.85 0.161-6.191 0.254-9.552 0.256h-0.006c-113.024 0-204.672-91.627-204.672-204.672 0-0.082 0-0.18 0-0.278 0-56.404 22.914-107.457 59.942-144.357l0.006-0.006c36.887-36.982 87.897-59.862 144.25-59.862 0.167 0 0.334 0 0.501 0.001h-0.026c56.581 0.026 107.783 23.034 144.783 60.194l0.007 0.008c32.106 32.107 50.133 75.094 55.317 122.838l-74.112 5.823c-16.79 2.090-22.187 14.805-11.968 28.373l125.568 155.351c10.155 13.546 23.957 11.882 30.613-3.755l71.19-173.974c6.656-15.637-1.75-26.688-18.56-24.618l-55.126 5.077c-6.25-57.643-32.192-108.523-68.458-148.864 38.654-17.19 83.752-27.2 131.188-27.2 0.109 0 0.218 0 0.327 0h-0.017c182.614 0 330.667 148.053 330.667 330.667s-148.053 330.666-330.667 330.666z" />
|
||||
<glyph unicode="" glyph-name="desktop" horiz-adv-x="887" d="M723.49 863.029h-559.515c-44.161 0-79.975-41.325-79.975-92.279v-461.126c0-50.866 35.814-92.188 79.975-92.188h159.873v-92.194h-159.873v-92.276h559.515v92.276h-159.872v92.194h159.872c44.080 0 79.899 41.322 79.899 92.187v461.127c0 50.954-35.82 92.279-79.899 92.279z" />
|
||||
<glyph unicode="" glyph-name="phone" horiz-adv-x="751" d="M547.718 899c39.814 0 72.090-44.012 72.090-98.304v-704.515c0-54.286-32.276-98.304-72.090-98.304h-344.429c-39.814 0-72.089 44.018-72.089 98.304v704.515c0 54.292 32.275 98.304 72.089 98.304h344.429zM375.503 281.87c-24.331 0-44.055-26.894-44.055-60.079 0-33.178 19.724-60.075 44.055-60.075s44.055 26.897 44.055 60.075c0 33.184-19.724 60.079-44.055 60.079z" />
|
||||
<glyph unicode="" glyph-name="send" d="M1015.485 483.536c-7.599 15.198-20.198 27.797-35.396 35.396l-865.299 432.649c-18.998 9.499-40.495 10.999-60.593 4.299s-36.296-20.898-45.795-39.795c-9.899-19.898-11.099-43.095-3.1-63.893l155.382-404.253-155.382-404.153c-7.599-19.798-6.999-41.395 1.6-60.693 8.599-19.398 24.297-34.196 43.995-41.795 9.199-3.5 18.798-5.299 28.497-5.299 12.199 0 24.397 2.8 35.496 8.299l865.199 432.749c39.095 19.598 54.994 67.392 35.396 106.488zM79.095 15.491l151.182 392.954h310.364c21.797 0 39.495 17.698 39.495 39.495s-17.698 39.495-39.495 39.495h-310.364l-150.782 393.354 865.199-432.649-865.599-432.649z" />
|
||||
<glyph unicode="" glyph-name="loading" d="M512 864c-229.717 0-416-186.283-416-416s186.283-416 416-416v104.021c-172.283 0.024-311.936 139.693-311.936 311.979 0 172.301 139.678 311.979 311.979 311.979s311.979-139.678 311.979-311.979v0h103.979c0 229.717-186.283 416-416 416z" />
|
||||
</font></defs></svg>
|
||||
|
Before Width: | Height: | Size: 92 KiB After Width: | Height: | Size: 93 KiB |
Binary file not shown.
Binary file not shown.
@@ -1,10 +1,10 @@
|
||||
@font-face {
|
||||
font-family: 'icomoon';
|
||||
src: url('fonts/icomoon.eot?6v0s5e');
|
||||
src: url('fonts/icomoon.eot?6v0s5e#iefix') format('embedded-opentype'),
|
||||
url('fonts/icomoon.ttf?6v0s5e') format('truetype'),
|
||||
url('fonts/icomoon.woff?6v0s5e') format('woff'),
|
||||
url('fonts/icomoon.svg?6v0s5e#icomoon') format('svg');
|
||||
src: url('fonts/icomoon.eot?rfb0hg');
|
||||
src: url('fonts/icomoon.eot?rfb0hg#iefix') format('embedded-opentype'),
|
||||
url('fonts/icomoon.ttf?rfb0hg') format('truetype'),
|
||||
url('fonts/icomoon.woff?rfb0hg') format('woff'),
|
||||
url('fonts/icomoon.svg?rfb0hg#icomoon') format('svg');
|
||||
font-weight: normal;
|
||||
font-style: normal;
|
||||
font-display: block;
|
||||
@@ -25,6 +25,12 @@
|
||||
-moz-osx-font-smoothing: grayscale;
|
||||
}
|
||||
|
||||
.icon-loading:before {
|
||||
content: "\e95c";
|
||||
}
|
||||
.icon-send:before {
|
||||
content: "\e95b";
|
||||
}
|
||||
.icon-email:before {
|
||||
content: "\e953";
|
||||
}
|
||||
|
||||
@@ -15,75 +15,70 @@ import {
|
||||
import toast from "react-hot-toast";
|
||||
import DatabaseService from "../../../utils/storage/databaseService";
|
||||
import { checkPlugin } from "../../../utils/common";
|
||||
import { getSummaryStream } from "../../../utils/request/reader";
|
||||
import { getAnswerStream } from "../../../utils/request/reader";
|
||||
import { marked } from "marked";
|
||||
import { sampleQuestion } from "../../../constants/settingList";
|
||||
declare var window: any;
|
||||
class PopupAssist extends React.Component<PopupAssistProps, PopupAssistState> {
|
||||
private chatBoxRef: React.RefObject<HTMLDivElement>;
|
||||
|
||||
constructor(props: PopupAssistProps) {
|
||||
super(props);
|
||||
this.state = {
|
||||
sumText: this.props.t("Please wait"),
|
||||
prototype: "",
|
||||
sumService:
|
||||
ConfigService.getReaderConfig("sumService") ||
|
||||
answer: "",
|
||||
aiService:
|
||||
ConfigService.getReaderConfig("aiService") ||
|
||||
"official-ai-assistant-plugin",
|
||||
sumTarget:
|
||||
ConfigService.getReaderConfig("sumTarget") ||
|
||||
getDefaultTransTarget({
|
||||
English: "English",
|
||||
"Simplified Chinese": "Simplified Chinese",
|
||||
"Traditional Chinese": "Traditional Chinese",
|
||||
}),
|
||||
isAddNew: false,
|
||||
isWaiting: false,
|
||||
question: "",
|
||||
chatHistory: [],
|
||||
askHistory: [],
|
||||
mode: "ask",
|
||||
inputQuestion: "",
|
||||
};
|
||||
this.chatBoxRef = React.createRef();
|
||||
}
|
||||
componentDidMount() {
|
||||
this.handleSum();
|
||||
}
|
||||
async handleSum() {
|
||||
let originalText = this.props.originalText
|
||||
.replace(/(\r\n|\n|\r)/gm, "")
|
||||
.replace(/-/gm, "")
|
||||
// Remove common garbage characters
|
||||
.replace(
|
||||
/[^\x20-\x7E\u00A0-\u00FF\u0100-\u017F\u4E00-\u9FFF\u3000-\u303F]/g,
|
||||
""
|
||||
)
|
||||
// Remove consecutive spaces
|
||||
.replace(/\s{2,}/g, " ")
|
||||
.trim();
|
||||
scrollToBottom = () => {
|
||||
if (this.chatBoxRef.current) {
|
||||
const scrollHeight = this.chatBoxRef.current.scrollHeight;
|
||||
const height = this.chatBoxRef.current.clientHeight;
|
||||
const maxScrollTop = scrollHeight - height;
|
||||
this.chatBoxRef.current.scrollTop = maxScrollTop > 0 ? maxScrollTop : 0;
|
||||
}
|
||||
};
|
||||
async handleAnswer() {
|
||||
let originalText =
|
||||
this.state.mode === "ask"
|
||||
? this.props.originalText
|
||||
.replace(/(\r\n|\n|\r)/gm, "")
|
||||
.replace(/-/gm, "")
|
||||
// Remove common garbage characters
|
||||
.replace(
|
||||
/[^\x20-\x7E\u00A0-\u00FF\u0100-\u017F\u4E00-\u9FFF\u3000-\u303F]/g,
|
||||
""
|
||||
)
|
||||
// Remove consecutive spaces
|
||||
.replace(/\s{2,}/g, " ")
|
||||
.trim()
|
||||
: "";
|
||||
if (
|
||||
(!this.state.sumService ||
|
||||
(!this.state.aiService ||
|
||||
this.props.plugins.findIndex(
|
||||
(item) => item.key === this.state.sumService
|
||||
(item) => item.key === this.state.aiService
|
||||
) === -1) &&
|
||||
!this.props.isAuthed
|
||||
) {
|
||||
this.setState({ isAddNew: true });
|
||||
}
|
||||
this.handleSummary(originalText);
|
||||
this.handleDoAnswer(originalText);
|
||||
}
|
||||
handleSummary = async (text: string) => {
|
||||
let sumText = "";
|
||||
handleDoAnswer = async (text: string) => {
|
||||
try {
|
||||
if (
|
||||
this.state.sumService &&
|
||||
this.state.sumService !== "official-ai-assistant-plugin"
|
||||
this.state.aiService &&
|
||||
this.state.aiService !== "official-ai-assistant-plugin"
|
||||
) {
|
||||
let plugin = this.props.plugins.find(
|
||||
(item) => item.key === this.state.sumService
|
||||
);
|
||||
if (!plugin) return;
|
||||
let dictFunc = plugin.script;
|
||||
// eslint-disable-next-line no-eval
|
||||
eval(dictFunc);
|
||||
sumText = await window.getsumText(
|
||||
text,
|
||||
"auto",
|
||||
this.state.sumTarget,
|
||||
axios,
|
||||
this.props.t,
|
||||
plugin.config
|
||||
);
|
||||
} else if (this.props.isAuthed) {
|
||||
let plugin = this.props.plugins.find(
|
||||
(item) => item.key === "official-ai-assistant-plugin"
|
||||
@@ -92,283 +87,437 @@ class PopupAssist extends React.Component<PopupAssistProps, PopupAssistState> {
|
||||
return;
|
||||
}
|
||||
let isFirst = true;
|
||||
getSummaryStream(
|
||||
let res = await getAnswerStream(
|
||||
text,
|
||||
ConfigService.getReaderConfig("sumTarget") ||
|
||||
getDefaultTransTarget(plugin.langList),
|
||||
this.state.question,
|
||||
this.state.mode === "ask"
|
||||
? this.state.askHistory
|
||||
: this.state.chatHistory,
|
||||
this.state.mode,
|
||||
(result) => {
|
||||
console.log(result, "result");
|
||||
if (result && result.text) {
|
||||
if (isFirst) {
|
||||
this.setState({
|
||||
sumText: result.text,
|
||||
answer: result.text,
|
||||
isWaiting: false,
|
||||
});
|
||||
isFirst = false;
|
||||
} else {
|
||||
this.setState({
|
||||
sumText: this.state.sumText + result.text,
|
||||
answer: this.state.answer + result.text,
|
||||
});
|
||||
}
|
||||
}
|
||||
this.scrollToBottom();
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
if (sumText.startsWith("https://")) {
|
||||
openExternalUrl(sumText, true);
|
||||
} else {
|
||||
this.setState(
|
||||
{
|
||||
sumText: sumText,
|
||||
},
|
||||
() => {
|
||||
let moreElement = document.querySelector(".dict-learn-more");
|
||||
if (moreElement) {
|
||||
moreElement.addEventListener("click", () => {
|
||||
openExternalUrl(
|
||||
window.learnMoreUrl || "https://www.koodoreader.com"
|
||||
);
|
||||
});
|
||||
}
|
||||
console.log(res, "res4534543");
|
||||
if (res.data && res.data.done) {
|
||||
if (this.state.mode === "ask") {
|
||||
this.setState({
|
||||
askHistory: [
|
||||
...this.state.askHistory,
|
||||
{
|
||||
role: "assistant",
|
||||
content: this.state.answer,
|
||||
},
|
||||
],
|
||||
answer: "",
|
||||
question: "",
|
||||
isWaiting: false,
|
||||
});
|
||||
} else {
|
||||
this.setState({
|
||||
chatHistory: [
|
||||
...this.state.chatHistory,
|
||||
{
|
||||
role: "assistant",
|
||||
content: this.state.answer,
|
||||
},
|
||||
],
|
||||
answer: "",
|
||||
question: "",
|
||||
isWaiting: false,
|
||||
});
|
||||
}
|
||||
);
|
||||
}
|
||||
if (res.code === 20006) {
|
||||
this.setState({
|
||||
isWaiting: false,
|
||||
answer: "",
|
||||
question: "",
|
||||
});
|
||||
}
|
||||
this.scrollToBottom();
|
||||
}
|
||||
} catch (error) {
|
||||
console.error(error);
|
||||
this.setState({
|
||||
sumText: this.props.t("Error happened"),
|
||||
answer: this.props.t("Error happened"),
|
||||
});
|
||||
}
|
||||
};
|
||||
handleChangesumService = (sumService: string) => {
|
||||
let plugin = this.props.plugins.find((item) => item.key === sumService);
|
||||
handleChangeAiService = (aiService: string) => {
|
||||
let plugin = this.props.plugins.find((item) => item.key === aiService);
|
||||
if (!plugin) {
|
||||
return;
|
||||
}
|
||||
this.setState(
|
||||
{
|
||||
sumService: sumService,
|
||||
aiService: aiService,
|
||||
isAddNew: false,
|
||||
},
|
||||
() => {
|
||||
ConfigService.setReaderConfig("sumService", sumService);
|
||||
ConfigService.setReaderConfig("aiService", aiService);
|
||||
if (!plugin) return;
|
||||
this.setState(
|
||||
{
|
||||
sumTarget: getDefaultTransTarget(plugin.langList),
|
||||
},
|
||||
() => {
|
||||
if (!plugin) return;
|
||||
ConfigService.setReaderConfig(
|
||||
"sumTarget",
|
||||
getDefaultTransTarget(plugin.langList)
|
||||
);
|
||||
this.handleSum();
|
||||
}
|
||||
);
|
||||
this.handleAnswer();
|
||||
}
|
||||
);
|
||||
};
|
||||
|
||||
render() {
|
||||
const renderSumBox = () => {
|
||||
handleRenderHistoryMessage = (message: any[]) => {
|
||||
return message.map((item, index) => {
|
||||
return (
|
||||
<div className="dict-container">
|
||||
<div className="dict-service-container">
|
||||
<select
|
||||
className="dict-service-selector"
|
||||
style={{ margin: 0 }}
|
||||
onChange={(event: React.ChangeEvent<HTMLSelectElement>) => {
|
||||
if (event.target.value === "add-new") {
|
||||
this.setState({ isAddNew: true });
|
||||
return;
|
||||
}
|
||||
this.handleChangesumService(event.target.value);
|
||||
}}
|
||||
>
|
||||
{this.props.plugins
|
||||
.filter((item) => item.type === "assistant")
|
||||
.map((item) => {
|
||||
return (
|
||||
<option
|
||||
value={item.key}
|
||||
key={item.key}
|
||||
className="add-dialog-shelf-list-option"
|
||||
selected={
|
||||
this.state.sumService === item.key ? true : false
|
||||
}
|
||||
>
|
||||
{this.props.t(item.displayName)}
|
||||
</option>
|
||||
);
|
||||
})}
|
||||
<option
|
||||
value={"add-new"}
|
||||
key={"add-new"}
|
||||
className="add-dialog-shelf-list-option"
|
||||
>
|
||||
{this.props.t("Add new plugin")}
|
||||
</option>
|
||||
</select>
|
||||
</div>
|
||||
|
||||
<div className="dict-service-container" style={{ right: 150 }}>
|
||||
<select
|
||||
className="dict-service-selector"
|
||||
style={{ margin: 0 }}
|
||||
onChange={(event: React.ChangeEvent<HTMLSelectElement>) => {
|
||||
let plugin = this.props.plugins.find(
|
||||
(item) => item.key === this.state.sumService
|
||||
);
|
||||
if (!plugin) {
|
||||
return;
|
||||
}
|
||||
this.setState(
|
||||
{
|
||||
sumTarget:
|
||||
event.target.value ||
|
||||
getDefaultTransTarget(plugin.langList),
|
||||
},
|
||||
() => {
|
||||
ConfigService.setReaderConfig(
|
||||
"sumTarget",
|
||||
event.target.value
|
||||
);
|
||||
this.handleSum();
|
||||
}
|
||||
);
|
||||
}}
|
||||
>
|
||||
{this.props.plugins.find(
|
||||
(item) => item.key === this.state.sumService
|
||||
)?.langList &&
|
||||
Object.keys(
|
||||
this.props.plugins.find(
|
||||
(item) => item.key === this.state.sumService
|
||||
)?.langList as any
|
||||
).map((item, index) => {
|
||||
return (
|
||||
<option
|
||||
value={item}
|
||||
key={index}
|
||||
className="add-dialog-shelf-list-option"
|
||||
selected={this.state.sumTarget === item ? true : false}
|
||||
>
|
||||
{this.props.t(
|
||||
Object.values(
|
||||
this.props.plugins.find(
|
||||
(item) => item.key === this.state.sumService
|
||||
)?.langList as any[]
|
||||
)[index]
|
||||
)}
|
||||
</option>
|
||||
);
|
||||
})}
|
||||
</select>
|
||||
</div>
|
||||
{this.state.isAddNew && (
|
||||
<div
|
||||
className="trans-add-new-container"
|
||||
style={{ fontWeight: 500, marginTop: "60px", height: "170px" }}
|
||||
>
|
||||
<textarea
|
||||
name="url"
|
||||
placeholder={this.props.t(
|
||||
"Paste the code of the plugin here, check out document to learn how to get more plugins"
|
||||
)}
|
||||
id="trans-add-content-box"
|
||||
className="trans-add-content-box"
|
||||
onContextMenu={() => {
|
||||
handleContextMenu("trans-add-content-box");
|
||||
}}
|
||||
/>
|
||||
<div className="trans-add-button-container">
|
||||
<div
|
||||
className="trans-add-cancel"
|
||||
style={{ color: "#2084e8" }}
|
||||
onClick={() => {
|
||||
if (
|
||||
ConfigService.getReaderConfig("lang") &&
|
||||
ConfigService.getReaderConfig("lang").startsWith("zh")
|
||||
) {
|
||||
openExternalUrl(WEBSITE_URL + "/zh/plugin");
|
||||
} else {
|
||||
openExternalUrl(WEBSITE_URL + "/en/plugin");
|
||||
}
|
||||
}}
|
||||
>
|
||||
<Trans>Document</Trans>
|
||||
</div>
|
||||
<div
|
||||
className="trans-add-cancel"
|
||||
onClick={() => {
|
||||
this.setState({ isAddNew: false });
|
||||
}}
|
||||
>
|
||||
<Trans>Cancel</Trans>
|
||||
</div>
|
||||
<div
|
||||
className="trans-add-confirm"
|
||||
style={{ backgroundColor: "#2084e8" }}
|
||||
onClick={async () => {
|
||||
let value: string = (
|
||||
document.querySelector(
|
||||
"#trans-add-content-box"
|
||||
) as HTMLTextAreaElement
|
||||
).value;
|
||||
if (value) {
|
||||
let plugin: any = JSON.parse(value);
|
||||
plugin.key = plugin.identifier;
|
||||
if (!(await checkPlugin(plugin))) {
|
||||
toast.error(this.props.t("Plugin verification failed"));
|
||||
return;
|
||||
}
|
||||
if (
|
||||
this.props.plugins.find(
|
||||
(item) => item.key === plugin.key
|
||||
)
|
||||
) {
|
||||
await DatabaseService.updateRecord(plugin, "plugins");
|
||||
} else {
|
||||
await DatabaseService.saveRecord(plugin, "plugins");
|
||||
}
|
||||
this.props.handleFetchPlugins();
|
||||
toast.success(this.props.t("Addition successful"));
|
||||
}
|
||||
this.setState({
|
||||
isAddNew: false,
|
||||
sumText: this.props.t("Please select the service"),
|
||||
});
|
||||
}}
|
||||
>
|
||||
<Trans>Confirm</Trans>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
{!this.state.isAddNew && (
|
||||
<>
|
||||
<div
|
||||
className="dict-text-box"
|
||||
style={{ marginTop: "60px", height: "180px" }}
|
||||
>
|
||||
{Parser(
|
||||
DOMPurify.sanitize(
|
||||
this.state.sumText + "<address></address>"
|
||||
) || " ",
|
||||
{
|
||||
replace: (_domNode) => {},
|
||||
}
|
||||
)}
|
||||
</div>
|
||||
<p className="dict-learn-more">
|
||||
{this.props.t("Generated with AI")}
|
||||
</p>
|
||||
</>
|
||||
<div
|
||||
key={index}
|
||||
className={
|
||||
item.role === "assistant"
|
||||
? "popup-message-assistant"
|
||||
: "popup-message-user"
|
||||
}
|
||||
>
|
||||
{Parser(
|
||||
DOMPurify.sanitize(
|
||||
marked.parse(item.content) + "<address></address>"
|
||||
) || " ",
|
||||
{
|
||||
replace: (_domNode) => {},
|
||||
}
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
return renderSumBox();
|
||||
});
|
||||
};
|
||||
handleNewQuestion = (question: string) => {
|
||||
if (this.state.mode === "ask") {
|
||||
this.setState(
|
||||
{
|
||||
askHistory: [
|
||||
...this.state.askHistory,
|
||||
{
|
||||
role: "user",
|
||||
content: this.props.t(question),
|
||||
},
|
||||
],
|
||||
question: this.props.t(question),
|
||||
answer: "",
|
||||
isWaiting: true,
|
||||
},
|
||||
() => {
|
||||
this.handleAnswer();
|
||||
}
|
||||
);
|
||||
} else {
|
||||
this.setState(
|
||||
{
|
||||
chatHistory: [
|
||||
...this.state.chatHistory,
|
||||
{
|
||||
role: "user",
|
||||
content: this.props.t(question),
|
||||
},
|
||||
],
|
||||
question: this.props.t(question),
|
||||
answer: this.props.t(""),
|
||||
isWaiting: true,
|
||||
},
|
||||
() => {
|
||||
this.handleAnswer();
|
||||
}
|
||||
);
|
||||
}
|
||||
setTimeout(() => {
|
||||
this.scrollToBottom();
|
||||
}, 100);
|
||||
};
|
||||
render() {
|
||||
return (
|
||||
<div className="dict-container">
|
||||
<div
|
||||
className="dict-service-container"
|
||||
style={{
|
||||
display: "flex",
|
||||
justifyContent: "space-between",
|
||||
alignItems: "center",
|
||||
width: "calc(100% - 50px)",
|
||||
top: "20px",
|
||||
}}
|
||||
>
|
||||
<div
|
||||
style={{
|
||||
display: "flex",
|
||||
alignItems: "center",
|
||||
justifyContent: "flex-start",
|
||||
}}
|
||||
>
|
||||
<div
|
||||
className={
|
||||
this.state.mode === "ask"
|
||||
? "trans-service-selector"
|
||||
: "trans-service-selector-inactive"
|
||||
}
|
||||
onClick={() => {
|
||||
this.setState({ isAddNew: false, mode: "ask" });
|
||||
}}
|
||||
>
|
||||
<span className={`icon-bookmark trans-icon`}></span>
|
||||
{this.props.t("Reading Assistant")}
|
||||
</div>
|
||||
<div
|
||||
className={
|
||||
this.state.mode === "chat"
|
||||
? "trans-service-selector"
|
||||
: "trans-service-selector-inactive"
|
||||
}
|
||||
onClick={() => {
|
||||
this.setState({ isAddNew: false, mode: "chat" });
|
||||
}}
|
||||
>
|
||||
<span className={`icon-idea trans-icon`}></span>
|
||||
{this.props.t("Chat Assistant")}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<select
|
||||
className="dict-service-selector"
|
||||
style={{ margin: 0, color: "#f16464" }}
|
||||
onChange={(event: React.ChangeEvent<HTMLSelectElement>) => {
|
||||
if (event.target.value === "add-new") {
|
||||
this.setState({ isAddNew: true });
|
||||
return;
|
||||
}
|
||||
this.handleChangeAiService(event.target.value);
|
||||
}}
|
||||
>
|
||||
{this.props.plugins
|
||||
.filter((item) => item.type === "assistant")
|
||||
.map((item) => {
|
||||
return (
|
||||
<option
|
||||
value={item.key}
|
||||
key={item.key}
|
||||
className="add-dialog-shelf-list-option"
|
||||
selected={this.state.aiService === item.key ? true : false}
|
||||
>
|
||||
{this.props.t(item.displayName)}
|
||||
</option>
|
||||
);
|
||||
})}
|
||||
<option
|
||||
value={"add-new"}
|
||||
key={"add-new"}
|
||||
className="add-dialog-shelf-list-option"
|
||||
>
|
||||
{this.props.t("Add new plugin")}
|
||||
</option>
|
||||
</select>
|
||||
</div>
|
||||
|
||||
{this.state.isAddNew && (
|
||||
<div
|
||||
className="trans-add-new-container"
|
||||
style={{ fontWeight: 500, marginTop: "60px", height: "170px" }}
|
||||
>
|
||||
<textarea
|
||||
name="url"
|
||||
placeholder={this.props.t(
|
||||
"Paste the code of the plugin here, check out document to learn how to get more plugins"
|
||||
)}
|
||||
id="trans-add-content-box"
|
||||
className="trans-add-content-box"
|
||||
onContextMenu={() => {
|
||||
handleContextMenu("trans-add-content-box");
|
||||
}}
|
||||
/>
|
||||
<div className="trans-add-button-container">
|
||||
<div
|
||||
className="trans-add-cancel"
|
||||
style={{ color: "#f16464" }}
|
||||
onClick={() => {
|
||||
if (
|
||||
ConfigService.getReaderConfig("lang") &&
|
||||
ConfigService.getReaderConfig("lang").startsWith("zh")
|
||||
) {
|
||||
openExternalUrl(WEBSITE_URL + "/zh/plugin");
|
||||
} else {
|
||||
openExternalUrl(WEBSITE_URL + "/en/plugin");
|
||||
}
|
||||
}}
|
||||
>
|
||||
<Trans>Document</Trans>
|
||||
</div>
|
||||
<div
|
||||
className="trans-add-cancel"
|
||||
onClick={() => {
|
||||
this.setState({ isAddNew: false });
|
||||
}}
|
||||
>
|
||||
<Trans>Cancel</Trans>
|
||||
</div>
|
||||
<div
|
||||
className="trans-add-confirm"
|
||||
onClick={async () => {
|
||||
let value: string = (
|
||||
document.querySelector(
|
||||
"#trans-add-content-box"
|
||||
) as HTMLTextAreaElement
|
||||
).value;
|
||||
if (value) {
|
||||
let plugin: any = JSON.parse(value);
|
||||
plugin.key = plugin.identifier;
|
||||
if (!(await checkPlugin(plugin))) {
|
||||
toast.error(this.props.t("Plugin verification failed"));
|
||||
return;
|
||||
}
|
||||
if (
|
||||
this.props.plugins.find((item) => item.key === plugin.key)
|
||||
) {
|
||||
await DatabaseService.updateRecord(plugin, "plugins");
|
||||
} else {
|
||||
await DatabaseService.saveRecord(plugin, "plugins");
|
||||
}
|
||||
this.props.handleFetchPlugins();
|
||||
toast.success(this.props.t("Addition successful"));
|
||||
}
|
||||
this.setState({
|
||||
isAddNew: false,
|
||||
answer: this.props.t("Please select the service"),
|
||||
});
|
||||
}}
|
||||
>
|
||||
<Trans>Confirm</Trans>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
{!this.state.isAddNew && (
|
||||
<>
|
||||
<div
|
||||
className="dict-text-box"
|
||||
style={{
|
||||
marginTop: "60px",
|
||||
height: "225px",
|
||||
paddingBottom: "60px",
|
||||
paddingLeft: "0px",
|
||||
paddingRight: "0px",
|
||||
}}
|
||||
ref={this.chatBoxRef}
|
||||
>
|
||||
{this.handleRenderHistoryMessage(
|
||||
this.state.mode === "ask"
|
||||
? this.state.askHistory
|
||||
: this.state.chatHistory
|
||||
)}
|
||||
{this.state.isWaiting ? (
|
||||
<div
|
||||
className="popup-message-assistant"
|
||||
style={{ float: "left" }}
|
||||
>
|
||||
<span
|
||||
className="icon-loading"
|
||||
style={{
|
||||
marginRight: "10px",
|
||||
marginTop: "5px",
|
||||
}}
|
||||
></span>
|
||||
<span>{this.props.t("Thinking, please wait...")}</span>
|
||||
</div>
|
||||
) : (this.state.mode === "ask"
|
||||
? this.state.askHistory
|
||||
: this.state.chatHistory
|
||||
).length > 0 ? (
|
||||
<div className="popup-message-assistant">
|
||||
{Parser(
|
||||
DOMPurify.sanitize(
|
||||
marked.parse(this.state.answer ? this.state.answer : "") +
|
||||
"<address></address>"
|
||||
) || " ",
|
||||
{
|
||||
replace: (_domNode) => {},
|
||||
}
|
||||
)}
|
||||
</div>
|
||||
) : (
|
||||
<div className="popup-message-assistant">
|
||||
{this.state.mode === "ask"
|
||||
? this.props.t(
|
||||
"Hi there! What questions do you have about this chapter?"
|
||||
)
|
||||
: this.props.t(
|
||||
"Hi there! I'm happy to help with any questions about reading or learning"
|
||||
)}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
<div className="popup-assist-shortcut-container">
|
||||
{sampleQuestion
|
||||
.filter((item) => item.mode === this.state.mode)
|
||||
.map((item) => {
|
||||
return (
|
||||
<div
|
||||
className="popup-assist-shortcut"
|
||||
onClick={() => {
|
||||
this.handleNewQuestion(item.question);
|
||||
}}
|
||||
>
|
||||
{this.props.t(item.question)}
|
||||
</div>
|
||||
);
|
||||
})}
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<textarea
|
||||
name="url"
|
||||
placeholder={this.props.t(
|
||||
this.state.mode === "ask"
|
||||
? "Ask anything about this chapter"
|
||||
: "Ask anything about reading or learning"
|
||||
)}
|
||||
id="trans-add-content-box"
|
||||
className="trans-add-content-box"
|
||||
style={{
|
||||
height: "40px",
|
||||
paddingRight: "40px",
|
||||
resize: "none",
|
||||
}}
|
||||
onContextMenu={() => {
|
||||
handleContextMenu("trans-add-content-box");
|
||||
}}
|
||||
value={this.state.inputQuestion}
|
||||
onChange={(event: React.ChangeEvent<HTMLTextAreaElement>) => {
|
||||
this.setState({
|
||||
inputQuestion: event.target.value,
|
||||
});
|
||||
}}
|
||||
/>
|
||||
<span
|
||||
className={`icon-send send-icon`}
|
||||
onClick={() => {
|
||||
if (this.state.answer || this.state.isWaiting) {
|
||||
return;
|
||||
}
|
||||
this.handleNewQuestion(this.state.inputQuestion);
|
||||
this.setState({
|
||||
inputQuestion: "",
|
||||
});
|
||||
}}
|
||||
></span>
|
||||
</div>
|
||||
</>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
||||
export default PopupAssist;
|
||||
|
||||
@@ -11,9 +11,13 @@ export interface PopupAssistProps {
|
||||
t: (title: string) => string;
|
||||
}
|
||||
export interface PopupAssistState {
|
||||
sumText: string;
|
||||
prototype: string;
|
||||
sumService: string;
|
||||
sumTarget: string;
|
||||
aiService: string;
|
||||
isAddNew: boolean;
|
||||
isWaiting: boolean;
|
||||
question: string;
|
||||
askHistory: any[];
|
||||
chatHistory: any[];
|
||||
answer: string;
|
||||
mode: string;
|
||||
inputQuestion: string;
|
||||
}
|
||||
|
||||
@@ -0,0 +1,50 @@
|
||||
.send-icon {
|
||||
position: absolute;
|
||||
bottom: 16px;
|
||||
right: 35px;
|
||||
font-size: 18px;
|
||||
cursor: pointer;
|
||||
}
|
||||
.popup-assist-shortcut-container {
|
||||
width: 100%;
|
||||
}
|
||||
.popup-assist-shortcut {
|
||||
margin-right: 20px;
|
||||
color: #f16464;
|
||||
opacity: 0.8;
|
||||
cursor: pointer;
|
||||
margin-top: 10px;
|
||||
}
|
||||
.trans-add-content-box::placeholder {
|
||||
opacity: 0.8;
|
||||
}
|
||||
.popup-message-user {
|
||||
background-color: #f0f0f0;
|
||||
border-radius: 8px;
|
||||
padding: 10px;
|
||||
margin-bottom: 10px;
|
||||
float: right;
|
||||
border-bottom-right-radius: 0px;
|
||||
}
|
||||
.popup-message-assistant {
|
||||
padding: 10px;
|
||||
padding-left: 0px;
|
||||
padding-right: 0px;
|
||||
margin-bottom: 10px;
|
||||
float: left;
|
||||
width: 100%;
|
||||
}
|
||||
@keyframes spin {
|
||||
from {
|
||||
transform: rotate(0deg);
|
||||
}
|
||||
to {
|
||||
transform: rotate(360deg);
|
||||
}
|
||||
}
|
||||
|
||||
/* Apply the animation to the icon-loading class */
|
||||
.icon-loading {
|
||||
display: inline-block;
|
||||
animation: spin 1s linear infinite;
|
||||
}
|
||||
|
||||
@@ -46,7 +46,10 @@ class PopupBox extends React.Component<PopupBoxProps, PopupBoxStates> {
|
||||
<>
|
||||
<div
|
||||
className="popup-box-container"
|
||||
style={{ marginLeft: this.props.isNavLocked ? 150 : 0 }}
|
||||
style={{
|
||||
marginLeft: this.props.isNavLocked ? 150 : 0,
|
||||
height: this.props.menuMode === "assistant" ? "400px" : "300px",
|
||||
}}
|
||||
>
|
||||
{this.props.menuMode === "note" ? (
|
||||
<PopupNote {...PopupProps} />
|
||||
|
||||
@@ -284,3 +284,21 @@ export const officialDictList = [
|
||||
nativeLang: "Classical Chinese",
|
||||
},
|
||||
];
|
||||
export const sampleQuestion = [
|
||||
{
|
||||
mode: "ask",
|
||||
question: "Summarize this chapter for me",
|
||||
},
|
||||
{
|
||||
mode: "ask",
|
||||
question: "What are the key points of this chapter",
|
||||
},
|
||||
{
|
||||
mode: "chat",
|
||||
question: "Recommend me some books from Colleen Hoover",
|
||||
},
|
||||
{
|
||||
mode: "chat",
|
||||
question: "Explain Stoicism and its principles to me",
|
||||
},
|
||||
];
|
||||
|
||||
@@ -39,6 +39,25 @@ export const getSummaryStream = async (
|
||||
);
|
||||
return result;
|
||||
};
|
||||
export const getAnswerStream = async (
|
||||
text: string,
|
||||
question: string,
|
||||
history: any[],
|
||||
mode: string,
|
||||
onMessage: (result) => void
|
||||
) => {
|
||||
let readerRequest = await getReaderRequest();
|
||||
let result = await readerRequest.getAnswerFetch(
|
||||
{
|
||||
text,
|
||||
question,
|
||||
history,
|
||||
mode,
|
||||
},
|
||||
onMessage
|
||||
);
|
||||
return result;
|
||||
};
|
||||
export const getDictionary = async (word: string, from: string, to: string) => {
|
||||
let readerRequest = await getReaderRequest();
|
||||
let result = await readerRequest.getDictionary({ word, from, to });
|
||||
|
||||
@@ -10547,10 +10547,10 @@ mammoth@^1.8.0:
|
||||
underscore "^1.13.1"
|
||||
xmlbuilder "^10.0.0"
|
||||
|
||||
marked@^15.0.3:
|
||||
version "15.0.3"
|
||||
resolved "https://registry.yarnpkg.com/marked/-/marked-15.0.3.tgz#f75ae6ca71aeeaea0cfa2c2f120016ac8373aede"
|
||||
integrity sha512-Ai0cepvl2NHnTcO9jYDtcOEtVBNVYR31XnEA3BndO7f5As1wzpcOceSUM8FDkNLJNIODcLpDTWay/qQhqbuMvg==
|
||||
marked@^15.0.11:
|
||||
version "15.0.11"
|
||||
resolved "https://registry.yarnpkg.com/marked/-/marked-15.0.11.tgz#08a8d12c285e16259e44287b89ce0d871c9d55e8"
|
||||
integrity sha512-1BEXAU2euRCG3xwgLVT1y0xbJEld1XOrmRJpUwRCcy7rxhSCwMrmEu9LXoPhHSCJG41V7YcQ2mjKRr5BA3ITIA==
|
||||
|
||||
matcher@^3.0.0:
|
||||
version "3.0.0"
|
||||
|
||||
Reference in New Issue
Block a user