feat(together): AI要約の手動再試行エンドポイント追加(failed/skipped/summary欠落に対応)
This commit is contained in:
parent
01d4ee926a
commit
7210c8301c
50
server.js
50
server.js
|
|
@ -2661,6 +2661,56 @@ ${excerpt}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// POST /together/share/:shareId/rearchive — AI要約の手動再試行
|
||||||
|
r.post('/together/share/:shareId/rearchive', async (req, res) => {
|
||||||
|
const shareId = parseInt(req.params.shareId, 10);
|
||||||
|
if (Number.isNaN(shareId)) return res.status(400).json({ error: 'invalid shareId' });
|
||||||
|
const username = normalizeTogetherUsername(req.query.u || req.body?.username);
|
||||||
|
const jwtUserId = getTogetherJwtUserId(req);
|
||||||
|
try {
|
||||||
|
const shareRow = await pool.query(
|
||||||
|
'SELECT id, url, full_content, archive_status, summary, group_id FROM together_shares WHERE id=$1',
|
||||||
|
[shareId]
|
||||||
|
);
|
||||||
|
if (shareRow.rows.length === 0) return res.status(404).json({ error: '見つかりません' });
|
||||||
|
const share = shareRow.rows[0];
|
||||||
|
if (!(await togetherEnsureMember(pool, res, share.group_id, username, jwtUserId))) return;
|
||||||
|
if (!checkRateLimit(`rearchive_${shareId}`, 'global', 3, 60 * 60 * 1000)) {
|
||||||
|
return res.status(429).json({ error: '再試行の上限に達しました。1時間後に再試行してください' });
|
||||||
|
}
|
||||||
|
// full_content が既にある(done だが summary なし)場合は Gemini のみ再実行
|
||||||
|
if (share.archive_status === 'done' && share.full_content && !share.summary) {
|
||||||
|
if (!togetherGenAI) return res.status(503).json({ error: 'AI が設定されていません' });
|
||||||
|
await pool.query(`UPDATE together_shares SET archive_status='pending' WHERE id=$1`, [shareId]);
|
||||||
|
res.json({ ok: true, status: 'pending' });
|
||||||
|
try {
|
||||||
|
const bodyStart = share.full_content.search(/^#{1,2}\s/m);
|
||||||
|
const excerpt = (bodyStart >= 0 ? share.full_content.slice(bodyStart) : share.full_content).slice(0, 4000);
|
||||||
|
const model = togetherGenAI.getGenerativeModel({ model: 'gemini-2.5-flash' });
|
||||||
|
const prompt = `以下の記事を分析して、JSONのみを返してください(コードブロック不要)。\n\n{"summary":"1〜2文の日本語要約","tags":["タグ1","タグ2","タグ3"]}\n\n- summary: 読者が読む価値があるかを判断できる1〜2文\n- tags: 内容を表す具体的な日本語タグを2〜4個。「その他」は絶対に使わないこと\n\n記事:\n${excerpt}`;
|
||||||
|
const timeoutP = new Promise((_, reject) => setTimeout(() => reject(new Error('timeout')), 30000));
|
||||||
|
const result = await Promise.race([model.generateContent(prompt), timeoutP]);
|
||||||
|
const raw = result.response.text().trim();
|
||||||
|
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)) : []; }
|
||||||
|
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]);
|
||||||
|
} catch (e) {
|
||||||
|
console.error('[rearchive gemini]', shareId, e.message);
|
||||||
|
await pool.query(`UPDATE together_shares SET archive_status='done' WHERE id=$1`, [shareId]);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// failed / skipped — フル再アーカイブ
|
||||||
|
await pool.query(`UPDATE together_shares SET archive_status='pending' WHERE id=$1`, [shareId]);
|
||||||
|
res.json({ ok: true, status: 'pending' });
|
||||||
|
archiveShare(shareId, share.url);
|
||||||
|
}
|
||||||
|
} catch (e) {
|
||||||
|
console.error('[rearchive]', e.message);
|
||||||
|
if (!res.headersSent) res.status(500).json({ error: 'Internal server error' });
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
// POST /together/groups — グループ作成
|
// POST /together/groups — グループ作成
|
||||||
r.post('/together/groups', async (req, res) => {
|
r.post('/together/groups', async (req, res) => {
|
||||||
const { name, username } = req.body || {};
|
const { name, username } = req.body || {};
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue