Compare commits
6 Commits
e0c2211e0d
...
6b05fc961d
| Author | SHA1 | Date |
|---|---|---|
|
|
6b05fc961d | |
|
|
4f6be0a21e | |
|
|
5d1b6dfb2b | |
|
|
7446568adf | |
|
|
324d892b28 | |
|
|
9f0bb8eae7 |
|
|
@ -38,7 +38,7 @@ echo ""
|
||||||
echo "→ Step 2: コンテナ再起動..."
|
echo "→ Step 2: コンテナ再起動..."
|
||||||
ssh -i "$VPS_KEY" -o BatchMode=yes "$VPS_HOST" "
|
ssh -i "$VPS_KEY" -o BatchMode=yes "$VPS_HOST" "
|
||||||
cd $APP_DIR && \
|
cd $APP_DIR && \
|
||||||
docker compose restart api && \
|
docker compose up -d api && \
|
||||||
echo ' 再起動完了。ログを確認中...' && \
|
echo ' 再起動完了。ログを確認中...' && \
|
||||||
sleep 5 && \
|
sleep 5 && \
|
||||||
docker compose logs api --tail 15
|
docker compose logs api --tail 15
|
||||||
|
|
|
||||||
44
server.js
44
server.js
|
|
@ -2594,6 +2594,13 @@ ${excerpt}
|
||||||
|
|
||||||
// fire-and-forget アーカイブ: Jina Reader → Gemini 要約(直列)
|
// fire-and-forget アーカイブ: Jina Reader → Gemini 要約(直列)
|
||||||
async function archiveShare(shareId, url) {
|
async function archiveShare(shareId, url) {
|
||||||
|
// x.com / twitter.com のトラッキングパラメータを除去(埋め込みURLの ?ref_src=twsrc... 等)
|
||||||
|
try {
|
||||||
|
const u = new URL(url);
|
||||||
|
if (/^(x\.com|twitter\.com)$/.test(u.hostname.replace('www.', ''))) {
|
||||||
|
url = `${u.origin}${u.pathname}`;
|
||||||
|
}
|
||||||
|
} catch (_) {}
|
||||||
if (!url || !isSsrfSafe(url)) {
|
if (!url || !isSsrfSafe(url)) {
|
||||||
await pool.query(`UPDATE together_shares SET archive_status='failed' WHERE id=$1`, [shareId]);
|
await pool.query(`UPDATE together_shares SET archive_status='failed' WHERE id=$1`, [shareId]);
|
||||||
return;
|
return;
|
||||||
|
|
@ -2612,11 +2619,37 @@ ${excerpt}
|
||||||
const titleMatch = fullContent.match(/^Title:\s*(.+)/m);
|
const titleMatch = fullContent.match(/^Title:\s*(.+)/m);
|
||||||
const jinaTitle = titleMatch ? titleMatch[1].trim().slice(0, 300) : null;
|
const jinaTitle = titleMatch ? titleMatch[1].trim().slice(0, 300) : null;
|
||||||
|
|
||||||
|
// x.com でタイトルがドメイン名のみ = ボット拒否ページ → AI 要約をスキップして skipped で保存
|
||||||
|
try {
|
||||||
|
const u = new URL(url);
|
||||||
|
const host = u.hostname.replace('www.', '');
|
||||||
|
if (/^(x\.com|twitter\.com)$/.test(host) && (!jinaTitle || jinaTitle === host)) {
|
||||||
|
await pool.query(
|
||||||
|
`UPDATE together_shares SET full_content=$1, archive_status='skipped' WHERE id=$2`,
|
||||||
|
[fullContent, shareId]
|
||||||
|
);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
} catch (_) {}
|
||||||
|
|
||||||
|
// 既存タイトルがドメイン名のみの場合は Jina タイトルで上書き(JS 側で判定)
|
||||||
|
let hostname = '';
|
||||||
|
try { hostname = new URL(url).hostname.replace('www.', ''); } catch (_) {}
|
||||||
|
const titleOverwrite = jinaTitle && hostname && jinaTitle !== hostname;
|
||||||
await pool.query(
|
await pool.query(
|
||||||
`UPDATE together_shares SET full_content=$1, title=COALESCE(title, $2) WHERE id=$3`,
|
`UPDATE together_shares
|
||||||
[fullContent, jinaTitle, shareId]
|
SET full_content=$1,
|
||||||
|
title=CASE WHEN title IS NULL OR ($2::boolean AND title=$3::text) THEN $4::text ELSE title END
|
||||||
|
WHERE id=$5`,
|
||||||
|
[fullContent, titleOverwrite, hostname, jinaTitle, shareId]
|
||||||
);
|
);
|
||||||
|
|
||||||
|
function parseGeminiJson(raw) {
|
||||||
|
// Gemini がコードブロックで返す場合に対応
|
||||||
|
const cleaned = raw.replace(/^```(?:json)?\s*/i, '').replace(/\s*```$/, '').trim();
|
||||||
|
return JSON.parse(cleaned);
|
||||||
|
}
|
||||||
|
|
||||||
let summary = null;
|
let summary = null;
|
||||||
let tags = [];
|
let tags = [];
|
||||||
if (togetherGenAI && fullContent) {
|
if (togetherGenAI && fullContent) {
|
||||||
|
|
@ -2631,7 +2664,7 @@ ${excerpt}
|
||||||
const result = await Promise.race([model.generateContent(prompt), timeoutP]);
|
const result = await Promise.race([model.generateContent(prompt), timeoutP]);
|
||||||
const raw = result.response.text().trim();
|
const raw = result.response.text().trim();
|
||||||
try {
|
try {
|
||||||
const parsed = JSON.parse(raw);
|
const parsed = parseGeminiJson(raw);
|
||||||
summary = (parsed.summary || '').slice(0, 300);
|
summary = (parsed.summary || '').slice(0, 300);
|
||||||
tags = Array.isArray(parsed.tags) ? parsed.tags.slice(0, 4).map(t => String(t).slice(0, 20)) : [];
|
tags = Array.isArray(parsed.tags) ? parsed.tags.slice(0, 4).map(t => String(t).slice(0, 20)) : [];
|
||||||
} catch {
|
} catch {
|
||||||
|
|
@ -2640,9 +2673,6 @@ ${excerpt}
|
||||||
break;
|
break;
|
||||||
} catch (aiErr) {
|
} catch (aiErr) {
|
||||||
console.error(`[together archive AI] ${modelName} share=${shareId}`, aiErr.message);
|
console.error(`[together archive AI] ${modelName} share=${shareId}`, aiErr.message);
|
||||||
if (modelName === modelsToTry[modelsToTry.length - 1]) {
|
|
||||||
// 全モデル失敗: Jina 本文は保存済みなので Reader は使える状態で done にする
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -2690,7 +2720,7 @@ ${excerpt}
|
||||||
const result = await Promise.race([model.generateContent(prompt), timeoutP]);
|
const result = await Promise.race([model.generateContent(prompt), timeoutP]);
|
||||||
const raw = result.response.text().trim();
|
const raw = result.response.text().trim();
|
||||||
let summary = null, tags = [];
|
let summary = null, tags = [];
|
||||||
try { const p = JSON.parse(raw); summary = (p.summary || '').slice(0, 300); tags = Array.isArray(p.tags) ? p.tags.slice(0, 4).map(t => String(t).slice(0, 20)) : []; }
|
try { const cleaned = raw.replace(/^```(?:json)?\s*/i,'').replace(/\s*```$/,'').trim(); const p = JSON.parse(cleaned); summary = (p.summary || '').slice(0, 300); tags = Array.isArray(p.tags) ? p.tags.slice(0, 4).map(t => String(t).slice(0, 20)) : []; }
|
||||||
catch { summary = raw.slice(0, 300); }
|
catch { summary = raw.slice(0, 300); }
|
||||||
await pool.query(`UPDATE together_shares SET summary=$1, tags=$2, archive_status='done' WHERE id=$3`, [summary, tags, shareId]);
|
await pool.query(`UPDATE together_shares SET summary=$1, tags=$2, archive_status='done' WHERE id=$3`, [summary, tags, shareId]);
|
||||||
rearchiveDone = true;
|
rearchiveDone = true;
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue