This commit is contained in:
root
2026-05-17 12:09:14 +00:00
parent a10ffe332d
commit ffdc887b8a
30 changed files with 728 additions and 111 deletions

View File

@@ -357,9 +357,11 @@ export default function LiveControlApp() {
const targetProc = currentProcRef.current.trim();
const requestId = ++urlConfigRequestSeqRef.current;
try {
const data = await fetchJson<{ content?: string }>(
`/get_url_config?process=${encodeURIComponent(targetProc)}`,
);
const params = new URLSearchParams({
process: targetProc,
_ts: String(Date.now()),
});
const data = await fetchJson<{ content?: string }>(`/get_url_config?${params}`);
if (requestId !== urlConfigRequestSeqRef.current || targetProc !== currentProcRef.current.trim()) return;
setUrlConfig(data.content || "");
} catch {
@@ -443,6 +445,7 @@ export default function LiveControlApp() {
const proc = (processOverride ?? currentProc).trim();
if (!proc) return;
const content = overrideContent ?? urlConfig;
const requestId = ++urlConfigRequestSeqRef.current;
setBusy("save:url");
try {
const params = new URLSearchParams({ process: proc });
@@ -451,6 +454,9 @@ export default function LiveControlApp() {
headers: { "Content-Type": "application/json" },
body: JSON.stringify({ content }),
});
if (requestId === urlConfigRequestSeqRef.current && proc === currentProcRef.current.trim()) {
setUrlConfig(content);
}
notify(t("URL 配置已保存", "URL config saved"));
} catch (e) {
notify((e as Error).message);

View File

@@ -167,9 +167,11 @@ export function LiveTiktokHdmiView({
const pullUrlConfigFromServer = useCallback(async () => {
if (!currentProc) return;
try {
const data = await fetchJson<{ content?: string }>(
`/get_url_config?process=${encodeURIComponent(currentProc)}`,
);
const params = new URLSearchParams({
process: currentProc,
_ts: String(Date.now()),
});
const data = await fetchJson<{ content?: string }>(`/get_url_config?${params}`);
applyUrlDraft(data.content ?? "");
} catch {
/* ignore */
@@ -249,9 +251,11 @@ export function LiveTiktokHdmiView({
setUrlFetchNote(null);
urlListDirtyRef.current = false;
try {
const data = await fetchJson<{ content?: string }>(
`/get_url_config?process=${encodeURIComponent(currentProc)}`,
);
const params = new URLSearchParams({
process: currentProc,
_ts: String(Date.now()),
});
const data = await fetchJson<{ content?: string }>(`/get_url_config?${params}`);
applyUrlDraft(data.content ?? "");
setUrlFetchNote(t("已从服务器读取 URL_config", "Reloaded URL_config from server"));
} catch (e) {

View File

@@ -404,9 +404,11 @@ export function LiveYoutubeUnmannedView({
if (!targetProc) return;
const requestId = ++urlRequestSeqRef.current;
try {
const data = await fetchJson<{ content?: string }>(
`/get_url_config?process=${encodeURIComponent(targetProc)}`,
);
const params = new URLSearchParams({
process: targetProc,
_ts: String(Date.now()),
});
const data = await fetchJson<{ content?: string }>(`/get_url_config?${params}`);
if (requestId !== urlRequestSeqRef.current || targetProc !== currentProcRef.current.trim()) return;
const c = data.content ?? "";
if (urlListDirtyRef.current) return;
@@ -448,9 +450,11 @@ export function LiveYoutubeUnmannedView({
urlListDirtyRef.current = false;
const requestId = ++urlRequestSeqRef.current;
try {
const data = await fetchJson<{ content?: string }>(
`/get_url_config?process=${encodeURIComponent(targetProc)}`,
);
const params = new URLSearchParams({
process: targetProc,
_ts: String(Date.now()),
});
const data = await fetchJson<{ content?: string }>(`/get_url_config?${params}`);
if (requestId !== urlRequestSeqRef.current || targetProc !== currentProcRef.current.trim()) return;
const c = data.content ?? "";
if (urlListDirtyRef.current) {
@@ -503,7 +507,7 @@ export function LiveYoutubeUnmannedView({
void (async () => {
try {
const remote = await fetchYoutubeProChannelsRemote(fetchJson);
if (remote.fetched && !cancelled) {
if (remote.fetched && remote.channels.length && !cancelled) {
setChannels(remote.channels);
saveYoutubeProChannels(remote.channels);
return;
@@ -538,7 +542,13 @@ export function LiveYoutubeUnmannedView({
}, [fetchJson]);
useEffect(() => {
if (channels.length && !activeCh) setActiveCh(channels[0].id);
if (!channels.length) {
if (activeCh) setActiveCh("");
return;
}
if (!activeCh || !channels.some((row) => row.id === activeCh)) {
setActiveCh(channels[0].id);
}
}, [channels, activeCh]);
const rowForPm2 = useCallback((pm2: string) => liveProcesses.find((p) => p.pm2 === pm2), [liveProcesses]);
@@ -546,26 +556,18 @@ export function LiveYoutubeUnmannedView({
(pm2: string) => rowForPm2(pm2)?.process_status,
[rowForPm2],
);
const currentProcRow = rowForPm2(currentProc);
const currentProcLooksYoutube = useMemo(
() => !!currentProc && (/youtube/i.test(currentProc) || /youtube/i.test(currentProcRow?.script ?? "") || /youtube/i.test(currentProcRow?.label ?? "")),
[currentProc, currentProcRow],
);
useEffect(() => {
if (mode !== "single" || !preferredSingleProc) return;
if (!singleProcessOptions.some((entry) => entry.pm2 === currentProc)) {
setCurrentProc(preferredSingleProc);
return;
}
const currentEntry = singleProcessOptions.find((entry) => entry.pm2 === currentProc);
const currentRow = rowForPm2(currentProc);
const preferredEntry = singleProcessOptions.find((entry) => entry.pm2 === preferredSingleProc);
const preferredRow = rowForPm2(preferredSingleProc);
if (
currentProc !== preferredSingleProc &&
currentEntry &&
preferredEntry &&
singleProcessPriority(preferredEntry, preferredRow) < singleProcessPriority(currentEntry, currentRow)
) {
if (!currentProc || !currentProcLooksYoutube) {
setCurrentProc(preferredSingleProc);
}
}, [currentProc, mode, preferredSingleProc, rowForPm2, setCurrentProc, singleProcessOptions]);
}, [currentProc, currentProcLooksYoutube, mode, preferredSingleProc, setCurrentProc]);
const applyChannelsLocal = useCallback((next: YoutubeProChannel[]) => {
setChannels(next);
@@ -773,9 +775,11 @@ export function LiveYoutubeUnmannedView({
setYtIniLoading(true);
setYtIniStatus(null);
try {
const data = await fetchJson<{ content?: string }>(
`/get_config?process=${encodeURIComponent(targetProc)}`,
);
const params = new URLSearchParams({
process: targetProc,
_ts: String(Date.now()),
});
const data = await fetchJson<{ content?: string }>(`/get_config?${params}`);
if (requestId !== ytIniRequestSeqRef.current || targetProc !== currentProcRef.current.trim()) return;
const text = data.content ?? "";
setYoutubeIniText(text);
@@ -872,7 +876,7 @@ export function LiveYoutubeUnmannedView({
}
}, [active, notify, persistActiveLaneDraft, setCurrentProc, t]);
const saveYoutubeIniToServer = async (content: string, processPm2 = currentProc) => {
const saveYoutubeIniToServer = async (content: string, processPm2 = currentProcRef.current) => {
const targetPm2 = processPm2.trim();
if (!targetPm2) {
setYtIniStatus(t("请选择进程", "Pick a process"));
@@ -911,7 +915,7 @@ export function LiveYoutubeUnmannedView({
headers: { "Content-Type": "application/json" },
body: JSON.stringify({
root: "app-config",
path: managedYoutubeIniRelPathForPm2(currentProc),
path: managedYoutubeIniRelPathForPm2(currentProcRef.current),
content: source,
action: "youtube_balanced_preset",
}),
@@ -943,6 +947,7 @@ export function LiveYoutubeUnmannedView({
try {
setLaneSaving(true);
setLaneStatus(null);
urlRequestSeqRef.current += 1;
const latestProUrlDraft = proUrlDraftRef.current;
const saved = await persistActiveLaneDraft();
setCurrentProc(saved.pm2);
@@ -959,12 +964,13 @@ export function LiveYoutubeUnmannedView({
};
const saveSingleUrlAndClearDirty = async () => {
const targetPm2 = currentProc.trim();
const targetPm2 = currentProcRef.current.trim();
if (!targetPm2) {
notify(t("请选择进程", "Pick a process"));
return;
}
try {
urlRequestSeqRef.current += 1;
await Promise.resolve(onSaveUrlConfig(singleUrlDraftRef.current, targetPm2));
urlListDirtyRef.current = false;
} catch {
@@ -982,7 +988,7 @@ export function LiveYoutubeUnmannedView({
headers: { "Content-Type": "application/json" },
body: JSON.stringify({
root: "app-config",
path: managedUrlConfigRelPathForPm2(currentProc),
path: managedUrlConfigRelPathForPm2(currentProcRef.current),
content: source,
action: "dedupe_urls",
}),
@@ -1099,7 +1105,7 @@ export function LiveYoutubeUnmannedView({
const lock = !!busy || ytIniLoading || urlReloading || urlOptimizing || laneSaving;
const singleRow = rowForPm2(currentProc);
const singleRow = currentProcRow;
const singleOnline = pm2StatusOnline(statusForPm2(currentProc));
const urlToolbar = (
@@ -1260,8 +1266,18 @@ export function LiveYoutubeUnmannedView({
<select
className="mt-2 w-full rounded-xl border border-white/10 bg-black/40 px-4 py-3 text-sm text-white"
value={currentProc}
onChange={(e) => setCurrentProc(e.target.value)}
onChange={(e) => {
const next = e.target.value;
currentProcRef.current = next;
setCurrentProc(next);
}}
>
{currentProc && !singleProcessOptions.some((entry) => entry.pm2 === currentProc) ? (
<option value={currentProc}>
{currentProc}
{currentProcRow ? ` (${businessStatusLabel(currentProcRow, t)})` : ""}
</option>
) : null}
{singleProcessOptions.map((e) => (
<option key={e.pm2} value={e.pm2}>
{singleModeProcessLabel(e, t)} ({e.pm2})
@@ -1343,7 +1359,10 @@ export function LiveYoutubeUnmannedView({
<button
type="button"
className="min-w-0 flex-1 text-left"
onClick={() => selectProChannel(c)}
onClick={() => {
currentProcRef.current = resolveChannelPm2(c);
selectProChannel(c);
}}
>
<div className="flex flex-wrap items-center gap-2">
<span className="font-medium text-zinc-100">{c.name}</span>
@@ -1573,11 +1592,11 @@ export function LiveYoutubeUnmannedView({
</p>
) : null}
</div>
<button
type="button"
disabled={lock}
className="inline-flex items-center gap-1 rounded-lg bg-violet-500/20 px-3 py-1.5 text-xs text-violet-100 ring-1 ring-violet-500/30"
onClick={() => {
<button
type="button"
disabled={lock}
className="inline-flex items-center gap-1 rounded-lg bg-violet-500/20 px-3 py-1.5 text-xs text-violet-100 ring-1 ring-violet-500/30"
onClick={() => {
const id = newChannelId();
const nc: YoutubeProChannel = {
id,
@@ -1587,11 +1606,16 @@ export function LiveYoutubeUnmannedView({
pm2Name: defaultPm2NameForChannel(id),
notes: "",
};
persistChannels([...channels, nc]);
setActiveCh(id);
setCurrentProc(resolveChannelPm2(nc));
applyProUrlDraft("");
}}
setLaneDrafts((prev) => ({
...prev,
[id]: laneDraftFromChannel(nc),
}));
persistChannels([...channels, nc]);
setActiveCh(id);
currentProcRef.current = resolveChannelPm2(nc);
setCurrentProc(currentProcRef.current);
applyProUrlDraft("");
}}
>
<Plus className="h-3.5 w-3.5" />
{t("新增线路", "Add lane")}