fix: chunk upload race — always return the completed response with filepath; add campaign name to setup wizard
All checks were successful
Build and Push / build (push) Successful in 1m14s
All checks were successful
Build and Push / build (push) Successful in 1m14s
This commit is contained in:
@@ -20,6 +20,9 @@ services:
|
|||||||
# Set to "true" after onboarding completes.
|
# Set to "true" after onboarding completes.
|
||||||
NAT20_ONBOARDING_COMPLETED: "false"
|
NAT20_ONBOARDING_COMPLETED: "false"
|
||||||
|
|
||||||
|
# Campaign name is set during the setup wizard (step 2).
|
||||||
|
# You can also rename it later in Settings → Campaigns.
|
||||||
|
|
||||||
# ── Transcription ─────────────────────────────────────
|
# ── Transcription ─────────────────────────────────────
|
||||||
NAT20_WHISPER_MODEL: medium
|
NAT20_WHISPER_MODEL: medium
|
||||||
NAT20_WHISPER_COMPUTE_TYPE: int8
|
NAT20_WHISPER_COMPUTE_TYPE: int8
|
||||||
|
|||||||
@@ -100,6 +100,17 @@ export const campaignApi = {
|
|||||||
};
|
};
|
||||||
|
|
||||||
export const api = {
|
export const api = {
|
||||||
|
// Campaign
|
||||||
|
updateCampaign: async (id: string, body: Partial<Pick<Campaign, 'name' | 'description'>>): Promise<Campaign> => {
|
||||||
|
const res = await fetch(`${BASE_URL}/campaigns/${encodeURIComponent(id)}`, {
|
||||||
|
method: 'PUT',
|
||||||
|
headers: { 'Content-Type': 'application/json' },
|
||||||
|
body: JSON.stringify(body),
|
||||||
|
});
|
||||||
|
if (!res.ok) throw new Error('Failed to update campaign');
|
||||||
|
return res.json();
|
||||||
|
},
|
||||||
|
|
||||||
// Settings
|
// Settings
|
||||||
getSettings: async (): Promise<GlobalSettings> => {
|
getSettings: async (): Promise<GlobalSettings> => {
|
||||||
const res = await fetch(`${BASE_URL}/settings`);
|
const res = await fetch(`${BASE_URL}/settings`);
|
||||||
@@ -241,6 +252,7 @@ export const api = {
|
|||||||
};
|
};
|
||||||
|
|
||||||
let lastResult: any = null;
|
let lastResult: any = null;
|
||||||
|
let completedResult: any = null;
|
||||||
for (let i = 0; i < totalChunks; i += CONCURRENCY) {
|
for (let i = 0; i < totalChunks; i += CONCURRENCY) {
|
||||||
const batch = [];
|
const batch = [];
|
||||||
for (let j = i; j < Math.min(i + CONCURRENCY, totalChunks); j++) {
|
for (let j = i; j < Math.min(i + CONCURRENCY, totalChunks); j++) {
|
||||||
@@ -248,9 +260,12 @@ export const api = {
|
|||||||
}
|
}
|
||||||
const results = await Promise.all(batch);
|
const results = await Promise.all(batch);
|
||||||
lastResult = results[results.length - 1];
|
lastResult = results[results.length - 1];
|
||||||
|
if (!completedResult) {
|
||||||
|
completedResult = results.find(r => r.status === 'completed');
|
||||||
|
}
|
||||||
onProgress(Math.round((Math.min(i + CONCURRENCY, totalChunks) / totalChunks) * 100));
|
onProgress(Math.round((Math.min(i + CONCURRENCY, totalChunks) / totalChunks) * 100));
|
||||||
}
|
}
|
||||||
return lastResult;
|
return completedResult || lastResult;
|
||||||
},
|
},
|
||||||
|
|
||||||
// Files
|
// Files
|
||||||
|
|||||||
@@ -20,18 +20,22 @@ const DEFAULT_SETTINGS: Record<string, string> = {
|
|||||||
|
|
||||||
export default function Setup({ onComplete }: { onComplete: () => void }) {
|
export default function Setup({ onComplete }: { onComplete: () => void }) {
|
||||||
const [step, setStep] = useState(1);
|
const [step, setStep] = useState(1);
|
||||||
|
const [campaignName, setCampaignName] = useState("Default Campaign");
|
||||||
const [settings, setSettings] = useState<Record<string, string>>(DEFAULT_SETTINGS);
|
const [settings, setSettings] = useState<Record<string, string>>(DEFAULT_SETTINGS);
|
||||||
const [saving, setSaving] = useState(false);
|
const [saving, setSaving] = useState(false);
|
||||||
const [error, setError] = useState("");
|
const [error, setError] = useState("");
|
||||||
|
|
||||||
// On first load, try to fetch existing settings from the "default" campaign
|
// On first load, try to fetch existing settings and name from the "default" campaign.
|
||||||
// (which is always created by init_db).
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
api.getCampaignSettings('default')
|
api.getCampaignSettings('default')
|
||||||
.then(data => {
|
.then(data => {
|
||||||
if (data) setSettings(prev => ({ ...prev, ...data }));
|
if (data) setSettings(prev => ({ ...prev, ...data }));
|
||||||
})
|
})
|
||||||
.catch(() => {});
|
.catch(() => {});
|
||||||
|
fetch('/api/campaigns/default')
|
||||||
|
.then(r => r.ok ? r.json() : null)
|
||||||
|
.then(c => { if (c?.name) setCampaignName(c.name); })
|
||||||
|
.catch(() => {});
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
const handleNext = () => setStep(prev => prev + 1);
|
const handleNext = () => setStep(prev => prev + 1);
|
||||||
@@ -41,6 +45,7 @@ export default function Setup({ onComplete }: { onComplete: () => void }) {
|
|||||||
setSaving(true);
|
setSaving(true);
|
||||||
setError("");
|
setError("");
|
||||||
try {
|
try {
|
||||||
|
await api.updateCampaign('default', { name: campaignName });
|
||||||
await api.updateCampaignSettings('default', settings);
|
await api.updateCampaignSettings('default', settings);
|
||||||
await api.updateSettings({ onboarding_completed: true });
|
await api.updateSettings({ onboarding_completed: true });
|
||||||
onComplete();
|
onComplete();
|
||||||
@@ -112,7 +117,18 @@ export default function Setup({ onComplete }: { onComplete: () => void }) {
|
|||||||
{step === 2 && (
|
{step === 2 && (
|
||||||
<div className="space-y-6">
|
<div className="space-y-6">
|
||||||
<div>
|
<div>
|
||||||
<h2 className="text-xl font-semibold mb-2">2. Summarization Engine (LLM)</h2>
|
<h2 className="text-xl font-semibold mb-2">Campaign Name</h2>
|
||||||
|
<p className="text-slate-400 text-sm mb-4">Give your campaign a name — you can change it later in Settings.</p>
|
||||||
|
<input
|
||||||
|
type="text"
|
||||||
|
value={campaignName}
|
||||||
|
onChange={e => setCampaignName(e.target.value)}
|
||||||
|
placeholder="My Campaign"
|
||||||
|
className="w-full bg-slate-900 border border-slate-700 rounded px-3 py-2 text-white"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<div className="border-t border-slate-700 pt-4">
|
||||||
|
<h2 className="text-xl font-semibold mb-2">Summarization Engine (LLM)</h2>
|
||||||
<p className="text-slate-400 text-sm mb-4">Choose where your post-transcription formatting and smart summaries are generated.</p>
|
<p className="text-slate-400 text-sm mb-4">Choose where your post-transcription formatting and smart summaries are generated.</p>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user