読み込み中...
;
+ if (error) return
+
+
RSS 配信エンドポイント
+
以下のRSSフィードURLをポッドキャストアプリに追加して音声コンテンツをお楽しみください。
+
+
+ {/* メインフィード */}
+
+ 📻 メインフィード
+
+
+
{endpoints.main.title}
+ 全エピソード
+
+
{endpoints.main.description}
+
+
{endpoints.main.url}
+
+
+
+
+
+
+
+
+ {/* カテゴリ別フィード */}
+ {endpoints.categories.length > 0 && (
+
+ 🏷️ カテゴリ別フィード
+
+ {endpoints.categories.map((endpoint, index) => (
+
+
+
{endpoint.title}
+ カテゴリ
+
+
{endpoint.description}
+
+
{endpoint.url}
+
+
+
+
+
+
+ ))}
+
+
+ )}
+
+ {/* フィード別フィード */}
+ {endpoints.feeds.length > 0 && (
+
+ 📡 フィード別配信
+
+ {endpoints.feeds.map((endpoint, index) => (
+
+
+
{endpoint.title}
+
+ フィード
+ {endpoint.feedCategory && (
+
+ {endpoint.feedCategory}
+
+ )}
+
+
+
{endpoint.description}
+
+
{endpoint.url}
+
+
+
+
+
+
+ ))}
+
+
+ )}
+
+ {/* 使用方法の説明 */}
+
+ 📱 使用方法
+
+
+
1. ポッドキャストアプリを選択
+
Apple Podcasts、Google Podcasts、Spotify、Pocket Casts など、RSS フィードに対応したポッドキャストアプリを使用してください。
+
+
+
2. RSS フィードを追加
+
上記のURLをコピーして、ポッドキャストアプリの「フィードを追加」や「URLから購読」機能を使用してください。
+
+
+
3. エピソードを楽しむ
+
フィードが追加されると、新しいエピソードが自動的にアプリに配信されます。
+
+
+
+
+ );
+}
\ No newline at end of file
diff --git a/frontend/src/styles.css b/frontend/src/styles.css
index 9b4f391..5357b03 100644
--- a/frontend/src/styles.css
+++ b/frontend/src/styles.css
@@ -223,3 +223,301 @@ body {
display: flex;
gap: 10px;
}
+
+/* Feed management specific styles */
+.feed-manager {
+ max-width: 800px;
+ margin: 0 auto;
+}
+
+.feed-section {
+ margin-bottom: 2rem;
+ padding: 1.5rem;
+ background: #f8f9fa;
+ border-radius: 8px;
+ border: 1px solid #e9ecef;
+}
+
+.feed-section h2 {
+ margin: 0 0 1rem 0;
+ color: #495057;
+ font-size: 1.5rem;
+ font-weight: 600;
+}
+
+/* RSS Endpoints specific styles */
+.rss-endpoints {
+ max-width: 1200px;
+ margin: 0 auto;
+ padding: 1rem;
+}
+
+.rss-endpoints-header {
+ text-align: center;
+ margin-bottom: 2rem;
+ padding: 1.5rem;
+ background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
+ color: white;
+ border-radius: 12px;
+}
+
+.rss-endpoints-header h1 {
+ margin: 0 0 0.5rem 0;
+ font-size: 2rem;
+ font-weight: 700;
+}
+
+.rss-endpoints-header p {
+ margin: 0;
+ opacity: 0.9;
+ font-size: 1.1rem;
+}
+
+.rss-section {
+ margin-bottom: 3rem;
+}
+
+.rss-section h2 {
+ margin: 0 0 1.5rem 0;
+ color: #2c3e50;
+ font-size: 1.5rem;
+ font-weight: 600;
+ border-bottom: 2px solid #3498db;
+ padding-bottom: 0.5rem;
+}
+
+.rss-endpoints-grid {
+ display: grid;
+ grid-template-columns: repeat(auto-fit, minmax(400px, 1fr));
+ gap: 1.5rem;
+}
+
+.rss-endpoint-card {
+ background: white;
+ border: 1px solid #e1e8ed;
+ border-radius: 12px;
+ padding: 1.5rem;
+ box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1);
+ transition: all 0.2s ease;
+ position: relative;
+}
+
+.rss-endpoint-card:hover {
+ box-shadow: 0 4px 16px rgba(0, 0, 0, 0.15);
+ transform: translateY(-2px);
+}
+
+.rss-endpoint-card.main-feed {
+ grid-column: 1 / -1;
+ background: linear-gradient(135deg, #f093fb 0%, #f5576c 100%);
+ color: white;
+ border: none;
+}
+
+.rss-endpoint-card.main-feed .endpoint-header h3 {
+ color: white;
+}
+
+.rss-endpoint-card.main-feed .endpoint-description {
+ color: rgba(255, 255, 255, 0.9);
+}
+
+.endpoint-header {
+ display: flex;
+ justify-content: space-between;
+ align-items: flex-start;
+ margin-bottom: 1rem;
+ flex-wrap: wrap;
+ gap: 0.5rem;
+}
+
+.endpoint-header h3 {
+ margin: 0;
+ color: #2c3e50;
+ font-size: 1.2rem;
+ font-weight: 600;
+ flex: 1;
+ min-width: 200px;
+}
+
+.endpoint-badges {
+ display: flex;
+ gap: 0.5rem;
+ flex-wrap: wrap;
+}
+
+.endpoint-badge {
+ padding: 0.25rem 0.75rem;
+ border-radius: 20px;
+ font-size: 0.8rem;
+ font-weight: 600;
+ text-transform: uppercase;
+ letter-spacing: 0.5px;
+}
+
+.endpoint-badge.main {
+ background: rgba(255, 255, 255, 0.2);
+ color: white;
+}
+
+.endpoint-badge.category {
+ background: #e3f2fd;
+ color: #1976d2;
+}
+
+.endpoint-badge.feed {
+ background: #f3e5f5;
+ color: #7b1fa2;
+}
+
+.endpoint-badge.category-tag {
+ background: #e8f5e8;
+ color: #2e7d32;
+}
+
+.endpoint-description {
+ color: #666;
+ margin-bottom: 1rem;
+ line-height: 1.5;
+}
+
+.endpoint-url {
+ background: #f8f9fa;
+ border: 1px solid #e9ecef;
+ border-radius: 8px;
+ padding: 1rem;
+ display: flex;
+ justify-content: space-between;
+ align-items: center;
+ gap: 1rem;
+ flex-wrap: wrap;
+}
+
+.rss-endpoint-card.main-feed .endpoint-url {
+ background: rgba(255, 255, 255, 0.1);
+ border: 1px solid rgba(255, 255, 255, 0.2);
+}
+
+.endpoint-url code {
+ background: none;
+ border: none;
+ color: #495057;
+ font-family: 'Monaco', 'Menlo', 'Ubuntu Mono', monospace;
+ font-size: 0.9rem;
+ word-break: break-all;
+ flex: 1;
+ min-width: 200px;
+}
+
+.rss-endpoint-card.main-feed .endpoint-url code {
+ color: rgba(255, 255, 255, 0.9);
+}
+
+.endpoint-actions {
+ display: flex;
+ gap: 0.5rem;
+ flex-shrink: 0;
+}
+
+.copy-btn, .open-btn {
+ background: #3498db;
+ color: white;
+ border: none;
+ border-radius: 6px;
+ padding: 0.5rem;
+ cursor: pointer;
+ transition: all 0.2s ease;
+ font-size: 1rem;
+ width: 40px;
+ height: 40px;
+ display: flex;
+ align-items: center;
+ justify-content: center;
+}
+
+.copy-btn:hover, .open-btn:hover {
+ background: #2980b9;
+ transform: scale(1.05);
+}
+
+.copy-btn.copied {
+ background: #27ae60;
+}
+
+.rss-endpoint-card.main-feed .copy-btn,
+.rss-endpoint-card.main-feed .open-btn {
+ background: rgba(255, 255, 255, 0.2);
+ color: white;
+}
+
+.rss-endpoint-card.main-feed .copy-btn:hover,
+.rss-endpoint-card.main-feed .open-btn:hover {
+ background: rgba(255, 255, 255, 0.3);
+}
+
+.rss-endpoint-card.main-feed .copy-btn.copied {
+ background: rgba(39, 174, 96, 0.8);
+}
+
+.usage-info {
+ background: #f8f9fa;
+ border-radius: 12px;
+ padding: 2rem;
+ margin-top: 3rem;
+}
+
+.usage-cards {
+ display: grid;
+ grid-template-columns: repeat(auto-fit, minmax(300px, 1fr));
+ gap: 1.5rem;
+ margin-top: 1.5rem;
+}
+
+.usage-card {
+ background: white;
+ border-radius: 8px;
+ padding: 1.5rem;
+ box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);
+}
+
+.usage-card h3 {
+ margin: 0 0 1rem 0;
+ color: #2c3e50;
+ font-size: 1.1rem;
+ font-weight: 600;
+}
+
+.usage-card p {
+ margin: 0;
+ color: #666;
+ line-height: 1.6;
+}
+
+@media (max-width: 768px) {
+ .rss-endpoints {
+ padding: 0.5rem;
+ }
+
+ .rss-endpoints-grid {
+ grid-template-columns: 1fr;
+ }
+
+ .endpoint-url {
+ flex-direction: column;
+ align-items: stretch;
+ gap: 0.75rem;
+ }
+
+ .endpoint-url code {
+ min-width: unset;
+ text-align: center;
+ }
+
+ .endpoint-actions {
+ justify-content: center;
+ }
+
+ .usage-cards {
+ grid-template-columns: 1fr;
+ }
+}
diff --git a/schema.sql b/schema.sql
index dac68ba..856e584 100644
--- a/schema.sql
+++ b/schema.sql
@@ -31,6 +31,7 @@ CREATE TABLE IF NOT EXISTS episodes (
audio_path TEXT NOT NULL,
duration INTEGER,
file_size INTEGER,
+ category TEXT,
created_at TEXT NOT NULL,
FOREIGN KEY(article_id) REFERENCES articles(id)
);
diff --git a/scripts/fetch_and_generate.ts b/scripts/fetch_and_generate.ts
index 09bf690..e5545b9 100644
--- a/scripts/fetch_and_generate.ts
+++ b/scripts/fetch_and_generate.ts
@@ -14,6 +14,7 @@ import {
} from "../services/database.js";
import {
openAI_ClassifyFeed,
+ openAI_ClassifyEpisode,
openAI_GeneratePodcastContent,
} from "../services/llm.js";
import { updatePodcastRSS } from "../services/podcast.js";
@@ -352,7 +353,7 @@ async function processRetryQueue(abortSignal?: AbortSignal): Promise