Add campaign layer, GPU model caching, file browser cleanup

- Campaigns: new table, CRUD API, React context + provider
- Sessions: scoped to campaigns, paths under campaigns/{id}/
- File browser: scoped per campaign, removed copy/paste/autoPlay
- Sidebar: campaign selector dropdown at top
- Transcribe: GPU model cached/released via job counter
- Jobs: status text updates dynamically in real-time
- Auto-redirect: blocked when summarize job is active
This commit is contained in:
KansaiGaijin
2026-07-10 21:41:29 +12:00
parent db19cfd05e
commit bc7ac32de3
19 changed files with 702 additions and 182 deletions

View File

@@ -0,0 +1,59 @@
import { createContext, useContext, useState, useEffect, useCallback, type ReactNode } from 'react';
import { campaignApi, type Campaign } from '../api';
interface CampaignContextType {
currentCampaign: Campaign | null;
setCampaign: (c: Campaign | null) => void;
campaigns: Campaign[];
refreshCampaigns: () => void;
}
const CampaignContext = createContext<CampaignContextType | null>(null);
export function CampaignProvider({ children }: { children: ReactNode }) {
const [campaigns, setCampaigns] = useState<Campaign[]>([]);
const [currentCampaign, setCurrentCampaign] = useState<Campaign | null>(null);
const refreshCampaigns = useCallback(async () => {
try {
const list = await campaignApi.list();
setCampaigns(list);
} catch {}
}, []);
useEffect(() => {
refreshCampaigns();
}, [refreshCampaigns]);
useEffect(() => {
if (campaigns.length === 0) return;
if (!currentCampaign) {
const stored = localStorage.getItem('campaignId');
const match = stored ? campaigns.find(c => c.id === stored) : null;
setCurrentCampaign(match || campaigns[0]);
} else {
const stillExists = campaigns.find(c => c.id === currentCampaign.id);
if (!stillExists) {
setCurrentCampaign(campaigns[0] || null);
}
}
}, [campaigns]);
const setCampaign = (c: Campaign | null) => {
if (c) localStorage.setItem('campaignId', c.id);
else localStorage.removeItem('campaignId');
setCurrentCampaign(c);
};
return (
<CampaignContext.Provider value={{ currentCampaign, setCampaign, campaigns, refreshCampaigns }}>
{children}
</CampaignContext.Provider>
);
}
export function useCampaign() {
const ctx = useContext(CampaignContext);
if (!ctx) throw new Error('useCampaign must be used within CampaignProvider');
return ctx;
}