1
0
forked from boos/BoosAPI

Merge origin/main into codex/single-source-consolidation

This commit is contained in:
Cita
2026-03-23 14:44:52 +08:00
21 changed files with 1409 additions and 224 deletions
+8
View File
@@ -61,6 +61,7 @@ type RouteCardProps = {
onTokenDraftChange: (channelId: number, tokenId: number) => void;
onSaveToken: (routeId: number, channelId: number, accountId: number) => void;
onDeleteChannel: (channelId: number, routeId: number) => void;
onToggleChannelEnabled: (channelId: number, routeId: number, enabled: boolean) => void;
onChannelDragEnd: (routeId: number, event: DragEndEvent) => void;
// Missing token hints
missingTokenSiteItems: MissingTokenRouteSiteActionItem[];
@@ -68,6 +69,8 @@ type RouteCardProps = {
onCreateTokenForMissing: (accountId: number, modelName: string) => void;
// Add channel
onAddChannel: (routeId: number) => void;
// Site block model
onSiteBlockModel: (channelId: number, routeId: number) => void;
// Source group expansion
expandedSourceGroupMap: Record<string, boolean>;
onToggleSourceGroup: (groupKey: string) => void;
@@ -119,11 +122,13 @@ function RouteCardInner({
onTokenDraftChange,
onSaveToken,
onDeleteChannel,
onToggleChannelEnabled,
onChannelDragEnd,
missingTokenSiteItems,
missingTokenGroupItems,
onCreateTokenForMissing,
onAddChannel,
onSiteBlockModel,
expandedSourceGroupMap,
onToggleSourceGroup,
}: RouteCardProps) {
@@ -548,6 +553,7 @@ function RouteCardInner({
onTokenDraftChange={onTokenDraftChange}
onSaveToken={() => onSaveToken(route.id, channel.id, channel.accountId)}
onDeleteChannel={() => onDeleteChannel(channel.id, route.id)}
onToggleEnabled={(enabled) => onToggleChannelEnabled(channel.id, route.id, enabled)}
/>
);
})}
@@ -572,6 +578,8 @@ function RouteCardInner({
onTokenDraftChange={onTokenDraftChange}
onSaveToken={() => onSaveToken(route.id, channel.id, channel.accountId)}
onDeleteChannel={() => onDeleteChannel(channel.id, route.id)}
onToggleEnabled={(enabled) => onToggleChannelEnabled(channel.id, route.id, enabled)}
onSiteBlockModel={() => onSiteBlockModel(channel.id, route.id)}
/>
);
})}
+38 -1
View File
@@ -5,6 +5,8 @@ import { tr } from '../../i18n.js';
import type { GroupFilter, GroupRouteItem } from './types.js';
import { resolveEndpointTypeIconModel, siteAvatarLetters } from './utils.js';
export type EnabledFilter = 'all' | 'enabled' | 'disabled';
type RouteFilterBarProps = {
totalRouteCount: number;
activeBrand: string | null;
@@ -15,6 +17,9 @@ type RouteFilterBarProps = {
setActiveEndpointType: (endpointType: string | null) => void;
activeGroupFilter: GroupFilter;
setActiveGroupFilter: (filter: GroupFilter) => void;
enabledFilter: EnabledFilter;
setEnabledFilter: (filter: EnabledFilter) => void;
enabledCounts: { enabled: number; disabled: number };
brandList: { list: [string, { count: number; brand: BrandInfo }][]; otherCount: number };
siteList: [string, { count: number; siteId: number }][];
endpointTypeList: [string, number][];
@@ -63,8 +68,11 @@ function ActiveFilterSummary({
activeSite,
activeGroupFilter,
activeEndpointType,
}: Pick<RouteFilterBarProps, 'activeBrand' | 'activeSite' | 'activeGroupFilter' | 'activeEndpointType'>) {
enabledFilter,
}: Pick<RouteFilterBarProps, 'activeBrand' | 'activeSite' | 'activeGroupFilter' | 'activeEndpointType' | 'enabledFilter'>) {
const tags: string[] = [];
if (enabledFilter === 'enabled') tags.push('状态=启用');
else if (enabledFilter === 'disabled') tags.push('状态=禁用');
if (activeBrand) tags.push(`品牌=${activeBrand === '__other__' ? '其他' : activeBrand}`);
if (activeSite) tags.push(`站点=${activeSite}`);
if (activeGroupFilter === '__all__') tags.push('群组=全部');
@@ -86,6 +94,9 @@ export default function RouteFilterBar(props: RouteFilterBarProps) {
setActiveEndpointType,
activeGroupFilter,
setActiveGroupFilter,
enabledFilter,
setEnabledFilter,
enabledCounts,
brandList,
siteList,
endpointTypeList,
@@ -117,12 +128,38 @@ export default function RouteFilterBar(props: RouteFilterBarProps) {
activeSite={activeSite}
activeGroupFilter={activeGroupFilter}
activeEndpointType={activeEndpointType}
enabledFilter={enabledFilter}
/>
</button>
{/* Expanded panel */}
{expandPresence.shouldRender && (
<div className={`route-filter-bar-expanded ${expandPresence.isVisible ? '' : 'is-closing'}`.trim()}>
{/* Status row */}
<FilterRow label={tr('状态')}>
<FilterChip
active={enabledFilter === 'all'}
label={tr('全部')}
count={totalRouteCount}
icon={<span style={{ fontSize: 10 }}>✦</span>}
onClick={() => setEnabledFilter('all')}
/>
<FilterChip
active={enabledFilter === 'enabled'}
label={tr('仅启用')}
count={enabledCounts.enabled}
icon={<span style={{ fontSize: 10, color: 'var(--color-success)' }}>●</span>}
onClick={() => setEnabledFilter(enabledFilter === 'enabled' ? 'all' : 'enabled')}
/>
<FilterChip
active={enabledFilter === 'disabled'}
label={tr('仅禁用')}
count={enabledCounts.disabled}
icon={<span style={{ fontSize: 10, color: 'var(--color-text-muted)' }}>●</span>}
onClick={() => setEnabledFilter(enabledFilter === 'disabled' ? 'all' : 'disabled')}
/>
</FilterRow>
{/* Brand row */}
<FilterRow label={tr('品牌')}>
<FilterChip
@@ -25,6 +25,8 @@ export function SortableChannelRow({
onTokenDraftChange,
onSaveToken,
onDeleteChannel,
onToggleEnabled,
onSiteBlockModel,
}: SortableChannelRowProps) {
const {
attributes,
@@ -42,16 +44,17 @@ export function SortableChannelRow({
const rowStyle: CSSProperties = {
transform: CSS.Transform.toString(transform),
transition,
opacity: isDragging ? 0.72 : 1,
opacity: isDragging ? 0.72 : channel.enabled === false ? 0.5 : 1,
zIndex: isDragging ? 10 : 1,
display: 'grid',
gridTemplateColumns: readOnly || mobile ? 'minmax(0, 1fr)' : 'minmax(0, 1fr) auto auto',
gridTemplateColumns: readOnly || mobile ? 'minmax(0, 1fr)' : 'minmax(0, 1fr) auto auto auto',
alignItems: mobile ? 'stretch' : 'center',
gap: 8,
padding: mobile ? '10px 12px' : '8px 12px',
borderLeft: '2px solid var(--color-primary)',
borderBottom: '1px solid var(--color-border)',
borderRadius: '0 var(--radius-sm) var(--radius-sm) 0',
background: isDragging ? 'rgba(59,130,246,0.08)' : 'rgba(79,70,229,0.02)',
background: isDragging ? 'rgba(59,130,246,0.08)' : 'var(--color-bg-card, rgba(79,70,229,0.02))',
boxShadow: isDragging ? 'var(--shadow-sm)' : 'none',
};
@@ -263,6 +266,22 @@ export function SortableChannelRow({
{isUpdatingToken ? <span className="spinner spinner-sm" /> : '保存'}
</button>
<button
onClick={() => onToggleEnabled(channel.enabled === false)}
className={`btn btn-link ${channel.enabled === false ? 'btn-link-info' : 'btn-link-warning'}`}
>
{channel.enabled === false ? '启用' : '禁用'}
</button>
{onSiteBlockModel && channel.site?.id ? (
<button
onClick={onSiteBlockModel}
className="btn btn-link btn-link-warning"
>
站点屏蔽
</button>
) : null}
<button
onClick={onDeleteChannel}
className="btn btn-link btn-link-danger"
@@ -376,6 +395,10 @@ export function SortableChannelRow({
</span>
) : null}
{channel.enabled === false ? (
<span className="badge badge-muted" style={{ fontSize: 10 }}>已禁用</span>
) : null}
<div style={{ display: 'flex', alignItems: 'center', gap: 8, width: '100%', marginTop: mobile ? 0 : 4, flexWrap: 'wrap' }}>
<span style={{ fontSize: 11, color: 'var(--color-text-muted)', whiteSpace: 'nowrap' }}>选中概率</span>
<div style={{ display: 'inline-flex', alignItems: 'center', gap: 6, minWidth: 120 }}>
@@ -457,6 +480,22 @@ export function SortableChannelRow({
{isUpdatingToken ? <span className="spinner spinner-sm" /> : '保存'}
</button>
<button
onClick={() => onToggleEnabled(channel.enabled === false)}
className={`btn btn-link ${channel.enabled === false ? 'btn-link-info' : 'btn-link-warning'}`}
>
{channel.enabled === false ? '启用' : '禁用'}
</button>
{onSiteBlockModel && channel.site?.id ? (
<button
onClick={onSiteBlockModel}
className="btn btn-link btn-link-warning"
>
站点屏蔽
</button>
) : null}
<button
onClick={onDeleteChannel}
className="btn btn-link btn-link-danger"
@@ -502,11 +541,31 @@ export function SortableChannelRow({
</div>
<button
onClick={onDeleteChannel}
className="btn btn-link btn-link-danger"
onClick={() => onToggleEnabled(channel.enabled === false)}
className={`btn btn-link ${channel.enabled === false ? 'btn-link-info' : 'btn-link-warning'}`}
data-tooltip={channel.enabled === false ? '启用此通道' : '禁用此通道'}
>
移除
{channel.enabled === false ? '启用' : '禁用'}
</button>
<div style={{ display: 'flex', alignItems: 'center', gap: 4 }}>
{onSiteBlockModel && channel.site?.id ? (
<button
onClick={onSiteBlockModel}
className="btn btn-link btn-link-warning"
data-tooltip={`将此模型加入站点「${channel.site?.name || '未知'}」的禁用列表,rebuild 后该站点的此模型通道将不再生成`}
>
站点屏蔽
</button>
) : null}
<button
onClick={onDeleteChannel}
className="btn btn-link btn-link-danger"
>
移除
</button>
</div>
</>
)
) : null}
+2
View File
@@ -135,6 +135,8 @@ export type SortableChannelRowProps = {
onTokenDraftChange: (channelId: number, tokenId: number) => void;
onSaveToken: () => void;
onDeleteChannel: () => void;
onToggleEnabled: (enabled: boolean) => void;
onSiteBlockModel?: () => void;
};
export type GroupRouteItem = {