// Sentiment Analyst - Main Application const { useState, useEffect, useMemo } = React; const { Layout, Row, Col, Card, Button, Input, Select, Slider, Table, Tag, Statistic, Space, Typography, Popconfirm, message, Progress, Empty, Tooltip, ConfigProvider } = antd; const { GlobalOutlined, TwitterOutlined, FileTextOutlined, DeleteOutlined, SaveOutlined, SmileOutlined, MehOutlined, FrownOutlined, BarChartOutlined, LinkOutlined, GoogleOutlined, DesktopOutlined, TeamOutlined, QuestionCircleOutlined } = icons; const { Header, Content } = Layout; const { Title, Text, Paragraph } = Typography; // Initialize dayjs locale dayjs.locale('ja'); // Constants const STORAGE_KEY = 'sentiment_records'; const SOURCE_OPTIONS = [ { value: 'Yahoo', label: 'Yahoo!ニュース', color: 'red' }, { value: 'Google', label: 'Google News', color: 'blue' }, { value: 'SNS', label: 'SNS', color: 'cyan' }, { value: 'TV', label: 'TV', color: 'purple' }, { value: 'Work', label: '仕事関連', color: 'orange' }, { value: 'Other', label: 'その他', color: 'default' } ]; const NEWS_LINKS = [ { name: 'Yahoo!ニュース', url: 'https://news.yahoo.co.jp/topics', icon: }, { name: 'Google News', url: 'https://news.google.com/topstories?hl=ja&gl=JP&ceid=JP:ja', icon: }, { name: '日本経済新聞', url: 'https://www.nikkei.com/', icon: }, { name: 'NHK NEWS', url: 'https://www3.nhk.or.jp/news/', icon: } ]; // Utility functions const generateId = () => { return 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, (c) => { const r = Math.random() * 16 | 0; const v = c === 'x' ? r : (r & 0x3 | 0x8); return v.toString(16); }); }; const getScoreColor = (score) => { if (score > 10) return '#52c41a'; if (score < -10) return '#ff4d4f'; return '#faad14'; }; const getScoreClass = (score) => { if (score > 10) return 'score-positive'; if (score < -10) return 'score-negative'; return 'score-neutral'; }; const getSliderClass = (score) => { if (score > 10) return 'positive-slider'; if (score < -10) return 'negative-slider'; return 'neutral-slider'; }; const getScoreIcon = (score) => { if (score > 10) return ; if (score < -10) return ; return ; }; // Main App Component const App = () => { const [records, setRecords] = useState([]); const [headline, setHeadline] = useState(''); const [source, setSource] = useState('Yahoo'); const [score, setScore] = useState(0); const [messageApi, contextHolder] = message.useMessage(); // Load records from localStorage on mount useEffect(() => { try { const stored = localStorage.getItem(STORAGE_KEY); if (stored) { const parsed = JSON.parse(stored); setRecords(parsed); } } catch (error) { console.error('Failed to load records:', error); } }, []); // Save records to localStorage whenever they change useEffect(() => { try { localStorage.setItem(STORAGE_KEY, JSON.stringify(records)); } catch (error) { console.error('Failed to save records:', error); } }, [records]); // Calculate average score const averageScore = useMemo(() => { if (records.length === 0) return 0; const sum = records.reduce((acc, r) => acc + r.score, 0); return Math.round(sum / records.length); }, [records]); // Handle form submission const handleSubmit = () => { if (!headline.trim()) { messageApi.warning('見出しを入力してください'); return; } const newRecord = { id: generateId(), timestamp: new Date().toISOString(), headline: headline.trim(), source, score }; setRecords(prev => [newRecord, ...prev]); setHeadline(''); setScore(0); messageApi.success('記録を保存しました'); }; // Handle record deletion const handleDelete = (id) => { setRecords(prev => prev.filter(r => r.id !== id)); messageApi.success('記録を削除しました'); }; // Table columns configuration const columns = [ { title: '日付', dataIndex: 'timestamp', key: 'timestamp', width: 140, render: (timestamp) => (
{dayjs(timestamp).format('MM/DD HH:mm')}
), sorter: (a, b) => new Date(b.timestamp) - new Date(a.timestamp), defaultSortOrder: 'descend' }, { title: 'ソース', dataIndex: 'source', key: 'source', width: 110, render: (source) => { const opt = SOURCE_OPTIONS.find(o => o.value === source); return {source}; }, filters: SOURCE_OPTIONS.map(o => ({ text: o.label, value: o.value })), onFilter: (value, record) => record.source === value }, { title: '見出し', dataIndex: 'headline', key: 'headline', ellipsis: { showTitle: false, }, render: (text) => ( {text} ) }, { title: 'スコア', dataIndex: 'score', key: 'score', width: 140, align: 'center', render: (score) => (
{getScoreIcon(score)} {score > 0 ? `+${score}` : score}
), sorter: (a, b) => a.score - b.score }, { title: '', key: 'action', width: 70, align: 'center', render: (_, record) => ( handleDelete(record.id)} okText="削除" cancelText="キャンセル" okButtonProps={{ danger: true }} > ))} {/* Right Column - Analyze Console */} Analyze Console } > {/* Headline Input */}
ニュース見出し setHeadline(e.target.value)} placeholder="見出しをここにペースト..." autoSize={{ minRows: 2, maxRows: 4 }} maxLength={500} showCount />
{/* Source Select */} ソース