Add category deletion feature
This commit is contained in:
@ -74,6 +74,17 @@ interface Setting {
|
|||||||
updatedAt: string;
|
updatedAt: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
interface CategoryData {
|
||||||
|
feedCategories: string[];
|
||||||
|
episodeCategories: string[];
|
||||||
|
allCategories: string[];
|
||||||
|
}
|
||||||
|
|
||||||
|
interface CategoryCounts {
|
||||||
|
feedCount: number;
|
||||||
|
episodeCount: number;
|
||||||
|
}
|
||||||
|
|
||||||
function App() {
|
function App() {
|
||||||
const [feeds, setFeeds] = useState<Feed[]>([]);
|
const [feeds, setFeeds] = useState<Feed[]>([]);
|
||||||
const [stats, setStats] = useState<Stats | null>(null);
|
const [stats, setStats] = useState<Stats | null>(null);
|
||||||
@ -92,8 +103,10 @@ function App() {
|
|||||||
{},
|
{},
|
||||||
);
|
);
|
||||||
const [editingSettings, setEditingSettings] = useState<{ [key: string]: string }>({});
|
const [editingSettings, setEditingSettings] = useState<{ [key: string]: string }>({});
|
||||||
|
const [categories, setCategories] = useState<CategoryData>({ feedCategories: [], episodeCategories: [], allCategories: [] });
|
||||||
|
const [categoryCounts, setCategoryCounts] = useState<{ [category: string]: CategoryCounts }>({});
|
||||||
const [activeTab, setActiveTab] = useState<
|
const [activeTab, setActiveTab] = useState<
|
||||||
"dashboard" | "feeds" | "episodes" | "env" | "settings" | "batch" | "requests"
|
"dashboard" | "feeds" | "episodes" | "env" | "settings" | "batch" | "requests" | "categories"
|
||||||
>("dashboard");
|
>("dashboard");
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
@ -103,7 +116,7 @@ function App() {
|
|||||||
const loadData = async () => {
|
const loadData = async () => {
|
||||||
setLoading(true);
|
setLoading(true);
|
||||||
try {
|
try {
|
||||||
const [feedsRes, statsRes, envRes, settingsRes, requestsRes, episodesRes] =
|
const [feedsRes, statsRes, envRes, settingsRes, requestsRes, episodesRes, categoriesRes] =
|
||||||
await Promise.all([
|
await Promise.all([
|
||||||
fetch("/api/admin/feeds"),
|
fetch("/api/admin/feeds"),
|
||||||
fetch("/api/admin/stats"),
|
fetch("/api/admin/stats"),
|
||||||
@ -111,6 +124,7 @@ function App() {
|
|||||||
fetch("/api/admin/settings"),
|
fetch("/api/admin/settings"),
|
||||||
fetch("/api/admin/feed-requests"),
|
fetch("/api/admin/feed-requests"),
|
||||||
fetch("/api/admin/episodes"),
|
fetch("/api/admin/episodes"),
|
||||||
|
fetch("/api/admin/categories/all"),
|
||||||
]);
|
]);
|
||||||
|
|
||||||
if (
|
if (
|
||||||
@ -119,12 +133,13 @@ function App() {
|
|||||||
!envRes.ok ||
|
!envRes.ok ||
|
||||||
!settingsRes.ok ||
|
!settingsRes.ok ||
|
||||||
!requestsRes.ok ||
|
!requestsRes.ok ||
|
||||||
!episodesRes.ok
|
!episodesRes.ok ||
|
||||||
|
!categoriesRes.ok
|
||||||
) {
|
) {
|
||||||
throw new Error("Failed to load data");
|
throw new Error("Failed to load data");
|
||||||
}
|
}
|
||||||
|
|
||||||
const [feedsData, statsData, envData, settingsData, requestsData, episodesData] =
|
const [feedsData, statsData, envData, settingsData, requestsData, episodesData, categoriesData] =
|
||||||
await Promise.all([
|
await Promise.all([
|
||||||
feedsRes.json(),
|
feedsRes.json(),
|
||||||
statsRes.json(),
|
statsRes.json(),
|
||||||
@ -132,6 +147,7 @@ function App() {
|
|||||||
settingsRes.json(),
|
settingsRes.json(),
|
||||||
requestsRes.json(),
|
requestsRes.json(),
|
||||||
episodesRes.json(),
|
episodesRes.json(),
|
||||||
|
categoriesRes.json(),
|
||||||
]);
|
]);
|
||||||
|
|
||||||
setFeeds(feedsData);
|
setFeeds(feedsData);
|
||||||
@ -140,6 +156,25 @@ function App() {
|
|||||||
setSettings(settingsData);
|
setSettings(settingsData);
|
||||||
setFeedRequests(requestsData);
|
setFeedRequests(requestsData);
|
||||||
setEpisodes(episodesData);
|
setEpisodes(episodesData);
|
||||||
|
setCategories(categoriesData);
|
||||||
|
|
||||||
|
// Load category counts for all categories
|
||||||
|
const countsPromises = categoriesData.allCategories.map(async (category: string) => {
|
||||||
|
const res = await fetch(`/api/admin/categories/${encodeURIComponent(category)}/counts`);
|
||||||
|
if (res.ok) {
|
||||||
|
const counts = await res.json();
|
||||||
|
return { category, counts };
|
||||||
|
}
|
||||||
|
return { category, counts: { feedCount: 0, episodeCount: 0 } };
|
||||||
|
});
|
||||||
|
|
||||||
|
const countsResults = await Promise.all(countsPromises);
|
||||||
|
const countsMap: { [category: string]: CategoryCounts } = {};
|
||||||
|
countsResults.forEach(({ category, counts }) => {
|
||||||
|
countsMap[category] = counts;
|
||||||
|
});
|
||||||
|
setCategoryCounts(countsMap);
|
||||||
|
|
||||||
setError(null);
|
setError(null);
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
setError("データの読み込みに失敗しました");
|
setError("データの読み込みに失敗しました");
|
||||||
@ -391,6 +426,41 @@ function App() {
|
|||||||
setEditingSettings({ ...editingSettings, [key]: value });
|
setEditingSettings({ ...editingSettings, [key]: value });
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const deleteCategory = async (category: string, target: "feeds" | "episodes" | "both") => {
|
||||||
|
const targetText = target === "both" ? "フィードとエピソード" : target === "feeds" ? "フィード" : "エピソード";
|
||||||
|
const counts = categoryCounts[category] || { feedCount: 0, episodeCount: 0 };
|
||||||
|
const totalCount = target === "both" ? counts.feedCount + counts.episodeCount :
|
||||||
|
target === "feeds" ? counts.feedCount : counts.episodeCount;
|
||||||
|
|
||||||
|
if (
|
||||||
|
!confirm(
|
||||||
|
`本当にカテゴリ「${category}」を${targetText}から削除しますか?\n\n${totalCount}件のアイテムが影響を受けます。この操作は取り消せません。`
|
||||||
|
)
|
||||||
|
) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
const res = await fetch(`/api/admin/categories/${encodeURIComponent(category)}`, {
|
||||||
|
method: "DELETE",
|
||||||
|
headers: { "Content-Type": "application/json" },
|
||||||
|
body: JSON.stringify({ target }),
|
||||||
|
});
|
||||||
|
|
||||||
|
const data = await res.json();
|
||||||
|
|
||||||
|
if (res.ok) {
|
||||||
|
setSuccess(data.message);
|
||||||
|
loadData(); // Reload data to update category list and counts
|
||||||
|
} else {
|
||||||
|
setError(data.error || "カテゴリ削除に失敗しました");
|
||||||
|
}
|
||||||
|
} catch (err) {
|
||||||
|
setError("カテゴリ削除に失敗しました");
|
||||||
|
console.error("Error deleting category:", err);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
const deleteEpisode = async (episodeId: string, episodeTitle: string) => {
|
const deleteEpisode = async (episodeId: string, episodeTitle: string) => {
|
||||||
if (
|
if (
|
||||||
!confirm(
|
!confirm(
|
||||||
@ -483,6 +553,12 @@ function App() {
|
|||||||
>
|
>
|
||||||
設定管理
|
設定管理
|
||||||
</button>
|
</button>
|
||||||
|
<button
|
||||||
|
className={`btn ${activeTab === "categories" ? "btn-primary" : "btn-secondary"}`}
|
||||||
|
onClick={() => setActiveTab("categories")}
|
||||||
|
>
|
||||||
|
カテゴリ管理
|
||||||
|
</button>
|
||||||
<button
|
<button
|
||||||
className={`btn ${activeTab === "env" ? "btn-primary" : "btn-secondary"}`}
|
className={`btn ${activeTab === "env" ? "btn-primary" : "btn-secondary"}`}
|
||||||
onClick={() => setActiveTab("env")}
|
onClick={() => setActiveTab("env")}
|
||||||
@ -1312,6 +1388,153 @@ function App() {
|
|||||||
</>
|
</>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
|
{activeTab === "categories" && (
|
||||||
|
<>
|
||||||
|
<h3>カテゴリ管理</h3>
|
||||||
|
<p style={{ marginBottom: "20px", color: "#7f8c8d" }}>
|
||||||
|
フィードとエピソードのカテゴリを管理できます。不要なカテゴリを削除してデータベースをクリーンアップできます。
|
||||||
|
</p>
|
||||||
|
|
||||||
|
<div style={{ marginBottom: "20px" }}>
|
||||||
|
<div className="stats-grid" style={{ gridTemplateColumns: "repeat(3, 1fr)" }}>
|
||||||
|
<div className="stat-card">
|
||||||
|
<div className="value">{categories.allCategories.length}</div>
|
||||||
|
<div className="label">総カテゴリ数</div>
|
||||||
|
</div>
|
||||||
|
<div className="stat-card">
|
||||||
|
<div className="value">{categories.feedCategories.length}</div>
|
||||||
|
<div className="label">フィードカテゴリ</div>
|
||||||
|
</div>
|
||||||
|
<div className="stat-card">
|
||||||
|
<div className="value">{categories.episodeCategories.length}</div>
|
||||||
|
<div className="label">エピソードカテゴリ</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{categories.allCategories.length === 0 ? (
|
||||||
|
<p
|
||||||
|
style={{
|
||||||
|
color: "#7f8c8d",
|
||||||
|
textAlign: "center",
|
||||||
|
padding: "20px",
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
カテゴリがありません
|
||||||
|
</p>
|
||||||
|
) : (
|
||||||
|
<div style={{ marginTop: "24px" }}>
|
||||||
|
<h4>カテゴリ一覧 ({categories.allCategories.length}件)</h4>
|
||||||
|
<div style={{ marginBottom: "16px", fontSize: "14px", color: "#666" }}>
|
||||||
|
削除対象を選択してから削除ボタンをクリックしてください。
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="category-list">
|
||||||
|
{categories.allCategories.map((category) => {
|
||||||
|
const counts = categoryCounts[category] || { feedCount: 0, episodeCount: 0 };
|
||||||
|
const isInFeeds = categories.feedCategories.includes(category);
|
||||||
|
const isInEpisodes = categories.episodeCategories.includes(category);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div
|
||||||
|
key={category}
|
||||||
|
className="feed-item"
|
||||||
|
style={{ marginBottom: "16px" }}
|
||||||
|
>
|
||||||
|
<div className="feed-info">
|
||||||
|
<h4 style={{ margin: "0 0 8px 0", fontSize: "16px" }}>
|
||||||
|
{category}
|
||||||
|
</h4>
|
||||||
|
<div style={{ fontSize: "14px", color: "#666", marginBottom: "8px" }}>
|
||||||
|
<span>フィード: {counts.feedCount}件</span>
|
||||||
|
<span style={{ margin: "0 12px" }}>|</span>
|
||||||
|
<span>エピソード: {counts.episodeCount}件</span>
|
||||||
|
<span style={{ margin: "0 12px" }}>|</span>
|
||||||
|
<span>合計: {counts.feedCount + counts.episodeCount}件</span>
|
||||||
|
</div>
|
||||||
|
<div style={{ fontSize: "12px", color: "#999" }}>
|
||||||
|
<span style={{
|
||||||
|
padding: "2px 6px",
|
||||||
|
background: isInFeeds ? "#e3f2fd" : "#f5f5f5",
|
||||||
|
color: isInFeeds ? "#1976d2" : "#999",
|
||||||
|
borderRadius: "4px",
|
||||||
|
marginRight: "8px"
|
||||||
|
}}>
|
||||||
|
フィード: {isInFeeds ? "使用中" : "未使用"}
|
||||||
|
</span>
|
||||||
|
<span style={{
|
||||||
|
padding: "2px 6px",
|
||||||
|
background: isInEpisodes ? "#e8f5e8" : "#f5f5f5",
|
||||||
|
color: isInEpisodes ? "#388e3c" : "#999",
|
||||||
|
borderRadius: "4px"
|
||||||
|
}}>
|
||||||
|
エピソード: {isInEpisodes ? "使用中" : "未使用"}
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div className="feed-actions" style={{ flexDirection: "column", gap: "8px", minWidth: "160px" }}>
|
||||||
|
{isInFeeds && (
|
||||||
|
<button
|
||||||
|
className="btn btn-warning"
|
||||||
|
onClick={() => deleteCategory(category, "feeds")}
|
||||||
|
style={{ fontSize: "12px", padding: "6px 12px" }}
|
||||||
|
>
|
||||||
|
フィードから削除
|
||||||
|
</button>
|
||||||
|
)}
|
||||||
|
{isInEpisodes && (
|
||||||
|
<button
|
||||||
|
className="btn btn-warning"
|
||||||
|
onClick={() => deleteCategory(category, "episodes")}
|
||||||
|
style={{ fontSize: "12px", padding: "6px 12px" }}
|
||||||
|
>
|
||||||
|
エピソードから削除
|
||||||
|
</button>
|
||||||
|
)}
|
||||||
|
{(isInFeeds || isInEpisodes) && (
|
||||||
|
<button
|
||||||
|
className="btn btn-danger"
|
||||||
|
onClick={() => deleteCategory(category, "both")}
|
||||||
|
style={{ fontSize: "12px", padding: "6px 12px" }}
|
||||||
|
>
|
||||||
|
すべてから削除
|
||||||
|
</button>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
})}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
|
||||||
|
<div
|
||||||
|
style={{
|
||||||
|
marginTop: "20px",
|
||||||
|
padding: "16px",
|
||||||
|
background: "#f8f9fa",
|
||||||
|
borderRadius: "4px",
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<h4>カテゴリ削除について</h4>
|
||||||
|
<ul
|
||||||
|
style={{
|
||||||
|
fontSize: "14px",
|
||||||
|
color: "#6c757d",
|
||||||
|
marginTop: "8px",
|
||||||
|
paddingLeft: "20px",
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<li><strong>フィードから削除:</strong> フィードのカテゴリのみを削除します</li>
|
||||||
|
<li><strong>エピソードから削除:</strong> エピソードのカテゴリのみを削除します</li>
|
||||||
|
<li><strong>すべてから削除:</strong> フィードとエピソード両方からカテゴリを削除します</li>
|
||||||
|
<li>削除されたカテゴリは NULL に設定され、分類が解除されます</li>
|
||||||
|
<li>この操作は元に戻すことができません</li>
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
</>
|
||||||
|
)}
|
||||||
|
|
||||||
{activeTab === "env" && (
|
{activeTab === "env" && (
|
||||||
<>
|
<>
|
||||||
<h3>環境変数設定</h3>
|
<h3>環境変数設定</h3>
|
||||||
|
@ -14,6 +14,11 @@ import {
|
|||||||
fetchEpisodesWithArticles,
|
fetchEpisodesWithArticles,
|
||||||
getAllCategories,
|
getAllCategories,
|
||||||
getAllFeedsIncludingInactive,
|
getAllFeedsIncludingInactive,
|
||||||
|
getAllUsedCategories,
|
||||||
|
getCategoryCounts,
|
||||||
|
deleteCategoryFromBoth,
|
||||||
|
deleteFeedCategory,
|
||||||
|
deleteEpisodeCategory,
|
||||||
getFeedByUrl,
|
getFeedByUrl,
|
||||||
getFeedRequests,
|
getFeedRequests,
|
||||||
getFeedsByCategory,
|
getFeedsByCategory,
|
||||||
@ -345,6 +350,81 @@ app.delete("/api/admin/episodes/:id", async (c) => {
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// Category management API endpoints
|
||||||
|
app.get("/api/admin/categories/all", async (c) => {
|
||||||
|
try {
|
||||||
|
const categories = await getAllUsedCategories();
|
||||||
|
return c.json(categories);
|
||||||
|
} catch (error) {
|
||||||
|
console.error("Error fetching all used categories:", error);
|
||||||
|
return c.json({ error: "Failed to fetch categories" }, 500);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
app.get("/api/admin/categories/:category/counts", async (c) => {
|
||||||
|
try {
|
||||||
|
const category = decodeURIComponent(c.req.param("category"));
|
||||||
|
const counts = await getCategoryCounts(category);
|
||||||
|
return c.json(counts);
|
||||||
|
} catch (error) {
|
||||||
|
console.error("Error fetching category counts:", error);
|
||||||
|
return c.json({ error: "Failed to fetch category counts" }, 500);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
app.delete("/api/admin/categories/:category", async (c) => {
|
||||||
|
try {
|
||||||
|
const category = decodeURIComponent(c.req.param("category"));
|
||||||
|
const { target } = await c.req.json<{ target: "feeds" | "episodes" | "both" }>();
|
||||||
|
|
||||||
|
if (!category || category.trim() === "") {
|
||||||
|
return c.json({ error: "Category name is required" }, 400);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!target || !["feeds", "episodes", "both"].includes(target)) {
|
||||||
|
return c.json({ error: "Valid target (feeds, episodes, or both) is required" }, 400);
|
||||||
|
}
|
||||||
|
|
||||||
|
console.log(`🗑️ Admin deleting category "${category}" from ${target}`);
|
||||||
|
|
||||||
|
let result;
|
||||||
|
if (target === "both") {
|
||||||
|
result = await deleteCategoryFromBoth(category);
|
||||||
|
return c.json({
|
||||||
|
result: "DELETED",
|
||||||
|
message: `Category "${category}" deleted from feeds and episodes`,
|
||||||
|
category,
|
||||||
|
feedChanges: result.feedChanges,
|
||||||
|
episodeChanges: result.episodeChanges,
|
||||||
|
totalChanges: result.feedChanges + result.episodeChanges
|
||||||
|
});
|
||||||
|
} else if (target === "feeds") {
|
||||||
|
const changes = await deleteFeedCategory(category);
|
||||||
|
return c.json({
|
||||||
|
result: "DELETED",
|
||||||
|
message: `Category "${category}" deleted from feeds`,
|
||||||
|
category,
|
||||||
|
feedChanges: changes,
|
||||||
|
episodeChanges: 0,
|
||||||
|
totalChanges: changes
|
||||||
|
});
|
||||||
|
} else if (target === "episodes") {
|
||||||
|
const changes = await deleteEpisodeCategory(category);
|
||||||
|
return c.json({
|
||||||
|
result: "DELETED",
|
||||||
|
message: `Category "${category}" deleted from episodes`,
|
||||||
|
category,
|
||||||
|
feedChanges: 0,
|
||||||
|
episodeChanges: changes,
|
||||||
|
totalChanges: changes
|
||||||
|
});
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
console.error("Error deleting category:", error);
|
||||||
|
return c.json({ error: "Failed to delete category" }, 500);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
// Database diagnostic endpoint
|
// Database diagnostic endpoint
|
||||||
app.get("/api/admin/db-diagnostic", async (c) => {
|
app.get("/api/admin/db-diagnostic", async (c) => {
|
||||||
try {
|
try {
|
||||||
|
@ -1633,6 +1633,97 @@ export async function updateEpisodeCategory(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Category cleanup functions
|
||||||
|
export async function deleteFeedCategory(category: string): Promise<number> {
|
||||||
|
try {
|
||||||
|
const stmt = db.prepare("UPDATE feeds SET category = NULL WHERE category = ?");
|
||||||
|
const result = stmt.run(category);
|
||||||
|
return result.changes;
|
||||||
|
} catch (error) {
|
||||||
|
console.error("Error deleting feed category:", error);
|
||||||
|
throw error;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function deleteEpisodeCategory(category: string): Promise<number> {
|
||||||
|
try {
|
||||||
|
const stmt = db.prepare("UPDATE episodes SET category = NULL WHERE category = ?");
|
||||||
|
const result = stmt.run(category);
|
||||||
|
return result.changes;
|
||||||
|
} catch (error) {
|
||||||
|
console.error("Error deleting episode category:", error);
|
||||||
|
throw error;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function deleteCategoryFromBoth(category: string): Promise<{feedChanges: number, episodeChanges: number}> {
|
||||||
|
try {
|
||||||
|
db.exec("BEGIN TRANSACTION");
|
||||||
|
|
||||||
|
const feedChanges = await deleteFeedCategory(category);
|
||||||
|
const episodeChanges = await deleteEpisodeCategory(category);
|
||||||
|
|
||||||
|
db.exec("COMMIT");
|
||||||
|
|
||||||
|
return { feedChanges, episodeChanges };
|
||||||
|
} catch (error) {
|
||||||
|
db.exec("ROLLBACK");
|
||||||
|
console.error("Error deleting category from both tables:", error);
|
||||||
|
throw error;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function getAllUsedCategories(): Promise<{feedCategories: string[], episodeCategories: string[], allCategories: string[]}> {
|
||||||
|
try {
|
||||||
|
// Get feed categories
|
||||||
|
const feedCatStmt = db.prepare(
|
||||||
|
"SELECT DISTINCT category FROM feeds WHERE category IS NOT NULL AND category != '' ORDER BY category"
|
||||||
|
);
|
||||||
|
const feedCatRows = feedCatStmt.all() as any[];
|
||||||
|
const feedCategories = feedCatRows.map(row => row.category);
|
||||||
|
|
||||||
|
// Get episode categories
|
||||||
|
const episodeCatStmt = db.prepare(
|
||||||
|
"SELECT DISTINCT category FROM episodes WHERE category IS NOT NULL AND category != '' ORDER BY category"
|
||||||
|
);
|
||||||
|
const episodeCatRows = episodeCatStmt.all() as any[];
|
||||||
|
const episodeCategories = episodeCatRows.map(row => row.category);
|
||||||
|
|
||||||
|
// Get all unique categories
|
||||||
|
const allCategoriesSet = new Set([...feedCategories, ...episodeCategories]);
|
||||||
|
const allCategories = Array.from(allCategoriesSet).sort();
|
||||||
|
|
||||||
|
return {
|
||||||
|
feedCategories,
|
||||||
|
episodeCategories,
|
||||||
|
allCategories
|
||||||
|
};
|
||||||
|
} catch (error) {
|
||||||
|
console.error("Error getting all used categories:", error);
|
||||||
|
throw error;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function getCategoryCounts(category: string): Promise<{feedCount: number, episodeCount: number}> {
|
||||||
|
try {
|
||||||
|
// Count feeds with this category
|
||||||
|
const feedCountStmt = db.prepare("SELECT COUNT(*) as count FROM feeds WHERE category = ?");
|
||||||
|
const feedCountResult = feedCountStmt.get(category) as { count: number };
|
||||||
|
|
||||||
|
// Count episodes with this category
|
||||||
|
const episodeCountStmt = db.prepare("SELECT COUNT(*) as count FROM episodes WHERE category = ?");
|
||||||
|
const episodeCountResult = episodeCountStmt.get(category) as { count: number };
|
||||||
|
|
||||||
|
return {
|
||||||
|
feedCount: feedCountResult.count,
|
||||||
|
episodeCount: episodeCountResult.count
|
||||||
|
};
|
||||||
|
} catch (error) {
|
||||||
|
console.error("Error getting category counts:", error);
|
||||||
|
throw error;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Migration function to classify existing episodes without categories
|
// Migration function to classify existing episodes without categories
|
||||||
export async function migrateEpisodesWithCategories(): Promise<void> {
|
export async function migrateEpisodesWithCategories(): Promise<void> {
|
||||||
try {
|
try {
|
||||||
|
@ -127,7 +127,10 @@ ${articleDetails}
|
|||||||
try {
|
try {
|
||||||
const response = await openai.chat.completions.create({
|
const response = await openai.chat.completions.create({
|
||||||
model: config.openai.modelName,
|
model: config.openai.modelName,
|
||||||
messages: [{ role: "system", content: prompt.trim() }, {role:"user", content: sendContent.trim()}],
|
messages: [
|
||||||
|
{ role: "system", content: prompt.trim() },
|
||||||
|
{ role: "user", content: sendContent.trim() },
|
||||||
|
],
|
||||||
temperature: 0.6,
|
temperature: 0.6,
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -171,7 +174,9 @@ export async function openAI_ClassifyEpisode(
|
|||||||
}
|
}
|
||||||
|
|
||||||
const prompt = `
|
const prompt = `
|
||||||
ポッドキャストエピソードの情報を見て、適切なトピックカテゴリに分類してください。
|
以下のポッドキャストエピソードの情報を見て、適切なトピックカテゴリに分類してください。
|
||||||
|
|
||||||
|
${textForClassification}
|
||||||
|
|
||||||
以下のカテゴリから1つを選択してください:
|
以下のカテゴリから1つを選択してください:
|
||||||
- テクノロジー
|
- テクノロジー
|
||||||
@ -191,8 +196,8 @@ export async function openAI_ClassifyEpisode(
|
|||||||
try {
|
try {
|
||||||
const response = await openai.chat.completions.create({
|
const response = await openai.chat.completions.create({
|
||||||
model: config.openai.modelName,
|
model: config.openai.modelName,
|
||||||
messages: [{ role: "system", content: prompt.trim() }, {role: "user", content: textForClassification.trim()}],
|
messages: [{ role: "user", content: prompt.trim() }],
|
||||||
temperature: 0.3,
|
temperature: 0.2,
|
||||||
});
|
});
|
||||||
|
|
||||||
const category = response.choices[0]?.message?.content?.trim();
|
const category = response.choices[0]?.message?.content?.trim();
|
||||||
|
Reference in New Issue
Block a user