diff --git a/server/index.js b/server/index.js
index 5238788..d228a35 100644
--- a/server/index.js
+++ b/server/index.js
@@ -4,10 +4,11 @@ import cors from 'cors';
import bcrypt from 'bcryptjs';
import jwt from 'jsonwebtoken';
import { tmpdir } from 'os';
-import { join } from 'path';
+import { dirname, join } from 'path';
import { promises as fs } from 'fs';
import { v4 as uuidV4 } from 'uuid';
import Epub from 'epub-gen';
+import { fileURLToPath } from 'url';
const requiredEnv = [
'SUPABASE_URL',
@@ -22,6 +23,7 @@ requiredEnv.forEach((key) => {
}
});
+const __dirname = dirname(fileURLToPath(import.meta.url));
const app = express();
const PORT = process.env.PORT || 4000;
const ORIGIN = process.env.CLIENT_ORIGIN || 'http://localhost:5173';
@@ -260,8 +262,10 @@ app.post('/translate', async (req, res) => {
return res.status(400).json({ message: 'Çevrilecek metin bulunamadı.' });
}
+ console.log('[Translate] İstek alındı', { length: text.length, snippet: text.slice(0, 60) });
try {
const translated = await translateWithGlm(text);
+ console.log('[Translate] Çeviri başarıyla döndü');
return res.json({ text: translated });
} catch (error) {
console.error('GLM çeviri hatası:', error);
@@ -275,9 +279,17 @@ app.post('/generate-epub', async (req, res) => {
return res.status(400).json({ message: 'text is required' });
}
- const title = meta?.title || 'imgPub OCR Export';
- const author = meta?.author || 'imgPub';
+ const title = meta?.title?.trim() || 'imgPub OCR Export';
const filename = meta?.filename || `imgpub${Date.now()}.epub`;
+ const authors =
+ Array.isArray(meta?.authors) && meta.authors.length
+ ? meta.authors.filter(Boolean)
+ : meta?.author
+ ? [meta.author]
+ : ['imgPub'];
+ const publisher = meta?.publisher || 'imgPub';
+ const language = meta?.language || 'tr';
+ const description = meta?.description || title;
const content = [
{
@@ -288,6 +300,18 @@ app.post('/generate-epub', async (req, res) => {
const outputPath = join(tmpdir(), `imgpub-${uuidV4()}.epub`);
let coverPath;
+ const metadataPayload = {
+ subtitle: meta?.subtitle,
+ description: meta?.description,
+ categories: Array.isArray(meta?.categories) ? meta.categories : [],
+ publishedDate: meta?.publishedDate,
+ language: meta?.language,
+ pageCount: meta?.pageCount,
+ averageRating: meta?.averageRating,
+ ratingsCount: meta?.ratingsCount,
+ identifiers: Array.isArray(meta?.identifiers) ? meta.identifiers : [],
+ infoLink: meta?.infoLink,
+ };
try {
if (cover?.data) {
@@ -298,7 +322,16 @@ app.post('/generate-epub', async (req, res) => {
await fs.writeFile(coverPath, coverBuffer);
}
- const epubOptions = { title, author, content };
+ const epubOptions = {
+ title,
+ author: authors,
+ publisher,
+ description,
+ lang: language,
+ content,
+ bookMetadata: metadataPayload,
+ customOpfTemplatePath: join(__dirname, 'templates', 'content.opf.ejs'),
+ };
if (coverPath) {
epubOptions.cover = coverPath;
}
diff --git a/server/src/services/glmClient.js b/server/src/services/glmClient.js
index 8919a4a..b474ecf 100644
--- a/server/src/services/glmClient.js
+++ b/server/src/services/glmClient.js
@@ -112,6 +112,12 @@ export const translateWithGlm = async (text) => {
};
}
+ console.log('[GLM] İstek hazırlanıyor', {
+ endpoint: GLM_API_URL,
+ model: GLM_MODEL,
+ snippet: text.slice(0, 80),
+ });
+
const response = await fetch(GLM_API_URL, {
method: 'POST',
headers: {
@@ -125,7 +131,20 @@ export const translateWithGlm = async (text) => {
body: JSON.stringify(body),
});
- const payload = await response.json().catch(() => ({}));
+ let payload = {};
+ try {
+ payload = await response.json();
+ } catch (error) {
+ console.error('[GLM] JSON parse başarısız', error);
+ }
+
+ console.log('[GLM] Yanıt alındı', {
+ status: response.status,
+ ok: response.ok,
+ hasOutput: Boolean(payload?.output || payload?.choices || payload?.content),
+ error: payload?.error,
+ });
+
if (!response.ok) {
const message =
payload?.error?.message ||
@@ -136,7 +155,9 @@ export const translateWithGlm = async (text) => {
const translated = extractContent(payload);
if (!translated) {
+ console.error('[GLM] Boş içerik döndü', payload);
throw new Error('GLM çıktısı boş döndü.');
}
+ console.log('[GLM] Çeviri tamamlandı');
return translated;
};
diff --git a/server/templates/content.opf.ejs b/server/templates/content.opf.ejs
new file mode 100644
index 0000000..90a6d10
--- /dev/null
+++ b/server/templates/content.opf.ejs
@@ -0,0 +1,100 @@
+
+
+
+
+
+ <%= id %>
+ 22
+ BookId
+ <%= title %>
+ <% if (bookMetadata && bookMetadata.subtitle) { %>
+ <%= bookMetadata.subtitle %>
+ <% } %>
+ <%= title %>
+ <%= lang || "en" %>
+ <%= lang || "en" %>
+ <%= (new Date()).toISOString().split(".")[0]+ "Z" %>
+ <%= author.length ? author.join(",") : author %>
+ <%= author.length ? author.join(",") : author %>
+ <%= publisher || "anonymous" %>
+ <%= publisher || "anonymous" %>
+ <% var date = new Date(); var year = date.getFullYear(); var month = date.getMonth() + 1; var day = date.getDate(); var stringDate = "" + year + "-" + month + "-" + day; %>
+ <%= bookMetadata && bookMetadata.publishedDate ? bookMetadata.publishedDate : stringDate %>
+ <%= bookMetadata && bookMetadata.publishedDate ? bookMetadata.publishedDate : stringDate %>
+ <% if (bookMetadata && bookMetadata.description) { %>
+ <%= bookMetadata.description %>
+ <%= bookMetadata.description %>
+ <% } %>
+ <% if (bookMetadata && bookMetadata.categories && bookMetadata.categories.length) { bookMetadata.categories.forEach(function(category){ %>
+ <%= category %>
+ <% }); } %>
+ <% if (bookMetadata && bookMetadata.identifiers && bookMetadata.identifiers.length) { bookMetadata.identifiers.forEach(function(identifier, idx){ %>
+ <%= identifier.identifier %>
+ <% }); } %>
+ <% if (bookMetadata && bookMetadata.pageCount) { %>
+ <%= bookMetadata.pageCount %>
+ <% } %>
+ <% if (bookMetadata && bookMetadata.averageRating) { %>
+ <%= bookMetadata.averageRating %>
+ <% } %>
+ <% if (bookMetadata && bookMetadata.ratingsCount) { %>
+ <%= bookMetadata.ratingsCount %>
+ <% } %>
+ <% if (bookMetadata && bookMetadata.infoLink) { %>
+ <%= bookMetadata.infoLink %>
+ <% } %>
+ All rights reserved
+ Copyright © <%= (new Date()).getFullYear() %> by <%= publisher || "anonymous" %>
+
+
+ true
+
+
+
+
+
+
+
+
+ <% if(locals.cover) { %>
+
+ <% } %>
+
+ <% images.forEach(function(image, index){ %>
+
+ <% }) %>
+
+ <% content.forEach(function(content, index){ %>
+
+ <% }) %>
+
+ <% fonts.forEach(function(font, index){%>
+
+ <%})%>
+
+
+
+ <% content.forEach(function(content, index){ %>
+ <% if(content.beforeToc && !content.excludeFromToc){ %>
+
+ <% } %>
+ <% }) %>
+
+ <% content.forEach(function(content, index){ %>
+ <% if(!content.beforeToc && !content.excludeFromToc){ %>
+
+ <% } %>
+ <% }) %>
+
+
+
+
+
diff --git a/src/App.jsx b/src/App.jsx
index 3200d3a..c5c2dd9 100644
--- a/src/App.jsx
+++ b/src/App.jsx
@@ -25,6 +25,7 @@ export const wizardSteps = [
{ label: 'Crop', path: '/crop' },
{ label: 'Toplu Crop', path: '/bulk-crop' },
{ label: 'OCR', path: '/ocr' },
+ { label: 'Çeviri', path: '/translate' },
{ label: 'EPUB Oluştur', path: '/epub' },
{ label: 'İndir', path: '/download' },
];
@@ -142,11 +143,16 @@ const App = () => {
{
+ resetFromStep('upload');
+ navigate('/');
+ }}
sx={{
fontFamily: '"Caudex", serif',
color: '#1C1815',
fontWeight: 700,
letterSpacing: 1,
+ cursor: 'pointer',
}}
>
imagepub
diff --git a/src/components/BulkCropStep.jsx b/src/components/BulkCropStep.jsx
index 3540809..3385d08 100644
--- a/src/components/BulkCropStep.jsx
+++ b/src/components/BulkCropStep.jsx
@@ -19,6 +19,7 @@ const BulkCropStep = () => {
const setCroppedImages = useAppStore((state) => state.setCroppedImages);
const setError = useAppStore((state) => state.setError);
const croppedImages = useAppStore((state) => state.croppedImages);
+ const bookMetadata = useAppStore((state) => state.bookMetadata);
const [processing, setProcessing] = useState(false);
const targetImages = useMemo(
@@ -90,6 +91,12 @@ const BulkCropStep = () => {
if (!targetImages.length) {
return (
+ {bookMetadata && (
+
+ Seçilen kitap: {bookMetadata.title}
+ {bookMetadata.authors?.length ? ` • ${bookMetadata.authors.join(', ')}` : ''}
+
+ )}
Kapak dışında crop uygulanacak görsel bulunmuyor. Bu adımı geçebilirsin.