Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 0 additions & 3 deletions electron/ipc/handlers.ts
Original file line number Diff line number Diff line change
Expand Up @@ -47,7 +47,6 @@ import {
rewindToMessage,
runChat,
runChatDefault,
runTimelineOperation,
selectSession,
} from "../ai-edition/chat-service";
import { DocumentService } from "../ai-edition/document-service";
Expand Down Expand Up @@ -3422,8 +3421,6 @@ export function registerIpcHandlers(
rewindToMessage(projectId, sessionId, messageId),
compactNow: (projectId, sessionId) =>
compactSessionNow(projectId, sessionId, aiEditionLlmConfig),
runTimelineOperation: (projectId, sessionId, op, conversationMessage) =>
runTimelineOperation(projectId, sessionId, op, conversationMessage, aiEditionDocuments),
getContextUsage: getSessionContextUsage,
runAiEditionChatDefault: (projectId, message, sink) =>
runChatDefault(projectId, message, aiEditionLlmConfig, sink),
Expand Down
23 changes: 0 additions & 23 deletions electron/ipc/nativeBridge.ts
Original file line number Diff line number Diff line change
Expand Up @@ -79,18 +79,6 @@ export interface NativeBridgeContext {
projectId: string,
sessionId: string,
) => Promise<import("../../src/native/contracts").AiEditionChatCompactResult | null>;
runTimelineOperation: (
projectId: string,
sessionId: string,
op: import("../../src/native/contracts").AxcutTimelineOperation,
conversationMessage: string,
) => Promise<
| {
success: true;
result: import("../../src/native/contracts").AppliedTimelineOperation;
}
| { success: false; error: string }
>;
getContextUsage: (
projectId: string,
sessionId: string,
Expand Down Expand Up @@ -247,7 +235,6 @@ export function registerNativeBridgeHandlers(context: NativeBridgeContext) {
undoLastToolBatch: context.undoAiEditionToolBatch,
rewindToMessage: context.rewindToMessage,
compactNow: context.compactNow,
runTimelineOperation: context.runTimelineOperation,
getContextUsage: context.getContextUsage,
getDefaultChatHistory: context.getAiEditionChatHistoryDefault,
clearDefaultChatHistory: context.clearAiEditionChatHistoryDefault,
Expand Down Expand Up @@ -644,16 +631,6 @@ export function registerNativeBridgeHandlers(context: NativeBridgeContext) {
sourceLanguage: request.payload.sourceLanguage,
}),
);
case "timeline.run":
return createSuccessResponse(
requestId,
await aiEditionService.chatRunTimelineOperation(
request.payload.projectId,
request.payload.sessionId,
request.payload.operation,
request.payload.conversationMessage,
),
);
default:
return createErrorResponse(
requestId,
Expand Down
19 changes: 0 additions & 19 deletions electron/native-bridge/services/aiEditionService.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,6 @@ import type {
AiEditionLlmDisconnectResult,
AiEditionLlmSnapshot,
AiEditionProjectSummary,
AxcutTimelineOperation,
} from "../../../src/native/contracts";
import {
type CaptionTranslateSegment,
Expand Down Expand Up @@ -61,15 +60,6 @@ export interface AiEditionServiceOptions {
}
| { success: false; error: string };
compactNow: (projectId: string, sessionId: string) => Promise<AiEditionChatCompactResult | null>;
runTimelineOperation: (
projectId: string,
sessionId: string,
operation: AxcutTimelineOperation,
conversationMessage: string,
) => Promise<
| { success: true; result: { document: unknown; summary: string } }
| { success: false; error: string }
>;
getContextUsage: (
projectId: string,
sessionId: string,
Expand Down Expand Up @@ -288,15 +278,6 @@ export class AiEditionService {
return this.options.compactNow(projectId, sessionId);
}

chatRunTimelineOperation(
projectId: string,
sessionId: string,
operation: AxcutTimelineOperation,
conversationMessage: string,
) {
return this.options.runTimelineOperation(projectId, sessionId, operation, conversationMessage);
}

async chatRunDefault(
projectId: string,
message: string,
Expand Down
52 changes: 1 addition & 51 deletions src/components/ai-edition/LeftPanel.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -7,13 +7,8 @@ import { useScopedT } from "@/contexts/I18nContext";
import { type AxcutAsset, ensureDocument } from "@/lib/ai-edition/schema";
import { useProjectStore } from "@/lib/ai-edition/store/projectStore";
import { useChatPromptBus } from "@/lib/ai-edition/store/useChatPromptBus";
import { useOptimisticTimelineOps } from "@/lib/ai-edition/store/useOptimisticTimelineOps";
import { nativeBridgeClient } from "@/native/client";
import type {
AiEditionLlmConfig,
AiEditionToolCallSummary,
AxcutTimelineOperation,
} from "@/native/contracts";
import type { AiEditionLlmConfig, AiEditionToolCallSummary } from "@/native/contracts";
import { formatBytes } from "@/utils/formatBytes";
import {
getReasoningEffortLabel,
Expand Down Expand Up @@ -1139,32 +1134,6 @@ function ChatStripPanel() {
[editingTitle, handleRename, cancelEditTitle],
);

const { queue: queueTimelineOp, busy: queueBusy } = useOptimisticTimelineOps(
projectId,
activeSessionId,
);
const runAddTrim = useCallback(() => {
const raw = window.prompt(t("chat.addSkipRangePrompt"), "5-8");
if (!raw) return;
const m = raw.match(/^\s*(\d+(?:\.\d+)?)\s*[-–]\s*(\d+(?:\.\d+)?)\s*$/);
if (!m) {
toast.error(t("chat.addSkipRangeFormatError"));
return;
}
const startSec = Number(m[1]);
const endSec = Number(m[2]);
if (!Number.isFinite(startSec) || !Number.isFinite(endSec) || endSec <= startSec) {
toast.error(t("chat.addSkipRangeOrderError"));
return;
}
const op: AxcutTimelineOperation = {
type: "add_trim_range",
startSec,
endSec,
};
void queueTimelineOp(op, t("chat.addSkipRangeApplied", { startSec, endSec }));
}, [queueTimelineOp, t]);

return (
<aside className={styles.panel}>
<div className={styles.panelHeader}>
Expand Down Expand Up @@ -1402,25 +1371,6 @@ function ChatStripPanel() {
<path d="M9 6V4a1 1 0 0 1 1-1h4a1 1 0 0 1 1 1v2" />
</svg>
</button>
<button
type="button"
title={t("chat.addSkipRange")}
aria-label={t("chat.addSkipRange")}
disabled={!activeSessionId || queueBusy}
onClick={runAddTrim}
style={{
background: "transparent",
border: "1px solid var(--border-soft)",
borderRadius: "var(--r-sm)",
color: "var(--fg-2)",
font: "500 10px var(--font-body)",
padding: "2px 6px",
cursor: queueBusy ? "wait" : "pointer",
whiteSpace: "nowrap",
}}
>
{t("chat.skipButtonShort")}
</button>
</div>
) : null}
</div>
Expand Down
71 changes: 18 additions & 53 deletions src/components/ai-edition/NewEditorShell.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ import { transcribeAsset } from "@/lib/ai-edition/document/transcribe";
import { type AxcutClip, documentSchema } from "@/lib/ai-edition/schema";
import { useProjectStore } from "@/lib/ai-edition/store/projectStore";
import { useUndoRedoShortcuts } from "@/lib/ai-edition/store/undo";
import { useSequentialTimelineOps } from "@/lib/ai-edition/store/useSequentialTimelineOps";
import { useTimeline } from "@/lib/ai-edition/store/useTimeline";
import { matchesShortcut } from "@/lib/shortcuts";
import { nativeBridgeClient } from "@/native";
Expand Down Expand Up @@ -111,10 +112,13 @@ export function NewEditorShell() {
}

// ponytail: serialise timeline-edit saves so two rapid Backspaces
// don't race each other's IPC save and overwrite one another in the
// store. Each new save chains off the previous one, so the store is
// always updated in the order the user issued the trims.
const saveQueueRef = useRef<Promise<void>>(Promise.resolve());
// don't race each other's save and overwrite one another in the
// store. The hook reads the doc inside the chain (after awaiting the
// previous save) — see its source for the race this fixes.
const { apply: applyTimelineOp } = useSequentialTimelineOps({
fallbackDocument: document,
saveDocument,
});

const promptUnsaved = useCallback(
(action: "close" | "new" | "open" | "record"): Promise<UnsavedChoice> => {
Expand Down Expand Up @@ -537,63 +541,24 @@ export function NewEditorShell() {
// trimRange (NOT a destructive word removal — the source text stays
// intact, the word is just hidden by the skip overlay). Mirrors
// axcut's `queueAddTrimRange` / `queueRemoveTrimRange` callbacks in
// apps/web/src/App.tsx.
// apps/web/src/App.tsx. The serialised save + inside-the-chain doc
// read is owned by `useSequentialTimelineOps` above.
const handleAddTrimRange = useCallback(
(assetId: string, startSec: number, endSec: number, reason: string) => {
// BUG corrigé : `doc` était lu de façon SYNCHRONE au moment de l'appel, puis seule la
// SAUVEGARDE était sérialisée via `saveQueueRef` — pas la LECTURE. Éditer le clip 1 puis
// le clip 2 avant que la chaîne async du premier save (import() + applyTimelineOperation +
// saveDocument, qui fait un aller-retour IPC) n'ait commit dans le store faisait lire au
// second appel le MÊME doc pré-edit-1 ; son propre saveDocument(next.document) écrasait
// alors le store avec un doc qui contient le trim du clip 2 mais PAS celui du clip 1 — les
// edits du clip 1 disparaissaient. La lecture doit donc elle aussi être mise dans la
// chaîne, après avoir attendu le tour précédent, pour toujours partir du doc déjà commit.
const queued = saveQueueRef.current
.then(() => import("@/lib/ai-edition/document/operations"))
.then(({ applyTimelineOperation }) => {
const doc = useProjectStore.getState().document ?? document;
if (!doc) return null;
return applyTimelineOperation(doc, {
type: "add_trim_range",
assetId,
startSec,
endSec,
reason,
});
})
.then((next) => next && saveDocument(next.document));
saveQueueRef.current = queued.then(
() => undefined,
() => undefined,
);
return queued;
void applyTimelineOp({ type: "add_trim_range", assetId, startSec, endSec, reason });
},
[document, saveDocument],
[applyTimelineOp],
);

const handleRemoveTrimRange = useCallback(
(trimId: string) => {
// See handleAddTrimRange above — same fix: the document read must be
// inside the queued chain, after awaiting the previous save.
const queued = saveQueueRef.current
.then(() => import("@/lib/ai-edition/document/operations"))
.then(({ applyTimelineOperation }) => {
const doc = useProjectStore.getState().document ?? document;
if (!doc) return null;
return applyTimelineOperation(doc, {
type: "remove_trim_range",
trimId,
reason: "Restored from transcript pane.",
});
})
.then((next) => next && saveDocument(next.document));
saveQueueRef.current = queued.then(
() => undefined,
() => undefined,
);
return queued;
void applyTimelineOp({
type: "remove_trim_range",
trimId,
reason: "Restored from transcript pane.",
});
},
[document, saveDocument],
[applyTimelineOp],
);

const handleSelectProject = useCallback(
Expand Down
6 changes: 0 additions & 6 deletions src/i18n/locales/ar/editor.json
Original file line number Diff line number Diff line change
Expand Up @@ -238,10 +238,6 @@
"deleteSessionFailed": "تعذّر حذف المحادثة",
"renameSessionFailed": "تعذّر إعادة تسمية المحادثة",
"reasoningEffortUpdateFailed": "تعذّر تحديث جهد الاستدلال",
"addSkipRangePrompt": "إضافة نطاق تخطٍّ\nالتنسيق: startSec-endSec (مثال: 1.2-3.4)",
"addSkipRangeFormatError": "استخدم startSec-endSec، مثال: 1.2-3.4",
"addSkipRangeOrderError": "يجب أن يكون endSec أكبر من startSec.",
"addSkipRangeApplied": "تمت إضافة تخطٍّ {{startSec}}–{{endSec}} ثانية.",
"contextTooltip": "{{usedTokens}} / {{budgetTokens}} رمز مقدّر",
"contextPercent": "{{percent}}% من السياق",
"compactContext": "ضغط السياق",
Expand All @@ -253,8 +249,6 @@
"renameConversation": "إعادة تسمية المحادثة",
"deleteConversation": "حذف المحادثة",
"confirmDeleteConversation": "حذف \"{{title}}\"؟",
"addSkipRange": "إضافة نطاق تخطٍّ",
"skipButtonShort": "+ تخطٍّ",
"emptyState": "لا توجد رسائل بعد. اطلب من الوكيل قص الصمت أو تقليص الوقفات أو إضافة ترجمات.",
"welcome": {
"title": "أحضر ذكاءك الاصطناعي الخاص",
Expand Down
6 changes: 0 additions & 6 deletions src/i18n/locales/en/editor.json
Original file line number Diff line number Diff line change
Expand Up @@ -238,10 +238,6 @@
"deleteSessionFailed": "Could not delete conversation",
"renameSessionFailed": "Could not rename conversation",
"reasoningEffortUpdateFailed": "Could not update reasoning effort",
"addSkipRangePrompt": "Add skip range\nFormat: startSec-endSec (e.g. 1.2-3.4)",
"addSkipRangeFormatError": "Use startSec-endSec, e.g. 1.2-3.4",
"addSkipRangeOrderError": "endSec must be greater than startSec.",
"addSkipRangeApplied": "Added skip {{startSec}}–{{endSec}}s.",
"contextTooltip": "{{usedTokens}} / {{budgetTokens}} estimated tokens",
"contextPercent": "{{percent}}% context",
"compactContext": "Compact context",
Expand All @@ -253,8 +249,6 @@
"renameConversation": "Rename conversation",
"deleteConversation": "Delete conversation",
"confirmDeleteConversation": "Delete \"{{title}}\"?",
"addSkipRange": "Add skip range",
"skipButtonShort": "+ skip",
"emptyState": "No messages yet. Ask the agent to cut silences, tighten pauses, or add captions.",
"welcome": {
"title": "Bring your own AI",
Expand Down
6 changes: 0 additions & 6 deletions src/i18n/locales/es/editor.json
Original file line number Diff line number Diff line change
Expand Up @@ -238,10 +238,6 @@
"deleteSessionFailed": "No se pudo eliminar la conversación",
"renameSessionFailed": "No se pudo renombrar la conversación",
"reasoningEffortUpdateFailed": "No se pudo actualizar el esfuerzo de razonamiento",
"addSkipRangePrompt": "Añadir rango de salto\nFormato: startSec-endSec (p. ej. 1.2-3.4)",
"addSkipRangeFormatError": "Usa startSec-endSec, p. ej. 1.2-3.4",
"addSkipRangeOrderError": "endSec debe ser mayor que startSec.",
"addSkipRangeApplied": "Se añadió un salto de {{startSec}}–{{endSec}}s.",
"contextTooltip": "{{usedTokens}} / {{budgetTokens}} tokens estimados",
"contextPercent": "{{percent}}% de contexto",
"compactContext": "Compactar contexto",
Expand All @@ -253,8 +249,6 @@
"renameConversation": "Renombrar conversación",
"deleteConversation": "Eliminar conversación",
"confirmDeleteConversation": "¿Eliminar \"{{title}}\"?",
"addSkipRange": "Añadir rango de salto",
"skipButtonShort": "+ salto",
"emptyState": "Aún no hay mensajes. Pide al agente que corte silencios, ajuste pausas o añada subtítulos.",
"welcome": {
"title": "Trae tu propia IA",
Expand Down
6 changes: 0 additions & 6 deletions src/i18n/locales/fr/editor.json
Original file line number Diff line number Diff line change
Expand Up @@ -238,10 +238,6 @@
"deleteSessionFailed": "Impossible de supprimer la conversation",
"renameSessionFailed": "Impossible de renommer la conversation",
"reasoningEffortUpdateFailed": "Impossible de mettre à jour l'effort de raisonnement",
"addSkipRangePrompt": "Ajouter une plage à ignorer\nFormat : startSec-endSec (ex. 1.2-3.4)",
"addSkipRangeFormatError": "Utilisez startSec-endSec, ex. 1.2-3.4",
"addSkipRangeOrderError": "endSec doit être supérieur à startSec.",
"addSkipRangeApplied": "Plage ignorée ajoutée {{startSec}}–{{endSec}}s.",
"contextTooltip": "{{usedTokens}} / {{budgetTokens}} jetons estimés",
"contextPercent": "{{percent}}% du contexte",
"compactContext": "Compacter le contexte",
Expand All @@ -253,8 +249,6 @@
"renameConversation": "Renommer la conversation",
"deleteConversation": "Supprimer la conversation",
"confirmDeleteConversation": "Supprimer « {{title}} » ?",
"addSkipRange": "Ajouter une plage à ignorer",
"skipButtonShort": "+ saut",
"emptyState": "Aucun message pour l'instant. Demandez à l'agent de couper les silences, resserrer les pauses ou ajouter des sous-titres.",
"welcome": {
"title": "Apportez votre IA",
Expand Down
6 changes: 0 additions & 6 deletions src/i18n/locales/it/editor.json
Original file line number Diff line number Diff line change
Expand Up @@ -238,10 +238,6 @@
"deleteSessionFailed": "Impossibile eliminare la conversazione",
"renameSessionFailed": "Impossibile rinominare la conversazione",
"reasoningEffortUpdateFailed": "Impossibile aggiornare lo sforzo di ragionamento",
"addSkipRangePrompt": "Aggiungi intervallo da saltare\nFormato: startSec-endSec (es. 1.2-3.4)",
"addSkipRangeFormatError": "Usa startSec-endSec, es. 1.2-3.4",
"addSkipRangeOrderError": "endSec deve essere maggiore di startSec.",
"addSkipRangeApplied": "Aggiunto salto {{startSec}}–{{endSec}}s.",
"contextTooltip": "{{usedTokens}} / {{budgetTokens}} token stimati",
"contextPercent": "{{percent}}% di contesto",
"compactContext": "Comprimi contesto",
Expand All @@ -253,8 +249,6 @@
"renameConversation": "Rinomina conversazione",
"deleteConversation": "Elimina conversazione",
"confirmDeleteConversation": "Eliminare \"{{title}}\"?",
"addSkipRange": "Aggiungi intervallo da saltare",
"skipButtonShort": "+ salto",
"emptyState": "Nessun messaggio ancora. Chiedi all'agente di tagliare i silenzi, stringere le pause o aggiungere sottotitoli.",
"welcome": {
"title": "Porta la tua IA",
Expand Down
6 changes: 0 additions & 6 deletions src/i18n/locales/ja-JP/editor.json
Original file line number Diff line number Diff line change
Expand Up @@ -238,10 +238,6 @@
"deleteSessionFailed": "会話を削除できませんでした",
"renameSessionFailed": "会話の名前を変更できませんでした",
"reasoningEffortUpdateFailed": "推論の労力を更新できませんでした",
"addSkipRangePrompt": "スキップ範囲を追加\n形式:startSec-endSec(例:1.2-3.4)",
"addSkipRangeFormatError": "startSec-endSec の形式を使用してください(例:1.2-3.4)",
"addSkipRangeOrderError": "endSec は startSec より大きくする必要があります。",
"addSkipRangeApplied": "{{startSec}}–{{endSec}}秒のスキップを追加しました。",
"contextTooltip": "推定トークン数 {{usedTokens}} / {{budgetTokens}}",
"contextPercent": "コンテキスト {{percent}}%",
"compactContext": "コンテキストを圧縮",
Expand All @@ -253,8 +249,6 @@
"renameConversation": "会話の名前を変更",
"deleteConversation": "会話を削除",
"confirmDeleteConversation": "「{{title}}」を削除しますか?",
"addSkipRange": "スキップ範囲を追加",
"skipButtonShort": "+ スキップ",
"emptyState": "まだメッセージはありません。エージェントに無音のカット、間の調整、字幕の追加を依頼してみましょう。",
"welcome": {
"title": "自分のAIを持ち込もう",
Expand Down
Loading
Loading