feat: add RSS account type - posts link directly without AI, update admin UI
This commit is contained in:
parent
da5a366cc1
commit
d8988dc870
|
|
@ -4,7 +4,7 @@ import AdminShell from '@/components/AdminShell';
|
|||
import { api } from '@/lib/api';
|
||||
import { useEffect, useState } from 'react';
|
||||
import {
|
||||
Plus, Play, Eye, Trash2, Power, PowerOff, RefreshCw, Newspaper,
|
||||
Plus, Play, Eye, Trash2, Power, PowerOff, RefreshCw, Newspaper, Rss,
|
||||
ChevronDown, ChevronUp, Bot, Clock, AlertCircle, CheckCircle, ExternalLink,
|
||||
} from 'lucide-react';
|
||||
|
||||
|
|
@ -270,7 +270,7 @@ export default function OfficialAccountsPage() {
|
|||
{/* Header */}
|
||||
<div className="p-4 flex items-center gap-4">
|
||||
<div className="w-10 h-10 bg-brand-100 rounded-full flex items-center justify-center flex-shrink-0">
|
||||
{cfg.account_type === 'news' ? <Newspaper className="w-5 h-5 text-brand-600" /> : <Bot className="w-5 h-5 text-brand-600" />}
|
||||
{cfg.account_type === 'news' ? <Newspaper className="w-5 h-5 text-brand-600" /> : cfg.account_type === 'rss' ? <Rss className="w-5 h-5 text-brand-600" /> : <Bot className="w-5 h-5 text-brand-600" />}
|
||||
</div>
|
||||
<div className="flex-1 min-w-0">
|
||||
<div className="flex items-center gap-2">
|
||||
|
|
@ -344,7 +344,7 @@ export default function OfficialAccountsPage() {
|
|||
</div>
|
||||
)}
|
||||
|
||||
{cfg.account_type === 'news' && sources.length > 0 && (
|
||||
{(cfg.account_type === 'news' || cfg.account_type === 'rss') && sources.length > 0 && (
|
||||
<div>
|
||||
<span className="text-sm font-medium text-gray-600">News Sources:</span>
|
||||
<div className="mt-1 space-y-1">
|
||||
|
|
@ -396,6 +396,9 @@ function CreateAccountForm({ onDone, initialProfile }: { onDone: () => void; ini
|
|||
if (accountType === 'news') {
|
||||
setNewsSources(DEFAULT_NEWS_SOURCES);
|
||||
setSystemPrompt(DEFAULT_NEWS_PROMPT);
|
||||
} else if (accountType === 'rss') {
|
||||
setNewsSources(DEFAULT_NEWS_SOURCES);
|
||||
setSystemPrompt('');
|
||||
} else {
|
||||
setNewsSources([]);
|
||||
setSystemPrompt(DEFAULT_GENERAL_PROMPT);
|
||||
|
|
@ -441,17 +444,22 @@ function CreateAccountForm({ onDone, initialProfile }: { onDone: () => void; ini
|
|||
<select value={accountType} onChange={(e) => setAccountType(e.target.value)}
|
||||
className="w-full px-3 py-2 border border-warm-300 rounded-lg text-sm">
|
||||
<option value="general">General</option>
|
||||
<option value="news">News</option>
|
||||
<option value="news">News (AI Commentary)</option>
|
||||
<option value="rss">RSS (Link Only)</option>
|
||||
<option value="community">Community</option>
|
||||
</select>
|
||||
</div>
|
||||
{accountType !== 'rss' && (
|
||||
<div>
|
||||
<label className="block text-sm font-medium text-gray-700 mb-1">Model</label>
|
||||
<ModelSelector value={modelId} onChange={setModelId} />
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
|
||||
<div className="grid grid-cols-4 gap-4 mb-4">
|
||||
{accountType !== 'rss' && (
|
||||
<>
|
||||
<div>
|
||||
<label className="block text-sm font-medium text-gray-700 mb-1">Temperature</label>
|
||||
<input type="number" step="0.1" min="0" max="2" value={temperature} onChange={(e) => setTemperature(Number(e.target.value))}
|
||||
|
|
@ -462,6 +470,8 @@ function CreateAccountForm({ onDone, initialProfile }: { onDone: () => void; ini
|
|||
<input type="number" min="50" max="4000" value={maxTokens} onChange={(e) => setMaxTokens(Number(e.target.value))}
|
||||
className="w-full px-3 py-2 border border-warm-300 rounded-lg text-sm" />
|
||||
</div>
|
||||
</>
|
||||
)}
|
||||
<div>
|
||||
<label className="block text-sm font-medium text-gray-700 mb-1">Interval (min)</label>
|
||||
<input type="number" min="5" value={intervalMin} onChange={(e) => setIntervalMin(Number(e.target.value))}
|
||||
|
|
@ -474,13 +484,15 @@ function CreateAccountForm({ onDone, initialProfile }: { onDone: () => void; ini
|
|||
</div>
|
||||
</div>
|
||||
|
||||
{accountType !== 'rss' && (
|
||||
<div className="mb-4">
|
||||
<label className="block text-sm font-medium text-gray-700 mb-1">System Prompt</label>
|
||||
<textarea value={systemPrompt} onChange={(e) => setSystemPrompt(e.target.value)} rows={6}
|
||||
className="w-full px-3 py-2 border border-warm-300 rounded-lg text-sm font-mono" />
|
||||
</div>
|
||||
)}
|
||||
|
||||
{accountType === 'news' && (
|
||||
{(accountType === 'news' || accountType === 'rss') && (
|
||||
<div className="mb-4">
|
||||
<label className="block text-sm font-medium text-gray-700 mb-2">News Sources (RSS Feeds)</label>
|
||||
{newsSources.map((src, i) => (
|
||||
|
|
@ -566,6 +578,8 @@ function EditAccountForm({ config, onDone }: { config: Config; onDone: () => voi
|
|||
<div className="border-t border-warm-200 pt-3 mt-3">
|
||||
<h3 className="text-sm font-semibold text-gray-700 mb-3">Edit Configuration</h3>
|
||||
<div className="grid grid-cols-4 gap-3 mb-3">
|
||||
{config.account_type !== 'rss' && (
|
||||
<>
|
||||
<div>
|
||||
<label className="block text-xs font-medium text-gray-600 mb-1">Model</label>
|
||||
<ModelSelector value={modelId} onChange={setModelId} />
|
||||
|
|
@ -575,6 +589,8 @@ function EditAccountForm({ config, onDone }: { config: Config; onDone: () => voi
|
|||
<input type="number" step="0.1" min="0" max="2" value={temperature} onChange={(e) => setTemperature(Number(e.target.value))}
|
||||
className="w-full px-2 py-1.5 border border-warm-300 rounded text-xs" />
|
||||
</div>
|
||||
</>
|
||||
)}
|
||||
<div>
|
||||
<label className="block text-xs font-medium text-gray-600 mb-1">Interval (min)</label>
|
||||
<input type="number" min="5" value={intervalMin} onChange={(e) => setIntervalMin(Number(e.target.value))}
|
||||
|
|
@ -586,13 +602,15 @@ function EditAccountForm({ config, onDone }: { config: Config; onDone: () => voi
|
|||
className="w-full px-2 py-1.5 border border-warm-300 rounded text-xs" />
|
||||
</div>
|
||||
</div>
|
||||
{config.account_type !== 'rss' && (
|
||||
<div className="mb-3">
|
||||
<label className="block text-xs font-medium text-gray-600 mb-1">System Prompt</label>
|
||||
<textarea value={systemPrompt} onChange={(e) => setSystemPrompt(e.target.value)} rows={4}
|
||||
className="w-full px-2 py-1.5 border border-warm-300 rounded text-xs font-mono" />
|
||||
</div>
|
||||
)}
|
||||
|
||||
{config.account_type === 'news' && (
|
||||
{(config.account_type === 'news' || config.account_type === 'rss') && (
|
||||
<div className="mb-3">
|
||||
<label className="block text-xs font-medium text-gray-600 mb-1">News Sources</label>
|
||||
{newsSources.map((src, i) => (
|
||||
|
|
|
|||
|
|
@ -668,9 +668,12 @@ func (s *OfficialAccountsService) runScheduledPosts() {
|
|||
}
|
||||
|
||||
// Time to post!
|
||||
if c.AccountType == "news" {
|
||||
switch c.AccountType {
|
||||
case "news":
|
||||
s.scheduleNewsPost(ctx, c.ID)
|
||||
} else {
|
||||
case "rss":
|
||||
s.scheduleRSSPost(ctx, c.ID)
|
||||
default:
|
||||
s.scheduleGeneralPost(ctx, c.ID)
|
||||
}
|
||||
}
|
||||
|
|
@ -702,6 +705,32 @@ func (s *OfficialAccountsService) scheduleNewsPost(ctx context.Context, configID
|
|||
_ = body // logged implicitly via post
|
||||
}
|
||||
|
||||
func (s *OfficialAccountsService) scheduleRSSPost(ctx context.Context, configID string) {
|
||||
items, sourceNames, err := s.FetchNewArticles(ctx, configID)
|
||||
if err != nil {
|
||||
log.Error().Err(err).Str("config", configID).Msg("[OfficialAccounts] Failed to fetch RSS articles")
|
||||
return
|
||||
}
|
||||
|
||||
if len(items) == 0 {
|
||||
log.Debug().Str("config", configID).Msg("[OfficialAccounts] No new RSS articles to post")
|
||||
return
|
||||
}
|
||||
|
||||
// Post the first new article — body is just the link
|
||||
article := items[0]
|
||||
sourceName := sourceNames[0]
|
||||
body := article.Link
|
||||
|
||||
postID, err := s.CreatePostForAccount(ctx, configID, body, &article, sourceName)
|
||||
if err != nil {
|
||||
log.Error().Err(err).Str("config", configID).Msg("[OfficialAccounts] Failed to create RSS post")
|
||||
return
|
||||
}
|
||||
|
||||
log.Info().Str("config", configID).Str("post_id", postID).Str("source", sourceName).Str("title", article.Title).Str("link", body).Msg("[OfficialAccounts] RSS post created")
|
||||
}
|
||||
|
||||
func (s *OfficialAccountsService) scheduleGeneralPost(ctx context.Context, configID string) {
|
||||
postID, body, err := s.GenerateAndPost(ctx, configID, nil, "")
|
||||
if err != nil {
|
||||
|
|
|
|||
Loading…
Reference in a new issue