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 — グループ作成
|
// 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 || {};
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,4 @@
|
||||||
#Requires -Version 5.1
|
#Requires -Version 5.1
|
||||||
<#
|
<#
|
||||||
.SYNOPSIS
|
.SYNOPSIS
|
||||||
Read-only local PC audit (developer / AI-tooling focused). No writes except report output.
|
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.
|
Directory for reports (created if missing). Default: tools/pc-audit/out under repo root.
|
||||||
|
|
||||||
.PARAMETER ProjectRoot
|
.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
|
.PARAMETER Format
|
||||||
json | md | both
|
json | md | both
|
||||||
|
|
@ -27,31 +27,8 @@ param(
|
||||||
|
|
||||||
$ErrorActionPreference = "SilentlyContinue"
|
$ErrorActionPreference = "SilentlyContinue"
|
||||||
$scriptDir = Split-Path -Parent $MyInvocation.MyCommand.Path
|
$scriptDir = Split-Path -Parent $MyInvocation.MyCommand.Path
|
||||||
$defaultRepoFromLayout = (Resolve-Path (Join-Path $scriptDir "..\..")).Path
|
$repoRoot = (Resolve-Path (Join-Path $scriptDir "..\..")).Path
|
||||||
$monorepoMarker = Join-Path $defaultRepoFromLayout "package.json"
|
if (-not $ProjectRoot) { $ProjectRoot = $repoRoot }
|
||||||
$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
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (-not $OutDir) { $OutDir = Join-Path $scriptDir "out" }
|
if (-not $OutDir) { $OutDir = Join-Path $scriptDir "out" }
|
||||||
|
|
||||||
if (-not (Test-Path -LiteralPath $OutDir)) {
|
if (-not (Test-Path -LiteralPath $OutDir)) {
|
||||||
|
|
@ -881,11 +858,7 @@ Write-Host " latest.json: $latestPath"
|
||||||
if ($Format -eq "md" -or $Format -eq "both") {
|
if ($Format -eq "md" -or $Format -eq "both") {
|
||||||
Write-Host " MD: $mdPath"
|
Write-Host " MD: $mdPath"
|
||||||
}
|
}
|
||||||
if ($isMonorepoLayout) {
|
|
||||||
$viewerPath = Join-Path $repoRoot "tools\pc-audit\report-viewer.html"
|
$viewerPath = Join-Path $repoRoot "tools\pc-audit\report-viewer.html"
|
||||||
} else {
|
|
||||||
$viewerPath = Join-Path $scriptDir "report-viewer.html"
|
|
||||||
}
|
|
||||||
Write-Host ""
|
Write-Host ""
|
||||||
Write-Host "ブラウザで見る: エクスプローラーで次の HTML を開き、同じく out フォルダの JSON を選んでください。"
|
Write-Host "ブラウザで見る: エクスプローラーで次の HTML を開き、同じく out フォルダの JSON を選んでください。"
|
||||||
Write-Host " $viewerPath"
|
Write-Host " $viewerPath"
|
||||||
|
|
|
||||||
|
|
@ -36,7 +36,7 @@
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Layout */
|
/* Layout */
|
||||||
.page { padding: 2rem 1.5rem 4rem; }
|
.page { max-width: 1280px; padding: 2rem 1.5rem 4rem; }
|
||||||
|
|
||||||
.header { margin-bottom: 2rem; }
|
.header { margin-bottom: 2rem; }
|
||||||
.header h1 { font-size: 1.1rem; font-weight: 600; letter-spacing: 0.05em; color: var(--text2); }
|
.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;
|
text-transform: uppercase; color: var(--text3); margin: 0 0 0.6rem;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Metric grid — 2-col, PC名 spans full width */
|
/* Metric grid */
|
||||||
.summary-grid { display: grid; grid-template-columns: 1fr 1fr; gap: 0.5rem; margin-bottom: 0.75rem; }
|
.summary-grid { display: flex; flex-direction: column; gap: 0.5rem; margin-bottom: 0.75rem; }
|
||||||
.metric {
|
.metric {
|
||||||
background: var(--surface); border: 1px solid var(--border);
|
background: var(--surface); border: 1px solid var(--border);
|
||||||
border-radius: var(--radius-sm); padding: 0.7rem 0.9rem;
|
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-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; }
|
.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">';
|
var html = '<div class="summary-grid">';
|
||||||
items.forEach(function (it) {
|
items.forEach(function (it) {
|
||||||
var full = (it.label === "PC 名" || it.label === "モード") ? ' full' : '';
|
html += '<div class="metric"><div class="metric-label">' + esc(it.label) + '</div><div class="metric-value">' + esc(it.value) + '</div></div>';
|
||||||
html += '<div class="metric' + full + '"><div class="metric-label">' + esc(it.label) + '</div><div class="metric-value">' + esc(it.value) + '</div></div>';
|
|
||||||
});
|
});
|
||||||
html += '</div>';
|
html += '</div>';
|
||||||
document.getElementById("summary").innerHTML = html;
|
document.getElementById("summary").innerHTML = html;
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue