article-summarizer-jp
Version:
CLI tool for summarizing web articles in Japanese using Anthropic Claude API. Fetches content from URLs and generates both 3-line summaries and full translations in polite Japanese.
212 lines • 7.76 kB
JavaScript
import blessed from 'blessed';
import { config } from './config.js';
import { fetchContent } from './fetcher.js';
import { summarizeContent } from './summarizer.js';
import { saveToMarkdown } from './markdown.js';
export async function startWatchMode() {
if (!config.hasApiKey()) {
console.log('APIキーが設定されていません。最初に設定を行ってください。');
await config.configure();
}
// Suppress debug output from various libraries
process.env.NODE_ENV = 'production';
process.env.DEBUG = '';
process.env.PUPPETEER_DEBUG = '';
// Capture and suppress console output during watch mode to prevent layout corruption
const originalConsole = {
log: console.log,
error: console.error,
warn: console.warn,
info: console.info,
debug: console.debug
};
let logBox = null;
// Override console methods to redirect to blessed UI
const setupConsoleRedirection = () => {
console.log = (...args) => {
if (logBox) {
const timestamp = new Date().toLocaleTimeString();
const message = args.join(' ');
// Filter out CSS-like content and other unwanted output that might corrupt the display
const shouldFilter = (message.includes('{') ||
message.includes('css') ||
message.includes('style') ||
message.includes('font-') ||
message.includes('color:') ||
message.includes('background') ||
message.includes('margin') ||
message.includes('padding') ||
message.includes('.class') ||
message.includes('#id') ||
message.includes('@media') ||
message.includes('px') ||
message.includes('rem') ||
message.includes('vh') ||
message.includes('vw') ||
message.includes('%') && message.includes(';') ||
message.match(/[\{\}]/g) ||
message.length > 500 // Very long messages are likely debug output
);
if (!shouldFilter) {
logBox.log(`[${timestamp}] ${message}`);
}
}
};
console.error = (...args) => {
if (logBox) {
const timestamp = new Date().toLocaleTimeString();
logBox.log(`[${timestamp}] ERROR: ${args.join(' ')}`);
}
};
console.warn = (...args) => {
if (logBox) {
const timestamp = new Date().toLocaleTimeString();
logBox.log(`[${timestamp}] WARN: ${args.join(' ')}`);
}
};
// Suppress info and debug to reduce noise
console.info = () => { };
console.debug = () => { };
};
// Restore console when exiting
const restoreConsole = () => {
console.log = originalConsole.log;
console.error = originalConsole.error;
console.warn = originalConsole.warn;
console.info = originalConsole.info;
console.debug = originalConsole.debug;
};
// Create a simple blessed screen
const screen = blessed.screen({
smartCSR: true,
fullUnicode: true,
dockBorders: false
});
// Main container
const container = blessed.box({
parent: screen,
top: 0,
left: 0,
width: '100%',
height: '100%'
});
// Log area
logBox = blessed.log({
parent: container,
top: 0,
left: 0,
width: '100%',
height: '100%-3',
border: {
type: 'line'
},
label: ' 📄 ログ ',
scrollable: true,
alwaysScroll: true,
mouse: true,
keys: true,
tags: false
});
// Input area
const inputBox = blessed.textbox({
parent: container,
bottom: 0,
left: 0,
width: '100%',
height: 3,
border: {
type: 'line'
},
label: ' 🔗 URL入力 (Enterで送信) ',
inputOnFocus: true
});
const processingQueue = new Map();
const waitingQueue = [];
const maxConcurrent = 5;
const addLog = (message) => {
const timestamp = new Date().toLocaleTimeString();
logBox.log(`[${timestamp}] ${message}`);
screen.render();
};
const processNextFromQueue = () => {
if (waitingQueue.length > 0 && processingQueue.size < maxConcurrent) {
const nextUrl = waitingQueue.shift();
const processPromise = processUrl(nextUrl);
processingQueue.set(nextUrl, processPromise);
}
};
const processUrl = async (url) => {
try {
addLog(`📄 処理開始: ${url}`);
addLog(' 📥 コンテンツを取得中...');
const { title, extractedUrl, htmlContent } = await fetchContent(url, true);
addLog(' 🤖 要約・翻訳中...');
const { summary, details, translatedTitle, tags, validImageUrl } = await summarizeContent(title, htmlContent, extractedUrl, true);
addLog(' 💾 マークダウンファイルに保存中...');
const filename = await saveToMarkdown(translatedTitle, extractedUrl, summary, details, tags, validImageUrl);
addLog(`✅ 完了: ${filename}`);
}
catch (error) {
const errorMessage = error instanceof Error ? error.message : 'Unknown error';
addLog(`❌ エラー (${url}): ${errorMessage}`);
}
finally {
processingQueue.delete(url);
processNextFromQueue();
addLog(`⏳ 待機中... (処理中: ${processingQueue.size}/${maxConcurrent}, キュー: ${waitingQueue.length})`);
}
};
// Handle input
inputBox.on('submit', (input) => {
const url = input.trim();
inputBox.clearValue();
if (!url) {
inputBox.focus();
return;
}
try {
new URL(url);
}
catch {
addLog('❌ 無効なURL形式です');
inputBox.focus();
return;
}
if (processingQueue.has(url) || waitingQueue.includes(url)) {
addLog('⚠️ このURLは既にキューに登録されています');
inputBox.focus();
return;
}
if (processingQueue.size < maxConcurrent) {
const processPromise = processUrl(url);
processingQueue.set(url, processPromise);
addLog(`🚀 処理開始 (${processingQueue.size}/${maxConcurrent})`);
}
else {
waitingQueue.push(url);
addLog(`📋 キューに追加 (待機: ${waitingQueue.length}件)`);
}
inputBox.focus();
});
// Setup console redirection after logBox is created
setupConsoleRedirection();
// Handle exit
screen.key(['C-c'], () => {
restoreConsole();
screen.destroy();
process.exit(0);
});
// Also restore console on process exit
process.on('exit', restoreConsole);
process.on('SIGINT', () => {
restoreConsole();
process.exit(0);
});
// Initial messages
addLog('🔍 ウォッチモードを開始しました');
addLog('URLを入力してEnterキーで送信してください(最大5件並行処理、キューイング対応)');
addLog(`⏳ 待機中... (処理中: 0/${maxConcurrent}, キュー: 0)`);
screen.render();
inputBox.focus();
}
//# sourceMappingURL=watch.js.map