chore: update claude-settings.json (memory-push hook) and server.js backup
This commit is contained in:
parent
749d1483f3
commit
b8b26373dd
52
server.js
52
server.js
|
|
@ -69,10 +69,8 @@ const pool = new Pool({
|
||||||
const genAI = process.env.GEMINI_API_KEY
|
const genAI = process.env.GEMINI_API_KEY
|
||||||
? new GoogleGenerativeAI(process.env.GEMINI_API_KEY) : null;
|
? new GoogleGenerativeAI(process.env.GEMINI_API_KEY) : null;
|
||||||
|
|
||||||
// Together 専用インスタンス(GEMINI_TOGETHER_API_KEY 優先、なければ共用キーにフォールバック)
|
// Together 専用インスタンス(メインキーを共用)
|
||||||
const genAITogether = process.env.GEMINI_TOGETHER_API_KEY
|
const genAITogether = genAI;
|
||||||
? new GoogleGenerativeAI(process.env.GEMINI_TOGETHER_API_KEY)
|
|
||||||
: genAI;
|
|
||||||
|
|
||||||
// ── API Key 認証 ──────────────────────────
|
// ── API Key 認証 ──────────────────────────
|
||||||
// API_KEYS="pk_maita_abc:maita,pk_partner_def:partner"
|
// API_KEYS="pk_maita_abc:maita,pk_partner_def:partner"
|
||||||
|
|
@ -1316,31 +1314,44 @@ ${excerpt}
|
||||||
try {
|
try {
|
||||||
const jinaRes = await fetch(`https://r.jina.ai/${url}`, {
|
const jinaRes = await fetch(`https://r.jina.ai/${url}`, {
|
||||||
headers: { 'Accept': 'text/plain', 'User-Agent': 'Posimai/1.0' },
|
headers: { 'Accept': 'text/plain', 'User-Agent': 'Posimai/1.0' },
|
||||||
signal: AbortSignal.timeout(10000),
|
signal: AbortSignal.timeout(30000),
|
||||||
});
|
});
|
||||||
if (!jinaRes.ok) throw new Error(`Jina ${jinaRes.status}`);
|
if (!jinaRes.ok) throw new Error(`Jina ${jinaRes.status}`);
|
||||||
const fullContent = await jinaRes.text();
|
const fullContent = await jinaRes.text();
|
||||||
|
|
||||||
|
// Jina Reader のレスポンス先頭から "Title: ..." を抽出
|
||||||
|
const titleMatch = fullContent.match(/^Title:\s*(.+)/m);
|
||||||
|
const jinaTitle = titleMatch ? titleMatch[1].trim().slice(0, 300) : null;
|
||||||
|
|
||||||
await pool.query(
|
await pool.query(
|
||||||
`UPDATE together_shares SET full_content=$1 WHERE id=$2`,
|
`UPDATE together_shares SET full_content=$1, title=COALESCE(title, $2) WHERE id=$3`,
|
||||||
[fullContent, shareId]
|
[fullContent, jinaTitle, shareId]
|
||||||
);
|
);
|
||||||
|
|
||||||
let summary = null;
|
let summary = null;
|
||||||
|
let tags = [];
|
||||||
if (genAI && fullContent) {
|
if (genAI && fullContent) {
|
||||||
// 最初の ## 見出し以降を本文とみなし 4000 字を Gemini に渡す
|
// 最初の ## 見出し以降を本文とみなし 4000 字を Gemini に渡す
|
||||||
const bodyStart = fullContent.search(/^#{1,2}\s/m);
|
const bodyStart = fullContent.search(/^#{1,2}\s/m);
|
||||||
const excerpt = (bodyStart >= 0 ? fullContent.slice(bodyStart) : fullContent).slice(0, 4000);
|
const excerpt = (bodyStart >= 0 ? fullContent.slice(bodyStart) : fullContent).slice(0, 4000);
|
||||||
const model = genAITogether.getGenerativeModel({ model: 'gemini-2.5-flash' });
|
const model = genAITogether.getGenerativeModel({ model: 'gemini-2.5-flash' });
|
||||||
const prompt = `以下の記事を1〜2文の日本語で要約してください。読者が読む価値があるかを判断できる内容にしてください。\n\n${excerpt}\n\n要約(1〜2文のみ):`;
|
const prompt = `以下の記事を分析して、JSONのみを返してください(コードブロック不要)。\n\n{"summary":"1〜2文の日本語要約","tags":["タグ1","タグ2","タグ3"]}\n\n- summary: 読者が読む価値があるかを判断できる1〜2文\n- tags: 内容を表す日本語タグを2〜4個(例: AI, テクノロジー, ビジネス, 健康, 旅行, 料理, スポーツ, 政治, 経済, エンタメ, ゲーム, 科学, デザイン)\n\n記事:\n${excerpt}`;
|
||||||
const timeoutP = new Promise((_, reject) => setTimeout(() => reject(new Error('timeout')), 12000));
|
const timeoutP = new Promise((_, reject) => setTimeout(() => reject(new Error('timeout')), 30000));
|
||||||
const result = await Promise.race([model.generateContent(prompt), timeoutP]);
|
const result = await Promise.race([model.generateContent(prompt), timeoutP]);
|
||||||
summary = result.response.text().trim().slice(0, 300);
|
const raw = result.response.text().trim();
|
||||||
|
try {
|
||||||
|
const parsed = JSON.parse(raw);
|
||||||
|
summary = (parsed.summary || '').slice(0, 300);
|
||||||
|
tags = Array.isArray(parsed.tags) ? parsed.tags.slice(0, 4).map(t => String(t).slice(0, 20)) : [];
|
||||||
|
} catch {
|
||||||
|
// JSON パース失敗時は全文を要約として扱う
|
||||||
|
summary = raw.slice(0, 300);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
await pool.query(
|
await pool.query(
|
||||||
`UPDATE together_shares SET summary=$1, archive_status='done' WHERE id=$2`,
|
`UPDATE together_shares SET summary=$1, tags=$2, archive_status='done' WHERE id=$3`,
|
||||||
[summary, shareId]
|
[summary, tags, shareId]
|
||||||
);
|
);
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
console.error('[together archive]', shareId, e.message);
|
console.error('[together archive]', shareId, e.message);
|
||||||
|
|
@ -1448,6 +1459,23 @@ ${excerpt}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// DELETE /together/share/:id — 自分の投稿を削除
|
||||||
|
r.delete('/together/share/:id', async (req, res) => {
|
||||||
|
const { username } = req.body || {};
|
||||||
|
if (!username) return res.status(400).json({ error: 'username は必須です' });
|
||||||
|
try {
|
||||||
|
const result = await pool.query(
|
||||||
|
'DELETE FROM together_shares WHERE id=$1 AND shared_by=$2 RETURNING id',
|
||||||
|
[req.params.id, username]
|
||||||
|
);
|
||||||
|
if (result.rows.length === 0) return res.status(403).json({ error: '削除できません' });
|
||||||
|
res.json({ ok: true });
|
||||||
|
} catch (e) {
|
||||||
|
console.error('[together/share DELETE]', e.message);
|
||||||
|
res.status(500).json({ error: e.message });
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
// GET /together/feed/:groupId — フィード(リアクション・コメント数付き)
|
// GET /together/feed/:groupId — フィード(リアクション・コメント数付き)
|
||||||
r.get('/together/feed/:groupId', async (req, res) => {
|
r.get('/together/feed/:groupId', async (req, res) => {
|
||||||
try {
|
try {
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue