新增移除任务

This commit is contained in:
kura 2026-05-01 15:20:12 +08:00
parent d8759d56c6
commit 508f28d092
10 changed files with 1348 additions and 0 deletions

View File

@ -50,6 +50,14 @@ fn update_segment_text(
task::update_segment_text(state, segment).map_err(error_to_string) task::update_segment_text(state, segment).map_err(error_to_string)
} }
#[tauri::command]
fn delete_task(
state: tauri::State<'_, AppState>,
task_id: String,
) -> std::result::Result<(), String> {
task::delete_task(state, task_id).map_err(error_to_string)
}
#[tauri::command] #[tauri::command]
fn export_subtitles( fn export_subtitles(
state: tauri::State<'_, AppState>, state: tauri::State<'_, AppState>,
@ -83,6 +91,7 @@ pub fn run() {
start_subtitle_task, start_subtitle_task,
list_tasks, list_tasks,
update_segment_text, update_segment_text,
delete_task,
export_subtitles, export_subtitles,
get_default_model_paths get_default_model_paths
]) ])

View File

@ -31,6 +31,12 @@ impl AppState {
Ok(tasks) Ok(tasks)
} }
pub fn delete_task(&self, task_id: &str) -> Result<()> {
let mut guard = self.tasks.lock().map_err(|_| anyhow!("task store poisoned"))?;
guard.remove(task_id);
Ok(())
}
pub fn update_segment(&self, segment: SubtitleSegment) -> Result<SubtitleTask> { pub fn update_segment(&self, segment: SubtitleSegment) -> Result<SubtitleTask> {
let mut guard = self.tasks.lock().map_err(|_| anyhow!("task store poisoned"))?; let mut guard = self.tasks.lock().map_err(|_| anyhow!("task store poisoned"))?;
let task = guard let task = guard

View File

@ -357,6 +357,10 @@ pub fn list_tasks(state: tauri::State<'_, AppState>) -> Result<Vec<SubtitleTask>
state.list_tasks() state.list_tasks()
} }
pub fn delete_task(state: tauri::State<'_, AppState>, task_id: String) -> Result<()> {
state.delete_task(&task_id)
}
pub fn export_task(state: tauri::State<'_, AppState>, task_id: String, format: String) -> Result<String> { pub fn export_task(state: tauri::State<'_, AppState>, task_id: String, format: String) -> Result<String> {
let task = state.get_task(&task_id)?; let task = state.get_task(&task_id)?;
let format = SubtitleFormat::try_from(format.as_str())?; let format = SubtitleFormat::try_from(format.as_str())?;

View File

@ -352,6 +352,7 @@ async function handleExport(format: 'srt' | 'vtt' | 'ass') {
:selected-task-id="taskStore.selectedTaskId" :selected-task-id="taskStore.selectedTaskId"
@select="taskStore.selectTask" @select="taskStore.selectTask"
@retry="taskStore.retryTask" @retry="taskStore.retryTask"
@delete="taskStore.deleteTask"
/> />
<SubtitleEditor <SubtitleEditor
:task="selectedTask" :task="selectedTask"

View File

@ -9,6 +9,7 @@ defineProps<{
const emit = defineEmits<{ const emit = defineEmits<{
select: [taskId: string] select: [taskId: string]
retry: [taskId: string] retry: [taskId: string]
delete: [taskId: string]
}>() }>()
</script> </script>
@ -52,6 +53,17 @@ const emit = defineEmits<{
<p class="error-text">{{ task.error }}</p> <p class="error-text">{{ task.error }}</p>
<button class="retry-button" type="button" @click.stop="emit('retry', task.id)">{{ $t('taskQueue.retry') }}</button> <button class="retry-button" type="button" @click.stop="emit('retry', task.id)">{{ $t('taskQueue.retry') }}</button>
</div> </div>
<button
class="delete-button"
type="button"
:title="$t('taskQueue.delete')"
@click.stop="emit('delete', task.id)"
>
<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
<polyline points="3 6 5 6 21 6" />
<path d="M19 6v14a2 2 0 0 1-2 2H7a2 2 0 0 1-2-2V6m3 0V4a2 2 0 0 1 2-2h4a2 2 0 0 1 2 2v2" />
</svg>
</button>
</button> </button>
</div> </div>
</aside> </aside>

View File

@ -49,6 +49,7 @@ export default {
subtitle: 'Select a task to view subtitles', subtitle: 'Select a task to view subtitles',
empty: 'No tasks', empty: 'No tasks',
retry: 'Retry', retry: 'Retry',
delete: 'Delete',
status: { status: {
queued: 'Queued', queued: 'Queued',
extracting: 'Extracting', extracting: 'Extracting',

View File

@ -49,6 +49,7 @@ export default {
subtitle: '选择任务查看字幕', subtitle: '选择任务查看字幕',
empty: '暂无任务', empty: '暂无任务',
retry: '重试', retry: '重试',
delete: '移除',
status: { status: {
queued: '排队中', queued: '排队中',
extracting: '抽取', extracting: '抽取',

View File

@ -140,6 +140,18 @@ export const useTaskStore = defineStore('tasks', {
if (index >= 0) this.tasks[index] = updated if (index >= 0) this.tasks[index] = updated
}, },
async deleteTask(taskId: string) {
await invoke('delete_task', { taskId })
const index = this.tasks.findIndex((t) => t.id === taskId)
if (index >= 0) {
this.tasks.splice(index, 1)
}
delete this.logsByTaskId[taskId]
if (this.selectedTaskId === taskId) {
this.selectedTaskId = ''
}
},
async exportTask(taskId: string, format: ExportFormat) { async exportTask(taskId: string, format: ExportFormat) {
return invoke<string>('export_subtitles', { taskId, format }) return invoke<string>('export_subtitles', { taskId, format })
}, },

View File

@ -488,6 +488,36 @@ textarea {
border-left: 3px solid transparent; border-left: 3px solid transparent;
} }
.task-item:hover .delete-button {
opacity: 1;
pointer-events: auto;
}
.delete-button {
position: absolute;
top: 8px;
right: 8px;
width: 26px;
height: 26px;
display: flex;
align-items: center;
justify-content: center;
border: 1px solid var(--c-border);
border-radius: var(--radius-sm);
background: var(--c-surface);
color: var(--c-text-tertiary);
cursor: pointer;
opacity: 0;
pointer-events: none;
transition: opacity var(--transition), color var(--transition), border-color var(--transition), background var(--transition);
}
.delete-button:hover {
color: var(--c-error);
border-color: var(--c-error);
background: rgba(220, 38, 38, 0.06);
}
.truncate { .truncate {
overflow: hidden; overflow: hidden;
text-overflow: ellipsis; text-overflow: ellipsis;

1272
yarn.lock Normal file

File diff suppressed because it is too large Load Diff