mirror of
https://github.com/Kilo-Org/kilocode.git
synced 2026-09-24 16:02:55 +08:00
Merge pull request #10258 from Kilo-Org/cedar-titanosaurus
Add experimental speech-to-text prompt input
This commit is contained in:
@@ -0,0 +1,6 @@
|
||||
---
|
||||
"kilo-code": minor
|
||||
"@kilocode/kilo-gateway": minor
|
||||
---
|
||||
|
||||
Support experimental speech-to-text voice input in VS Code prompt fields through Kilo Gateway.
|
||||
@@ -132,6 +132,21 @@ export function createKiloRoutes(deps: KiloRoutesDeps) {
|
||||
cost: z.number().optional(),
|
||||
})
|
||||
|
||||
const TranscriptionResponse = z.object({
|
||||
text: z.string(),
|
||||
usage: z.unknown().optional(),
|
||||
})
|
||||
|
||||
const getProxyAuth = async () => {
|
||||
const auth = await Auth.get("kilo")
|
||||
const token = auth?.type === "api" ? auth.key : auth?.type === "oauth" ? auth.access : undefined
|
||||
return {
|
||||
auth,
|
||||
token,
|
||||
organizationId: auth?.type === "oauth" ? auth.accountId : undefined,
|
||||
}
|
||||
}
|
||||
|
||||
return new Hono()
|
||||
.get(
|
||||
"/profile",
|
||||
@@ -324,19 +339,16 @@ export function createKiloRoutes(deps: KiloRoutesDeps) {
|
||||
}),
|
||||
),
|
||||
async (c: any) => {
|
||||
const auth = await Auth.get("kilo")
|
||||
const proxy = await getProxyAuth()
|
||||
|
||||
if (!auth) {
|
||||
if (!proxy.auth) {
|
||||
return c.json({ error: "Not authenticated with Kilo Gateway" }, 401)
|
||||
}
|
||||
|
||||
const token = auth.type === "api" ? auth.key : auth.type === "oauth" ? auth.access : undefined
|
||||
if (!token) {
|
||||
if (!proxy.token) {
|
||||
return c.json({ error: "No valid token found" }, 401)
|
||||
}
|
||||
|
||||
const organizationId = auth.type === "oauth" ? auth.accountId : undefined
|
||||
|
||||
const { prefix, suffix, model, maxTokens, temperature } = c.req.valid("json")
|
||||
const fimModel = model ?? "mistralai/codestral-2501"
|
||||
const fimMaxTokens = maxTokens ?? 256
|
||||
@@ -347,8 +359,8 @@ export function createKiloRoutes(deps: KiloRoutesDeps) {
|
||||
|
||||
const headers = {
|
||||
"Content-Type": "application/json",
|
||||
Authorization: `Bearer ${token}`,
|
||||
...buildKiloHeaders(undefined, { kilocodeOrganizationId: organizationId }),
|
||||
Authorization: `Bearer ${proxy.token}`,
|
||||
...buildKiloHeaders(undefined, { kilocodeOrganizationId: proxy.organizationId }),
|
||||
[HEADER_FEATURE]: "autocomplete",
|
||||
}
|
||||
|
||||
@@ -390,6 +402,66 @@ export function createKiloRoutes(deps: KiloRoutesDeps) {
|
||||
})
|
||||
},
|
||||
)
|
||||
.post(
|
||||
"/audio/transcriptions",
|
||||
describeRoute({
|
||||
summary: "Speech to text transcription",
|
||||
description: "Proxy an audio transcription request to the Kilo Gateway",
|
||||
operationId: "kilo.audio.transcriptions",
|
||||
responses: {
|
||||
200: {
|
||||
description: "Transcription response",
|
||||
content: {
|
||||
"application/json": {
|
||||
schema: resolver(TranscriptionResponse),
|
||||
},
|
||||
},
|
||||
},
|
||||
...errors(400, 401),
|
||||
},
|
||||
}),
|
||||
validator(
|
||||
"json",
|
||||
z.object({
|
||||
model: z.string(),
|
||||
input_audio: z.object({
|
||||
data: z.string(),
|
||||
format: z.string(),
|
||||
}),
|
||||
language: z.string().optional(),
|
||||
temperature: z.number().optional(),
|
||||
}),
|
||||
),
|
||||
async (c: any) => {
|
||||
const proxy = await getProxyAuth()
|
||||
if (!proxy.auth) return c.json({ error: "Not authenticated with Kilo Gateway" }, 401)
|
||||
|
||||
if (!proxy.token) return c.json({ error: "No valid token found" }, 401)
|
||||
|
||||
const body = c.req.valid("json")
|
||||
const headers = {
|
||||
"Content-Type": "application/json",
|
||||
Authorization: `Bearer ${proxy.token}`,
|
||||
...buildKiloHeaders(undefined, { kilocodeOrganizationId: proxy.organizationId }),
|
||||
[HEADER_FEATURE]: "vscode-extension",
|
||||
}
|
||||
|
||||
const response = await fetch(`${KILO_API_BASE}/api/gateway/v1/audio/transcriptions`, {
|
||||
method: "POST",
|
||||
headers,
|
||||
signal: c.req.raw.signal,
|
||||
body: JSON.stringify(body),
|
||||
})
|
||||
|
||||
const text = await response.text()
|
||||
return new Response(text, {
|
||||
status: response.status,
|
||||
headers: {
|
||||
"Content-Type": response.headers.get("Content-Type") ?? "application/json",
|
||||
},
|
||||
})
|
||||
},
|
||||
)
|
||||
.get(
|
||||
"/notifications",
|
||||
describeRoute({
|
||||
|
||||
@@ -776,6 +776,15 @@
|
||||
"default": false,
|
||||
"description": "Enable chat textarea autocomplete"
|
||||
},
|
||||
"kilo-code.new.speechToText.enabled": {
|
||||
"type": "boolean",
|
||||
"default": false,
|
||||
"description": "Enable experimental speech-to-text voice input in prompt fields. Uses your Kilo account through the Kilo Gateway."
|
||||
},
|
||||
"kilo-code.new.speechToText.model": {
|
||||
"type": "string",
|
||||
"description": "Model to use for experimental speech-to-text voice input. Requires Kilo Gateway."
|
||||
},
|
||||
"kilo-code.new.claudeCodeCompat": {
|
||||
"type": "boolean",
|
||||
"default": false,
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
import { $ } from "bun"
|
||||
import { join } from "node:path"
|
||||
import { existsSync, mkdirSync, rmSync, chmodSync } from "node:fs"
|
||||
import { ensureFfmpegForTarget } from "./ffmpeg-helper"
|
||||
|
||||
const packageJsonPath = join(import.meta.dir, "..", "package.json")
|
||||
const packageJson = await Bun.file(packageJsonPath).json()
|
||||
@@ -81,6 +82,9 @@ for (const config of targets) {
|
||||
|
||||
console.log(` ✅ Binary ready at ${targetBinary}`)
|
||||
|
||||
console.log("Adding bundled FFmpeg helper...")
|
||||
await ensureFfmpegForTarget(config.target, binDir)
|
||||
|
||||
console.log(` 📦 Packaging .vsix for ${config.target}${prerelease ? " (pre-release)" : ""}...`)
|
||||
const vsixPath = join(outDir, `kilo-vscode-${config.target}.vsix`)
|
||||
const args = ["--no-dependencies", "--skip-license", "--target", config.target, "-o", vsixPath]
|
||||
|
||||
@@ -0,0 +1,49 @@
|
||||
import { $ } from "bun"
|
||||
import { chmodSync, copyFileSync, existsSync, mkdirSync, rmSync } from "node:fs"
|
||||
import { join } from "node:path"
|
||||
|
||||
const packages: Record<string, string> = {
|
||||
"darwin-x64": "@ffmpeg-installer/darwin-x64@4.1.0",
|
||||
"darwin-arm64": "@ffmpeg-installer/darwin-arm64@4.1.5",
|
||||
"linux-x64": "@ffmpeg-installer/linux-x64@4.1.0",
|
||||
"linux-arm64": "@ffmpeg-installer/linux-arm64@4.1.4",
|
||||
"alpine-x64": "@ffmpeg-installer/linux-x64@4.1.0",
|
||||
"alpine-arm64": "@ffmpeg-installer/linux-arm64@4.1.4",
|
||||
"win32-x64": "@ffmpeg-installer/win32-x64@4.1.0",
|
||||
}
|
||||
|
||||
export async function ensureFfmpegForTarget(target: string, bin: string): Promise<void> {
|
||||
if (target === "win32-arm64") {
|
||||
console.warn("No Windows ARM64 FFmpeg helper package is available; speech input will use system FFmpeg.")
|
||||
return
|
||||
}
|
||||
|
||||
const spec = packages[target]
|
||||
if (!spec) throw new Error(`No FFmpeg helper package configured for target ${target}`)
|
||||
|
||||
const exe = target.startsWith("win32") ? "ffmpeg.exe" : "ffmpeg"
|
||||
const dest = join(bin, exe)
|
||||
if (existsSync(dest)) return
|
||||
|
||||
const tmp = join(bin, ".ffmpeg-tmp")
|
||||
rmSync(tmp, { recursive: true, force: true })
|
||||
mkdirSync(tmp, { recursive: true })
|
||||
|
||||
try {
|
||||
const packed = await $`npm pack ${spec} --pack-destination ${tmp}`.quiet()
|
||||
const name = packed.text().trim().split(/\s+/).pop()
|
||||
if (!name) throw new Error(`npm pack did not return a tarball for ${spec}`)
|
||||
|
||||
await $`tar -xzf ${join(tmp, name)} -C ${tmp}`.quiet()
|
||||
copyFileSync(join(tmp, "package", exe), dest)
|
||||
if (!target.startsWith("win32")) chmodSync(dest, 0o755)
|
||||
} finally {
|
||||
rmSync(tmp, { recursive: true, force: true })
|
||||
}
|
||||
}
|
||||
|
||||
export function currentFfmpegTarget(): string {
|
||||
const os = process.platform === "win32" ? "win32" : process.platform
|
||||
const arch = process.arch === "x64" ? "x64" : process.arch === "arm64" ? "arm64" : process.arch
|
||||
return `${os}-${arch}`
|
||||
}
|
||||
@@ -2,6 +2,7 @@
|
||||
import { $ } from "bun"
|
||||
import { join, relative, dirname, basename } from "node:path"
|
||||
import { chmodSync, statSync, rmSync, readdirSync, existsSync } from "node:fs"
|
||||
import { currentFfmpegTarget, ensureFfmpegForTarget } from "./ffmpeg-helper"
|
||||
|
||||
const forceRebuild = process.argv.includes("--force")
|
||||
|
||||
@@ -158,6 +159,7 @@ async function main() {
|
||||
log(
|
||||
`CLI binary already present at ${relative(kiloVscodeDir, targetBinPath)} (${Math.round(st.size / 1024 / 1024)}MB). Use --force to rebuild.`,
|
||||
)
|
||||
await ensureFfmpegForTarget(currentFfmpegTarget(), targetBinDir)
|
||||
return
|
||||
}
|
||||
|
||||
@@ -181,6 +183,7 @@ async function main() {
|
||||
await $`mkdir -p ${targetBinDir}`
|
||||
await $`cp ${sourceBinPath} ${targetBinPath}`
|
||||
chmodSync(targetBinPath, 0o755)
|
||||
await ensureFfmpegForTarget(currentFfmpegTarget(), targetBinDir)
|
||||
|
||||
// Record the CLI source version so future runs detect when a rebuild is needed
|
||||
const hash = await cliSourceHash()
|
||||
|
||||
@@ -68,10 +68,12 @@ import { handleNetworkEvent, clearNetworkWaits } from "./kilo-provider/network"
|
||||
import { abortSession } from "./kilo-provider/abort"
|
||||
import {
|
||||
buildAutocompleteSettingsMessage,
|
||||
routeAutocompleteMessage,
|
||||
validAutocompleteSetting,
|
||||
watchAutocompleteConfig,
|
||||
} from "./services/autocomplete/settings"
|
||||
import { validSpeechToTextSetting } from "./speech-to-text/settings"
|
||||
import { sendSpeechToTextSettings, watchSpeechToTextConfig } from "./speech-to-text/config"
|
||||
import { routeEarlyMessage } from "./kilo-provider/early-message"
|
||||
import * as ModelState from "./kilo-provider/model-state"
|
||||
import { handleForkSession } from "./kilo-provider/fork-session"
|
||||
import { openConfig } from "./kilo-provider/open-config"
|
||||
@@ -112,7 +114,7 @@ import {
|
||||
handleQuestionReject,
|
||||
fetchAndSendPendingQuestions,
|
||||
} from "./kilo-provider/handlers/question"
|
||||
import { fetchAndSendPendingSuggestions, routeSuggestionWebviewMessage } from "./kilo-provider/handlers/suggestion"
|
||||
import { fetchAndSendPendingSuggestions } from "./kilo-provider/handlers/suggestion"
|
||||
import { nativeTitle } from "./kilo-provider/native-tab-title"
|
||||
|
||||
import {
|
||||
@@ -229,6 +231,7 @@ export class KiloProvider implements vscode.WebviewViewProvider, TelemetryProper
|
||||
private webviewMessageDisposable: vscode.Disposable | null = null
|
||||
private autocompleteConfigDisposable: vscode.Disposable | null = null
|
||||
private telemetryStateDisposable: vscode.Disposable | null = null
|
||||
private speechToTextDisposable: vscode.Disposable | null = null
|
||||
private viewStateDisposable: vscode.Disposable | null = null
|
||||
private visibilityDisposable: vscode.Disposable | null = null
|
||||
private autoApproveBridge: ReturnType<typeof createAutoApproveBridge> | null = null
|
||||
@@ -592,6 +595,8 @@ export class KiloProvider implements vscode.WebviewViewProvider, TelemetryProper
|
||||
this.webviewMessageDisposable?.dispose()
|
||||
this.autocompleteConfigDisposable?.dispose()
|
||||
this.autocompleteConfigDisposable = watchAutocompleteConfig((msg) => this.postMessage(msg))
|
||||
this.speechToTextDisposable?.dispose()
|
||||
this.speechToTextDisposable = watchSpeechToTextConfig(this)
|
||||
this.telemetryStateDisposable?.dispose()
|
||||
this.telemetryStateDisposable = watchTelemetryState((msg) => this.postMessage(msg))
|
||||
this.webviewMessageDisposable = webview.onDidReceiveMessage(async (message) => {
|
||||
@@ -604,9 +609,17 @@ export class KiloProvider implements vscode.WebviewViewProvider, TelemetryProper
|
||||
if (intercepted === null) return
|
||||
message = intercepted
|
||||
|
||||
await routeSuggestionWebviewMessage(this.questionCtx, message)
|
||||
if (await ModelState.handleMessage(message.type, message, this.client, (msg) => this.postMessage(msg))) return
|
||||
if (await routeAutocompleteMessage(message, (msg) => this.postMessage(msg))) return
|
||||
if (
|
||||
await routeEarlyMessage(message, {
|
||||
question: this.questionCtx,
|
||||
client: this.client,
|
||||
connection: this.connectionService,
|
||||
dir: this.getWorkspaceDirectory(this.currentSession?.id),
|
||||
post: (msg) => this.postMessage(msg),
|
||||
})
|
||||
) {
|
||||
return
|
||||
}
|
||||
if (this.handleEditorOpenMessage(message)) return
|
||||
if (
|
||||
await handleSidebarWorktreeMessage(message, {
|
||||
@@ -1297,6 +1310,7 @@ export class KiloProvider implements vscode.WebviewViewProvider, TelemetryProper
|
||||
this.postMessage({ type: "gitStatus", repo: this.cachedGitRepo })
|
||||
this.sendNotificationSettings()
|
||||
this.sendTimelineSetting()
|
||||
this.sendSpeechToTextSettings()
|
||||
this.postMessage({ type: "extensionDataReady" })
|
||||
|
||||
if (this.cachedGitRepo) this.startStatsPolling()
|
||||
@@ -2337,6 +2351,10 @@ export class KiloProvider implements vscode.WebviewViewProvider, TelemetryProper
|
||||
})
|
||||
}
|
||||
|
||||
private sendSpeechToTextSettings(): void {
|
||||
sendSpeechToTextSettings(this)
|
||||
}
|
||||
|
||||
/** Returns the number of sessions currently in "busy" state. */
|
||||
private getBusySessionCount(): number {
|
||||
return getBusySessionCount(this.sessionStatusMap)
|
||||
@@ -2953,6 +2971,7 @@ export class KiloProvider implements vscode.WebviewViewProvider, TelemetryProper
|
||||
private async handleUpdateSetting(key: string, value: unknown): Promise<void> {
|
||||
const { section, leaf } = buildSettingPath(key)
|
||||
if (section === "autocomplete" && !validAutocompleteSetting(leaf, value)) return
|
||||
if (section === "speechToText" && !validSpeechToTextSetting(leaf, value)) return
|
||||
const config = vscode.workspace.getConfiguration(`kilo-code.new${section ? `.${section}` : ""}`)
|
||||
await config.update(leaf, value, vscode.ConfigurationTarget.Global)
|
||||
}
|
||||
@@ -2993,6 +3012,7 @@ export class KiloProvider implements vscode.WebviewViewProvider, TelemetryProper
|
||||
|
||||
// Re-send all settings to the webview so the UI reflects the reset
|
||||
this.postMessage(buildAutocompleteSettingsMessage())
|
||||
this.sendSpeechToTextSettings()
|
||||
this.sendBrowserSettings()
|
||||
this.sendNotificationSettings()
|
||||
this.sendTimelineSetting()
|
||||
@@ -3494,6 +3514,7 @@ export class KiloProvider implements vscode.WebviewViewProvider, TelemetryProper
|
||||
this.visibilityDisposable?.dispose()
|
||||
this.webviewMessageDisposable?.dispose()
|
||||
this.autocompleteConfigDisposable?.dispose()
|
||||
this.speechToTextDisposable?.dispose()
|
||||
this.telemetryStateDisposable?.dispose()
|
||||
this.autoApproveBridge?.dispose()
|
||||
this.streams.dispose()
|
||||
|
||||
@@ -0,0 +1,20 @@
|
||||
import { routeSuggestionWebviewMessage } from "./handlers/suggestion"
|
||||
import * as ModelState from "./model-state"
|
||||
import { routeInputToolMessage } from "../services/input-tools"
|
||||
import type { KiloConnectionService } from "../services/cli-backend/connection-service"
|
||||
import type { SuggestionContext } from "./handlers/suggestion"
|
||||
import type { KiloClient } from "@kilocode/sdk/v2/client"
|
||||
|
||||
type Ctx = {
|
||||
question: SuggestionContext
|
||||
client: KiloClient | null
|
||||
connection: KiloConnectionService
|
||||
dir: string
|
||||
post: (msg: unknown) => void
|
||||
}
|
||||
|
||||
export async function routeEarlyMessage(message: { type: string }, ctx: Ctx): Promise<boolean> {
|
||||
await routeSuggestionWebviewMessage(ctx.question, message)
|
||||
if (await ModelState.handleMessage(message.type, message, ctx.client, ctx.post)) return true
|
||||
return await routeInputToolMessage(message, { connection: ctx.connection, dir: ctx.dir, post: ctx.post })
|
||||
}
|
||||
@@ -0,0 +1,49 @@
|
||||
import type { KiloConnectionService } from "./cli-backend/connection-service"
|
||||
import { routeAutocompleteMessage } from "./autocomplete/settings"
|
||||
import { buildSpeechToTextSettingsMessage } from "../speech-to-text/settings"
|
||||
import { handleSpeechToTextCancel, handleSpeechToTextStart, handleSpeechToTextStop } from "../speech-to-text/handler"
|
||||
|
||||
type Msg = {
|
||||
type: string
|
||||
requestId?: string
|
||||
model?: string
|
||||
language?: string
|
||||
}
|
||||
|
||||
type Ctx = {
|
||||
connection: KiloConnectionService
|
||||
dir: string
|
||||
post: (msg: unknown) => void
|
||||
}
|
||||
|
||||
export async function routeInputToolMessage(message: Msg, ctx: Ctx): Promise<boolean> {
|
||||
if (await routeAutocompleteMessage(message, ctx.post)) return true
|
||||
|
||||
if (message.type === "requestSpeechToTextSettings") {
|
||||
ctx.post(buildSpeechToTextSettingsMessage())
|
||||
return true
|
||||
}
|
||||
|
||||
if (message.type === "speechToTextStart") {
|
||||
if (!message.requestId) return true
|
||||
handleSpeechToTextStart(
|
||||
{ requestId: message.requestId, model: message.model, language: message.language },
|
||||
ctx.post,
|
||||
)
|
||||
return true
|
||||
}
|
||||
|
||||
if (message.type === "speechToTextStop") {
|
||||
if (!message.requestId) return true
|
||||
handleSpeechToTextStop(ctx.connection, { requestId: message.requestId }, ctx.dir, ctx.post)
|
||||
return true
|
||||
}
|
||||
|
||||
if (message.type === "speechToTextCancel") {
|
||||
if (!message.requestId) return true
|
||||
handleSpeechToTextCancel({ requestId: message.requestId }, ctx.post)
|
||||
return true
|
||||
}
|
||||
|
||||
return false
|
||||
}
|
||||
@@ -0,0 +1,278 @@
|
||||
import { existsSync } from "fs"
|
||||
import { readFile, stat, unlink } from "fs/promises"
|
||||
import * as os from "os"
|
||||
import * as path from "path"
|
||||
import type { ChildProcess } from "child_process"
|
||||
import { exec, spawn } from "../util/process"
|
||||
|
||||
type Input = {
|
||||
requestId: string
|
||||
model: string
|
||||
language?: string
|
||||
}
|
||||
|
||||
type Recording = Input & {
|
||||
file: string
|
||||
proc: ChildProcess
|
||||
stderr: string[]
|
||||
stopped: boolean
|
||||
exit?: {
|
||||
code: number | null
|
||||
signal: string | null
|
||||
}
|
||||
}
|
||||
|
||||
type Audio = {
|
||||
data: string
|
||||
format: "wav"
|
||||
model: string
|
||||
language?: string
|
||||
}
|
||||
|
||||
let active: Recording | undefined
|
||||
let starting: string | undefined
|
||||
|
||||
export async function startSpeechCapture(input: Input): Promise<boolean> {
|
||||
if (active || starting) throw new Error("Speech recording is already in progress")
|
||||
|
||||
starting = input.requestId
|
||||
try {
|
||||
const bin = await findFFmpeg()
|
||||
const file = path.join(os.tmpdir(), `kilo-stt-${process.pid}-${Date.now()}.wav`)
|
||||
const state = await startWithArgs(bin, file, input, await inputArgSets(bin))
|
||||
return !state.stopped
|
||||
} finally {
|
||||
if (starting === input.requestId) starting = undefined
|
||||
}
|
||||
}
|
||||
|
||||
export async function stopSpeechCapture(requestId: string): Promise<Audio> {
|
||||
const state = requireActive(requestId)
|
||||
state.stopped = true
|
||||
active = undefined
|
||||
|
||||
await stopProcess(state)
|
||||
|
||||
const size = await stat(state.file)
|
||||
.then((info) => info.size)
|
||||
.catch((err: unknown) => {
|
||||
console.warn("[Kilo New] Failed to stat speech recording", err)
|
||||
return 0
|
||||
})
|
||||
|
||||
if (size < 44) {
|
||||
await removeFile(state.file)
|
||||
throw new Error(summary(state, "No audio was recorded"))
|
||||
}
|
||||
|
||||
const file = await readFile(state.file)
|
||||
await removeFile(state.file)
|
||||
return { data: file.toString("base64"), format: "wav", model: state.model, language: state.language }
|
||||
}
|
||||
|
||||
export async function cancelSpeechCapture(requestId: string): Promise<void> {
|
||||
const state = active
|
||||
if (!state || state.requestId !== requestId) return
|
||||
state.stopped = true
|
||||
active = undefined
|
||||
await stopProcess(state)
|
||||
await removeFile(state.file)
|
||||
}
|
||||
|
||||
async function waitForStart(state: Recording): Promise<void> {
|
||||
await new Promise<void>((resolve, reject) => {
|
||||
const done = () => {
|
||||
state.proc.off("error", onError)
|
||||
state.proc.off("exit", onExit)
|
||||
state.proc.stderr?.off("data", onData)
|
||||
clearTimeout(timer)
|
||||
resolve()
|
||||
}
|
||||
const onError = (err: Error) => {
|
||||
state.proc.off("exit", onExit)
|
||||
state.proc.stderr?.off("data", onData)
|
||||
clearTimeout(timer)
|
||||
reject(err)
|
||||
}
|
||||
const onExit = () => {
|
||||
if (state.stopped) {
|
||||
done()
|
||||
return
|
||||
}
|
||||
onError(new Error(summary(state, "Could not start microphone recording")))
|
||||
}
|
||||
const onData = (data: Buffer) => {
|
||||
if (/Output #0|Press \[q\]|size=\s*\d+/i.test(data.toString())) done()
|
||||
}
|
||||
const timer = setTimeout(() => {
|
||||
onError(new Error(summary(state, "Timed out starting microphone recording")))
|
||||
}, 5000)
|
||||
|
||||
state.proc.stderr?.on("data", onData)
|
||||
state.proc.once("error", onError)
|
||||
state.proc.once("exit", onExit)
|
||||
}).catch(async (err: unknown) => {
|
||||
if (active === state) active = undefined
|
||||
await stopProcess(state)
|
||||
await removeFile(state.file)
|
||||
throw err
|
||||
})
|
||||
}
|
||||
|
||||
async function startWithArgs(bin: string, file: string, input: Input, args: string[][]): Promise<Recording> {
|
||||
const [first, ...rest] = args
|
||||
if (!first) throw new Error(`Unsupported platform for speech input: ${process.platform}`)
|
||||
|
||||
const proc = spawn(bin, ["-y", ...first, "-acodec", "pcm_s16le", "-ar", "16000", "-ac", "1", "-f", "wav", file], {
|
||||
stdio: ["pipe", "ignore", "pipe"],
|
||||
})
|
||||
const state: Recording = { ...input, file, proc, stderr: [], stopped: false }
|
||||
active = state
|
||||
|
||||
proc.stderr?.on("data", (data: Buffer) => {
|
||||
if (state.stderr.length < 20) state.stderr.push(data.toString())
|
||||
})
|
||||
proc.on("exit", (code, signal) => {
|
||||
state.exit = { code, signal }
|
||||
if (active === state && !state.stopped) active = undefined
|
||||
})
|
||||
proc.on("error", (err) => {
|
||||
state.stderr.push(err.message)
|
||||
if (active === state && !state.stopped) active = undefined
|
||||
})
|
||||
|
||||
try {
|
||||
await waitForStart(state)
|
||||
return state
|
||||
} catch (err) {
|
||||
if (state.stopped) return state
|
||||
if (rest.length === 0) throw err
|
||||
return startWithArgs(bin, file, input, rest)
|
||||
}
|
||||
}
|
||||
|
||||
async function stopProcess(state: Recording): Promise<void> {
|
||||
if (state.proc.exitCode !== null || state.proc.signalCode) return
|
||||
|
||||
await new Promise<void>((resolve) => {
|
||||
const timer = setTimeout(() => {
|
||||
if (!state.proc.killed) state.proc.kill("SIGKILL")
|
||||
resolve()
|
||||
}, 2000)
|
||||
|
||||
state.proc.once("exit", () => {
|
||||
clearTimeout(timer)
|
||||
resolve()
|
||||
})
|
||||
|
||||
if (state.proc.stdin?.writable) {
|
||||
state.proc.stdin.write("q")
|
||||
state.proc.stdin.end()
|
||||
return
|
||||
}
|
||||
|
||||
state.proc.kill("SIGTERM")
|
||||
})
|
||||
}
|
||||
|
||||
function requireActive(requestId: string): Recording {
|
||||
if (!active || active.requestId !== requestId) throw new Error("No active speech recording")
|
||||
return active
|
||||
}
|
||||
|
||||
async function findFFmpeg(): Promise<string> {
|
||||
const paths = [
|
||||
process.env.KILO_FFMPEG_PATH,
|
||||
process.env.FFMPEG_PATH,
|
||||
bundledPath(),
|
||||
...platformPaths(),
|
||||
"ffmpeg",
|
||||
].filter(Boolean)
|
||||
|
||||
for (const bin of paths) {
|
||||
if (!bin) continue
|
||||
if (path.isAbsolute(bin) && !existsSync(bin)) continue
|
||||
try {
|
||||
await exec(bin, ["-version"], { timeout: 3000 })
|
||||
return bin
|
||||
} catch (err) {
|
||||
console.warn(`[Kilo New] FFmpeg candidate failed: ${bin}`, err)
|
||||
}
|
||||
}
|
||||
|
||||
throw new Error("Speech input needs the bundled FFmpeg helper, but it was not found. Rebuild or reinstall Kilo Code.")
|
||||
}
|
||||
|
||||
function bundledPath(): string {
|
||||
return path.join(__dirname, "..", "bin", process.platform === "win32" ? "ffmpeg.exe" : "ffmpeg")
|
||||
}
|
||||
|
||||
function platformPaths(): string[] {
|
||||
if (process.platform === "darwin")
|
||||
return ["/opt/homebrew/bin/ffmpeg", "/usr/local/bin/ffmpeg", "/opt/local/bin/ffmpeg"]
|
||||
if (process.platform === "win32") {
|
||||
return [
|
||||
"C:\\ffmpeg\\bin\\ffmpeg.exe",
|
||||
process.env.ProgramFiles ? path.join(process.env.ProgramFiles, "ffmpeg", "bin", "ffmpeg.exe") : undefined,
|
||||
process.env["ProgramFiles(x86)"]
|
||||
? path.join(process.env["ProgramFiles(x86)"], "ffmpeg", "bin", "ffmpeg.exe")
|
||||
: undefined,
|
||||
process.env.USERPROFILE
|
||||
? path.join(process.env.USERPROFILE, "scoop", "apps", "ffmpeg", "current", "bin", "ffmpeg.exe")
|
||||
: undefined,
|
||||
"C:\\ProgramData\\chocolatey\\bin\\ffmpeg.exe",
|
||||
].filter((item): item is string => !!item)
|
||||
}
|
||||
return ["/usr/bin/ffmpeg", "/usr/local/bin/ffmpeg", "/snap/bin/ffmpeg", "/home/linuxbrew/.linuxbrew/bin/ffmpeg"]
|
||||
}
|
||||
|
||||
async function inputArgSets(bin: string): Promise<string[][]> {
|
||||
if (process.platform === "darwin") return [["-f", "avfoundation", "-i", ":default"]]
|
||||
if (process.platform === "linux")
|
||||
return [
|
||||
["-f", "pulse", "-i", "default"],
|
||||
["-f", "alsa", "-i", "default"],
|
||||
]
|
||||
if (process.platform === "win32") return await windowsInputArgSets(bin)
|
||||
return []
|
||||
}
|
||||
|
||||
async function windowsInputArgSets(bin: string): Promise<string[][]> {
|
||||
const configured = process.env.KILO_FFMPEG_AUDIO_DEVICE
|
||||
if (configured) return [["-f", "dshow", "-i", `audio=${configured}`]]
|
||||
|
||||
const devices = await listDshowAudioDevices(bin)
|
||||
if (devices.length === 0) throw new Error("No Windows audio input devices found for speech input")
|
||||
return devices.map((device) => ["-f", "dshow", "-i", `audio=${device}`])
|
||||
}
|
||||
|
||||
async function listDshowAudioDevices(bin: string): Promise<string[]> {
|
||||
const raw = await exec(bin, ["-list_devices", "true", "-f", "dshow", "-i", "dummy"], { timeout: 5000 })
|
||||
.then((result) => `${result.stdout}\n${result.stderr}`)
|
||||
.catch((err: unknown) => processOutput(err))
|
||||
return parseDshowAudioDevices(raw)
|
||||
}
|
||||
|
||||
export function parseDshowAudioDevices(raw: string): string[] {
|
||||
const devices = new Set<string>()
|
||||
for (const match of raw.matchAll(/"([^"]+)"\s+\(audio\)/g)) devices.add(match[1]!)
|
||||
return [...devices]
|
||||
}
|
||||
|
||||
function processOutput(err: unknown): string {
|
||||
if (!err || typeof err !== "object") return ""
|
||||
const result = err as { stdout?: unknown; stderr?: unknown }
|
||||
return `${typeof result.stdout === "string" ? result.stdout : ""}\n${typeof result.stderr === "string" ? result.stderr : ""}`
|
||||
}
|
||||
|
||||
function summary(state: Recording, fallback: string): string {
|
||||
const stderr = state.stderr.join("\n").trim()
|
||||
if (!stderr) return fallback
|
||||
return `${fallback}: ${stderr.slice(-800)}`
|
||||
}
|
||||
|
||||
async function removeFile(file: string): Promise<void> {
|
||||
await unlink(file).catch((err: unknown) => {
|
||||
console.warn("[Kilo New] Failed to remove speech recording", err)
|
||||
})
|
||||
}
|
||||
@@ -0,0 +1,13 @@
|
||||
import * as vscode from "vscode"
|
||||
import { buildSpeechToTextSettingsMessage } from "./settings"
|
||||
import type { KiloProvider } from "../KiloProvider"
|
||||
|
||||
export function watchSpeechToTextConfig(provider: KiloProvider): vscode.Disposable {
|
||||
return vscode.workspace.onDidChangeConfiguration((e) => {
|
||||
if (e.affectsConfiguration("kilo-code.new.speechToText")) sendSpeechToTextSettings(provider)
|
||||
})
|
||||
}
|
||||
|
||||
export function sendSpeechToTextSettings(provider: KiloProvider): void {
|
||||
provider.postMessage(buildSpeechToTextSettingsMessage())
|
||||
}
|
||||
@@ -0,0 +1,129 @@
|
||||
import { getErrorMessage } from "../kilo-provider-utils"
|
||||
import { transcribeSpeech } from "./transcribe"
|
||||
import { cancelSpeechCapture, startSpeechCapture, stopSpeechCapture } from "./capture"
|
||||
import type { KiloConnectionService } from "../services/cli-backend/connection-service"
|
||||
|
||||
type Msg = {
|
||||
requestId: string
|
||||
model?: string
|
||||
language?: string
|
||||
}
|
||||
|
||||
type Post = (msg: unknown) => void
|
||||
|
||||
const aborts = new Map<string, AbortController>()
|
||||
const cancelled = new Set<string>()
|
||||
const starts = new Map<string, Promise<boolean>>()
|
||||
const stopping = new Set<string>()
|
||||
|
||||
export function handleSpeechToTextStart(message: Msg, post: Post): void {
|
||||
const task = startSpeechCapture({
|
||||
requestId: message.requestId,
|
||||
model: message.model || "",
|
||||
language: message.language,
|
||||
})
|
||||
|
||||
starts.set(message.requestId, task)
|
||||
|
||||
void task
|
||||
.then((started) => {
|
||||
starts.delete(message.requestId)
|
||||
if (!started) return
|
||||
if (stopping.has(message.requestId) || cancelled.has(message.requestId)) return
|
||||
post({ type: "speechToTextStarted", requestId: message.requestId })
|
||||
})
|
||||
.catch((err: unknown) => {
|
||||
starts.delete(message.requestId)
|
||||
stopping.delete(message.requestId)
|
||||
if (cancelled.delete(message.requestId)) return
|
||||
post({
|
||||
type: "speechToTextError",
|
||||
error: getErrorMessage(err) || "Speech recording failed",
|
||||
requestId: message.requestId,
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
export function handleSpeechToTextStop(connection: KiloConnectionService, message: Msg, dir: string, post: Post): void {
|
||||
const ctrl = new AbortController()
|
||||
const ready = starts.get(message.requestId)?.catch(() => false) ?? Promise.resolve(true)
|
||||
aborts.set(message.requestId, ctrl)
|
||||
stopping.add(message.requestId)
|
||||
|
||||
void ready
|
||||
.then((started) => {
|
||||
if (!started) return undefined
|
||||
return stopSpeechCapture(message.requestId).then((audio) => transcribeSpeech(connection, audio, dir, ctrl.signal))
|
||||
})
|
||||
.then((result) => {
|
||||
aborts.delete(message.requestId)
|
||||
stopping.delete(message.requestId)
|
||||
if (!result) return
|
||||
if (cancelled.delete(message.requestId)) return
|
||||
if (!result.ok && result.code === "cancelled") return
|
||||
if (result.ok) {
|
||||
post({ type: "speechToTextResult", text: result.text, requestId: message.requestId })
|
||||
return
|
||||
}
|
||||
post({ type: "speechToTextError", error: result.error, code: result.code, requestId: message.requestId })
|
||||
})
|
||||
.catch((err: unknown) => {
|
||||
aborts.delete(message.requestId)
|
||||
stopping.delete(message.requestId)
|
||||
if (cancelled.delete(message.requestId)) return
|
||||
post({
|
||||
type: "speechToTextError",
|
||||
error: getErrorMessage(err) || "Speech to text request failed",
|
||||
requestId: message.requestId,
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
export function handleSpeechToTextCancel(message: Msg, post: Post): void {
|
||||
const ctrl = aborts.get(message.requestId)
|
||||
if (ctrl) {
|
||||
cancelled.add(message.requestId)
|
||||
stopping.delete(message.requestId)
|
||||
ctrl.abort()
|
||||
aborts.delete(message.requestId)
|
||||
post({ type: "speechToTextCancelled", requestId: message.requestId })
|
||||
return
|
||||
}
|
||||
|
||||
const ready = starts.get(message.requestId)
|
||||
if (ready) {
|
||||
cancelled.add(message.requestId)
|
||||
stopping.delete(message.requestId)
|
||||
void ready
|
||||
.catch(() => false)
|
||||
.then((started) => {
|
||||
if (!started) return undefined
|
||||
return cancelSpeechCapture(message.requestId)
|
||||
})
|
||||
.then(() => {
|
||||
cancelled.delete(message.requestId)
|
||||
post({ type: "speechToTextCancelled", requestId: message.requestId })
|
||||
})
|
||||
.catch((err: unknown) => {
|
||||
cancelled.delete(message.requestId)
|
||||
post({
|
||||
type: "speechToTextError",
|
||||
error: getErrorMessage(err) || "Speech recording cancellation failed",
|
||||
requestId: message.requestId,
|
||||
})
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
void cancelSpeechCapture(message.requestId)
|
||||
.then(() => {
|
||||
post({ type: "speechToTextCancelled", requestId: message.requestId })
|
||||
})
|
||||
.catch((err: unknown) => {
|
||||
post({
|
||||
type: "speechToTextError",
|
||||
error: getErrorMessage(err) || "Speech recording cancellation failed",
|
||||
requestId: message.requestId,
|
||||
})
|
||||
})
|
||||
}
|
||||
@@ -0,0 +1,48 @@
|
||||
export interface SpeechToTextModelDef {
|
||||
readonly id: string
|
||||
readonly label: string
|
||||
readonly provider: string
|
||||
}
|
||||
|
||||
const models: SpeechToTextModelDef[] = [
|
||||
{
|
||||
id: "openai/gpt-4o-mini-transcribe",
|
||||
label: "GPT-4o Mini Transcribe",
|
||||
provider: "OpenAI",
|
||||
},
|
||||
{
|
||||
id: "openai/gpt-4o-transcribe",
|
||||
label: "GPT-4o Transcribe",
|
||||
provider: "OpenAI",
|
||||
},
|
||||
{
|
||||
id: "openai/whisper-1",
|
||||
label: "Whisper 1",
|
||||
provider: "OpenAI",
|
||||
},
|
||||
{
|
||||
id: "openai/whisper-large-v3-turbo",
|
||||
label: "Whisper Large V3 Turbo",
|
||||
provider: "OpenAI-compatible",
|
||||
},
|
||||
{
|
||||
id: "openai/whisper-large-v3",
|
||||
label: "Whisper Large V3",
|
||||
provider: "OpenAI-compatible",
|
||||
},
|
||||
{
|
||||
id: "google/chirp-3",
|
||||
label: "Chirp 3",
|
||||
provider: "Google",
|
||||
},
|
||||
]
|
||||
|
||||
export const SPEECH_TO_TEXT_MODELS: readonly SpeechToTextModelDef[] = models
|
||||
export const DEFAULT_SPEECH_TO_TEXT_MODEL: SpeechToTextModelDef = models[0]!
|
||||
|
||||
export function getSpeechToTextModel(id: string | undefined): SpeechToTextModelDef {
|
||||
for (const model of models) {
|
||||
if (model.id === id) return model
|
||||
}
|
||||
return DEFAULT_SPEECH_TO_TEXT_MODEL
|
||||
}
|
||||
@@ -0,0 +1,19 @@
|
||||
import * as vscode from "vscode"
|
||||
import { getSpeechToTextModel } from "./models"
|
||||
|
||||
export function buildSpeechToTextSettingsMessage() {
|
||||
const config = vscode.workspace.getConfiguration("kilo-code.new.speechToText")
|
||||
return {
|
||||
type: "speechToTextSettingsLoaded" as const,
|
||||
settings: {
|
||||
enabled: config.get<boolean>("enabled", false),
|
||||
model: getSpeechToTextModel(config.get<string>("model")).id,
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
export function validSpeechToTextSetting(key: string, value: unknown) {
|
||||
if (key === "enabled") return typeof value === "boolean"
|
||||
if (key === "model") return typeof value === "string" && getSpeechToTextModel(value).id === value
|
||||
return false
|
||||
}
|
||||
@@ -0,0 +1,106 @@
|
||||
import type { KiloConnectionService } from "../services/cli-backend/connection-service"
|
||||
import { getErrorMessage } from "../kilo-provider-utils"
|
||||
import { getSpeechToTextModel } from "./models"
|
||||
|
||||
const PATH = "/kilo/audio/transcriptions"
|
||||
|
||||
type Req = {
|
||||
model?: string
|
||||
data: string
|
||||
format: string
|
||||
language?: string
|
||||
}
|
||||
|
||||
type Res = {
|
||||
text?: unknown
|
||||
}
|
||||
|
||||
type Ok = {
|
||||
ok: true
|
||||
text: string
|
||||
}
|
||||
|
||||
type Err = {
|
||||
ok: false
|
||||
error: string
|
||||
code?: string
|
||||
}
|
||||
|
||||
export type SpeechToTextResult = Ok | Err
|
||||
|
||||
export async function transcribeSpeech(
|
||||
connection: KiloConnectionService,
|
||||
input: Req,
|
||||
dir: string,
|
||||
signal?: AbortSignal,
|
||||
): Promise<SpeechToTextResult> {
|
||||
const cfg = connection.getServerConfig()
|
||||
if (!cfg) return { ok: false, error: "Not connected to the Kilo backend", code: "not_connected" }
|
||||
|
||||
const auth = Buffer.from(`kilo:${cfg.password}`).toString("base64")
|
||||
const url = new URL(PATH, cfg.baseUrl)
|
||||
if (dir) url.searchParams.set("directory", dir)
|
||||
|
||||
try {
|
||||
const res = await fetch(url, {
|
||||
method: "POST",
|
||||
signal,
|
||||
headers: {
|
||||
Authorization: `Basic ${auth}`,
|
||||
"Content-Type": "application/json",
|
||||
},
|
||||
body: JSON.stringify({
|
||||
model: getSpeechToTextModel(input.model).id,
|
||||
input_audio: {
|
||||
data: input.data,
|
||||
format: input.format,
|
||||
},
|
||||
...(input.language ? { language: input.language } : {}),
|
||||
}),
|
||||
})
|
||||
|
||||
const raw = await res.text()
|
||||
const body = parse(raw)
|
||||
|
||||
if (!res.ok) {
|
||||
return {
|
||||
ok: false,
|
||||
error: errorMessage(body, raw) ?? `Speech to text failed with status ${res.status}`,
|
||||
code: res.status === 401 ? "not_authenticated" : undefined,
|
||||
}
|
||||
}
|
||||
|
||||
const text = typeof body?.text === "string" ? body.text.trim() : ""
|
||||
if (!text) return { ok: false, error: "No speech was detected", code: "empty_transcript" }
|
||||
|
||||
return { ok: true, text }
|
||||
} catch (err) {
|
||||
if (signal?.aborted) return { ok: false, error: "Speech transcription cancelled", code: "cancelled" }
|
||||
const msg = getErrorMessage(err) || "Speech to text request failed"
|
||||
return { ok: false, error: msg, code: msg === "Failed to fetch" ? "not_available" : undefined }
|
||||
}
|
||||
}
|
||||
|
||||
function parse(raw: string): Res | Record<string, unknown> | null {
|
||||
if (!raw) return null
|
||||
try {
|
||||
return JSON.parse(raw) as Res | Record<string, unknown>
|
||||
} catch {
|
||||
return null
|
||||
}
|
||||
}
|
||||
|
||||
function errorMessage(body: Record<string, unknown> | Res | null, raw: string): string | undefined {
|
||||
if (body) {
|
||||
const obj = body as Record<string, unknown>
|
||||
const err = obj.error
|
||||
if (typeof err === "string") return err
|
||||
if (err && typeof err === "object") {
|
||||
const msg = (err as Record<string, unknown>).message
|
||||
if (typeof msg === "string") return msg
|
||||
}
|
||||
const msg = obj.message
|
||||
if (typeof msg === "string") return msg
|
||||
}
|
||||
return raw.trim() || undefined
|
||||
}
|
||||
@@ -4,6 +4,7 @@ import {
|
||||
dirName,
|
||||
buildHighlightSegments,
|
||||
atEnd,
|
||||
insertSpacedText,
|
||||
isPromptBlocked,
|
||||
isPromptBusy,
|
||||
isSuggesting,
|
||||
@@ -196,6 +197,29 @@ describe("isPromptBusy", () => {
|
||||
})
|
||||
})
|
||||
|
||||
describe("insertSpacedText", () => {
|
||||
it("inserts transcript into empty text", () => {
|
||||
expect(insertSpacedText("", "hello", 0, 0)).toEqual({ text: "hello", pos: 5 })
|
||||
})
|
||||
|
||||
it("adds spaces between surrounding words", () => {
|
||||
expect(insertSpacedText("helloworld", "beautiful", 5, 5)).toEqual({ text: "hello beautiful world", pos: 16 })
|
||||
})
|
||||
|
||||
it("does not duplicate existing spaces", () => {
|
||||
expect(insertSpacedText("hello world", "beautiful", 6, 6)).toEqual({ text: "hello beautiful world", pos: 16 })
|
||||
})
|
||||
|
||||
it("replaces selected text and keeps caret after transcript", () => {
|
||||
expect(insertSpacedText("hello bad world", "beautiful", 6, 9)).toEqual({ text: "hello beautiful world", pos: 15 })
|
||||
})
|
||||
|
||||
it("preserves leading and trailing insertion positions", () => {
|
||||
expect(insertSpacedText("world", "hello", 0, 0)).toEqual({ text: "hello world", pos: 6 })
|
||||
expect(insertSpacedText("hello", "world", 5, 5)).toEqual({ text: "hello world", pos: 11 })
|
||||
})
|
||||
})
|
||||
|
||||
describe("isSuggesting", () => {
|
||||
it("returns true when not blocked and suggestions > 0", () => {
|
||||
expect(isSuggesting(false, 1)).toBe(true)
|
||||
|
||||
@@ -0,0 +1,22 @@
|
||||
import { describe, expect, it } from "bun:test"
|
||||
import { parseDshowAudioDevices } from "../../src/speech-to-text/capture"
|
||||
|
||||
describe("parseDshowAudioDevices", () => {
|
||||
it("extracts Windows dshow audio device names", () => {
|
||||
const raw = `
|
||||
[dshow @ 000001] DirectShow audio devices
|
||||
[dshow @ 000001] "Microphone Array (Realtek Audio)" (audio)
|
||||
[dshow @ 000001] "Webcam Microphone" (audio)
|
||||
[dshow @ 000001] DirectShow video devices
|
||||
[dshow @ 000001] "Integrated Camera" (video)
|
||||
`
|
||||
|
||||
expect(parseDshowAudioDevices(raw)).toEqual(["Microphone Array (Realtek Audio)", "Webcam Microphone"])
|
||||
})
|
||||
|
||||
it("deduplicates repeated dshow audio device names", () => {
|
||||
const raw = `"Microphone" (audio)\n"Microphone" (audio)`
|
||||
|
||||
expect(parseDshowAudioDevices(raw)).toEqual(["Microphone"])
|
||||
})
|
||||
})
|
||||
@@ -0,0 +1,19 @@
|
||||
import { describe, expect, it } from "bun:test"
|
||||
import { readFileSync } from "node:fs"
|
||||
import { join } from "node:path"
|
||||
import { DEFAULT_SPEECH_TO_TEXT_MODEL, SPEECH_TO_TEXT_MODELS } from "../../src/speech-to-text/models"
|
||||
|
||||
describe("speech-to-text model settings", () => {
|
||||
const pkg = JSON.parse(readFileSync(join(__dirname, "../../package.json"), "utf8"))
|
||||
const prop = pkg.contributes.configuration.properties["kilo-code.new.speechToText.model"]
|
||||
|
||||
it("keeps the default model in code instead of duplicating it in package.json", () => {
|
||||
expect(prop.default).toBeUndefined()
|
||||
expect(DEFAULT_SPEECH_TO_TEXT_MODEL.id).toBe(SPEECH_TO_TEXT_MODELS[0]?.id)
|
||||
})
|
||||
|
||||
it("keeps the selectable model list out of package.json", () => {
|
||||
expect(prop.enum).toBeUndefined()
|
||||
expect(prop.enumDescriptions).toBeUndefined()
|
||||
})
|
||||
})
|
||||
@@ -15,8 +15,11 @@ import { useVSCode } from "../src/context/vscode"
|
||||
import { useServer } from "../src/context/server"
|
||||
import { useSession } from "../src/context/session"
|
||||
import { useProvider } from "../src/context/provider"
|
||||
import { useConfig } from "../src/context/config"
|
||||
import { ModelSelectorBase } from "../src/components/shared/ModelSelector"
|
||||
import { ModeSwitcherBase } from "../src/components/shared/ModeSwitcher"
|
||||
import { SpeechToTextButton } from "../src/components/speech-to-text/SpeechToTextButton"
|
||||
import { canUseSpeechToText, selectedSpeechToTextModel } from "../src/components/speech-to-text/availability"
|
||||
import { ThinkingSelectorBase } from "../src/components/shared/ThinkingSelector"
|
||||
import {
|
||||
MultiModelSelector,
|
||||
@@ -27,7 +30,9 @@ import {
|
||||
} from "./MultiModelSelector"
|
||||
import { useLanguage } from "../src/context/language"
|
||||
import { useImageAttachments, type ImageAttachment } from "../src/hooks/useImageAttachments"
|
||||
import { useSpeechToText } from "../src/components/speech-to-text/useSpeechToText"
|
||||
import { convertToMentionPath } from "../src/utils/path-mentions"
|
||||
import { insertSpacedText } from "../src/components/chat/prompt-input-utils"
|
||||
import { BranchSelect, BranchSelectPopover } from "../src/components/shared/BranchSelect"
|
||||
|
||||
type VersionCount = 1 | 2 | 3 | 4
|
||||
@@ -65,6 +70,7 @@ export const NewWorktreeDialog: Component<{ onClose: () => void; defaultBaseBran
|
||||
const server = useServer()
|
||||
const session = useSession()
|
||||
const provider = useProvider()
|
||||
const { config, settings } = useConfig()
|
||||
|
||||
const [tab, setTab] = createSignal<DialogTab>("new")
|
||||
|
||||
@@ -91,6 +97,9 @@ export const NewWorktreeDialog: Component<{ onClose: () => void; defaultBaseBran
|
||||
const [compareOpen, setCompareOpen] = createSignal(false)
|
||||
const [highlightedIndex, setHighlightedIndex] = createSignal(0)
|
||||
const [variant, setVariant] = createSignal<string | undefined>(session.currentVariant())
|
||||
const speech = useSpeechToText(vscode, server, { t })
|
||||
const canUseSpeech = () => canUseSpeechToText(settings(), config(), provider.connected(), server.profileData())
|
||||
const speechModel = () => selectedSpeechToTextModel(settings())
|
||||
|
||||
// Variant list for the currently selected model
|
||||
const variants = createMemo(() => {
|
||||
@@ -196,6 +205,7 @@ export const NewWorktreeDialog: Component<{ onClose: () => void; defaultBaseBran
|
||||
|
||||
const canSubmit = () => {
|
||||
if (starting()) return false
|
||||
if (speech.active()) return false
|
||||
if (compareMode() && totalAllocations(modelAllocations()) === 0) return false
|
||||
return true
|
||||
}
|
||||
@@ -250,6 +260,26 @@ export const NewWorktreeDialog: Component<{ onClose: () => void; defaultBaseBran
|
||||
textareaRef.style.height = `${Math.min(textareaRef.scrollHeight, 200)}px`
|
||||
}
|
||||
|
||||
const insertSpeechText = (value: string) => {
|
||||
const ref = textareaRef
|
||||
const current = prompt()
|
||||
const start = ref?.selectionStart ?? current.length
|
||||
const end = ref?.selectionEnd ?? start
|
||||
const result = insertSpacedText(current, value, start, end)
|
||||
|
||||
setPrompt(result.text)
|
||||
persistPrompt(result.text)
|
||||
if (!ref) return
|
||||
ref.value = result.text
|
||||
ref.setSelectionRange(result.pos, result.pos)
|
||||
ref.focus()
|
||||
adjustHeight()
|
||||
}
|
||||
|
||||
const startSpeech = () => {
|
||||
speech.start({ model: speechModel(), insert: insertSpeechText })
|
||||
}
|
||||
|
||||
// --- Import tab state ---
|
||||
const [prUrl, setPrUrl] = createSignal("")
|
||||
const [prPending, setPrPending] = createSignal(false)
|
||||
@@ -422,7 +452,11 @@ export const NewWorktreeDialog: Component<{ onClose: () => void; defaultBaseBran
|
||||
</Show>
|
||||
</Show>
|
||||
</div>
|
||||
<div class="prompt-input-hint-actions" />
|
||||
<div class="prompt-input-hint-actions">
|
||||
<Show when={canUseSpeech()}>
|
||||
<SpeechToTextButton speech={speech} disabled={starting()} start={startSpeech} label={t} />
|
||||
</Show>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
@@ -19,8 +19,11 @@ import { useLanguage } from "../../context/language"
|
||||
import { useVSCode } from "../../context/vscode"
|
||||
import { useWorktreeMode } from "../../context/worktree-mode"
|
||||
import { useConfig } from "../../context/config"
|
||||
import { useProvider } from "../../context/provider"
|
||||
import { ModelSelector } from "../shared/ModelSelector"
|
||||
import { ModeSwitcher } from "../shared/ModeSwitcher"
|
||||
import { SpeechToTextButton } from "../speech-to-text/SpeechToTextButton"
|
||||
import { canUseSpeechToText, selectedSpeechToTextModel } from "../speech-to-text/availability"
|
||||
import { ThinkingSelector } from "../shared/ThinkingSelector"
|
||||
import { useFileMention } from "../../hooks/useFileMention"
|
||||
import { useTerminalContext } from "../../hooks/useTerminalContext"
|
||||
@@ -29,11 +32,12 @@ import { hasTerminalMention } from "../../hooks/terminal-context-utils"
|
||||
import { hasGitChangesMention } from "../../hooks/git-changes-context-utils"
|
||||
import { useSlashCommand } from "../../hooks/useSlashCommand"
|
||||
import { useGhostText } from "../../hooks/useGhostText"
|
||||
import { useSpeechToText } from "../speech-to-text/useSpeechToText"
|
||||
import { useImageAttachments, type ImageAttachment } from "../../hooks/useImageAttachments"
|
||||
import { convertToMentionPath } from "../../utils/path-mentions"
|
||||
import { usePromptHistory } from "../../hooks/usePromptHistory"
|
||||
import { WandSparkles } from "@kilocode/kilo-ui/lucide"
|
||||
import { fileName, dirName, buildHighlightSegments, atEnd, isPromptBusy } from "./prompt-input-utils"
|
||||
import { fileName, dirName, buildHighlightSegments, atEnd, insertSpacedText, isPromptBusy } from "./prompt-input-utils"
|
||||
import type { ReviewComment, TextPart } from "../../types/messages"
|
||||
import { formatReviewCommentsMarkdown } from "../../utils/review-comment-markdown"
|
||||
import { pendingDraftKey, scopeDraftKey, sessionDraftKey } from "../../utils/prompt-drafts"
|
||||
@@ -66,7 +70,8 @@ export const PromptInput: Component<PromptInputProps> = (props) => {
|
||||
const session = useSession()
|
||||
const server = useServer()
|
||||
const indexing = useIndexing()
|
||||
const { features } = useConfig()
|
||||
const { config, features, settings } = useConfig()
|
||||
const provider = useProvider()
|
||||
const language = useLanguage()
|
||||
const vscode = useVSCode()
|
||||
const worktree = useWorktreeMode()
|
||||
@@ -135,6 +140,7 @@ export const PromptInput: Component<PromptInputProps> = (props) => {
|
||||
let preEnhanceText: string | null = null
|
||||
|
||||
const ghost = useGhostText(vscode, text, () => server.isConnected())
|
||||
const speech = useSpeechToText(vscode, server, language)
|
||||
|
||||
const replaceReviewComments = (next: ReviewComment[]) => {
|
||||
setReviewComments(next)
|
||||
@@ -325,8 +331,11 @@ export const PromptInput: Component<PromptInputProps> = (props) => {
|
||||
|
||||
const isBusy = () => isPromptBusy(session.status(), !!props.suggesting?.(), !!props.questioning?.())
|
||||
const isDisabled = () => !server.isConnected()
|
||||
const canUseSpeech = () => canUseSpeechToText(settings(), config(), provider.connected(), server.profileData())
|
||||
const speechModel = () => selectedSpeechToTextModel(settings())
|
||||
const hasInput = () => text().trim().length > 0 || imageAttach.images().length > 0 || reviewComments().length > 0
|
||||
const canSend = () => hasInput() && !isDisabled() && !terminal.pending() && !git.pending() && !props.blocked?.()
|
||||
const canSend = () =>
|
||||
hasInput() && !isDisabled() && !speech.active() && !terminal.pending() && !git.pending() && !props.blocked?.()
|
||||
const showStop = () => isBusy() && !hasInput()
|
||||
const isAtEnd = () =>
|
||||
textareaRef ? atEnd(textareaRef.selectionStart, textareaRef.selectionEnd, textareaRef.value.length) : false
|
||||
@@ -645,6 +654,27 @@ export const PromptInput: Component<PromptInputProps> = (props) => {
|
||||
vscode.postMessage({ type: "enhancePrompt", text: draft, requestId: `enhance-${draftKey()}-${enhanceCounter}` })
|
||||
}
|
||||
|
||||
const insertSpeechText = (value: string) => {
|
||||
const ref = textareaRef
|
||||
const current = text()
|
||||
const start = ref?.selectionStart ?? current.length
|
||||
const end = ref?.selectionEnd ?? start
|
||||
const result = insertSpacedText(current, value, start, end)
|
||||
|
||||
setText(result.text)
|
||||
if (!ref) return
|
||||
ref.value = result.text
|
||||
ref.setSelectionRange(result.pos, result.pos)
|
||||
ref.focus()
|
||||
adjustHeight()
|
||||
syncHighlightScroll()
|
||||
ghost.scheduleRequest(result.text, ref)
|
||||
}
|
||||
|
||||
const startSpeech = () => {
|
||||
speech.start({ model: speechModel(), insert: insertSpeechText })
|
||||
}
|
||||
|
||||
const handleSend = async () => {
|
||||
const draft = text().trim()
|
||||
|
||||
@@ -676,7 +706,14 @@ export const PromptInput: Component<PromptInputProps> = (props) => {
|
||||
const pending = reviewComments()
|
||||
const review = pending.length > 0 ? formatReviewCommentsMarkdown(pending) : ""
|
||||
const message = draft && review ? `${review}\n\n${draft}` : draft || review
|
||||
if ((!message && imgs.length === 0) || isDisabled() || terminal.pending() || git.pending() || props.blocked?.())
|
||||
if (
|
||||
(!message && imgs.length === 0) ||
|
||||
isDisabled() ||
|
||||
speech.active() ||
|
||||
terminal.pending() ||
|
||||
git.pending() ||
|
||||
props.blocked?.()
|
||||
)
|
||||
return
|
||||
|
||||
const mentionFiles = mention.parseFileAttachments(draft)
|
||||
@@ -1042,6 +1079,9 @@ export const PromptInput: Component<PromptInputProps> = (props) => {
|
||||
<WandSparkles size={16} class={enhancing() ? "enhance-spinner" : ""} />
|
||||
</Button>
|
||||
</Tooltip>
|
||||
<Show when={canUseSpeech()}>
|
||||
<SpeechToTextButton speech={speech} disabled={isDisabled()} start={startSpeech} label={language.t} />
|
||||
</Show>
|
||||
<Show
|
||||
when={showStop()}
|
||||
fallback={
|
||||
|
||||
@@ -50,6 +50,23 @@ export function atEnd(start: number, end: number, len: number): boolean {
|
||||
return start === end && end === len
|
||||
}
|
||||
|
||||
export function insertSpacedText(
|
||||
text: string,
|
||||
value: string,
|
||||
start: number,
|
||||
end: number,
|
||||
): { text: string; pos: number } {
|
||||
const before = text.slice(0, start)
|
||||
const after = text.slice(end)
|
||||
const prefix = before && !/\s$/.test(before) ? " " : ""
|
||||
const suffix = after && !/^\s/.test(after) ? " " : ""
|
||||
const inserted = `${prefix}${value}${suffix}`
|
||||
return {
|
||||
text: `${before}${inserted}${after}`,
|
||||
pos: before.length + inserted.length,
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Whether the input prompt should be blocked.
|
||||
*
|
||||
|
||||
@@ -5,9 +5,14 @@ import { TextField } from "@kilocode/kilo-ui/text-field"
|
||||
import { Card } from "@kilocode/kilo-ui/card"
|
||||
import { useConfig } from "../../context/config"
|
||||
import { useLanguage } from "../../context/language"
|
||||
import { useProvider } from "../../context/provider"
|
||||
import { useServer } from "../../context/server"
|
||||
import { useVSCode } from "../../context/vscode"
|
||||
import type { ExtensionMessage } from "../../types/messages"
|
||||
import SettingsRow from "./SettingsRow"
|
||||
import { DEFAULT_SPEECH_TO_TEXT_MODEL } from "../../../../src/speech-to-text/models"
|
||||
import { hasSpeechToTextAccess, selectedSpeechToTextModel } from "../speech-to-text/availability"
|
||||
import { SPEECH_TO_TEXT_MODEL_OPTIONS } from "../speech-to-text/model-selector"
|
||||
|
||||
interface ShareOption {
|
||||
value: string
|
||||
@@ -21,8 +26,10 @@ const SHARE_OPTIONS: ShareOption[] = [
|
||||
]
|
||||
|
||||
const ExperimentalTab: Component = () => {
|
||||
const { config, updateConfig } = useConfig()
|
||||
const { config, settings, updateConfig, updateSetting } = useConfig()
|
||||
const language = useLanguage()
|
||||
const provider = useProvider()
|
||||
const server = useServer()
|
||||
const vscode = useVSCode()
|
||||
const [active, setActive] = createSignal(false)
|
||||
|
||||
@@ -39,6 +46,8 @@ const ExperimentalTab: Component = () => {
|
||||
})
|
||||
|
||||
const experimental = createMemo(() => config().experimental ?? {})
|
||||
const kiloReady = createMemo(() => hasSpeechToTextAccess(config(), provider.connected(), server.profileData()))
|
||||
const speechModel = createMemo(() => selectedSpeechToTextModel(settings()))
|
||||
|
||||
const updateExperimental = (key: string, value: unknown) => {
|
||||
updateConfig({
|
||||
@@ -193,6 +202,42 @@ const ExperimentalTab: Component = () => {
|
||||
</Switch>
|
||||
</SettingsRow>
|
||||
|
||||
<SettingsRow
|
||||
title={language.t("settings.experimental.speechToText.title")}
|
||||
description={
|
||||
kiloReady()
|
||||
? language.t("settings.experimental.speechToText.description")
|
||||
: language.t("settings.experimental.speechToText.disabledDescription")
|
||||
}
|
||||
>
|
||||
<Switch
|
||||
checked={Boolean(settings()["speechToText.enabled"] ?? false)}
|
||||
onChange={(checked) => updateSetting("speechToText.enabled", checked)}
|
||||
disabled={!kiloReady()}
|
||||
hideLabel
|
||||
>
|
||||
{language.t("settings.experimental.speechToText.title")}
|
||||
</Switch>
|
||||
</SettingsRow>
|
||||
|
||||
<SettingsRow
|
||||
title={language.t("settings.experimental.speechToTextModel.title")}
|
||||
description={language.t("settings.experimental.speechToTextModel.description")}
|
||||
>
|
||||
<Select
|
||||
options={SPEECH_TO_TEXT_MODEL_OPTIONS}
|
||||
current={SPEECH_TO_TEXT_MODEL_OPTIONS.find((item) => item.value === speechModel())}
|
||||
value={(item) => item.value}
|
||||
label={(item) => `${item.label} (${item.provider})`}
|
||||
onSelect={(item) => updateSetting("speechToText.model", item?.value ?? DEFAULT_SPEECH_TO_TEXT_MODEL.id)}
|
||||
variant="secondary"
|
||||
size="small"
|
||||
triggerVariant="settings"
|
||||
disabled={!kiloReady()}
|
||||
placeholder={DEFAULT_SPEECH_TO_TEXT_MODEL.label}
|
||||
/>
|
||||
</SettingsRow>
|
||||
|
||||
<SettingsRow
|
||||
title={language.t("settings.experimental.continueOnDeny.title")}
|
||||
description={language.t("settings.experimental.continueOnDeny.description")}
|
||||
|
||||
@@ -0,0 +1,72 @@
|
||||
import { Button } from "@kilocode/kilo-ui/button"
|
||||
import { Tooltip } from "@kilocode/kilo-ui/tooltip"
|
||||
import { Spinner } from "@kilocode/kilo-ui/spinner"
|
||||
import type { Component } from "solid-js"
|
||||
import type { SpeechToText } from "./useSpeechToText"
|
||||
|
||||
type Props = {
|
||||
speech: SpeechToText
|
||||
disabled?: boolean
|
||||
start: () => void
|
||||
label: (key: string) => string
|
||||
}
|
||||
|
||||
export const SpeechToTextButton: Component<Props> = (props) => {
|
||||
const disabled = () => !!props.disabled
|
||||
const label = () => {
|
||||
if (props.speech.state() === "recording") return props.label("speechToText.tooltip.stop")
|
||||
if (props.speech.state() === "transcribing") return props.label("speechToText.tooltip.transcribing")
|
||||
if (props.speech.state() === "error") return props.speech.error() || props.label("speechToText.tooltip.error")
|
||||
return props.label("speechToText.tooltip.start")
|
||||
}
|
||||
|
||||
const click = () => {
|
||||
if (props.speech.state() === "recording") {
|
||||
props.speech.stop()
|
||||
return
|
||||
}
|
||||
if (props.speech.state() === "transcribing") {
|
||||
props.speech.cancel()
|
||||
return
|
||||
}
|
||||
if (props.speech.state() === "error") {
|
||||
props.speech.clear()
|
||||
return
|
||||
}
|
||||
if (disabled()) return
|
||||
props.start()
|
||||
}
|
||||
|
||||
return (
|
||||
<Tooltip value={label()} placement="top">
|
||||
<Button
|
||||
variant="ghost"
|
||||
size="small"
|
||||
onClick={click}
|
||||
disabled={disabled()}
|
||||
aria-label={label()}
|
||||
aria-pressed={props.speech.state() === "recording"}
|
||||
class={`prompt-speech-button prompt-speech-button--${props.speech.state()}`}
|
||||
>
|
||||
{props.speech.state() === "transcribing" ? (
|
||||
<Spinner style={{ width: "16px", height: "16px" }} />
|
||||
) : (
|
||||
<svg class="prompt-speech-icon" width="16" height="16" viewBox="0 0 16 16" fill="none" aria-hidden="true">
|
||||
<path
|
||||
d="M8 10.5C9.38 10.5 10.5 9.38 10.5 8V4C10.5 2.62 9.38 1.5 8 1.5C6.62 1.5 5.5 2.62 5.5 4V8C5.5 9.38 6.62 10.5 8 10.5Z"
|
||||
stroke="currentColor"
|
||||
stroke-width="1.2"
|
||||
/>
|
||||
<path
|
||||
d="M3.5 7.5C3.5 10 5.5 12 8 12C10.5 12 12.5 10 12.5 7.5"
|
||||
stroke="currentColor"
|
||||
stroke-width="1.2"
|
||||
stroke-linecap="round"
|
||||
/>
|
||||
<path d="M8 12V14.5M5.5 14.5H10.5" stroke="currentColor" stroke-width="1.2" stroke-linecap="round" />
|
||||
</svg>
|
||||
)}
|
||||
</Button>
|
||||
</Tooltip>
|
||||
)
|
||||
}
|
||||
@@ -0,0 +1,23 @@
|
||||
import { KILO_PROVIDER_ID } from "../../../../src/shared/provider-model"
|
||||
import { getSpeechToTextModel } from "../../../../src/speech-to-text/models"
|
||||
|
||||
type Cfg = {
|
||||
disabled_providers?: string[]
|
||||
}
|
||||
|
||||
export function hasSpeechToTextAccess(cfg: Cfg, providers: readonly string[], profile: unknown | null): boolean {
|
||||
return providers.includes(KILO_PROVIDER_ID) && !cfg.disabled_providers?.includes(KILO_PROVIDER_ID) && !!profile
|
||||
}
|
||||
|
||||
export function canUseSpeechToText(
|
||||
settings: Record<string, unknown>,
|
||||
cfg: Cfg,
|
||||
providers: readonly string[],
|
||||
profile: unknown | null,
|
||||
): boolean {
|
||||
return settings["speechToText.enabled"] === true && hasSpeechToTextAccess(cfg, providers, profile)
|
||||
}
|
||||
|
||||
export function selectedSpeechToTextModel(settings: Record<string, unknown>): string {
|
||||
return getSpeechToTextModel(String(settings["speechToText.model"] ?? "")).id
|
||||
}
|
||||
@@ -0,0 +1,13 @@
|
||||
import { SPEECH_TO_TEXT_MODELS } from "../../../../src/speech-to-text/models"
|
||||
|
||||
export type SpeechToTextModelOption = {
|
||||
value: string
|
||||
label: string
|
||||
provider: string
|
||||
}
|
||||
|
||||
export const SPEECH_TO_TEXT_MODEL_OPTIONS: SpeechToTextModelOption[] = SPEECH_TO_TEXT_MODELS.map((model) => ({
|
||||
value: model.id,
|
||||
label: model.label,
|
||||
provider: model.provider,
|
||||
}))
|
||||
@@ -0,0 +1,167 @@
|
||||
import { createSignal, onCleanup } from "solid-js"
|
||||
import { showToast } from "@kilocode/kilo-ui/toast"
|
||||
import type { Accessor } from "solid-js"
|
||||
import type { ExtensionMessage, WebviewMessage } from "../../types/messages"
|
||||
|
||||
type VSCode = {
|
||||
postMessage: (message: WebviewMessage) => void
|
||||
onMessage: (handler: (message: ExtensionMessage) => void) => () => void
|
||||
}
|
||||
|
||||
type Server = {
|
||||
profileData: Accessor<unknown | null>
|
||||
goToLogin: () => void
|
||||
}
|
||||
|
||||
type Lang = {
|
||||
t: (key: string) => string
|
||||
}
|
||||
|
||||
export type SpeechState = "idle" | "recording" | "transcribing" | "error"
|
||||
|
||||
export type InsertTranscript = (text: string) => void
|
||||
|
||||
type StartOptions = {
|
||||
model: string
|
||||
insert: InsertTranscript
|
||||
}
|
||||
|
||||
export type SpeechToText = {
|
||||
state: Accessor<SpeechState>
|
||||
error: Accessor<string | undefined>
|
||||
active: Accessor<boolean>
|
||||
start: (opts: StartOptions) => void
|
||||
stop: () => void
|
||||
cancel: () => void
|
||||
clear: () => void
|
||||
}
|
||||
|
||||
export function useSpeechToText(vscode: VSCode, server: Server, lang: Lang): SpeechToText {
|
||||
const [state, setState] = createSignal<SpeechState>("idle")
|
||||
const [error, setError] = createSignal<string | undefined>()
|
||||
const active = () => state() === "recording" || state() === "transcribing"
|
||||
const prefix = globalThis.crypto?.randomUUID?.() ?? `stt-${Math.random().toString(36).slice(2)}`
|
||||
|
||||
let request = ""
|
||||
let counter = 0
|
||||
let insert: InsertTranscript | undefined
|
||||
|
||||
const unsub = vscode.onMessage((msg) => {
|
||||
if (!isSpeechMessage(msg)) return
|
||||
if (msg.requestId !== request) return
|
||||
|
||||
if (msg.type === "speechToTextStarted") {
|
||||
setState("recording")
|
||||
return
|
||||
}
|
||||
|
||||
if (msg.type === "speechToTextCancelled") {
|
||||
cleanup()
|
||||
setState("idle")
|
||||
setError(undefined)
|
||||
return
|
||||
}
|
||||
|
||||
if (msg.type === "speechToTextError") {
|
||||
fail(msg.error)
|
||||
return
|
||||
}
|
||||
|
||||
const text = msg.text.trim()
|
||||
if (!text) {
|
||||
fail(lang.t("speechToText.error.emptyTranscript"))
|
||||
return
|
||||
}
|
||||
|
||||
insert?.(text)
|
||||
cleanup()
|
||||
setState("idle")
|
||||
setError(undefined)
|
||||
})
|
||||
|
||||
onCleanup(() => {
|
||||
unsub()
|
||||
cancel()
|
||||
})
|
||||
|
||||
function start(opts: StartOptions) {
|
||||
if (active()) return
|
||||
insert = opts.insert
|
||||
setError(undefined)
|
||||
|
||||
if (!server.profileData()) {
|
||||
showToast({
|
||||
variant: "error",
|
||||
title: lang.t("speechToText.error.loginRequired"),
|
||||
actions: [
|
||||
{ label: lang.t("common.signIn"), onClick: server.goToLogin },
|
||||
{ label: lang.t("common.dismiss"), onClick: "dismiss" },
|
||||
],
|
||||
})
|
||||
fail(lang.t("speechToText.error.loginRequired"), false)
|
||||
return
|
||||
}
|
||||
|
||||
counter++
|
||||
request = `${prefix}-${counter}`
|
||||
setState("recording")
|
||||
vscode.postMessage({
|
||||
type: "speechToTextStart",
|
||||
requestId: request,
|
||||
model: opts.model,
|
||||
language: langCode(),
|
||||
})
|
||||
}
|
||||
|
||||
function stop() {
|
||||
if (state() !== "recording") return
|
||||
setState("transcribing")
|
||||
vscode.postMessage({ type: "speechToTextStop", requestId: request })
|
||||
}
|
||||
|
||||
function cancel() {
|
||||
if (request && active()) vscode.postMessage({ type: "speechToTextCancel", requestId: request })
|
||||
cleanup()
|
||||
setState("idle")
|
||||
setError(undefined)
|
||||
}
|
||||
|
||||
function clear() {
|
||||
if (state() !== "error") return
|
||||
cleanup()
|
||||
setState("idle")
|
||||
setError(undefined)
|
||||
}
|
||||
|
||||
function fail(message: string, toast = true) {
|
||||
cleanup()
|
||||
setState("error")
|
||||
setError(message)
|
||||
if (toast) showToast({ variant: "error", title: lang.t("speechToText.error.title"), description: message })
|
||||
}
|
||||
|
||||
function cleanup() {
|
||||
request = ""
|
||||
insert = undefined
|
||||
}
|
||||
|
||||
return { state, error, active, start, stop, cancel, clear }
|
||||
}
|
||||
|
||||
function isSpeechMessage(
|
||||
msg: ExtensionMessage,
|
||||
): msg is Extract<
|
||||
ExtensionMessage,
|
||||
{ type: "speechToTextStarted" | "speechToTextCancelled" | "speechToTextResult" | "speechToTextError" }
|
||||
> {
|
||||
return (
|
||||
msg.type === "speechToTextStarted" ||
|
||||
msg.type === "speechToTextCancelled" ||
|
||||
msg.type === "speechToTextResult" ||
|
||||
msg.type === "speechToTextError"
|
||||
)
|
||||
}
|
||||
|
||||
function langCode() {
|
||||
return (navigator.language || "en").split("-")[0] || "en"
|
||||
}
|
||||
@@ -72,14 +72,19 @@ export const ConfigProvider: ParentComponent = (props) => {
|
||||
// a configLoaded message that arrives before the DOM mount.
|
||||
const unsubscribe = vscode.onMessage((message: ExtensionMessage) => {
|
||||
if (message.type === "autocompleteSettingsLoaded") {
|
||||
const patch = {
|
||||
mergeSettings({
|
||||
"autocomplete.enableAutoTrigger": message.settings.enableAutoTrigger,
|
||||
"autocomplete.enableSmartInlineTaskKeybinding": message.settings.enableSmartInlineTaskKeybinding,
|
||||
"autocomplete.enableChatAutocomplete": message.settings.enableChatAutocomplete,
|
||||
"autocomplete.model": message.settings.model,
|
||||
}
|
||||
setSavedSettings((prev) => ({ ...prev, ...patch }))
|
||||
setSettings((prev) => ({ ...prev, ...patch, ...settingsDraft() }))
|
||||
})
|
||||
return
|
||||
}
|
||||
if (message.type === "speechToTextSettingsLoaded") {
|
||||
mergeSettings({
|
||||
"speechToText.enabled": message.settings.enabled,
|
||||
"speechToText.model": message.settings.model,
|
||||
})
|
||||
return
|
||||
}
|
||||
if (message.type === "configLoaded") {
|
||||
@@ -142,15 +147,24 @@ export const ConfigProvider: ParentComponent = (props) => {
|
||||
|
||||
onCleanup(unsubscribe)
|
||||
|
||||
function mergeSettings(patch: Record<string, unknown>) {
|
||||
setSavedSettings((prev) => ({ ...prev, ...patch }))
|
||||
setSettings((prev) => ({ ...prev, ...patch, ...settingsDraft() }))
|
||||
}
|
||||
|
||||
const requestInitialData = () => {
|
||||
vscode.postMessage({ type: "requestConfig" })
|
||||
vscode.postMessage({ type: "requestAutocompleteSettings" })
|
||||
vscode.postMessage({ type: "requestSpeechToTextSettings" })
|
||||
}
|
||||
|
||||
// Request config immediately; if the extension's httpClient is not yet ready,
|
||||
// extensionDataReady will fire once initialization completes and we retry once.
|
||||
vscode.postMessage({ type: "requestConfig" })
|
||||
vscode.postMessage({ type: "requestAutocompleteSettings" })
|
||||
requestInitialData()
|
||||
|
||||
const fallback = setTimeout(() => {
|
||||
if (loading()) {
|
||||
vscode.postMessage({ type: "requestConfig" })
|
||||
vscode.postMessage({ type: "requestAutocompleteSettings" })
|
||||
requestInitialData()
|
||||
}
|
||||
}, 3000)
|
||||
|
||||
@@ -159,8 +173,7 @@ export const ConfigProvider: ParentComponent = (props) => {
|
||||
unsubReady()
|
||||
clearTimeout(fallback)
|
||||
if (loading()) {
|
||||
vscode.postMessage({ type: "requestConfig" })
|
||||
vscode.postMessage({ type: "requestAutocompleteSettings" })
|
||||
requestInitialData()
|
||||
}
|
||||
})
|
||||
|
||||
|
||||
+21
@@ -278,6 +278,20 @@ export const dict = {
|
||||
"prompt.action.enhanceDescription":
|
||||
"زر «حسّن الموجه» يطوّر موجهك بإضافة سياق أو توضيح أو إعادة صياغة. جرّب اكتب موجه هنا ثم اضغط الزر مرة ثانية وشوف النتيجة.",
|
||||
|
||||
"speechToText.tooltip.start": "بدء الإدخال الصوتي",
|
||||
"speechToText.tooltip.stop": "إيقاف التسجيل",
|
||||
"speechToText.tooltip.transcribing": "جاري تحويل الصوت إلى نص... انقر للإلغاء.",
|
||||
"speechToText.tooltip.error": "فشل الإدخال الصوتي. انقر للمسح.",
|
||||
"speechToText.error.title": "فشل الإدخال الصوتي",
|
||||
"speechToText.error.loginRequired": "قم بتسجيل الدخول إلى Kilo لاستخدام الإدخال الصوتي.",
|
||||
"speechToText.error.permission": "تم رفض إذن الوصول إلى الميكروفون.",
|
||||
"speechToText.error.microphone": "تعذر بدء تشغيل الميكروفون.",
|
||||
"speechToText.error.recording": "فشل التسجيل.",
|
||||
"speechToText.error.emptyRecording": "لم يتم تسجيل أي صوت.",
|
||||
"speechToText.error.emptyTranscript": "لم يتم اكتشاف أي كلام.",
|
||||
"speechToText.error.encoding": "تعذر تشفير التسجيل.",
|
||||
"speechToText.toast.transcribed": "تم إدراج النص المنسوخ",
|
||||
|
||||
"prompt.toast.pasteUnsupported.title": "لصق غير مدعوم",
|
||||
"prompt.toast.pasteUnsupported.description": "يمكن لصق الصور أو ملفات PDF فقط هنا.",
|
||||
"prompt.toast.modelAgentRequired.title": "حدد وكيلاً ونموذجاً",
|
||||
@@ -1212,6 +1226,13 @@ export const dict = {
|
||||
"settings.experimental.agentManagerTool.title": "أداة Agent Manager",
|
||||
"settings.experimental.agentManagerTool.description":
|
||||
"السماح للوكلاء ببدء جلسات Agent Manager المحلية وجلسات worktree من استدعاء أداة",
|
||||
"settings.experimental.speechToText.title": "تحويل الصوت إلى نص",
|
||||
"settings.experimental.speechToText.description":
|
||||
"تمكين الإدخال الصوتي في حقول المطالبة باستخدام حساب Kilo الخاص بك من خلال Kilo Gateway.",
|
||||
"settings.experimental.speechToText.disabledDescription":
|
||||
"قم بتمكين وتسجيل الدخول إلى Kilo provider لاستخدام الإدخال الصوتي في حقول المطالبة.",
|
||||
"settings.experimental.speechToTextModel.title": "نموذج تحويل الصوت إلى نص",
|
||||
"settings.experimental.speechToTextModel.description": "اختر نموذج نسخ Kilo Gateway للإدخال الصوتي.",
|
||||
"settings.experimental.continueOnDeny.title": "المتابعة عند الرفض",
|
||||
"settings.experimental.continueOnDeny.description": "متابعة حلقة الوكيل عند رفض الإذن",
|
||||
"settings.experimental.mcpTimeout.title": "مهلة MCP (مللي ثانية)",
|
||||
|
||||
+22
@@ -282,6 +282,20 @@ export const dict = {
|
||||
"O botão 'Aprimorar prompt' ajuda a melhorar seu pedido fornecendo contexto adicional, esclarecimentos ou reformulações. Tente digitar um pedido aqui e clique no botão novamente para ver como funciona.",
|
||||
"prompt.action.indexing": "Configurações de indexação",
|
||||
|
||||
"speechToText.tooltip.start": "Iniciar entrada de voz",
|
||||
"speechToText.tooltip.stop": "Parar gravação",
|
||||
"speechToText.tooltip.transcribing": "Transcrevendo... Clique para cancelar.",
|
||||
"speechToText.tooltip.error": "Falha na entrada de voz. Clique para limpar.",
|
||||
"speechToText.error.title": "Falha na entrada de voz",
|
||||
"speechToText.error.loginRequired": "Faça login no Kilo para usar a entrada de voz.",
|
||||
"speechToText.error.permission": "A permissão do microfone foi negada.",
|
||||
"speechToText.error.microphone": "Não foi possível iniciar o microfone.",
|
||||
"speechToText.error.recording": "A gravação falhou.",
|
||||
"speechToText.error.emptyRecording": "Nenhum áudio foi gravado.",
|
||||
"speechToText.error.emptyTranscript": "Nenhuma fala foi detectada.",
|
||||
"speechToText.error.encoding": "Não foi possível codificar a gravação.",
|
||||
"speechToText.toast.transcribed": "Transcrição inserida",
|
||||
|
||||
"prompt.toast.pasteUnsupported.title": "Colagem não suportada",
|
||||
"prompt.toast.pasteUnsupported.description": "Somente imagens ou PDFs podem ser colados aqui.",
|
||||
"prompt.toast.modelAgentRequired.title": "Selecione um agente e modelo",
|
||||
@@ -1241,6 +1255,14 @@ export const dict = {
|
||||
"settings.experimental.agentManagerTool.title": "Ferramenta Agent Manager",
|
||||
"settings.experimental.agentManagerTool.description":
|
||||
"Permitir que agentes iniciem sessões locais e sessões worktree do Agent Manager a partir de uma chamada de ferramenta",
|
||||
"settings.experimental.speechToText.title": "Fala para texto",
|
||||
"settings.experimental.speechToText.description":
|
||||
"Ative a entrada de voz nos campos de prompt usando sua conta do Kilo por meio do Kilo Gateway.",
|
||||
"settings.experimental.speechToText.disabledDescription":
|
||||
"Ative e faça login no Kilo provider para usar a entrada de voz nos campos de prompt.",
|
||||
"settings.experimental.speechToTextModel.title": "Modelo de fala para texto",
|
||||
"settings.experimental.speechToTextModel.description":
|
||||
"Escolha o modelo de transcrição do Kilo Gateway para entrada de voz.",
|
||||
"settings.experimental.continueOnDeny.title": "Continuar ao negar",
|
||||
"settings.experimental.continueOnDeny.description": "Continuar o loop do agente quando uma permissão é negada",
|
||||
"settings.experimental.mcpTimeout.title": "Tempo limite MCP (ms)",
|
||||
|
||||
+22
@@ -282,6 +282,20 @@ export const dict = {
|
||||
"prompt.action.enhanceDescription":
|
||||
"Dugme 'Poboljšaj prompt' pomaže poboljšati vaš zahtjev pružajući dodatni kontekst, pojašnjenje ili preformulaciju. Pokušajte upisati zahtjev ovdje i ponovo kliknite na dugme da vidite kako funkcioniše.",
|
||||
|
||||
"speechToText.tooltip.start": "Započni glasovni unos",
|
||||
"speechToText.tooltip.stop": "Zaustavi snimanje",
|
||||
"speechToText.tooltip.transcribing": "Prepisivanje... Kliknite da otkažete.",
|
||||
"speechToText.tooltip.error": "Glasovni unos nije uspio. Kliknite da očistite.",
|
||||
"speechToText.error.title": "Glasovni unos nije uspio",
|
||||
"speechToText.error.loginRequired": "Prijavite se na Kilo da koristite glasovni unos.",
|
||||
"speechToText.error.permission": "Dozvola za mikrofon je odbijena.",
|
||||
"speechToText.error.microphone": "Nije moguće pokrenuti mikrofon.",
|
||||
"speechToText.error.recording": "Snimanje nije uspjelo.",
|
||||
"speechToText.error.emptyRecording": "Zvuk nije snimljen.",
|
||||
"speechToText.error.emptyTranscript": "Govor nije otkriven.",
|
||||
"speechToText.error.encoding": "Nije moguće kodirati snimak.",
|
||||
"speechToText.toast.transcribed": "Prijepis ubačen",
|
||||
|
||||
"prompt.toast.pasteUnsupported.title": "Nepodržano lijepljenje",
|
||||
"prompt.toast.pasteUnsupported.description": "Ovdje se mogu zalijepiti samo slike ili PDF-ovi.",
|
||||
"prompt.toast.modelAgentRequired.title": "Odaberi agenta i model",
|
||||
@@ -1241,6 +1255,14 @@ export const dict = {
|
||||
"settings.experimental.agentManagerTool.title": "Agent Manager alat",
|
||||
"settings.experimental.agentManagerTool.description":
|
||||
"Dozvoli agentima da pokreću lokalne Agent Manager sesije i worktree sesije iz poziva alata",
|
||||
"settings.experimental.speechToText.title": "Govor u tekst",
|
||||
"settings.experimental.speechToText.description":
|
||||
"Omogućite glasovni unos u poljima za promptove koristeći vaš Kilo račun preko Kilo Gateway.",
|
||||
"settings.experimental.speechToText.disabledDescription":
|
||||
"Omogućite i prijavite se na Kilo provider da koristite glasovni unos u poljima za promptove.",
|
||||
"settings.experimental.speechToTextModel.title": "Model govora u tekst",
|
||||
"settings.experimental.speechToTextModel.description":
|
||||
"Odaberite Kilo Gateway model za transkripciju za glasovni unos.",
|
||||
"settings.experimental.continueOnDeny.title": "Nastavi pri odbijanju",
|
||||
"settings.experimental.continueOnDeny.description": "Nastavi petlju agenta kada je dozvola odbijena",
|
||||
"settings.experimental.mcpTimeout.title": "MCP istek vremena (ms)",
|
||||
|
||||
+21
@@ -281,6 +281,20 @@ export const dict = {
|
||||
"prompt.action.enhanceDescription":
|
||||
"Knappen 'Forbedr prompt' hjælper med at forbedre din forespørgsel ved at give ekstra kontekst, præcisering eller omformulering. Prøv at skrive en forespørgsel her og klik på knappen igen for at se hvordan det virker.",
|
||||
|
||||
"speechToText.tooltip.start": "Start stemmeinput",
|
||||
"speechToText.tooltip.stop": "Stop optagelse",
|
||||
"speechToText.tooltip.transcribing": "Transskriberer... Klik for at annullere.",
|
||||
"speechToText.tooltip.error": "Stemmeinput mislykkedes. Klik for at rydde.",
|
||||
"speechToText.error.title": "Stemmeinput mislykkedes",
|
||||
"speechToText.error.loginRequired": "Log ind på Kilo for at bruge stemmeinput.",
|
||||
"speechToText.error.permission": "Mikrofontilladelse blev nægtet.",
|
||||
"speechToText.error.microphone": "Kunne ikke starte mikrofonen.",
|
||||
"speechToText.error.recording": "Optagelse mislykkedes.",
|
||||
"speechToText.error.emptyRecording": "Ingen lyd blev optaget.",
|
||||
"speechToText.error.emptyTranscript": "Ingen tale blev registreret.",
|
||||
"speechToText.error.encoding": "Kunne ikke kode optagelsen.",
|
||||
"speechToText.toast.transcribed": "Transskription indsat",
|
||||
|
||||
"prompt.toast.pasteUnsupported.title": "Ikke understøttet indsæt",
|
||||
"prompt.toast.pasteUnsupported.description": "Kun billeder eller PDF'er kan indsættes her.",
|
||||
"prompt.toast.modelAgentRequired.title": "Vælg en agent og model",
|
||||
@@ -1234,6 +1248,13 @@ export const dict = {
|
||||
"settings.experimental.agentManagerTool.title": "Agent Manager-værktøj",
|
||||
"settings.experimental.agentManagerTool.description":
|
||||
"Tillad agenter at starte lokale Agent Manager-sessioner og worktree-sessioner fra et værktøjskald",
|
||||
"settings.experimental.speechToText.title": "Tale til tekst",
|
||||
"settings.experimental.speechToText.description":
|
||||
"Aktivér stemmeinput i prompt-felter ved hjælp af din Kilo-konto gennem Kilo Gateway.",
|
||||
"settings.experimental.speechToText.disabledDescription":
|
||||
"Aktivér og log ind på Kilo provider for at bruge stemmeinput i prompt-felter.",
|
||||
"settings.experimental.speechToTextModel.title": "Model til tale til tekst",
|
||||
"settings.experimental.speechToTextModel.description": "Vælg Kilo Gateway-transskriptionsmodellen til stemmeinput.",
|
||||
"settings.experimental.continueOnDeny.title": "Fortsæt ved afvisning",
|
||||
"settings.experimental.continueOnDeny.description": "Fortsæt agentløkken, når en tilladelse afvises",
|
||||
"settings.experimental.mcpTimeout.title": "MCP-timeout (ms)",
|
||||
|
||||
+22
@@ -285,6 +285,20 @@ export const dict = {
|
||||
"prompt.action.enhanceDescription":
|
||||
"Die Schaltfläche 'Prompt verbessern' hilft, deine Anfrage durch zusätzlichen Kontext, Klarstellungen oder Umformulierungen zu verbessern. Versuche, hier eine Anfrage einzugeben und klicke erneut auf die Schaltfläche, um zu sehen, wie es funktioniert.",
|
||||
|
||||
"speechToText.tooltip.start": "Spracheingabe starten",
|
||||
"speechToText.tooltip.stop": "Aufnahme beenden",
|
||||
"speechToText.tooltip.transcribing": "Transkribieren... Zum Abbrechen klicken.",
|
||||
"speechToText.tooltip.error": "Spracheingabe fehlgeschlagen. Zum Löschen klicken.",
|
||||
"speechToText.error.title": "Spracheingabe fehlgeschlagen",
|
||||
"speechToText.error.loginRequired": "Melden Sie sich bei Kilo an, um die Spracheingabe zu verwenden.",
|
||||
"speechToText.error.permission": "Mikrofonberechtigung wurde verweigert.",
|
||||
"speechToText.error.microphone": "Mikrofon konnte nicht gestartet werden.",
|
||||
"speechToText.error.recording": "Aufnahme fehlgeschlagen.",
|
||||
"speechToText.error.emptyRecording": "Es wurde kein Audio aufgenommen.",
|
||||
"speechToText.error.emptyTranscript": "Es wurde keine Sprache erkannt.",
|
||||
"speechToText.error.encoding": "Aufnahme konnte nicht codiert werden.",
|
||||
"speechToText.toast.transcribed": "Transkription eingefügt",
|
||||
|
||||
"prompt.toast.pasteUnsupported.title": "Nicht unterstütztes Einfügen",
|
||||
"prompt.toast.pasteUnsupported.description": "Hier können nur Bilder oder PDFs eingefügt werden.",
|
||||
"prompt.toast.modelAgentRequired.title": "Wählen Sie einen Agenten und ein Modell",
|
||||
@@ -1256,6 +1270,14 @@ export const dict = {
|
||||
"settings.experimental.agentManagerTool.title": "Agent Manager-Werkzeug",
|
||||
"settings.experimental.agentManagerTool.description":
|
||||
"Agenten erlauben, lokale Agent Manager-Sitzungen und Worktree-Sitzungen per Werkzeugaufruf zu starten",
|
||||
"settings.experimental.speechToText.title": "Sprache zu Text",
|
||||
"settings.experimental.speechToText.description":
|
||||
"Aktivieren Sie die Spracheingabe in Prompt-Feldern mit Ihrem Kilo-Konto über Kilo Gateway.",
|
||||
"settings.experimental.speechToText.disabledDescription":
|
||||
"Aktivieren Sie Kilo provider und melden Sie sich an, um die Spracheingabe in Prompt-Feldern zu verwenden.",
|
||||
"settings.experimental.speechToTextModel.title": "Sprache-zu-Text-Modell",
|
||||
"settings.experimental.speechToTextModel.description":
|
||||
"Wählen Sie das Kilo Gateway-Transkriptionsmodell für die Spracheingabe.",
|
||||
"settings.experimental.continueOnDeny.title": "Bei Ablehnung fortfahren",
|
||||
"settings.experimental.continueOnDeny.description":
|
||||
"Agent-Schleife fortsetzen, wenn eine Berechtigung abgelehnt wird",
|
||||
|
||||
@@ -280,6 +280,19 @@ export const dict = {
|
||||
"prompt.action.resetModel": "Reset model to default",
|
||||
"prompt.action.enhanceDescription":
|
||||
"The 'Enhance Prompt' button helps improve your prompt by providing additional context, clarification, or rephrasing. Try typing a prompt in here and clicking the button again to see how it works.",
|
||||
"speechToText.tooltip.start": "Start voice input",
|
||||
"speechToText.tooltip.stop": "Stop recording",
|
||||
"speechToText.tooltip.transcribing": "Transcribing... Click to cancel.",
|
||||
"speechToText.tooltip.error": "Speech input failed. Click to clear.",
|
||||
"speechToText.error.title": "Speech input failed",
|
||||
"speechToText.error.loginRequired": "Sign in to Kilo to use speech input.",
|
||||
"speechToText.error.permission": "Microphone permission was denied.",
|
||||
"speechToText.error.microphone": "Could not start the microphone.",
|
||||
"speechToText.error.recording": "Recording failed.",
|
||||
"speechToText.error.emptyRecording": "No audio was recorded.",
|
||||
"speechToText.error.emptyTranscript": "No speech was detected.",
|
||||
"speechToText.error.encoding": "Could not encode the recording.",
|
||||
"speechToText.toast.transcribed": "Transcription inserted",
|
||||
|
||||
"prompt.toast.pasteUnsupported.title": "Unsupported paste",
|
||||
"prompt.toast.pasteUnsupported.description": "Only images or PDFs can be pasted here.",
|
||||
@@ -1225,6 +1238,13 @@ export const dict = {
|
||||
"settings.experimental.agentManagerTool.title": "Agent Manager Tool",
|
||||
"settings.experimental.agentManagerTool.description":
|
||||
"Allow agents to start Agent Manager local sessions and worktree sessions from a tool call",
|
||||
"settings.experimental.speechToText.title": "Speech to Text",
|
||||
"settings.experimental.speechToText.description":
|
||||
"Enable voice input in prompt fields using your Kilo account through Kilo Gateway.",
|
||||
"settings.experimental.speechToText.disabledDescription":
|
||||
"Enable and sign in to the Kilo provider to use voice input in prompt fields.",
|
||||
"settings.experimental.speechToTextModel.title": "Speech to Text Model",
|
||||
"settings.experimental.speechToTextModel.description": "Choose the Kilo Gateway transcription model for voice input.",
|
||||
"settings.experimental.continueOnDeny.title": "Continue on Deny",
|
||||
"settings.experimental.continueOnDeny.description": "Continue the agent loop when a permission is denied",
|
||||
"settings.experimental.mcpTimeout.title": "MCP Timeout (ms)",
|
||||
|
||||
+22
@@ -283,6 +283,20 @@ export const dict = {
|
||||
"El botón 'Mejorar el mensaje' ayuda a mejorar tu petición proporcionando contexto adicional, aclaraciones o reformulaciones. Intenta escribir una petición aquí y haz clic en el botón nuevamente para ver cómo funciona.",
|
||||
"prompt.action.indexing": "Configuración de indexación",
|
||||
|
||||
"speechToText.tooltip.start": "Iniciar entrada de voz",
|
||||
"speechToText.tooltip.stop": "Detener grabación",
|
||||
"speechToText.tooltip.transcribing": "Transcribiendo... Haz clic para cancelar.",
|
||||
"speechToText.tooltip.error": "Falló la entrada de voz. Haz clic para borrar.",
|
||||
"speechToText.error.title": "Falló la entrada de voz",
|
||||
"speechToText.error.loginRequired": "Inicia sesión en Kilo para usar la entrada de voz.",
|
||||
"speechToText.error.permission": "Se denegó el permiso del micrófono.",
|
||||
"speechToText.error.microphone": "No se pudo iniciar el micrófono.",
|
||||
"speechToText.error.recording": "La grabación falló.",
|
||||
"speechToText.error.emptyRecording": "No se grabó ningún audio.",
|
||||
"speechToText.error.emptyTranscript": "No se detectó voz.",
|
||||
"speechToText.error.encoding": "No se pudo codificar la grabación.",
|
||||
"speechToText.toast.transcribed": "Transcripción insertada",
|
||||
|
||||
"prompt.toast.pasteUnsupported.title": "Pegado no soportado",
|
||||
"prompt.toast.pasteUnsupported.description": "Solo se pueden pegar imágenes o PDFs aquí.",
|
||||
"prompt.toast.modelAgentRequired.title": "Selecciona un agente y modelo",
|
||||
@@ -1248,6 +1262,14 @@ export const dict = {
|
||||
"settings.experimental.agentManagerTool.title": "Herramienta Agent Manager",
|
||||
"settings.experimental.agentManagerTool.description":
|
||||
"Permitir que los agentes inicien sesiones locales y sesiones de worktree de Agent Manager desde una llamada de herramienta",
|
||||
"settings.experimental.speechToText.title": "Voz a texto",
|
||||
"settings.experimental.speechToText.description":
|
||||
"Habilita la entrada de voz en los campos de prompt usando tu cuenta de Kilo a través de Kilo Gateway.",
|
||||
"settings.experimental.speechToText.disabledDescription":
|
||||
"Habilita e inicia sesión en Kilo provider para usar la entrada de voz en los campos de prompt.",
|
||||
"settings.experimental.speechToTextModel.title": "Modelo de voz a texto",
|
||||
"settings.experimental.speechToTextModel.description":
|
||||
"Elige el modelo de transcripción de Kilo Gateway para la entrada de voz.",
|
||||
"settings.experimental.continueOnDeny.title": "Continuar al denegar",
|
||||
"settings.experimental.continueOnDeny.description": "Continuar el bucle del agente cuando se deniega un permiso",
|
||||
"settings.experimental.mcpTimeout.title": "Tiempo de espera MCP (ms)",
|
||||
|
||||
+22
@@ -284,6 +284,20 @@ export const dict = {
|
||||
"Le bouton 'Améliorer la requête' aide à améliorer votre demande en fournissant un contexte supplémentaire, des clarifications ou des reformulations. Essayez de taper une demande ici et cliquez à nouveau sur le bouton pour voir comment cela fonctionne.",
|
||||
"prompt.action.indexing": "Paramètres d'indexation",
|
||||
|
||||
"speechToText.tooltip.start": "Démarrer la saisie vocale",
|
||||
"speechToText.tooltip.stop": "Arrêter l'enregistrement",
|
||||
"speechToText.tooltip.transcribing": "Transcription en cours... Cliquez pour annuler.",
|
||||
"speechToText.tooltip.error": "La saisie vocale a échoué. Cliquez pour effacer.",
|
||||
"speechToText.error.title": "La saisie vocale a échoué",
|
||||
"speechToText.error.loginRequired": "Connectez-vous à Kilo pour utiliser la saisie vocale.",
|
||||
"speechToText.error.permission": "L'autorisation du microphone a été refusée.",
|
||||
"speechToText.error.microphone": "Impossible de démarrer le microphone.",
|
||||
"speechToText.error.recording": "L'enregistrement a échoué.",
|
||||
"speechToText.error.emptyRecording": "Aucun son n'a été enregistré.",
|
||||
"speechToText.error.emptyTranscript": "Aucune voix n'a été détectée.",
|
||||
"speechToText.error.encoding": "Impossible d'encoder l'enregistrement.",
|
||||
"speechToText.toast.transcribed": "Transcription insérée",
|
||||
|
||||
"prompt.toast.pasteUnsupported.title": "Collage non supporté",
|
||||
"prompt.toast.pasteUnsupported.description": "Seules les images ou les PDF peuvent être collés ici.",
|
||||
"prompt.toast.modelAgentRequired.title": "Sélectionnez un agent et un modèle",
|
||||
@@ -1260,6 +1274,14 @@ export const dict = {
|
||||
"settings.experimental.agentManagerTool.title": "Outil Agent Manager",
|
||||
"settings.experimental.agentManagerTool.description":
|
||||
"Autoriser les agents à démarrer des sessions locales Agent Manager et des sessions worktree depuis un appel d'outil",
|
||||
"settings.experimental.speechToText.title": "Transcription vocale",
|
||||
"settings.experimental.speechToText.description":
|
||||
"Activez la saisie vocale dans les champs de prompt en utilisant votre compte Kilo via Kilo Gateway.",
|
||||
"settings.experimental.speechToText.disabledDescription":
|
||||
"Activez Kilo provider et connectez-vous pour utiliser la saisie vocale dans les champs de prompt.",
|
||||
"settings.experimental.speechToTextModel.title": "Modèle de transcription vocale",
|
||||
"settings.experimental.speechToTextModel.description":
|
||||
"Choisissez le modèle de transcription Kilo Gateway pour la saisie vocale.",
|
||||
"settings.experimental.continueOnDeny.title": "Continuer en cas de refus",
|
||||
"settings.experimental.continueOnDeny.description":
|
||||
"Continuer la boucle de l'agent lorsqu'une autorisation est refusée",
|
||||
|
||||
+21
@@ -279,6 +279,20 @@ export const dict = {
|
||||
"「プロンプトを強化」ボタンは、追加コンテキスト、説明、または言い換えを提供することで、リクエストを改善します。ここにリクエストを入力し、ボタンを再度クリックして動作を確認してください。",
|
||||
"prompt.action.indexing": "インデックス設定",
|
||||
|
||||
"speechToText.tooltip.start": "音声入力を開始",
|
||||
"speechToText.tooltip.stop": "録音を停止",
|
||||
"speechToText.tooltip.transcribing": "文字起こし中... クリックしてキャンセル。",
|
||||
"speechToText.tooltip.error": "音声入力に失敗しました。クリックしてクリア。",
|
||||
"speechToText.error.title": "音声入力に失敗しました",
|
||||
"speechToText.error.loginRequired": "音声入力を使用するにはKiloにサインインしてください。",
|
||||
"speechToText.error.permission": "マイクの許可が拒否されました。",
|
||||
"speechToText.error.microphone": "マイクを起動できませんでした。",
|
||||
"speechToText.error.recording": "録音に失敗しました。",
|
||||
"speechToText.error.emptyRecording": "音声が録音されていません。",
|
||||
"speechToText.error.emptyTranscript": "音声が検出されませんでした。",
|
||||
"speechToText.error.encoding": "録音をエンコードできませんでした。",
|
||||
"speechToText.toast.transcribed": "文字起こしを挿入しました",
|
||||
|
||||
"prompt.toast.pasteUnsupported.title": "サポートされていない貼り付け",
|
||||
"prompt.toast.pasteUnsupported.description": "ここでは画像またはPDFのみ貼り付け可能です。",
|
||||
"prompt.toast.modelAgentRequired.title": "エージェントとモデルを選択",
|
||||
@@ -1229,6 +1243,13 @@ export const dict = {
|
||||
"settings.experimental.agentManagerTool.title": "Agent Manager ツール",
|
||||
"settings.experimental.agentManagerTool.description":
|
||||
"エージェントがツール呼び出しから Agent Manager のローカルセッションとワークツリーセッションを開始できるようにする",
|
||||
"settings.experimental.speechToText.title": "音声認識",
|
||||
"settings.experimental.speechToText.description":
|
||||
"Kilo Gateway経由でKiloアカウントを使用して、プロンプトフィールドでの音声入力を有効にします。",
|
||||
"settings.experimental.speechToText.disabledDescription":
|
||||
"プロンプトフィールドで音声入力を使用するには、Kilo providerを有効にしてサインインしてください。",
|
||||
"settings.experimental.speechToTextModel.title": "音声認識モデル",
|
||||
"settings.experimental.speechToTextModel.description": "音声入力に使用するKilo Gateway文字起こしモデルを選択します。",
|
||||
"settings.experimental.continueOnDeny.title": "拒否時に続行",
|
||||
"settings.experimental.continueOnDeny.description": "権限が拒否された場合にエージェントループを続行",
|
||||
"settings.experimental.mcpTimeout.title": "MCPタイムアウト(ミリ秒)",
|
||||
|
||||
+21
@@ -281,6 +281,20 @@ export const dict = {
|
||||
"prompt.action.enhanceDescription":
|
||||
"'프롬프트 향상' 버튼은 추가 컨텍스트, 명확화 또는 재구성을 제공하여 요청을 개선합니다. 여기에 요청을 입력한 다음 버튼을 다시 클릭하여 작동 방식을 확인해보세요.",
|
||||
|
||||
"speechToText.tooltip.start": "음성 입력 시작",
|
||||
"speechToText.tooltip.stop": "녹음 중지",
|
||||
"speechToText.tooltip.transcribing": "변환 중... 취소하려면 클릭하세요.",
|
||||
"speechToText.tooltip.error": "음성 입력에 실패했습니다. 지우려면 클릭하세요.",
|
||||
"speechToText.error.title": "음성 입력 실패",
|
||||
"speechToText.error.loginRequired": "음성 입력을 사용하려면 Kilo에 로그인하세요.",
|
||||
"speechToText.error.permission": "마이크 권한이 거부되었습니다.",
|
||||
"speechToText.error.microphone": "마이크를 시작할 수 없습니다.",
|
||||
"speechToText.error.recording": "녹음에 실패했습니다.",
|
||||
"speechToText.error.emptyRecording": "녹음된 오디오가 없습니다.",
|
||||
"speechToText.error.emptyTranscript": "음성이 감지되지 않았습니다.",
|
||||
"speechToText.error.encoding": "녹음을 인코딩할 수 없습니다.",
|
||||
"speechToText.toast.transcribed": "변환 텍스트 삽입됨",
|
||||
|
||||
"prompt.toast.pasteUnsupported.title": "지원되지 않는 붙여넣기",
|
||||
"prompt.toast.pasteUnsupported.description": "이미지나 PDF만 붙여넣을 수 있습니다.",
|
||||
"prompt.toast.modelAgentRequired.title": "에이전트 및 모델 선택",
|
||||
@@ -1223,6 +1237,13 @@ export const dict = {
|
||||
"settings.experimental.agentManagerTool.title": "Agent Manager 도구",
|
||||
"settings.experimental.agentManagerTool.description":
|
||||
"에이전트가 도구 호출로 Agent Manager 로컬 세션 및 워크트리 세션을 시작하도록 허용",
|
||||
"settings.experimental.speechToText.title": "음성 텍스트 변환",
|
||||
"settings.experimental.speechToText.description":
|
||||
"Kilo Gateway를 통해 Kilo 계정을 사용하여 프롬프트 필드에서 음성 입력을 활성화합니다.",
|
||||
"settings.experimental.speechToText.disabledDescription":
|
||||
"프롬프트 필드에서 음성 입력을 사용하려면 Kilo provider를 활성화하고 로그인하세요.",
|
||||
"settings.experimental.speechToTextModel.title": "음성 텍스트 변환 모델",
|
||||
"settings.experimental.speechToTextModel.description": "음성 입력에 사용할 Kilo Gateway 변환 모델을 선택하세요.",
|
||||
"settings.experimental.continueOnDeny.title": "거부 시 계속",
|
||||
"settings.experimental.continueOnDeny.description": "권한이 거부되면 에이전트 루프 계속",
|
||||
"settings.experimental.mcpTimeout.title": "MCP 타임아웃 (ms)",
|
||||
|
||||
+21
@@ -283,6 +283,20 @@ export const dict = {
|
||||
"prompt.action.enhanceDescription":
|
||||
"De knop 'Prompt verbeteren' helpt je prompt te verbeteren door extra context, verduidelijking of herformulering te bieden. Typ hier een prompt en klik nogmaals op de knop om te zien hoe het werkt.",
|
||||
|
||||
"speechToText.tooltip.start": "Spraakinvoer starten",
|
||||
"speechToText.tooltip.stop": "Opname stoppen",
|
||||
"speechToText.tooltip.transcribing": "Transcriberen... Klik om te annuleren.",
|
||||
"speechToText.tooltip.error": "Spraakinvoer mislukt. Klik om te wissen.",
|
||||
"speechToText.error.title": "Spraakinvoer mislukt",
|
||||
"speechToText.error.loginRequired": "Meld u aan bij Kilo om spraakinvoer te gebruiken.",
|
||||
"speechToText.error.permission": "Microfoontoegang is geweigerd.",
|
||||
"speechToText.error.microphone": "Kon de microfoon niet starten.",
|
||||
"speechToText.error.recording": "Opname mislukt.",
|
||||
"speechToText.error.emptyRecording": "Er is geen audio opgenomen.",
|
||||
"speechToText.error.emptyTranscript": "Er is geen spraak gedetecteerd.",
|
||||
"speechToText.error.encoding": "Kon de opname niet coderen.",
|
||||
"speechToText.toast.transcribed": "Transcriptie ingevoegd",
|
||||
|
||||
"prompt.toast.pasteUnsupported.title": "Plakken niet ondersteund",
|
||||
"prompt.toast.pasteUnsupported.description": "Hier kunnen alleen afbeeldingen of PDF's worden geplakt.",
|
||||
"prompt.toast.modelAgentRequired.title": "Selecteer een agent en model",
|
||||
@@ -1240,6 +1254,13 @@ export const dict = {
|
||||
"settings.experimental.agentManagerTool.title": "Agent Manager-tool",
|
||||
"settings.experimental.agentManagerTool.description":
|
||||
"Sta agents toe om lokale Agent Manager-sessies en worktree-sessies te starten vanuit een tool call",
|
||||
"settings.experimental.speechToText.title": "Spraak naar tekst",
|
||||
"settings.experimental.speechToText.description":
|
||||
"Schakel spraakinvoer in promptvelden in met uw Kilo-account via Kilo Gateway.",
|
||||
"settings.experimental.speechToText.disabledDescription":
|
||||
"Schakel Kilo provider in en meld u aan om spraakinvoer in promptvelden te gebruiken.",
|
||||
"settings.experimental.speechToTextModel.title": "Spraak-naar-tekst-model",
|
||||
"settings.experimental.speechToTextModel.description": "Kies het Kilo Gateway-transcriptiemodel voor spraakinvoer.",
|
||||
"settings.experimental.continueOnDeny.title": "Doorgaan bij weigering",
|
||||
"settings.experimental.continueOnDeny.description":
|
||||
"Ga door met de agent loop wanneer een toestemming wordt geweigerd",
|
||||
|
||||
+21
@@ -285,6 +285,20 @@ export const dict = {
|
||||
"prompt.action.enhanceDescription":
|
||||
"Knappen 'Forbedre prompt' hjelper med å forbedre forespørselen din ved å gi ekstra kontekst, avklaring eller omformulering. Prøv å skrive en forespørsel her og klikk på knappen igjen for å se hvordan det fungerer.",
|
||||
|
||||
"speechToText.tooltip.start": "Start taleinndata",
|
||||
"speechToText.tooltip.stop": "Stopp opptak",
|
||||
"speechToText.tooltip.transcribing": "Transkriberer... Klikk for å avbryte.",
|
||||
"speechToText.tooltip.error": "Taleinndata mislyktes. Klikk for å tømme.",
|
||||
"speechToText.error.title": "Taleinndata mislyktes",
|
||||
"speechToText.error.loginRequired": "Logg på Kilo for å bruke taleinndata.",
|
||||
"speechToText.error.permission": "Mikrofontillatelse ble nektet.",
|
||||
"speechToText.error.microphone": "Kunne ikke starte mikrofonen.",
|
||||
"speechToText.error.recording": "Opptak mislyktes.",
|
||||
"speechToText.error.emptyRecording": "Ingen lyd ble tatt opp.",
|
||||
"speechToText.error.emptyTranscript": "Ingen tale ble oppdaget.",
|
||||
"speechToText.error.encoding": "Kunne ikke kode opptaket.",
|
||||
"speechToText.toast.transcribed": "Transkripsjon satt inn",
|
||||
|
||||
"prompt.toast.pasteUnsupported.title": "Liming ikke støttet",
|
||||
"prompt.toast.pasteUnsupported.description": "Kun bilder eller PDF-er kan limes inn her.",
|
||||
"prompt.toast.modelAgentRequired.title": "Velg en agent og modell",
|
||||
@@ -1200,6 +1214,13 @@ export const dict = {
|
||||
"settings.experimental.agentManagerTool.title": "Agent Manager-verktøy",
|
||||
"settings.experimental.agentManagerTool.description":
|
||||
"Tillat agenter å starte lokale Agent Manager-økter og worktree-økter fra et verktøykall",
|
||||
"settings.experimental.speechToText.title": "Tale til tekst",
|
||||
"settings.experimental.speechToText.description":
|
||||
"Aktiver taleinndata i prompt-felt ved å bruke din Kilo-konto gjennom Kilo Gateway.",
|
||||
"settings.experimental.speechToText.disabledDescription":
|
||||
"Aktiver Kilo provider og logg på for å bruke taleinndata i prompt-felt.",
|
||||
"settings.experimental.speechToTextModel.title": "Tale-til-tekst-modell",
|
||||
"settings.experimental.speechToTextModel.description": "Velg Kilo Gateway-transkripsjonsmodellen for taleinndata.",
|
||||
"settings.experimental.continueOnDeny.title": "Fortsett ved avvisning",
|
||||
"settings.experimental.continueOnDeny.description": "Fortsett agentløkken når en tillatelse avvises",
|
||||
"settings.experimental.mcpTimeout.title": "MCP-tidsavbrudd (ms)",
|
||||
|
||||
+22
@@ -282,6 +282,20 @@ export const dict = {
|
||||
"Przycisk 'Ulepsz podpowiedź' pomaga ulepszyć Twoją prośbę, dostarczając dodatkowy kontekst, wyjaśnienia lub przeformułowania. Spróbuj wpisać prośbę tutaj i kliknij przycisk ponownie, aby zobaczyć, jak to działa.",
|
||||
"prompt.action.indexing": "Ustawienia indeksowania",
|
||||
|
||||
"speechToText.tooltip.start": "Rozpocznij wprowadzanie głosowe",
|
||||
"speechToText.tooltip.stop": "Zatrzymaj nagrywanie",
|
||||
"speechToText.tooltip.transcribing": "Transkrybowanie... Kliknij, aby anulować.",
|
||||
"speechToText.tooltip.error": "Wprowadzanie głosowe nie powiodło się. Kliknij, aby wyczyścić.",
|
||||
"speechToText.error.title": "Wprowadzanie głosowe nie powiodło się",
|
||||
"speechToText.error.loginRequired": "Zaloguj się do Kilo, aby korzystać z wprowadzania głosowego.",
|
||||
"speechToText.error.permission": "Odmówiono dostępu do mikrofonu.",
|
||||
"speechToText.error.microphone": "Nie udało się uruchomić mikrofonu.",
|
||||
"speechToText.error.recording": "Nagrywanie nie powiodło się.",
|
||||
"speechToText.error.emptyRecording": "Nie nagrano żadnego dźwięku.",
|
||||
"speechToText.error.emptyTranscript": "Nie wykryto mowy.",
|
||||
"speechToText.error.encoding": "Nie udało się zakodować nagrania.",
|
||||
"speechToText.toast.transcribed": "Wstawiono transkrypcję",
|
||||
|
||||
"prompt.toast.pasteUnsupported.title": "Nieobsługiwane wklejanie",
|
||||
"prompt.toast.pasteUnsupported.description": "Tylko obrazy lub pliki PDF mogą być tutaj wklejane.",
|
||||
"prompt.toast.modelAgentRequired.title": "Wybierz agenta i model",
|
||||
@@ -1199,6 +1213,14 @@ export const dict = {
|
||||
"settings.experimental.agentManagerTool.title": "Narzędzie Agent Manager",
|
||||
"settings.experimental.agentManagerTool.description":
|
||||
"Zezwól agentom na uruchamianie lokalnych sesji Agent Manager i sesji worktree z wywołania narzędzia",
|
||||
"settings.experimental.speechToText.title": "Mowa na tekst",
|
||||
"settings.experimental.speechToText.description":
|
||||
"Włącz wprowadzanie głosowe w polach promptów przy użyciu konta Kilo za pośrednictwem Kilo Gateway.",
|
||||
"settings.experimental.speechToText.disabledDescription":
|
||||
"Włącz Kilo provider i zaloguj się, aby korzystać z wprowadzania głosowego w polach promptów.",
|
||||
"settings.experimental.speechToTextModel.title": "Model mowy na tekst",
|
||||
"settings.experimental.speechToTextModel.description":
|
||||
"Wybierz model transkrypcji Kilo Gateway dla wprowadzania głosowego.",
|
||||
"settings.experimental.continueOnDeny.title": "Kontynuuj przy odmowie",
|
||||
"settings.experimental.continueOnDeny.description": "Kontynuuj pętlę agenta po odmowie uprawnienia",
|
||||
"settings.experimental.mcpTimeout.title": "Limit czasu MCP (ms)",
|
||||
|
||||
+22
@@ -280,6 +280,20 @@ export const dict = {
|
||||
"prompt.action.enhanceDescription":
|
||||
"Кнопка 'Улучшить запрос' помогает сделать ваш запрос лучше, предоставляя дополнительный контекст, уточнения или переформулировку. Попробуйте ввести запрос и снова нажать кнопку, чтобы увидеть, как это работает.",
|
||||
|
||||
"speechToText.tooltip.start": "Начать голосовой ввод",
|
||||
"speechToText.tooltip.stop": "Остановить запись",
|
||||
"speechToText.tooltip.transcribing": "Распознавание... Нажмите для отмены.",
|
||||
"speechToText.tooltip.error": "Ошибка голосового ввода. Нажмите, чтобы очистить.",
|
||||
"speechToText.error.title": "Ошибка голосового ввода",
|
||||
"speechToText.error.loginRequired": "Войдите в Kilo, чтобы использовать голосовой ввод.",
|
||||
"speechToText.error.permission": "В доступе к микрофону отказано.",
|
||||
"speechToText.error.microphone": "Не удалось запустить микрофон.",
|
||||
"speechToText.error.recording": "Ошибка записи.",
|
||||
"speechToText.error.emptyRecording": "Звук не был записан.",
|
||||
"speechToText.error.emptyTranscript": "Речь не обнаружена.",
|
||||
"speechToText.error.encoding": "Не удалось закодировать запись.",
|
||||
"speechToText.toast.transcribed": "Транскрипция вставлена",
|
||||
|
||||
"prompt.toast.pasteUnsupported.title": "Неподдерживаемая вставка",
|
||||
"prompt.toast.pasteUnsupported.description": "Сюда можно вставлять только изображения или PDF.",
|
||||
"prompt.toast.modelAgentRequired.title": "Выберите агента и модель",
|
||||
@@ -1237,6 +1251,14 @@ export const dict = {
|
||||
"settings.experimental.agentManagerTool.title": "Инструмент Agent Manager",
|
||||
"settings.experimental.agentManagerTool.description":
|
||||
"Разрешить агентам запускать локальные сеансы Agent Manager и сеансы worktree через вызов инструмента",
|
||||
"settings.experimental.speechToText.title": "Речь в текст",
|
||||
"settings.experimental.speechToText.description":
|
||||
"Включите голосовой ввод в полях запросов, используя вашу учетную запись Kilo через Kilo Gateway.",
|
||||
"settings.experimental.speechToText.disabledDescription":
|
||||
"Включите Kilo provider и войдите в него, чтобы использовать голосовой ввод в полях запросов.",
|
||||
"settings.experimental.speechToTextModel.title": "Модель речи в текст",
|
||||
"settings.experimental.speechToTextModel.description":
|
||||
"Выберите модель транскрипции Kilo Gateway для голосового ввода.",
|
||||
"settings.experimental.continueOnDeny.title": "Продолжить при отказе",
|
||||
"settings.experimental.continueOnDeny.description": "Продолжить цикл агента при отказе в разрешении",
|
||||
"settings.experimental.mcpTimeout.title": "Таймаут MCP (мс)",
|
||||
|
||||
+22
@@ -280,6 +280,20 @@ export const dict = {
|
||||
"ปุ่ม 'ปรับปรุงพรอมต์' ช่วยปรับปรุงพรอมต์ของคุณโดยให้บริบทเพิ่มเติม ชี้แจง หรือเขียนใหม่ ลองพิมพ์พรอมต์ที่นี่และคลิกปุ่มอีกครั้งเพื่อดูว่ามันทำงานอย่างไร",
|
||||
"prompt.action.indexing": "การตั้งค่าการสร้างดัชนี",
|
||||
|
||||
"speechToText.tooltip.start": "เริ่มการป้อนข้อมูลด้วยเสียง",
|
||||
"speechToText.tooltip.stop": "หยุดบันทึก",
|
||||
"speechToText.tooltip.transcribing": "กำลังถอดเสียง... คลิกเพื่อยกเลิก",
|
||||
"speechToText.tooltip.error": "การป้อนข้อมูลด้วยเสียงล้มเหลว คลิกเพื่อล้าง",
|
||||
"speechToText.error.title": "การป้อนข้อมูลด้วยเสียงล้มเหลว",
|
||||
"speechToText.error.loginRequired": "ลงชื่อเข้าใช้ Kilo เพื่อใช้การป้อนข้อมูลด้วยเสียง",
|
||||
"speechToText.error.permission": "การอนุญาตเข้าถึงไมโครโฟนถูกปฏิเสธ",
|
||||
"speechToText.error.microphone": "ไม่สามารถเริ่มต้นไมโครโฟนได้",
|
||||
"speechToText.error.recording": "การบันทึกล้มเหลว",
|
||||
"speechToText.error.emptyRecording": "ไม่มีการบันทึกเสียง",
|
||||
"speechToText.error.emptyTranscript": "ตรวจไม่พบเสียงพูด",
|
||||
"speechToText.error.encoding": "ไม่สามารถเข้ารหัสการบันทึกได้",
|
||||
"speechToText.toast.transcribed": "แทรกข้อความที่ถอดเสียงแล้ว",
|
||||
|
||||
"prompt.toast.pasteUnsupported.title": "การวางไม่รองรับ",
|
||||
"prompt.toast.pasteUnsupported.description": "สามารถวางรูปภาพหรือ PDF เท่านั้น",
|
||||
"prompt.toast.modelAgentRequired.title": "เลือกเอเจนต์และโมเดล",
|
||||
@@ -1220,6 +1234,14 @@ export const dict = {
|
||||
"settings.experimental.agentManagerTool.title": "เครื่องมือ Agent Manager",
|
||||
"settings.experimental.agentManagerTool.description":
|
||||
"อนุญาตให้เอเจนต์เริ่มเซสชัน Agent Manager ในเครื่องและเซสชัน worktree จากการเรียกเครื่องมือ",
|
||||
"settings.experimental.speechToText.title": "แปลงเสียงเป็นข้อความ",
|
||||
"settings.experimental.speechToText.description":
|
||||
"เปิดใช้งานการป้อนข้อมูลด้วยเสียงในช่องพรอมต์โดยใช้บัญชี Kilo ของคุณผ่าน Kilo Gateway",
|
||||
"settings.experimental.speechToText.disabledDescription":
|
||||
"เปิดใช้งานและลงชื่อเข้าใช้ Kilo provider เพื่อใช้การป้อนข้อมูลด้วยเสียงในช่องพรอมต์",
|
||||
"settings.experimental.speechToTextModel.title": "โมเดลแปลงเสียงเป็นข้อความ",
|
||||
"settings.experimental.speechToTextModel.description":
|
||||
"เลือกโมเดลการถอดเสียง Kilo Gateway สำหรับการป้อนข้อมูลด้วยเสียง",
|
||||
"settings.experimental.continueOnDeny.title": "ดำเนินต่อเมื่อถูกปฏิเสธ",
|
||||
"settings.experimental.continueOnDeny.description": "ดำเนินลูปเอเจนต์ต่อเมื่อสิทธิ์ถูกปฏิเสธ",
|
||||
"settings.experimental.mcpTimeout.title": "หมดเวลา MCP (มิลลิวินาที)",
|
||||
|
||||
+21
@@ -280,6 +280,20 @@ export const dict = {
|
||||
"prompt.action.enhanceDescription":
|
||||
"'Komutu Geliştir' düğmesi, ek bağlam, açıklama veya yeniden ifadelendirme sağlayarak komutunuzu iyileştirmeye yardımcı olur. Buraya bir komut yazıp düğmeye tekrar tıklayarak nasıl çalıştığını görebilirsiniz.",
|
||||
|
||||
"speechToText.tooltip.start": "Sesli girişi başlat",
|
||||
"speechToText.tooltip.stop": "Kaydı durdur",
|
||||
"speechToText.tooltip.transcribing": "Metne dönüştürülüyor... İptal etmek için tıklayın.",
|
||||
"speechToText.tooltip.error": "Sesli giriş başarısız oldu. Temizlemek için tıklayın.",
|
||||
"speechToText.error.title": "Sesli giriş başarısız oldu",
|
||||
"speechToText.error.loginRequired": "Sesli girişi kullanmak için Kilo'ya giriş yapın.",
|
||||
"speechToText.error.permission": "Mikrofon izni reddedildi.",
|
||||
"speechToText.error.microphone": "Mikrofon başlatılamadı.",
|
||||
"speechToText.error.recording": "Kayıt başarısız oldu.",
|
||||
"speechToText.error.emptyRecording": "Hiçbir ses kaydedilmedi.",
|
||||
"speechToText.error.emptyTranscript": "Hiçbir konuşma algılanmadı.",
|
||||
"speechToText.error.encoding": "Kayıt kodlanamadı.",
|
||||
"speechToText.toast.transcribed": "Döküm eklendi",
|
||||
|
||||
"prompt.toast.pasteUnsupported.title": "Desteklenmeyen yapıştırma",
|
||||
"prompt.toast.pasteUnsupported.description": "Buraya sadece görsel veya PDF yapıştırılabilir.",
|
||||
"prompt.toast.modelAgentRequired.title": "Bir ajan ve model seçin",
|
||||
@@ -1233,6 +1247,13 @@ export const dict = {
|
||||
"settings.experimental.agentManagerTool.title": "Agent Manager Aracı",
|
||||
"settings.experimental.agentManagerTool.description":
|
||||
"Ajanların bir araç çağrısından Agent Manager yerel oturumları ve worktree oturumları başlatmasına izin ver",
|
||||
"settings.experimental.speechToText.title": "Sesten metne",
|
||||
"settings.experimental.speechToText.description":
|
||||
"Kilo Gateway üzerinden Kilo hesabınızı kullanarak komut alanlarında sesli girişi etkinleştirin.",
|
||||
"settings.experimental.speechToText.disabledDescription":
|
||||
"Komut alanlarında sesli girişi kullanmak için Kilo provider'ı etkinleştirin ve giriş yapın.",
|
||||
"settings.experimental.speechToTextModel.title": "Sesten metne modeli",
|
||||
"settings.experimental.speechToTextModel.description": "Sesli giriş için Kilo Gateway transkripsiyon modelini seçin.",
|
||||
"settings.experimental.continueOnDeny.title": "Reddetme Durumunda Devam Et",
|
||||
"settings.experimental.continueOnDeny.description": "Bir izin reddedildiğinde ajan döngüsüne devam et",
|
||||
"settings.experimental.mcpTimeout.title": "MCP Zaman Aşımı (ms)",
|
||||
|
||||
+22
@@ -282,6 +282,20 @@ export const dict = {
|
||||
"prompt.action.enhanceDescription":
|
||||
"Кнопка 'Покращити запит' допомагає вдосконалити ваш запит, надаючи додатковий контекст, уточнення або перефразування. Введіть запит тут і натисніть кнопку ще раз, щоб побачити, як це працює.",
|
||||
|
||||
"speechToText.tooltip.start": "Почати голосове введення",
|
||||
"speechToText.tooltip.stop": "Зупинити запис",
|
||||
"speechToText.tooltip.transcribing": "Транскрибування... Натисніть, щоб скасувати.",
|
||||
"speechToText.tooltip.error": "Помилка голосового введення. Натисніть, щоб очистити.",
|
||||
"speechToText.error.title": "Помилка голосового введення",
|
||||
"speechToText.error.loginRequired": "Увійдіть до Kilo, щоб використовувати голосове введення.",
|
||||
"speechToText.error.permission": "Доступ до мікрофона заборонено.",
|
||||
"speechToText.error.microphone": "Не вдалося запустити мікрофон.",
|
||||
"speechToText.error.recording": "Помилка запису.",
|
||||
"speechToText.error.emptyRecording": "Аудіо не записано.",
|
||||
"speechToText.error.emptyTranscript": "Мовлення не виявлено.",
|
||||
"speechToText.error.encoding": "Не вдалося закодувати запис.",
|
||||
"speechToText.toast.transcribed": "Транскрипцію вставлено",
|
||||
|
||||
"prompt.toast.pasteUnsupported.title": "Вставка не підтримується",
|
||||
"prompt.toast.pasteUnsupported.description": "Сюди можна вставляти лише зображення або PDF.",
|
||||
"prompt.toast.modelAgentRequired.title": "Виберіть агента і модель",
|
||||
@@ -1233,6 +1247,14 @@ export const dict = {
|
||||
"settings.experimental.agentManagerTool.title": "Інструмент Agent Manager",
|
||||
"settings.experimental.agentManagerTool.description":
|
||||
"Дозволити агентам запускати локальні сесії Agent Manager і сесії worktree через виклик інструмента",
|
||||
"settings.experimental.speechToText.title": "Мовлення в текст",
|
||||
"settings.experimental.speechToText.description":
|
||||
"Увімкніть голосове введення в полях запитів, використовуючи ваш обліковий запис Kilo через Kilo Gateway.",
|
||||
"settings.experimental.speechToText.disabledDescription":
|
||||
"Увімкніть Kilo provider і увійдіть, щоб використовувати голосове введення в полях запитів.",
|
||||
"settings.experimental.speechToTextModel.title": "Модель мовлення в текст",
|
||||
"settings.experimental.speechToTextModel.description":
|
||||
"Виберіть модель транскрипції Kilo Gateway для голосового введення.",
|
||||
"settings.experimental.continueOnDeny.title": "Продовжувати при відхиленні",
|
||||
"settings.experimental.continueOnDeny.description": "Продовжувати цикл агента, коли дозвіл відхилено",
|
||||
"settings.experimental.mcpTimeout.title": "Тайм-аут MCP (мс)",
|
||||
|
||||
+19
@@ -279,6 +279,20 @@ export const dict = {
|
||||
"prompt.action.enhanceDescription":
|
||||
"'增强提示'按钮通过提供额外上下文、澄清或重新表述来帮助改进您的请求。尝试在此处输入请求,然后再次点击按钮查看其工作原理。",
|
||||
|
||||
"speechToText.tooltip.start": "开始语音输入",
|
||||
"speechToText.tooltip.stop": "停止录音",
|
||||
"speechToText.tooltip.transcribing": "正在转录... 点击取消。",
|
||||
"speechToText.tooltip.error": "语音输入失败。点击清除。",
|
||||
"speechToText.error.title": "语音输入失败",
|
||||
"speechToText.error.loginRequired": "登录 Kilo 以使用语音输入。",
|
||||
"speechToText.error.permission": "麦克风权限被拒绝。",
|
||||
"speechToText.error.microphone": "无法启动麦克风。",
|
||||
"speechToText.error.recording": "录音失败。",
|
||||
"speechToText.error.emptyRecording": "未录制任何音频。",
|
||||
"speechToText.error.emptyTranscript": "未检测到语音。",
|
||||
"speechToText.error.encoding": "无法编码录音。",
|
||||
"speechToText.toast.transcribed": "已插入转录文本",
|
||||
|
||||
"prompt.toast.pasteUnsupported.title": "不支持的粘贴",
|
||||
"prompt.toast.pasteUnsupported.description": "这里只能粘贴图片或 PDF 文件。",
|
||||
"prompt.toast.modelAgentRequired.title": "请选择智能体和模型",
|
||||
@@ -1202,6 +1216,11 @@ export const dict = {
|
||||
"settings.experimental.codebaseSearch.description": "启用 AI 驱动的自然语言代码库搜索",
|
||||
"settings.experimental.agentManagerTool.title": "Agent Manager 工具",
|
||||
"settings.experimental.agentManagerTool.description": "允许智能体通过工具调用启动 Agent Manager 本地会话和工作树会话",
|
||||
"settings.experimental.speechToText.title": "语音转文本",
|
||||
"settings.experimental.speechToText.description": "通过 Kilo Gateway 使用您的 Kilo 帐户在提示词字段中启用语音输入。",
|
||||
"settings.experimental.speechToText.disabledDescription": "启用并登录 Kilo provider 以在提示词字段中使用语音输入。",
|
||||
"settings.experimental.speechToTextModel.title": "语音转文本模型",
|
||||
"settings.experimental.speechToTextModel.description": "选择用于语音输入的 Kilo Gateway 转录模型。",
|
||||
"settings.experimental.continueOnDeny.title": "拒绝后继续",
|
||||
"settings.experimental.continueOnDeny.description": "权限被拒绝时继续智能体循环",
|
||||
"settings.experimental.mcpTimeout.title": "MCP 超时(毫秒)",
|
||||
|
||||
+19
@@ -278,6 +278,20 @@ export const dict = {
|
||||
"prompt.action.enhanceDescription":
|
||||
"「強化提示詞」按鈕可透過提供額外內容、說明或改寫來協助改善提示詞。試著在這裡輸入提示詞,再點選一次按鈕以了解其運作方式。",
|
||||
|
||||
"speechToText.tooltip.start": "開始語音輸入",
|
||||
"speechToText.tooltip.stop": "停止錄音",
|
||||
"speechToText.tooltip.transcribing": "正在轉錄... 點擊取消。",
|
||||
"speechToText.tooltip.error": "語音輸入失敗。點擊清除。",
|
||||
"speechToText.error.title": "語音輸入失敗",
|
||||
"speechToText.error.loginRequired": "登入 Kilo 以使用語音輸入。",
|
||||
"speechToText.error.permission": "麥克風權限被拒絕。",
|
||||
"speechToText.error.microphone": "無法啟動麥克風。",
|
||||
"speechToText.error.recording": "錄音失敗。",
|
||||
"speechToText.error.emptyRecording": "未錄製任何音訊。",
|
||||
"speechToText.error.emptyTranscript": "未偵測到語音。",
|
||||
"speechToText.error.encoding": "無法編碼錄音。",
|
||||
"speechToText.toast.transcribed": "已插入轉錄文字",
|
||||
|
||||
"prompt.toast.pasteUnsupported.title": "不支援的貼上",
|
||||
"prompt.toast.pasteUnsupported.description": "這裡只能貼上圖片或 PDF 檔案。",
|
||||
"prompt.toast.modelAgentRequired.title": "請選擇 Agent 和模型",
|
||||
@@ -1171,6 +1185,11 @@ export const dict = {
|
||||
"settings.experimental.agentManagerTool.title": "Agent Manager 工具",
|
||||
"settings.experimental.agentManagerTool.description":
|
||||
"允許 Agent 透過工具呼叫啟動 Agent Manager 本機工作階段和工作樹工作階段",
|
||||
"settings.experimental.speechToText.title": "語音轉文字",
|
||||
"settings.experimental.speechToText.description": "透過 Kilo Gateway 使用您的 Kilo 帳戶在提示詞欄位中啟用語音輸入。",
|
||||
"settings.experimental.speechToText.disabledDescription": "啟用並登入 Kilo provider 以在提示詞欄位中使用語音輸入。",
|
||||
"settings.experimental.speechToTextModel.title": "語音轉文字模型",
|
||||
"settings.experimental.speechToTextModel.description": "選擇用於語音輸入的 Kilo Gateway 轉錄模型。",
|
||||
"settings.experimental.continueOnDeny.title": "拒絕後繼續",
|
||||
"settings.experimental.continueOnDeny.description": "權限被拒絕時繼續 Agent 迴圈",
|
||||
"settings.experimental.mcpTimeout.title": "MCP 逾時(毫秒)",
|
||||
|
||||
@@ -563,6 +563,54 @@
|
||||
box-shadow: 0 0 0 1px var(--surface-base, var(--vscode-editor-background));
|
||||
}
|
||||
|
||||
.prompt-speech-button {
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.prompt-speech-button--recording {
|
||||
color: var(--vscode-errorForeground, #f48771);
|
||||
|
||||
[data-slot="icon-svg"] {
|
||||
color: currentColor !important;
|
||||
}
|
||||
|
||||
.prompt-speech-icon {
|
||||
animation: prompt-speech-recording-pulse 1.1s ease-in-out infinite;
|
||||
}
|
||||
}
|
||||
|
||||
.prompt-speech-button--recording::after {
|
||||
content: "";
|
||||
position: absolute;
|
||||
right: 4px;
|
||||
top: 4px;
|
||||
width: 5px;
|
||||
height: 5px;
|
||||
border-radius: 999px;
|
||||
background: currentColor;
|
||||
box-shadow: 0 0 0 2px var(--surface-base, var(--vscode-editor-background));
|
||||
}
|
||||
|
||||
.prompt-speech-button--error {
|
||||
color: var(--vscode-charts-yellow, #cca700);
|
||||
|
||||
[data-slot="icon-svg"] {
|
||||
color: currentColor !important;
|
||||
}
|
||||
}
|
||||
|
||||
@keyframes prompt-speech-recording-pulse {
|
||||
0%,
|
||||
100% {
|
||||
opacity: 1;
|
||||
transform: scale(1);
|
||||
}
|
||||
50% {
|
||||
opacity: 0.72;
|
||||
transform: scale(0.94);
|
||||
}
|
||||
}
|
||||
|
||||
/* ============================================
|
||||
Enhance Prompt Spinner
|
||||
============================================ */
|
||||
|
||||
@@ -333,12 +333,43 @@ export interface AutocompleteSettingsLoadedMessage {
|
||||
}
|
||||
}
|
||||
|
||||
export interface SpeechToTextSettingsLoadedMessage {
|
||||
type: "speechToTextSettingsLoaded"
|
||||
settings: {
|
||||
enabled: boolean
|
||||
model: string
|
||||
}
|
||||
}
|
||||
|
||||
export interface ChatCompletionResultMessage {
|
||||
type: "chatCompletionResult"
|
||||
text: string
|
||||
requestId: string
|
||||
}
|
||||
|
||||
export interface SpeechToTextResultMessage {
|
||||
type: "speechToTextResult"
|
||||
text: string
|
||||
requestId: string
|
||||
}
|
||||
|
||||
export interface SpeechToTextStartedMessage {
|
||||
type: "speechToTextStarted"
|
||||
requestId: string
|
||||
}
|
||||
|
||||
export interface SpeechToTextCancelledMessage {
|
||||
type: "speechToTextCancelled"
|
||||
requestId: string
|
||||
}
|
||||
|
||||
export interface SpeechToTextErrorMessage {
|
||||
type: "speechToTextError"
|
||||
error: string
|
||||
code?: string
|
||||
requestId: string
|
||||
}
|
||||
|
||||
export interface FileSearchItem {
|
||||
path: string
|
||||
type: "file" | "folder" | "opened-file"
|
||||
@@ -929,7 +960,12 @@ export type ExtensionMessage =
|
||||
| SkillsLoadedMessage
|
||||
| CommandsLoadedMessage
|
||||
| AutocompleteSettingsLoadedMessage
|
||||
| SpeechToTextSettingsLoadedMessage
|
||||
| ChatCompletionResultMessage
|
||||
| SpeechToTextStartedMessage
|
||||
| SpeechToTextCancelledMessage
|
||||
| SpeechToTextResultMessage
|
||||
| SpeechToTextErrorMessage
|
||||
| FileSearchResultMessage
|
||||
| TerminalContextResultMessage
|
||||
| TerminalContextErrorMessage
|
||||
|
||||
@@ -322,12 +322,33 @@ export interface RequestAutocompleteSettingsMessage {
|
||||
type: "requestAutocompleteSettings"
|
||||
}
|
||||
|
||||
export interface RequestSpeechToTextSettingsMessage {
|
||||
type: "requestSpeechToTextSettings"
|
||||
}
|
||||
|
||||
export interface RequestChatCompletionMessage {
|
||||
type: "requestChatCompletion"
|
||||
text: string
|
||||
requestId: string
|
||||
}
|
||||
|
||||
export interface SpeechToTextStartMessage {
|
||||
type: "speechToTextStart"
|
||||
requestId: string
|
||||
model: string
|
||||
language?: string
|
||||
}
|
||||
|
||||
export interface SpeechToTextStopMessage {
|
||||
type: "speechToTextStop"
|
||||
requestId: string
|
||||
}
|
||||
|
||||
export interface SpeechToTextCancelMessage {
|
||||
type: "speechToTextCancel"
|
||||
requestId: string
|
||||
}
|
||||
|
||||
export interface RequestFileSearchMessage {
|
||||
type: "requestFileSearch"
|
||||
query: string
|
||||
@@ -1077,7 +1098,11 @@ export type WebviewMessage =
|
||||
| DeleteSessionRequest
|
||||
| RenameSessionRequest
|
||||
| RequestAutocompleteSettingsMessage
|
||||
| RequestSpeechToTextSettingsMessage
|
||||
| RequestChatCompletionMessage
|
||||
| SpeechToTextStartMessage
|
||||
| SpeechToTextStopMessage
|
||||
| SpeechToTextCancelMessage
|
||||
| RequestFileSearchMessage
|
||||
| RequestTerminalContextMessage
|
||||
| RequestGitChangesContextMessage
|
||||
|
||||
@@ -62,6 +62,8 @@ import type {
|
||||
GlobalUpgradeResponses,
|
||||
IndexingStatusResponses,
|
||||
InstanceDisposeResponses,
|
||||
KiloAudioTranscriptionsErrors,
|
||||
KiloAudioTranscriptionsResponses,
|
||||
KiloClawChatCredentialsResponses,
|
||||
KiloClawStatusResponses,
|
||||
KiloCloudSessionGetErrors,
|
||||
@@ -5745,6 +5747,58 @@ export class Organization extends HeyApiClient {
|
||||
}
|
||||
}
|
||||
|
||||
export class Audio extends HeyApiClient {
|
||||
/**
|
||||
* Speech to text transcription
|
||||
*
|
||||
* Proxy an audio transcription request to the Kilo Gateway
|
||||
*/
|
||||
public transcriptions<ThrowOnError extends boolean = false>(
|
||||
parameters?: {
|
||||
directory?: string
|
||||
workspace?: string
|
||||
model?: string
|
||||
input_audio?: {
|
||||
data: string
|
||||
format: string
|
||||
}
|
||||
language?: string
|
||||
temperature?: number
|
||||
},
|
||||
options?: Options<never, ThrowOnError>,
|
||||
) {
|
||||
const params = buildClientParams(
|
||||
[parameters],
|
||||
[
|
||||
{
|
||||
args: [
|
||||
{ in: "query", key: "directory" },
|
||||
{ in: "query", key: "workspace" },
|
||||
{ in: "body", key: "model" },
|
||||
{ in: "body", key: "input_audio" },
|
||||
{ in: "body", key: "language" },
|
||||
{ in: "body", key: "temperature" },
|
||||
],
|
||||
},
|
||||
],
|
||||
)
|
||||
return (options?.client ?? this.client).post<
|
||||
KiloAudioTranscriptionsResponses,
|
||||
KiloAudioTranscriptionsErrors,
|
||||
ThrowOnError
|
||||
>({
|
||||
url: "/kilo/audio/transcriptions",
|
||||
...options,
|
||||
...params,
|
||||
headers: {
|
||||
"Content-Type": "application/json",
|
||||
...options?.headers,
|
||||
...params.headers,
|
||||
},
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
export class Session3 extends HeyApiClient {
|
||||
/**
|
||||
* Get cloud session
|
||||
@@ -6066,6 +6120,11 @@ export class Kilo extends HeyApiClient {
|
||||
return (this._organization ??= new Organization({ client: this.client }))
|
||||
}
|
||||
|
||||
private _audio?: Audio
|
||||
get audio(): Audio {
|
||||
return (this._audio ??= new Audio({ client: this.client }))
|
||||
}
|
||||
|
||||
private _cloud?: Cloud
|
||||
get cloud(): Cloud {
|
||||
return (this._cloud ??= new Cloud({ client: this.client }))
|
||||
|
||||
@@ -7247,6 +7247,45 @@ export type KiloFimResponses = {
|
||||
|
||||
export type KiloFimResponse = KiloFimResponses[keyof KiloFimResponses]
|
||||
|
||||
export type KiloAudioTranscriptionsData = {
|
||||
body?: {
|
||||
model: string
|
||||
input_audio: {
|
||||
data: string
|
||||
format: string
|
||||
}
|
||||
language?: string
|
||||
temperature?: number
|
||||
}
|
||||
path?: never
|
||||
query?: {
|
||||
directory?: string
|
||||
workspace?: string
|
||||
}
|
||||
url: "/kilo/audio/transcriptions"
|
||||
}
|
||||
|
||||
export type KiloAudioTranscriptionsErrors = {
|
||||
/**
|
||||
* Bad request
|
||||
*/
|
||||
400: BadRequestError
|
||||
}
|
||||
|
||||
export type KiloAudioTranscriptionsError = KiloAudioTranscriptionsErrors[keyof KiloAudioTranscriptionsErrors]
|
||||
|
||||
export type KiloAudioTranscriptionsResponses = {
|
||||
/**
|
||||
* Transcription response
|
||||
*/
|
||||
200: {
|
||||
text: string
|
||||
usage?: unknown
|
||||
}
|
||||
}
|
||||
|
||||
export type KiloAudioTranscriptionsResponse = KiloAudioTranscriptionsResponses[keyof KiloAudioTranscriptionsResponses]
|
||||
|
||||
export type KiloNotificationsData = {
|
||||
body?: never
|
||||
path?: never
|
||||
|
||||
Reference in New Issue
Block a user