Compare commits
No commits in common. "7210c8301c290a76fc810635a05c50c492cf10e5" and "d36b7a8fe4663b1242d40a20f86200e75059dcd7" have entirely different histories.
7210c8301c
...
d36b7a8fe4
50
server.js
50
server.js
|
|
@ -2661,56 +2661,6 @@ ${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 — グループ作成
|
||||
r.post('/together/groups', async (req, res) => {
|
||||
const { name, username } = req.body || {};
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
#Requires -Version 5.1
|
||||
#Requires -Version 5.1
|
||||
<#
|
||||
.SYNOPSIS
|
||||
Read-only local PC audit (developer / AI-tooling focused). No writes except report output.
|
||||
|
|
@ -8,7 +8,7 @@
|
|||
Directory for reports (created if missing). Default: tools/pc-audit/out under repo root.
|
||||
|
||||
.PARAMETER ProjectRoot
|
||||
Scan root for env-like files. In monorepo layout: repo root. In portable ZIP: defaults to USERPROFILE.
|
||||
Repo root to scan for env-like files. Default: detected from script location.
|
||||
|
||||
.PARAMETER Format
|
||||
json | md | both
|
||||
|
|
@ -27,31 +27,8 @@ param(
|
|||
|
||||
$ErrorActionPreference = "SilentlyContinue"
|
||||
$scriptDir = Split-Path -Parent $MyInvocation.MyCommand.Path
|
||||
$defaultRepoFromLayout = (Resolve-Path (Join-Path $scriptDir "..\..")).Path
|
||||
$monorepoMarker = Join-Path $defaultRepoFromLayout "package.json"
|
||||
$expectedScriptInRepo = Join-Path $defaultRepoFromLayout "tools\pc-audit\Invoke-PcAudit.ps1"
|
||||
$currentScriptPath = $MyInvocation.MyCommand.Path
|
||||
if (-not $currentScriptPath) { $currentScriptPath = (Join-Path $scriptDir "Invoke-PcAudit.ps1") }
|
||||
$isMonorepoLayout = $false
|
||||
if ((Test-Path -LiteralPath $monorepoMarker) -and (Test-Path -LiteralPath $expectedScriptInRepo)) {
|
||||
try {
|
||||
$expPath = (Resolve-Path -LiteralPath $expectedScriptInRepo).Path
|
||||
$curPath = (Resolve-Path -LiteralPath $currentScriptPath).Path
|
||||
if ($expPath -eq $curPath) { $isMonorepoLayout = $true }
|
||||
} catch {}
|
||||
}
|
||||
if ($isMonorepoLayout) {
|
||||
$repoRoot = $defaultRepoFromLayout
|
||||
} else {
|
||||
$repoRoot = $scriptDir
|
||||
}
|
||||
if (-not $ProjectRoot) {
|
||||
if ($isMonorepoLayout) {
|
||||
$ProjectRoot = $repoRoot
|
||||
} else {
|
||||
$ProjectRoot = $env:USERPROFILE
|
||||
}
|
||||
}
|
||||
$repoRoot = (Resolve-Path (Join-Path $scriptDir "..\..")).Path
|
||||
if (-not $ProjectRoot) { $ProjectRoot = $repoRoot }
|
||||
if (-not $OutDir) { $OutDir = Join-Path $scriptDir "out" }
|
||||
|
||||
if (-not (Test-Path -LiteralPath $OutDir)) {
|
||||
|
|
@ -881,11 +858,7 @@ Write-Host " latest.json: $latestPath"
|
|||
if ($Format -eq "md" -or $Format -eq "both") {
|
||||
Write-Host " MD: $mdPath"
|
||||
}
|
||||
if ($isMonorepoLayout) {
|
||||
$viewerPath = Join-Path $repoRoot "tools\pc-audit\report-viewer.html"
|
||||
} else {
|
||||
$viewerPath = Join-Path $scriptDir "report-viewer.html"
|
||||
}
|
||||
$viewerPath = Join-Path $repoRoot "tools\pc-audit\report-viewer.html"
|
||||
Write-Host ""
|
||||
Write-Host "ブラウザで見る: エクスプローラーで次の HTML を開き、同じく out フォルダの JSON を選んでください。"
|
||||
Write-Host " $viewerPath"
|
||||
|
|
|
|||
|
|
@ -36,7 +36,7 @@
|
|||
}
|
||||
|
||||
/* Layout */
|
||||
.page { padding: 2rem 1.5rem 4rem; }
|
||||
.page { max-width: 1280px; padding: 2rem 1.5rem 4rem; }
|
||||
|
||||
.header { margin-bottom: 2rem; }
|
||||
.header h1 { font-size: 1.1rem; font-weight: 600; letter-spacing: 0.05em; color: var(--text2); }
|
||||
|
|
@ -90,13 +90,12 @@
|
|||
text-transform: uppercase; color: var(--text3); margin: 0 0 0.6rem;
|
||||
}
|
||||
|
||||
/* Metric grid — 2-col, PC名 spans full width */
|
||||
.summary-grid { display: grid; grid-template-columns: 1fr 1fr; gap: 0.5rem; margin-bottom: 0.75rem; }
|
||||
/* Metric grid */
|
||||
.summary-grid { display: flex; flex-direction: column; gap: 0.5rem; margin-bottom: 0.75rem; }
|
||||
.metric {
|
||||
background: var(--surface); border: 1px solid var(--border);
|
||||
border-radius: var(--radius-sm); padding: 0.7rem 0.9rem;
|
||||
}
|
||||
.metric.full { grid-column: 1 / -1; }
|
||||
.metric-label { font-size: 0.7rem; color: var(--text3); margin-bottom: 0.15rem; }
|
||||
.metric-value { font-size: 0.9rem; font-weight: 600; color: var(--text); word-break: break-all; }
|
||||
|
||||
|
|
@ -298,8 +297,7 @@
|
|||
|
||||
var html = '<div class="summary-grid">';
|
||||
items.forEach(function (it) {
|
||||
var full = (it.label === "PC 名" || it.label === "モード") ? ' full' : '';
|
||||
html += '<div class="metric' + full + '"><div class="metric-label">' + esc(it.label) + '</div><div class="metric-value">' + esc(it.value) + '</div></div>';
|
||||
html += '<div class="metric"><div class="metric-label">' + esc(it.label) + '</div><div class="metric-value">' + esc(it.value) + '</div></div>';
|
||||
});
|
||||
html += '</div>';
|
||||
document.getElementById("summary").innerHTML = html;
|
||||
|
|
|
|||
Loading…
Reference in New Issue