Skip to content

Commit

Permalink
Support document(epub) (#1160)
Browse files Browse the repository at this point in the history
* add document model

* may add document

* document card

* basic document renderer

* may render epub

* basic layout

* handle book href

* refactor epub renderer

* refactor translate button

* toggle player

* cache/restore last read position

* refactor

* add more columns to speeches

* start shadow from document

* add compact layout for media shadow

* refactor

* refactor

* refactor

* add document config

* locales

* auto translate

* selected notify for update document

* refactor

* add document provider

* fix perf issue

* refactor

* refactor

* may toggle player

* clean

* refactor

* clean code

* auto play speech

* fix document config update

* refactor

* fix epub image

* fix epub image

* html document

* refactor

* ui

* save document source

* fix document source

* update document model

* cache translation remote

* update UI

* fix package

* refactor

* fix

* support text/markdown files

* fix auto speech
  • Loading branch information
an-lee authored Nov 8, 2024
1 parent f62bd88 commit 76bee71
Show file tree
Hide file tree
Showing 69 changed files with 3,366 additions and 272 deletions.
7 changes: 7 additions & 0 deletions enjoy/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,7 @@
"@types/intl-tel-input": "^18.1.4",
"@types/lodash": "^4.17.12",
"@types/mark.js": "^8.11.12",
"@types/mime-types": "^2",
"@types/mustache": "^4.2.5",
"@types/node": "^22.7.7",
"@types/prop-types": "^15.7.13",
Expand Down Expand Up @@ -120,6 +121,7 @@
"@radix-ui/react-toggle": "^1.1.0",
"@radix-ui/react-tooltip": "^1.1.3",
"@rails/actioncable": "7.2.101",
"@types/turndown": "^5.0.5",
"@uidotdev/usehooks": "^2.4.1",
"@vidstack/react": "^1.12.11",
"ahoy.js": "^0.4.4",
Expand All @@ -145,7 +147,9 @@
"electron-settings": "^4.0.4",
"electron-squirrel-startup": "^1.0.1",
"ffmpeg-static": "^5.2.0",
"file-type": "^19.6.0",
"fluent-ffmpeg": "^2.1.3",
"foliate-js": "https://github.com/johnfactotum/foliate-js",
"fs-extra": "^11.2.0",
"html-to-text": "^9.0.5",
"https-proxy-agent": "^7.0.5",
Expand All @@ -158,6 +162,7 @@
"lucide-react": "^0.453.0",
"mark.js": "^8.11.1",
"microsoft-cognitiveservices-speech-sdk": "^1.41.0",
"mime-types": "^2.1.35",
"mustache": "^4.2.0",
"next-themes": "^0.3.0",
"openai": "^4.68.1",
Expand All @@ -179,13 +184,15 @@
"react-shadow-root": "^6.2.0",
"react-tooltip": "^5.28.0",
"reflect-metadata": "^0.2.2",
"remark-gfm": "^4.0.0",
"rimraf": "^6.0.1",
"semver": "^7.6.3",
"sequelize": "^6.37.4",
"sequelize-typescript": "^2.1.6",
"sonner": "^1.5.0",
"sqlite3": "^5.1.7",
"tailwind-scrollbar-hide": "^1.1.7",
"turndown": "^7.2.0",
"umzug": "^3.8.2",
"unzipper": "^0.12.3",
"update-electron-app": "^3.0.0",
Expand Down
38 changes: 35 additions & 3 deletions enjoy/src/api/client.ts
Original file line number Diff line number Diff line change
Expand Up @@ -69,8 +69,8 @@ export class Client {
this.logger.error(
err.response.status,
err.response.config.method.toUpperCase(),
err.response.config.baseURL + err.response.config.url,
err.response.data
err.response.config.baseURL + err.response.config.url
// err.response.data
);

if (err.response.data) {
Expand All @@ -86,7 +86,6 @@ export class Client {
return Promise.reject(err);
}

this.logger.error(err.message);
return Promise.reject(err);
}
);
Expand Down Expand Up @@ -616,4 +615,37 @@ export class Client {
params: decamelizeKeys(params),
});
}

syncDocument(document: Partial<DocumentEType>) {
return this.api.post("/api/mine/documents", decamelizeKeys(document));
}

deleteDocument(id: string) {
return this.api.delete(`/api/mine/documents/${id}`);
}

translations(params?: {
md5?: string;
translatedLanguage?: string;
engine?: string;
}): Promise<
{
translations: TranslationType[];
} & PagyResponseType
> {
return this.api.get("/api/translations", {
params: decamelizeKeys(params),
});
}

createTranslation(params: {
md5: string;
content: string;
translatedContent: string;
language: string;
translatedLanguage: string;
engine: string;
}): Promise<TranslationType> {
return this.api.post("/api/translations", decamelizeKeys(params));
}
}
2 changes: 2 additions & 0 deletions enjoy/src/constants/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,8 @@ export const AudioFormats = ["mp3", "wav", "ogg", "flac", "m4a", "wma", "aac"];

export const VideoFormats = ["mp4", "mkv", "avi", "mov", "wmv", "flv", "webm"];

export const DocumentFormats = ["epub", "md", "markdown", "html", "txt"];

export const PROCESS_TIMEOUT = 1000 * 60 * 15;

export const NOT_SUPPORT_JSON_FORMAT_MODELS = [
Expand Down
18 changes: 17 additions & 1 deletion enjoy/src/i18n/en.json
Original file line number Diff line number Diff line change
Expand Up @@ -189,6 +189,11 @@
"onlyGPTAgentCanBeAddedToThisChat": "Only GPT agent can be added to this chat",
"invalidAgentType": "Invalid agent type",
"invalidMembers": "Invalid members"
},
"document": {
"fileNotFound": "File not found {{file}}",
"fileNotSupported": "File not supported {{file}}",
"failedToCopyFile": "Failed to copy file {{file}}"
}
},
"sidebar": {
Expand All @@ -199,6 +204,7 @@
"audios": "Audios",
"videos": "Videos",
"stories": "Stories",
"documents": "Documents",
"books": "Books",
"vocabulary": "Vocabulary",
"library": "Library",
Expand Down Expand Up @@ -529,6 +535,8 @@
"addedStories": "added stories",
"addedAudios": "added audios",
"addedVideos": "added videos",
"addedDocuments": "added documents",
"document": "document",
"frontSide": "front side",
"backSide": "back side",
"aiExtractVocabulary": "AI extract vocabulary",
Expand Down Expand Up @@ -891,5 +899,13 @@
"diskUsageDescription": "The disk usage of Enjoy App.",
"releaseDiskSpace": "Release",
"bulkDeleteRecordings": "Delete Recordings",
"bulkDeleteAborted": "Bulk delete aborted"
"bulkDeleteAborted": "Bulk delete aborted",
"notFound": "Not found",
"saved": "Saved",
"autoTranslate": "Auto translate",
"autoNextSpeech": "Auto speech",
"failedToLoadLink": "Failed to load link",
"refreshSpeech": "Refresh speech",
"locateParagraph": "Locate paragraph",
"close": "Close"
}
18 changes: 17 additions & 1 deletion enjoy/src/i18n/zh-CN.json
Original file line number Diff line number Diff line change
Expand Up @@ -189,6 +189,11 @@
"onlyGPTAgentCanBeAddedToThisChat": "只有 GPT 智能体可以添加到此聊天",
"invalidAgentType": "无效的智能体类型",
"invalidMembers": "无效的成员"
},
"document": {
"fileNotFound": "文件未找到 {{file}}",
"fileNotSupported": "文件格式不支持 {{file}}",
"failedToCopyFile": "复制文件失败 {{file}}"
}
},
"sidebar": {
Expand All @@ -199,6 +204,7 @@
"audios": "音频",
"videos": "视频",
"stories": "文章",
"documents": "文档",
"books": "电子书",
"vocabulary": "生词本",
"library": "资料库",
Expand Down Expand Up @@ -529,6 +535,8 @@
"addedStories": "添加的文章",
"addedAudios": "添加的音频",
"addedVideos": "添加的视频",
"addedDocuments": "添加的文档",
"document": "文档",
"frontSide": "正面",
"backSide": "反面",
"aiExtractVocabulary": "AI 提取生词",
Expand Down Expand Up @@ -891,5 +899,13 @@
"diskUsageDescription": "Enjoy App 的磁盘使用情况",
"releaseDiskSpace": "释放磁盘",
"bulkDeleteRecordings": "删除录音",
"bulkDeleteAborted": "批量删除已中止"
"bulkDeleteAborted": "批量删除已中止",
"notFound": "未找到",
"saved": "已保存",
"autoTranslate": "自动翻译",
"autoNextSpeech": "连续朗读",
"failedToLoadLink": "加载链接失败",
"refreshSpeech": "刷新语音",
"locateParagraph": "定位段落",
"close": "关闭"
}
6 changes: 5 additions & 1 deletion enjoy/src/main.ts
Original file line number Diff line number Diff line change
Expand Up @@ -117,7 +117,11 @@ app.on("ready", async () => {

protocol.handle("enjoy", (request) => {
let url = request.url.replace("enjoy://", "");
if (url.match(/library\/(audios|videos|recordings|speeches|segments)/g)) {
if (
url.match(
/library\/(audios|videos|recordings|speeches|segments|documents)/g
)
) {
url = url.replace("library/", "");
url = path.join(settings.userDataPath(), url);
} else if (url.startsWith("library")) {
Expand Down
158 changes: 158 additions & 0 deletions enjoy/src/main/db/handlers/documents-handler.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,158 @@
import { ipcMain, IpcMainEvent } from "electron";
import { Document } from "@main/db/models";
import { FindOptions, WhereOptions, Attributes, Op } from "sequelize";
import downloader from "@main/downloader";
import log from "@main/logger";
import { t } from "i18next";

const logger = log.scope("db/handlers/documents-handler");

class DocumentsHandler {
private async findAll(
_event: IpcMainEvent,
options: FindOptions<Attributes<Document>> & { query?: string }
) {
const { query, where = {} } = options || {};
delete options.query;
delete options.where;

if (query) {
(where as any).title = {
[Op.like]: `%${query}%`,
};
}
const documents = await Document.findAll({
order: [
["lastReadAt", "DESC"],
["updatedAt", "DESC"],
],
where,
...options,
});

if (!documents) {
return [];
}
return documents.map((document) => document.toJSON());
}

private async findOne(
_event: IpcMainEvent,
where: WhereOptions<Attributes<Document>>
) {
const document = await Document.findOne({
where: {
...where,
},
});
if (!document) return;

if (!document.isSynced) {
document.sync().catch(() => {});
}

return document.toJSON();
}

private async create(
event: IpcMainEvent,
params: {
uri: string;
title?: string;
config?: Record<string, any>;
source?: string;
}
) {
let { uri, title, config, source } = params;
if (uri.startsWith("http")) {
uri = await downloader.download(uri, {
webContents: event.sender,
});
if (!uri) throw new Error("Failed to download file");
}

try {
const document = await Document.buildFromLocalFile(uri, {
title,
config,
source,
});

return document.toJSON();
} catch (err) {
logger.error(err.message);
throw err;
}
}

private async update(
_event: IpcMainEvent,
id: string,
params: Attributes<Document>
) {
const { title, metadata, lastReadPosition, lastReadAt, config } = params;

const document = await Document.findByPk(id);

if (!document) {
throw new Error(t("models.document.notFound"));
}
return await document.update({
title,
metadata,
lastReadPosition,
lastReadAt,
config,
});
}

private async destroy(_event: IpcMainEvent, id: string) {
const document = await Document.findByPk(id);

if (!document) {
throw new Error(t("models.document.notFound"));
}
return await document.destroy();
}

private async upload(event: IpcMainEvent, id: string) {
const document = await Document.findByPk(id);
if (!document) {
throw new Error(t("models.document.notFound"));
}

return await document.upload();
}

private async cleanUp() {
const documents = await Document.findAll();

for (const document of documents) {
if (!document.src) {
document.destroy();
}
}
}

register() {
ipcMain.handle("documents-find-all", this.findAll);
ipcMain.handle("documents-find-one", this.findOne);
ipcMain.handle("documents-create", this.create);
ipcMain.handle("documents-update", this.update);
ipcMain.handle("documents-destroy", this.destroy);
ipcMain.handle("documents-upload", this.upload);
ipcMain.handle("documents-clean-up", this.cleanUp);
}

unregister() {
ipcMain.removeHandler("documents-find-all");
ipcMain.removeHandler("documents-find-one");
ipcMain.removeHandler("documents-create");
ipcMain.removeHandler("documents-update");
ipcMain.removeHandler("documents-destroy");
ipcMain.removeHandler("documents-upload");
ipcMain.removeHandler("documents-clean-up");
}
}

export const documentsHandler = new DocumentsHandler();
1 change: 1 addition & 0 deletions enjoy/src/main/db/handlers/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,3 +14,4 @@ export * from "./segments-handler";
export * from "./transcriptions-handler";
export * from "./user-settings-handler";
export * from "./videos-handler";
export * from "./documents-handler";
Loading

0 comments on commit 76bee71

Please sign in to comment.