mirror of
https://github.com/kunkunsh/kunkun-ext-speech-to-text.git
synced 2025-04-03 17:56:43 +00:00

- Added project configuration files (tsconfig, eslint, vite, etc.) - Implemented Svelte5 frontend with routing - Created Deno backend for audio transcription using OpenAI Whisper - Added preferences page for API key configuration - Configured Tailwind CSS and theming - Implemented file selection and transcription functionality
133 lines
3.0 KiB
Svelte
133 lines
3.0 KiB
Svelte
<script lang="ts">
|
|
import { onMount } from 'svelte';
|
|
import { Button, Input, Label, Textarea } from '@kksh/svelte5';
|
|
import { ui, event, fs, dialog, shell, kv, clipboard, toast } from '@kksh/api/ui/iframe';
|
|
import type { API } from '../api.types';
|
|
import { goto } from '$app/navigation';
|
|
|
|
let filepath = $state('');
|
|
let openaiKey = $state('');
|
|
let transcription = $state('');
|
|
let transcribing = $state(false);
|
|
|
|
onMount(() => {
|
|
kv.get('OPENAI_API_KEY').then((key) => {
|
|
if (!key) {
|
|
toast.warning('Please enter your OpenAI API key');
|
|
return goto('/preferences');
|
|
}
|
|
openaiKey = key;
|
|
});
|
|
event.onDragDrop((payload) => {
|
|
payload.paths;
|
|
});
|
|
});
|
|
|
|
async function getAPI() {
|
|
const { rpcChannel, process, command } = await shell.createDenoRpcChannel<object, API>(
|
|
'$EXTENSION/deno-src/index.ts',
|
|
[],
|
|
{
|
|
allowEnv: [
|
|
'OPENAI_API_KEY',
|
|
'OPENAI_BASE_URL',
|
|
'OPENAI_ORG_ID',
|
|
'OPENAI_PROJECT_ID',
|
|
'DEBUG'
|
|
],
|
|
allowNet: ['api.openai.com'],
|
|
allowRead: [filepath],
|
|
env: {
|
|
OPENAI_API_KEY: openaiKey
|
|
}
|
|
},
|
|
{}
|
|
);
|
|
return { rpcChannel, process, command, api: rpcChannel.getAPI() };
|
|
}
|
|
|
|
function pickFile() {
|
|
dialog
|
|
.open({
|
|
directory: false,
|
|
filters: [
|
|
{ extensions: ['mp3', 'mp4', 'mpeg', 'mpga', 'm4a', 'wav', 'webm'], name: 'Audio Files' }
|
|
]
|
|
})
|
|
.then((path: string) => {
|
|
if (!path) {
|
|
return toast.error('No file selected');
|
|
}
|
|
filepath = path;
|
|
})
|
|
.catch((err: any) => {
|
|
toast.error('Failed to pick file', { description: err.message });
|
|
return null;
|
|
});
|
|
}
|
|
|
|
async function transcribe() {
|
|
if (!filepath) {
|
|
return toast.error('No file selected');
|
|
}
|
|
await fs.exists(filepath).then((exists) => {
|
|
if (!exists) {
|
|
return toast.error('File does not exist');
|
|
}
|
|
});
|
|
toast.info('Transcribing...');
|
|
const rpc = await getAPI();
|
|
if (!rpc) {
|
|
return;
|
|
}
|
|
const { api, process } = rpc;
|
|
transcribing = true;
|
|
return api
|
|
.transcribe(filepath, 'en')
|
|
.then((text) => {
|
|
console.log(text);
|
|
transcription = text;
|
|
toast.success('Transcription completed');
|
|
})
|
|
.catch((err) => {
|
|
toast.error('Failed to transcribe', { description: err.message });
|
|
})
|
|
.finally(() => {
|
|
process.kill();
|
|
transcribing = false;
|
|
});
|
|
}
|
|
</script>
|
|
|
|
<main class="container flex flex-col gap-2">
|
|
<Label>Pick an Audio File</Label>
|
|
<div class="flex gap-1">
|
|
<Input bind:value={filepath} />
|
|
<Button onclick={pickFile}>Pick File</Button>
|
|
</div>
|
|
<Button disabled={transcribing || !filepath} class="w-full" onclick={transcribe}>
|
|
Transcribe
|
|
</Button>
|
|
{#if transcribing}
|
|
<h2 class="text-center">Transcribing...</h2>
|
|
{:else}
|
|
<Textarea bind:value={transcription} class="min-h-64 w-full" />
|
|
<Button
|
|
class="w-full"
|
|
disabled={!transcription}
|
|
onclick={() => {
|
|
clipboard
|
|
.writeText(transcription)
|
|
.then(() => {
|
|
toast.success('Copied to clipboard');
|
|
})
|
|
.catch((err) => {
|
|
toast.error('Failed to copy to clipboard', { description: err.message });
|
|
});
|
|
}}
|
|
>
|
|
Copy
|
|
</Button>
|
|
{/if}
|
|
</main>
|