mirror of
https://github.com/Kilo-Org/kilocode.git
synced 2026-10-29 18:52:45 +08:00
fix(vscode): restore speech input availability
This commit is contained in:
@@ -0,0 +1,7 @@
|
||||
---
|
||||
"@kilocode/cli": patch
|
||||
"@kilocode/sdk": patch
|
||||
"kilo-code": patch
|
||||
---
|
||||
|
||||
Restore speech input when profile details are unavailable, move transcription model selection to the Models tab, and default transcription to Whisper Large V3 Turbo.
|
||||
@@ -41,7 +41,7 @@ Enable and sign in to the Kilo provider to use voice input in prompt fields. Req
|
||||
|
||||
## Choose a model
|
||||
|
||||
You can optionally choose a transcription model in **Settings** > **Experimental** > **Speech to Text Model**. Kilo stores this choice as `experimental.speech_to_text_model` in your global Kilo CLI config (`~/.config/kilo/kilo.jsonc`).
|
||||
You can optionally choose a transcription model in **Settings** > **Models** > **Speech to Text Model**. Kilo stores this choice as `experimental.speech_to_text_model` in your global Kilo CLI config (`~/.config/kilo/kilo.jsonc`).
|
||||
|
||||
---
|
||||
|
||||
|
||||
@@ -47,6 +47,18 @@ Use **Local Config** or **Global Config** in the Settings header to open the mat
|
||||
If you check config files into version control, make sure they do not contain API keys or other secrets (e.g., `provider.*.options.apiKey`). Use environment variables for credentials instead.
|
||||
{% /callout %}
|
||||
|
||||
### Voice Transcription Model
|
||||
|
||||
When the Kilo provider is enabled and you are signed in, choose the transcription model under **Models** > **Speech to Text Model**. This stores `experimental.speech_to_text_model` in your global Kilo CLI config:
|
||||
|
||||
```json
|
||||
{
|
||||
"experimental": {
|
||||
"speech_to_text_model": "openai/whisper-large-v3-turbo"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### Reasoning Blocks
|
||||
|
||||
Reasoning blocks stay expanded by default in the VS Code chat UI. Enable **Auto-Collapse Reasoning** in the Display tab, or set `auto_collapse_reasoning` in `kilo.jsonc`, to collapse them after the agent finishes writing them:
|
||||
@@ -198,20 +210,9 @@ Available experimental settings include:
|
||||
- **Share mode** - `manual`, `auto`, or `disabled` session sharing
|
||||
- **LSP integration** - expose language server diagnostics to the agent
|
||||
- **Paste summary** - summarize large clipboard pastes before including them
|
||||
- **Speech to Text Model** - optionally select the transcription model
|
||||
- **Batch tool** - allow the agent to batch multiple tool calls in one step
|
||||
- **OpenTelemetry** - enable Kilo telemetry and optional OTLP export when configured
|
||||
|
||||
Voice input appears automatically when the Kilo provider is enabled and you are signed in. Choosing **Speech to Text Model** stores `experimental.speech_to_text_model` in your global Kilo CLI config (`~/.config/kilo/kilo.jsonc`):
|
||||
|
||||
```json
|
||||
{
|
||||
"experimental": {
|
||||
"speech_to_text_model": "openai/gpt-4o-mini-transcribe"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
Advanced options not exposed in the UI can be configured via the `experimental` key in `kilo.jsonc`:
|
||||
|
||||
```json
|
||||
|
||||
@@ -902,10 +902,6 @@
|
||||
"default": false,
|
||||
"description": "Enable chat textarea autocomplete"
|
||||
},
|
||||
"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,
|
||||
|
||||
@@ -9,7 +9,7 @@ import {
|
||||
sanitizeCustomProviderConfig,
|
||||
withCustomProviderDeletions,
|
||||
} from "./shared/custom-provider"
|
||||
import { isCustomProviderPackage, KILO_AUTO, parseModelString } from "./shared/provider-model"
|
||||
import { isCustomProviderPackage, KILO_AUTO, KILO_PROVIDER_ID, parseModelString } from "./shared/provider-model"
|
||||
import { configFeatures } from "./features"
|
||||
|
||||
/**
|
||||
@@ -50,7 +50,7 @@ function same(a: unknown, b: unknown): boolean {
|
||||
return akeys.every((key, index) => key === bkeys[index] && same(a[key], b[key]))
|
||||
}
|
||||
|
||||
/** Fetch auth methods alongside the provider list. Auth states default to empty (endpoint not yet available). */
|
||||
/** Fetch provider availability and authentication state without exposing stored credentials. */
|
||||
export async function fetchProviderData(client: KiloClient, dir: string) {
|
||||
const authRequest =
|
||||
typeof client.provider.auth === "function"
|
||||
@@ -59,10 +59,18 @@ export async function fetchProviderData(client: KiloClient, dir: string) {
|
||||
.then((r) => r.data ?? {})
|
||||
.catch(() => ({}))
|
||||
: Promise.resolve({})
|
||||
const kiloRequest =
|
||||
typeof client.kilo?.authStatus === "function"
|
||||
? client.kilo
|
||||
.authStatus({ directory: dir }, { throwOnError: true })
|
||||
.then((r) => (r.data?.authenticated ? (r.data.type ?? null) : null))
|
||||
.catch(() => null)
|
||||
: Promise.resolve(null)
|
||||
|
||||
const [{ data: response }, authMethods] = await Promise.all([
|
||||
const [{ data: response }, authMethods, kiloAuth] = await Promise.all([
|
||||
client.provider.list({ directory: dir }, { throwOnError: true }),
|
||||
authRequest,
|
||||
kiloRequest,
|
||||
])
|
||||
const authStates: Record<string, AuthState> = {}
|
||||
const storedKeys: Record<string, StoredProviderKey> = {}
|
||||
@@ -83,6 +91,8 @@ export async function fetchProviderData(client: KiloClient, dir: string) {
|
||||
delete next.key
|
||||
return next as (typeof response.all)[number]
|
||||
})
|
||||
delete authStates[KILO_PROVIDER_ID]
|
||||
if (kiloAuth) authStates[KILO_PROVIDER_ID] = kiloAuth
|
||||
return { response: { ...response, all }, authMethods, authStates, storedKeys }
|
||||
}
|
||||
|
||||
|
||||
@@ -6,6 +6,11 @@ export interface SpeechToTextModelDef {
|
||||
}
|
||||
|
||||
const models: SpeechToTextModelDef[] = [
|
||||
{
|
||||
id: "openai/whisper-large-v3-turbo",
|
||||
label: "Whisper Large V3 Turbo",
|
||||
provider: "OpenAI-compatible",
|
||||
},
|
||||
{
|
||||
id: "openai/gpt-4o-mini-transcribe",
|
||||
label: "GPT-4o Mini Transcribe",
|
||||
@@ -23,11 +28,6 @@ const models: SpeechToTextModelDef[] = [
|
||||
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",
|
||||
|
||||
@@ -133,6 +133,13 @@ test("settings and mode editing expose distinct model field purposes", async ({
|
||||
/Override the default model for specific modes/,
|
||||
)
|
||||
|
||||
await load(page, "settings--models-speech-to-text")
|
||||
const speech = page.getByRole("button", { name: "Speech to Text Model: Chirp 3" })
|
||||
await expect(speech).toBeEnabled()
|
||||
await speech.click()
|
||||
await page.getByRole("option", { name: "GPT-4o Mini Transcribe (OpenAI)" }).click()
|
||||
await expect(page.getByRole("button", { name: "Speech to Text Model: GPT-4o Mini Transcribe" })).toBeVisible()
|
||||
|
||||
await load(page, "settings--mode-edit-export")
|
||||
await expect(page.getByRole("button", { name: /Model Override:/ })).toHaveAccessibleDescription(
|
||||
"Override the default model for this agent",
|
||||
|
||||
@@ -444,6 +444,50 @@ describe("fetchProviderData", () => {
|
||||
expect("key" in item).toBe(false)
|
||||
})
|
||||
|
||||
it("uses local Kilo auth status instead of profile availability", async () => {
|
||||
const client = {
|
||||
provider: {
|
||||
list: async () => ({
|
||||
data: {
|
||||
all: [{ id: "kilo", name: "Kilo Gateway", source: "custom", env: [], models: {} }],
|
||||
connected: ["kilo"],
|
||||
default: { kilo: "kilo-auto/frontier" },
|
||||
},
|
||||
}),
|
||||
auth: async () => ({ data: {} }),
|
||||
},
|
||||
kilo: {
|
||||
authStatus: async () => ({ data: { authenticated: true, type: "oauth" } }),
|
||||
},
|
||||
} as unknown as Parameters<typeof fetchProviderData>[0]
|
||||
|
||||
const result = await fetchProviderData(client, "/tmp")
|
||||
|
||||
expect(result.authStates).toEqual({ kilo: "oauth" })
|
||||
})
|
||||
|
||||
it("does not infer Kilo speech access without stored Gateway auth", async () => {
|
||||
const client = {
|
||||
provider: {
|
||||
list: async () => ({
|
||||
data: {
|
||||
all: [{ id: "kilo", name: "Kilo Gateway", source: "config", key: "configured", env: [], models: {} }],
|
||||
connected: ["kilo"],
|
||||
default: { kilo: "kilo-auto/frontier" },
|
||||
},
|
||||
}),
|
||||
auth: async () => ({ data: {} }),
|
||||
},
|
||||
kilo: {
|
||||
authStatus: async () => ({ data: { authenticated: false } }),
|
||||
},
|
||||
} as unknown as Parameters<typeof fetchProviderData>[0]
|
||||
|
||||
const result = await fetchProviderData(client, "/tmp")
|
||||
|
||||
expect(result.authStates).toEqual({})
|
||||
})
|
||||
|
||||
it("retains stripped keys for providers with a configured baseURL", async () => {
|
||||
const client = {
|
||||
provider: {
|
||||
|
||||
@@ -6,17 +6,20 @@ import {
|
||||
import { DEFAULT_SPEECH_TO_TEXT_MODEL } from "../../src/speech-to-text/models"
|
||||
|
||||
describe("speech-to-text availability", () => {
|
||||
const providers = ["kilo"]
|
||||
const profile = {}
|
||||
|
||||
it("shows speech input by default when Kilo access exists", () => {
|
||||
expect(canUseSpeechToText({}, providers, profile)).toBe(true)
|
||||
it("shows speech input for stored Kilo credentials", () => {
|
||||
expect(canUseSpeechToText({}, { kilo: "oauth" })).toBe(true)
|
||||
expect(canUseSpeechToText({}, { kilo: "api" })).toBe(true)
|
||||
})
|
||||
|
||||
it("hides speech input without a signed-in, enabled Kilo provider", () => {
|
||||
expect(canUseSpeechToText({}, [], profile)).toBe(false)
|
||||
expect(canUseSpeechToText({}, providers, null)).toBe(false)
|
||||
expect(canUseSpeechToText({ disabled_providers: ["kilo"] }, providers, profile)).toBe(false)
|
||||
it("hides speech input without usable Kilo credentials", () => {
|
||||
expect(canUseSpeechToText({}, {})).toBe(false)
|
||||
expect(canUseSpeechToText({}, { kilo: "wellknown" })).toBe(false)
|
||||
})
|
||||
|
||||
it("honors enabled and disabled provider configuration", () => {
|
||||
expect(canUseSpeechToText({ disabled_providers: ["kilo"] }, { kilo: "oauth" })).toBe(false)
|
||||
expect(canUseSpeechToText({ enabled_providers: ["openai"] }, { kilo: "oauth" })).toBe(false)
|
||||
expect(canUseSpeechToText({ enabled_providers: ["kilo"] }, { kilo: "oauth" })).toBe(true)
|
||||
})
|
||||
|
||||
it("normalizes configured and unknown transcription models", () => {
|
||||
|
||||
@@ -6,7 +6,8 @@ import {
|
||||
} from "../../src/speech-to-text/models"
|
||||
|
||||
describe("speech-to-text model catalog", () => {
|
||||
it("keeps the first speech model as the fallback default", () => {
|
||||
it("uses Whisper Large V3 Turbo as the fallback default", () => {
|
||||
expect(DEFAULT_SPEECH_TO_TEXT_MODEL.id).toBe("openai/whisper-large-v3-turbo")
|
||||
expect(DEFAULT_SPEECH_TO_TEXT_MODEL.id).toBe(SPEECH_TO_TEXT_MODELS[0]?.id)
|
||||
})
|
||||
|
||||
|
||||
@@ -2,8 +2,13 @@ import { describe, expect, it, mock } from "bun:test"
|
||||
import { createRoot } from "solid-js"
|
||||
import type { ExtensionMessage, WebviewMessage } from "../../webview-ui/src/types/messages"
|
||||
|
||||
type Toast = {
|
||||
actions?: Array<{ onClick: string | (() => void) }>
|
||||
}
|
||||
|
||||
const toasts: Toast[] = []
|
||||
mock.module("@kilocode/kilo-ui/toast", () => ({
|
||||
showToast: () => undefined,
|
||||
showToast: (toast: Toast) => toasts.push(toast),
|
||||
}))
|
||||
|
||||
const { useSpeechToText } = await import("../../webview-ui/src/components/speech-to-text/useSpeechToText")
|
||||
@@ -11,6 +16,8 @@ const { useSpeechToText } = await import("../../webview-ui/src/components/speech
|
||||
function setup() {
|
||||
const sent: WebviewMessage[] = []
|
||||
let handler: ((message: ExtensionMessage) => void) | undefined
|
||||
let logins = 0
|
||||
toasts.length = 0
|
||||
|
||||
const root = createRoot((dispose) => ({
|
||||
dispose,
|
||||
@@ -24,16 +31,37 @@ function setup() {
|
||||
}
|
||||
},
|
||||
},
|
||||
{ profileData: () => ({}), goToLogin: () => {} },
|
||||
{ goToLogin: () => logins++ },
|
||||
{ t: (key) => key },
|
||||
),
|
||||
}))
|
||||
|
||||
const fire = (message: ExtensionMessage) => handler?.(message)
|
||||
return { ...root, fire, sent }
|
||||
return { ...root, fire, sent, logins: () => logins }
|
||||
}
|
||||
|
||||
describe("useSpeechToText", () => {
|
||||
it("offers sign-in when stored credentials stop authenticating", () => {
|
||||
const ctx = setup()
|
||||
|
||||
ctx.speech.start({ model: "scribe", insert: () => {} })
|
||||
const start = ctx.sent[0]
|
||||
if (start?.type !== "speechToTextStart") throw new Error("speech start message missing")
|
||||
|
||||
ctx.fire({
|
||||
type: "speechToTextError",
|
||||
requestId: start.requestId,
|
||||
error: "Unauthorized",
|
||||
code: "not_authenticated",
|
||||
})
|
||||
const action = toasts[0]?.actions?.find((item) => typeof item.onClick === "function")
|
||||
if (typeof action?.onClick === "function") action.onClick()
|
||||
|
||||
expect(ctx.logins()).toBe(1)
|
||||
expect(ctx.speech.error()).toBe("speechToText.error.loginRequired")
|
||||
ctx.dispose()
|
||||
})
|
||||
|
||||
it("runs the stop completion after inserting a transcript", () => {
|
||||
const ctx = setup()
|
||||
const text: string[] = []
|
||||
|
||||
@@ -92,7 +92,7 @@ export const DiffPanel: Component<DiffPanelProps> = (props) => {
|
||||
const provider = useProvider()
|
||||
const { config } = useConfig()
|
||||
const speech = useSpeechToText(vscode, server, { t })
|
||||
const canUseSpeech = () => canUseSpeechToText(config(), provider.connected(), server.profileData())
|
||||
const canUseSpeech = () => canUseSpeechToText(config(), provider.authStates())
|
||||
const speechModel = () => selectedSpeechToTextModel(config())
|
||||
const isMac = typeof navigator !== "undefined" && /Mac|iPhone|iPad/.test(navigator.userAgent)
|
||||
const sendAllKeybind = () =>
|
||||
|
||||
@@ -103,7 +103,7 @@ export const NewWorktreeDialog: Component<{ onClose: () => void; defaultBaseBran
|
||||
const [highlightedIndex, setHighlightedIndex] = createSignal(0)
|
||||
const [variant, setVariant] = createSignal<string | undefined>(session.currentVariant())
|
||||
const speech = useSpeechToText(vscode, server, { t })
|
||||
const canUseSpeech = () => canUseSpeechToText(config(), provider.connected(), server.profileData())
|
||||
const canUseSpeech = () => canUseSpeechToText(config(), provider.authStates())
|
||||
const speechModel = () => selectedSpeechToTextModel(config())
|
||||
|
||||
// Variant list for the currently selected model
|
||||
|
||||
@@ -96,7 +96,7 @@ export const FullScreenDiffView: Component<FullScreenDiffViewProps> = (props) =>
|
||||
const provider = useProvider()
|
||||
const { config } = useConfig()
|
||||
const speech = useSpeechToText(vscode, server, { t })
|
||||
const canUseSpeech = () => canUseSpeechToText(config(), provider.connected(), server.profileData())
|
||||
const canUseSpeech = () => canUseSpeechToText(config(), provider.authStates())
|
||||
const speechModel = () => selectedSpeechToTextModel(config())
|
||||
const isMac = typeof navigator !== "undefined" && /Mac|iPhone|iPad/.test(navigator.userAgent)
|
||||
const sendAllKeybind = () =>
|
||||
|
||||
@@ -295,7 +295,7 @@ export const PromptInput: Component<PromptInputProps> = (props) => {
|
||||
const isBusy = () =>
|
||||
isPromptBusy(session.status(), !!props.suggesting?.(), !!props.questioning?.(), session.submitting())
|
||||
const isDisabled = () => !server.isConnected()
|
||||
const canUseSpeech = () => canUseSpeechToText(config(), provider.connected(), server.profileData())
|
||||
const canUseSpeech = () => canUseSpeechToText(config(), provider.authStates())
|
||||
const speechModel = () => selectedSpeechToTextModel(config())
|
||||
const hasInput = () => text().trim().length > 0 || imageAttach.images().length > 0 || reviewComments().length > 0
|
||||
const canSend = () =>
|
||||
|
||||
@@ -1,19 +1,13 @@
|
||||
import { Component, For, Show, createMemo, createSignal, onCleanup, onMount } from "solid-js"
|
||||
import { Switch } from "@kilocode/kilo-ui/switch"
|
||||
import { Tooltip } from "@kilocode/kilo-ui/tooltip"
|
||||
import { Select } from "@kilocode/kilo-ui/select"
|
||||
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
|
||||
@@ -29,8 +23,6 @@ const SHARE_OPTIONS: ShareOption[] = [
|
||||
const ExperimentalTab: Component = () => {
|
||||
const { config, updateConfig } = useConfig()
|
||||
const language = useLanguage()
|
||||
const provider = useProvider()
|
||||
const server = useServer()
|
||||
const vscode = useVSCode()
|
||||
const [active, setActive] = createSignal(false)
|
||||
|
||||
@@ -47,8 +39,6 @@ const ExperimentalTab: Component = () => {
|
||||
})
|
||||
|
||||
const experimental = createMemo(() => config().experimental ?? {})
|
||||
const kiloReady = createMemo(() => hasSpeechToTextAccess(config(), provider.connected(), server.profileData()))
|
||||
const speechModel = createMemo(() => selectedSpeechToTextModel(config()))
|
||||
|
||||
const updateExperimental = (key: string, value: unknown) => {
|
||||
updateConfig({
|
||||
@@ -164,36 +154,6 @@ const ExperimentalTab: Component = () => {
|
||||
</Switch>
|
||||
</SettingsRow>
|
||||
|
||||
<SettingsRow
|
||||
title={language.t("settings.experimental.speechToTextModel.title")}
|
||||
description={
|
||||
kiloReady()
|
||||
? language.t("settings.experimental.speechToTextModel.description")
|
||||
: language.t("settings.experimental.speechToText.disabledDescription")
|
||||
}
|
||||
>
|
||||
<Tooltip
|
||||
value={language.t("settings.experimental.speechToText.disabledDescription")}
|
||||
placement="top"
|
||||
inactive={kiloReady()}
|
||||
>
|
||||
<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) =>
|
||||
updateExperimental("speech_to_text_model", item?.value ?? DEFAULT_SPEECH_TO_TEXT_MODEL.id)
|
||||
}
|
||||
variant="secondary"
|
||||
size="small"
|
||||
triggerVariant="settings"
|
||||
disabled={!kiloReady()}
|
||||
placeholder={DEFAULT_SPEECH_TO_TEXT_MODEL.label}
|
||||
/>
|
||||
</Tooltip>
|
||||
</SettingsRow>
|
||||
|
||||
<SettingsRow
|
||||
title={language.t("settings.experimental.continueOnDeny.title")}
|
||||
description={language.t("settings.experimental.continueOnDeny.description")}
|
||||
|
||||
@@ -1,6 +1,8 @@
|
||||
import { Component, For, Show, createMemo } from "solid-js"
|
||||
import { Card } from "@kilocode/kilo-ui/card"
|
||||
import { Select } from "@kilocode/kilo-ui/select"
|
||||
import { Switch } from "@kilocode/kilo-ui/switch"
|
||||
import { Tooltip } from "@kilocode/kilo-ui/tooltip"
|
||||
import { useConfig } from "../../context/config"
|
||||
import { useLanguage } from "../../context/language"
|
||||
import { useProvider } from "../../context/provider"
|
||||
@@ -9,6 +11,9 @@ import { parseModelString } from "../../../../src/shared/provider-model"
|
||||
import { ModelSelectorBase } from "../shared/ModelSelector"
|
||||
import { ThinkingSelectorBase } from "../shared/ThinkingSelector"
|
||||
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"
|
||||
import { AUTOCOMPLETE_SELECTOR_MODELS, getAutocompleteSelection } from "./autocomplete-model-selector"
|
||||
|
||||
const ModelsTab: Component = () => {
|
||||
@@ -37,6 +42,11 @@ const ModelsTab: Component = () => {
|
||||
}
|
||||
|
||||
const subagentModel = createMemo(() => parseModelString(config().subagent_model ?? undefined))
|
||||
const speechModel = createMemo(() => selectedSpeechToTextModel(config()))
|
||||
const speechOption = createMemo(
|
||||
() => SPEECH_TO_TEXT_MODEL_OPTIONS.find((item) => item.value === speechModel()) ?? SPEECH_TO_TEXT_MODEL_OPTIONS[0],
|
||||
)
|
||||
const kiloReady = createMemo(() => hasSpeechToTextAccess(config(), provider.authStates()))
|
||||
const variantKey = createMemo(() => config().subagent_model ?? undefined)
|
||||
const subagentVariants = createMemo(() => Object.keys(provider.findModel(subagentModel())?.variants ?? {}))
|
||||
const subagentVariant = createMemo(() => {
|
||||
@@ -168,6 +178,43 @@ const ModelsTab: Component = () => {
|
||||
description={language.t("settings.autocomplete.model.description")}
|
||||
/>
|
||||
</SettingsRow>
|
||||
<SettingsRow
|
||||
title={language.t("settings.models.speechToTextModel.title")}
|
||||
description={
|
||||
kiloReady()
|
||||
? language.t("settings.models.speechToTextModel.description")
|
||||
: language.t("settings.models.speechToText.disabledDescription")
|
||||
}
|
||||
>
|
||||
<Tooltip
|
||||
value={language.t("settings.models.speechToText.disabledDescription")}
|
||||
placement="top"
|
||||
inactive={kiloReady()}
|
||||
>
|
||||
<Select
|
||||
options={SPEECH_TO_TEXT_MODEL_OPTIONS}
|
||||
current={speechOption()}
|
||||
value={(item) => item.value}
|
||||
label={(item) => `${item.label} (${item.provider})`}
|
||||
onSelect={(item) =>
|
||||
updateConfig({
|
||||
experimental: {
|
||||
...config().experimental,
|
||||
speech_to_text_model: item?.value ?? DEFAULT_SPEECH_TO_TEXT_MODEL.id,
|
||||
},
|
||||
})
|
||||
}
|
||||
variant="secondary"
|
||||
size="small"
|
||||
triggerVariant="settings"
|
||||
triggerProps={{
|
||||
"aria-label": `${language.t("settings.models.speechToTextModel.title")}: ${speechOption()?.label}`,
|
||||
}}
|
||||
disabled={!kiloReady()}
|
||||
placeholder={DEFAULT_SPEECH_TO_TEXT_MODEL.label}
|
||||
/>
|
||||
</Tooltip>
|
||||
</SettingsRow>
|
||||
<SettingsRow
|
||||
title={language.t("settings.models.hidePromptTraining.title")}
|
||||
description={language.t("settings.models.hidePromptTraining.description")}
|
||||
|
||||
@@ -35,7 +35,7 @@ const ProvidersTab: Component = () => {
|
||||
|
||||
onCleanup(action.dispose)
|
||||
|
||||
const kiloLoggedIn = createMemo(() => !!server.profileData())
|
||||
const kiloLoggedIn = createMemo(() => !!provider.authStates()[KILO_PROVIDER_ID])
|
||||
|
||||
const connectedProviders = createMemo(() => {
|
||||
const ids = visibleConnectedIds(provider.connected(), provider.authStates())
|
||||
|
||||
@@ -2,18 +2,23 @@ import { KILO_PROVIDER_ID } from "../../../../src/shared/provider-model"
|
||||
import { getSpeechToTextModel } from "../../../../src/speech-to-text/models"
|
||||
|
||||
type Cfg = {
|
||||
enabled_providers?: string[]
|
||||
disabled_providers?: string[]
|
||||
experimental?: {
|
||||
speech_to_text_model?: 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
|
||||
type AuthState = "api" | "oauth" | "wellknown"
|
||||
|
||||
export function hasSpeechToTextAccess(cfg: Cfg, auth: Readonly<Record<string, AuthState>>): boolean {
|
||||
const enabled = !cfg.enabled_providers || cfg.enabled_providers.includes(KILO_PROVIDER_ID)
|
||||
const type = auth[KILO_PROVIDER_ID]
|
||||
return enabled && !cfg.disabled_providers?.includes(KILO_PROVIDER_ID) && (type === "api" || type === "oauth")
|
||||
}
|
||||
|
||||
export function canUseSpeechToText(cfg: Cfg, providers: readonly string[], profile: unknown | null): boolean {
|
||||
return hasSpeechToTextAccess(cfg, providers, profile)
|
||||
export function canUseSpeechToText(cfg: Cfg, auth: Readonly<Record<string, AuthState>>): boolean {
|
||||
return hasSpeechToTextAccess(cfg, auth)
|
||||
}
|
||||
|
||||
export function selectedSpeechToTextModel(cfg: Cfg): string {
|
||||
|
||||
@@ -9,7 +9,6 @@ type VSCode = {
|
||||
}
|
||||
|
||||
type Server = {
|
||||
profileData: Accessor<unknown | null>
|
||||
goToLogin: () => void
|
||||
}
|
||||
|
||||
@@ -70,6 +69,10 @@ export function useSpeechToText(vscode: VSCode, server: Server, lang: Lang): Spe
|
||||
}
|
||||
|
||||
if (msg.type === "speechToTextError") {
|
||||
if (msg.code === "not_authenticated") {
|
||||
login()
|
||||
return
|
||||
}
|
||||
fail(msg.error)
|
||||
return
|
||||
}
|
||||
@@ -98,19 +101,6 @@ export function useSpeechToText(vscode: VSCode, server: Server, lang: Lang): Spe
|
||||
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")
|
||||
@@ -144,6 +134,19 @@ export function useSpeechToText(vscode: VSCode, server: Server, lang: Lang): Spe
|
||||
setError(undefined)
|
||||
}
|
||||
|
||||
function login() {
|
||||
const message = lang.t("speechToText.error.loginRequired")
|
||||
showToast({
|
||||
variant: "error",
|
||||
title: message,
|
||||
actions: [
|
||||
{ label: lang.t("common.signIn"), onClick: server.goToLogin },
|
||||
{ label: lang.t("common.dismiss"), onClick: "dismiss" },
|
||||
],
|
||||
})
|
||||
fail(message, false)
|
||||
}
|
||||
|
||||
function fail(message: string, toast = true) {
|
||||
cleanup()
|
||||
setState("error")
|
||||
|
||||
+3
-3
@@ -1298,10 +1298,10 @@ export const dict = {
|
||||
"settings.experimental.speechToText.title": "تحويل الصوت إلى نص",
|
||||
"settings.experimental.speechToText.description":
|
||||
"تمكين الإدخال الصوتي في حقول المطالبة باستخدام حساب Kilo الخاص بك من خلال Kilo Gateway.",
|
||||
"settings.experimental.speechToText.disabledDescription":
|
||||
"settings.models.speechToText.disabledDescription":
|
||||
"قم بتمكين وتسجيل الدخول إلى مزود Kilo لاستخدام Speech to Text. ميزة Speech to Text مدعومة حاليًا فقط مع Kilo Gateway.",
|
||||
"settings.experimental.speechToTextModel.title": "نموذج تحويل الصوت إلى نص",
|
||||
"settings.experimental.speechToTextModel.description": "اختر نموذج نسخ Kilo Gateway للإدخال الصوتي.",
|
||||
"settings.models.speechToTextModel.title": "نموذج تحويل الصوت إلى نص",
|
||||
"settings.models.speechToTextModel.description": "اختر نموذج نسخ Kilo Gateway للإدخال الصوتي.",
|
||||
"settings.experimental.continueOnDeny.title": "المتابعة عند الرفض",
|
||||
"settings.experimental.continueOnDeny.description": "متابعة حلقة الوكيل عند رفض الإذن",
|
||||
"settings.experimental.mcpTimeout.title": "مهلة MCP (مللي ثانية)",
|
||||
|
||||
+3
-3
@@ -1329,10 +1329,10 @@ export const dict = {
|
||||
"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":
|
||||
"settings.models.speechToText.disabledDescription":
|
||||
"Ative e faça login no provedor Kilo para usar o Speech to Text. Atualmente, o Speech to Text é compatível apenas com o Kilo Gateway.",
|
||||
"settings.experimental.speechToTextModel.title": "Modelo de fala para texto",
|
||||
"settings.experimental.speechToTextModel.description":
|
||||
"settings.models.speechToTextModel.title": "Modelo de fala para texto",
|
||||
"settings.models.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",
|
||||
|
||||
+3
-4
@@ -1328,11 +1328,10 @@ export const dict = {
|
||||
"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":
|
||||
"settings.models.speechToText.disabledDescription":
|
||||
"Omogućite i prijavite se na Kilo provajder da biste koristili Speech to Text. Speech to Text je trenutno podržan samo uz Kilo Gateway.",
|
||||
"settings.experimental.speechToTextModel.title": "Model govora u tekst",
|
||||
"settings.experimental.speechToTextModel.description":
|
||||
"Odaberite Kilo Gateway model za transkripciju za glasovni unos.",
|
||||
"settings.models.speechToTextModel.title": "Model govora u tekst",
|
||||
"settings.models.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)",
|
||||
|
||||
+3
-3
@@ -1322,10 +1322,10 @@ export const dict = {
|
||||
"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":
|
||||
"settings.models.speechToText.disabledDescription":
|
||||
"Aktivér og log ind på Kilo-udbyderen for at bruge Speech to Text. Speech to Text understøttes i øjeblikket kun med Kilo Gateway.",
|
||||
"settings.experimental.speechToTextModel.title": "Model til tale til tekst",
|
||||
"settings.experimental.speechToTextModel.description": "Vælg Kilo Gateway-transskriptionsmodellen til stemmeinput.",
|
||||
"settings.models.speechToTextModel.title": "Model til tale til tekst",
|
||||
"settings.models.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)",
|
||||
|
||||
+3
-3
@@ -1345,10 +1345,10 @@ export const dict = {
|
||||
"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":
|
||||
"settings.models.speechToText.disabledDescription":
|
||||
"Aktivieren Sie den Kilo-Anbieter und melden Sie sich an, um Speech to Text zu verwenden. Speech to Text wird derzeit nur mit Kilo Gateway unterstützt.",
|
||||
"settings.experimental.speechToTextModel.title": "Sprache-zu-Text-Modell",
|
||||
"settings.experimental.speechToTextModel.description":
|
||||
"settings.models.speechToTextModel.title": "Sprache-zu-Text-Modell",
|
||||
"settings.models.speechToTextModel.description":
|
||||
"Wählen Sie das Kilo Gateway-Transkriptionsmodell für die Spracheingabe.",
|
||||
"settings.experimental.continueOnDeny.title": "Bei Ablehnung fortfahren",
|
||||
"settings.experimental.continueOnDeny.description":
|
||||
|
||||
@@ -1305,10 +1305,10 @@ export const dict = {
|
||||
"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":
|
||||
"settings.models.speechToText.disabledDescription":
|
||||
"Enable and sign in to the Kilo provider to use Speech to Text. Speech to Text is currently only supported through Kilo Gateway.",
|
||||
"settings.experimental.speechToTextModel.title": "Speech to Text Model",
|
||||
"settings.experimental.speechToTextModel.description": "Choose the Kilo Gateway transcription model for voice input.",
|
||||
"settings.models.speechToTextModel.title": "Speech to Text Model",
|
||||
"settings.models.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)",
|
||||
|
||||
+3
-3
@@ -1337,10 +1337,10 @@ export const dict = {
|
||||
"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":
|
||||
"settings.models.speechToText.disabledDescription":
|
||||
"Habilita e inicia sesión en el proveedor Kilo para usar Speech to Text. Actualmente, Speech to Text solo es compatible con Kilo Gateway.",
|
||||
"settings.experimental.speechToTextModel.title": "Modelo de voz a texto",
|
||||
"settings.experimental.speechToTextModel.description":
|
||||
"settings.models.speechToTextModel.title": "Modelo de voz a texto",
|
||||
"settings.models.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",
|
||||
|
||||
+3
-3
@@ -1352,10 +1352,10 @@ export const dict = {
|
||||
"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":
|
||||
"settings.models.speechToText.disabledDescription":
|
||||
"Activez et connectez-vous au fournisseur Kilo pour utiliser Speech to Text. Speech to Text n'est actuellement pris en charge qu'avec Kilo Gateway.",
|
||||
"settings.experimental.speechToTextModel.title": "Modèle de transcription vocale",
|
||||
"settings.experimental.speechToTextModel.description":
|
||||
"settings.models.speechToTextModel.title": "Modèle de transcription vocale",
|
||||
"settings.models.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":
|
||||
|
||||
+3
-4
@@ -1535,11 +1535,10 @@ export const dict = {
|
||||
"settings.experimental.speechToText.title": "Da voce a testo",
|
||||
"settings.experimental.speechToText.description":
|
||||
"Abilita input vocale nei campi prompt usando il tuo account Kilo tramite Kilo Gateway.",
|
||||
"settings.experimental.speechToText.disabledDescription":
|
||||
"settings.models.speechToText.disabledDescription":
|
||||
"Abilita e accedi al provider Kilo per usare Da voce a testo. Da voce a testo è attualmente supportato solo tramite Kilo Gateway.",
|
||||
"settings.experimental.speechToTextModel.title": "Modello Da voce a testo",
|
||||
"settings.experimental.speechToTextModel.description":
|
||||
"Scegli il modello di trascrizione Kilo Gateway per l'input vocale.",
|
||||
"settings.models.speechToTextModel.title": "Modello Da voce a testo",
|
||||
"settings.models.speechToTextModel.description": "Scegli il modello di trascrizione Kilo Gateway per l'input vocale.",
|
||||
|
||||
// Compaction limit
|
||||
"settings.context.compactionLimit.title": "Limite compattazione automatica",
|
||||
|
||||
+3
-3
@@ -1316,10 +1316,10 @@ export const dict = {
|
||||
"settings.experimental.speechToText.title": "音声認識",
|
||||
"settings.experimental.speechToText.description":
|
||||
"Kilo Gateway経由でKiloアカウントを使用して、プロンプトフィールドでの音声入力を有効にします。",
|
||||
"settings.experimental.speechToText.disabledDescription":
|
||||
"settings.models.speechToText.disabledDescription":
|
||||
"Speech to Text を使用するには、Kilo プロバイダーを有効にしてサインインしてください。現在、Speech to Text は Kilo Gateway でのみサポートされています。",
|
||||
"settings.experimental.speechToTextModel.title": "音声認識モデル",
|
||||
"settings.experimental.speechToTextModel.description": "音声入力に使用するKilo Gateway文字起こしモデルを選択します。",
|
||||
"settings.models.speechToTextModel.title": "音声認識モデル",
|
||||
"settings.models.speechToTextModel.description": "音声入力に使用するKilo Gateway文字起こしモデルを選択します。",
|
||||
"settings.experimental.continueOnDeny.title": "拒否時に続行",
|
||||
"settings.experimental.continueOnDeny.description": "権限が拒否された場合にエージェントループを続行",
|
||||
"settings.experimental.mcpTimeout.title": "MCPタイムアウト(ミリ秒)",
|
||||
|
||||
+3
-3
@@ -1310,10 +1310,10 @@ export const dict = {
|
||||
"settings.experimental.speechToText.title": "음성 텍스트 변환",
|
||||
"settings.experimental.speechToText.description":
|
||||
"Kilo Gateway를 통해 Kilo 계정을 사용하여 프롬프트 필드에서 음성 입력을 활성화합니다.",
|
||||
"settings.experimental.speechToText.disabledDescription":
|
||||
"settings.models.speechToText.disabledDescription":
|
||||
"Speech to Text를 사용하려면 Kilo 제공자를 활성화하고 로그인하세요. 현재 Speech to Text는 Kilo Gateway에서만 지원됩니다.",
|
||||
"settings.experimental.speechToTextModel.title": "음성 텍스트 변환 모델",
|
||||
"settings.experimental.speechToTextModel.description": "음성 입력에 사용할 Kilo Gateway 변환 모델을 선택하세요.",
|
||||
"settings.models.speechToTextModel.title": "음성 텍스트 변환 모델",
|
||||
"settings.models.speechToTextModel.description": "음성 입력에 사용할 Kilo Gateway 변환 모델을 선택하세요.",
|
||||
"settings.experimental.continueOnDeny.title": "거부 시 계속",
|
||||
"settings.experimental.continueOnDeny.description": "권한이 거부되면 에이전트 루프 계속",
|
||||
"settings.experimental.mcpTimeout.title": "MCP 타임아웃 (ms)",
|
||||
|
||||
+3
-3
@@ -1323,10 +1323,10 @@ export const dict = {
|
||||
"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":
|
||||
"settings.models.speechToText.disabledDescription":
|
||||
"Schakel de Kilo-provider in en meld u aan om Speech to Text te gebruiken. Speech to Text wordt momenteel alleen ondersteund met Kilo Gateway.",
|
||||
"settings.experimental.speechToTextModel.title": "Spraak-naar-tekst-model",
|
||||
"settings.experimental.speechToTextModel.description": "Kies het Kilo Gateway-transcriptiemodel voor spraakinvoer.",
|
||||
"settings.models.speechToTextModel.title": "Spraak-naar-tekst-model",
|
||||
"settings.models.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",
|
||||
|
||||
+3
-3
@@ -1287,10 +1287,10 @@ export const dict = {
|
||||
"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":
|
||||
"settings.models.speechToText.disabledDescription":
|
||||
"Aktiver og logg på Kilo-leverandøren for å bruke Speech to Text. Speech to Text støttes for øyeblikket bare med Kilo Gateway.",
|
||||
"settings.experimental.speechToTextModel.title": "Tale-til-tekst-modell",
|
||||
"settings.experimental.speechToTextModel.description": "Velg Kilo Gateway-transkripsjonsmodellen for taleinndata.",
|
||||
"settings.models.speechToTextModel.title": "Tale-til-tekst-modell",
|
||||
"settings.models.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)",
|
||||
|
||||
+3
-3
@@ -1287,10 +1287,10 @@ export const dict = {
|
||||
"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":
|
||||
"settings.models.speechToText.disabledDescription":
|
||||
"Włącz i zaloguj się do dostawcy Kilo, aby korzystać ze Speech to Text. Speech to Text jest obecnie obsługiwane tylko z Kilo Gateway.",
|
||||
"settings.experimental.speechToTextModel.title": "Model mowy na tekst",
|
||||
"settings.experimental.speechToTextModel.description":
|
||||
"settings.models.speechToTextModel.title": "Model mowy na tekst",
|
||||
"settings.models.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",
|
||||
|
||||
+3
-4
@@ -1327,11 +1327,10 @@ export const dict = {
|
||||
"settings.experimental.speechToText.title": "Речь в текст",
|
||||
"settings.experimental.speechToText.description":
|
||||
"Включите голосовой ввод в полях запросов, используя вашу учетную запись Kilo через Kilo Gateway.",
|
||||
"settings.experimental.speechToText.disabledDescription":
|
||||
"settings.models.speechToText.disabledDescription":
|
||||
"Включите провайдер Kilo и выполните вход, чтобы использовать Speech to Text. В настоящее время Speech to Text поддерживается только с Kilo Gateway.",
|
||||
"settings.experimental.speechToTextModel.title": "Модель речи в текст",
|
||||
"settings.experimental.speechToTextModel.description":
|
||||
"Выберите модель транскрипции Kilo Gateway для голосового ввода.",
|
||||
"settings.models.speechToTextModel.title": "Модель речи в текст",
|
||||
"settings.models.speechToTextModel.description": "Выберите модель транскрипции Kilo Gateway для голосового ввода.",
|
||||
"settings.experimental.continueOnDeny.title": "Продолжить при отказе",
|
||||
"settings.experimental.continueOnDeny.description": "Продолжить цикл агента при отказе в разрешении",
|
||||
"settings.experimental.mcpTimeout.title": "Таймаут MCP (мс)",
|
||||
|
||||
+3
-4
@@ -1306,11 +1306,10 @@ export const dict = {
|
||||
"settings.experimental.speechToText.title": "แปลงเสียงเป็นข้อความ",
|
||||
"settings.experimental.speechToText.description":
|
||||
"เปิดใช้งานการป้อนข้อมูลด้วยเสียงในช่องพรอมต์โดยใช้บัญชี Kilo ของคุณผ่าน Kilo Gateway",
|
||||
"settings.experimental.speechToText.disabledDescription":
|
||||
"settings.models.speechToText.disabledDescription":
|
||||
"เปิดใช้งานและลงชื่อเข้าใช้ผู้ให้บริการ Kilo เพื่อใช้ Speech to Text ปัจจุบัน Speech to Text รองรับเฉพาะกับ Kilo Gateway เท่านั้น",
|
||||
"settings.experimental.speechToTextModel.title": "โมเดลแปลงเสียงเป็นข้อความ",
|
||||
"settings.experimental.speechToTextModel.description":
|
||||
"เลือกโมเดลการถอดเสียง Kilo Gateway สำหรับการป้อนข้อมูลด้วยเสียง",
|
||||
"settings.models.speechToTextModel.title": "โมเดลแปลงเสียงเป็นข้อความ",
|
||||
"settings.models.speechToTextModel.description": "เลือกโมเดลการถอดเสียง Kilo Gateway สำหรับการป้อนข้อมูลด้วยเสียง",
|
||||
"settings.experimental.continueOnDeny.title": "ดำเนินต่อเมื่อถูกปฏิเสธ",
|
||||
"settings.experimental.continueOnDeny.description": "ดำเนินลูปเอเจนต์ต่อเมื่อสิทธิ์ถูกปฏิเสธ",
|
||||
"settings.experimental.mcpTimeout.title": "หมดเวลา MCP (มิลลิวินาที)",
|
||||
|
||||
+3
-3
@@ -1316,10 +1316,10 @@ export const dict = {
|
||||
"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":
|
||||
"settings.models.speechToText.disabledDescription":
|
||||
"Speech to Text kullanmak için Kilo sağlayıcısını etkinleştirin ve giriş yapın. Speech to Text şu anda yalnızca Kilo Gateway ile desteklenmektedir.",
|
||||
"settings.experimental.speechToTextModel.title": "Sesten metne modeli",
|
||||
"settings.experimental.speechToTextModel.description": "Sesli giriş için Kilo Gateway transkripsiyon modelini seçin.",
|
||||
"settings.models.speechToTextModel.title": "Sesten metne modeli",
|
||||
"settings.models.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)",
|
||||
|
||||
+3
-4
@@ -1315,11 +1315,10 @@ export const dict = {
|
||||
"settings.experimental.speechToText.title": "Мовлення в текст",
|
||||
"settings.experimental.speechToText.description":
|
||||
"Увімкніть голосове введення в полях запитів, використовуючи ваш обліковий запис Kilo через Kilo Gateway.",
|
||||
"settings.experimental.speechToText.disabledDescription":
|
||||
"settings.models.speechToText.disabledDescription":
|
||||
"Увімкніть провайдер Kilo та виконайте вхід, щоб використовувати Speech to Text. Наразі Speech to Text підтримується лише з Kilo Gateway.",
|
||||
"settings.experimental.speechToTextModel.title": "Модель мовлення в текст",
|
||||
"settings.experimental.speechToTextModel.description":
|
||||
"Виберіть модель транскрипції Kilo Gateway для голосового введення.",
|
||||
"settings.models.speechToTextModel.title": "Модель мовлення в текст",
|
||||
"settings.models.speechToTextModel.description": "Виберіть модель транскрипції Kilo Gateway для голосового введення.",
|
||||
"settings.experimental.continueOnDeny.title": "Продовжувати при відхиленні",
|
||||
"settings.experimental.continueOnDeny.description": "Продовжувати цикл агента, коли дозвіл відхилено",
|
||||
"settings.experimental.mcpTimeout.title": "Тайм-аут MCP (мс)",
|
||||
|
||||
+3
-3
@@ -1286,10 +1286,10 @@ export const dict = {
|
||||
"settings.experimental.codebaseSearch.description": "启用 AI 驱动的自然语言代码库搜索",
|
||||
"settings.experimental.speechToText.title": "语音转文本",
|
||||
"settings.experimental.speechToText.description": "通过 Kilo Gateway 使用您的 Kilo 帐户在提示词字段中启用语音输入。",
|
||||
"settings.experimental.speechToText.disabledDescription":
|
||||
"settings.models.speechToText.disabledDescription":
|
||||
"启用并登录 Kilo 提供商以使用 Speech to Text。Speech to Text 目前仅支持通过 Kilo Gateway 使用。",
|
||||
"settings.experimental.speechToTextModel.title": "语音转文本模型",
|
||||
"settings.experimental.speechToTextModel.description": "选择用于语音输入的 Kilo Gateway 转录模型。",
|
||||
"settings.models.speechToTextModel.title": "语音转文本模型",
|
||||
"settings.models.speechToTextModel.description": "选择用于语音输入的 Kilo Gateway 转录模型。",
|
||||
"settings.experimental.continueOnDeny.title": "拒绝后继续",
|
||||
"settings.experimental.continueOnDeny.description": "权限被拒绝时继续智能体循环",
|
||||
"settings.experimental.mcpTimeout.title": "MCP 超时(毫秒)",
|
||||
|
||||
+3
-3
@@ -1254,10 +1254,10 @@ export const dict = {
|
||||
"settings.experimental.codebaseSearch.description": "啟用 AI 驅動的自然語言程式碼庫搜尋",
|
||||
"settings.experimental.speechToText.title": "語音轉文字",
|
||||
"settings.experimental.speechToText.description": "透過 Kilo Gateway 使用您的 Kilo 帳戶在提示詞欄位中啟用語音輸入。",
|
||||
"settings.experimental.speechToText.disabledDescription":
|
||||
"settings.models.speechToText.disabledDescription":
|
||||
"啟用並登入 Kilo 供應商以使用 Speech to Text。Speech to Text 目前僅支援透過 Kilo Gateway 使用。",
|
||||
"settings.experimental.speechToTextModel.title": "語音轉文字模型",
|
||||
"settings.experimental.speechToTextModel.description": "選擇用於語音輸入的 Kilo Gateway 轉錄模型。",
|
||||
"settings.models.speechToTextModel.title": "語音轉文字模型",
|
||||
"settings.models.speechToTextModel.description": "選擇用於語音輸入的 Kilo Gateway 轉錄模型。",
|
||||
"settings.experimental.continueOnDeny.title": "拒絕後繼續",
|
||||
"settings.experimental.continueOnDeny.description": "權限被拒絕時繼續 Agent 迴圈",
|
||||
"settings.experimental.mcpTimeout.title": "MCP 逾時(毫秒)",
|
||||
|
||||
@@ -84,6 +84,17 @@ export const ModelsAccessibleLabels: Story = {
|
||||
),
|
||||
}
|
||||
|
||||
export const ModelsSpeechToText: Story = {
|
||||
name: "ModelsTab — speech-to-text model",
|
||||
render: () => (
|
||||
<StoryProviders kiloAuth config={{ experimental: { speech_to_text_model: "google/chirp-3" } } as any}>
|
||||
<div style={{ "max-height": "700px", overflow: "auto" }}>
|
||||
<ModelsTab />
|
||||
</div>
|
||||
</StoryProviders>
|
||||
),
|
||||
}
|
||||
|
||||
function OpenModelPicker(props: { children: any }) {
|
||||
let ref: HTMLDivElement | undefined
|
||||
onMount(() => {
|
||||
|
||||
@@ -33,6 +33,11 @@ export const ProfileWithBalance = Schema.Struct({
|
||||
currentOrgId: Schema.NullOr(Schema.String),
|
||||
})
|
||||
|
||||
export const AuthStatus = Schema.Struct({
|
||||
authenticated: Schema.Boolean,
|
||||
type: Schema.optional(Schema.Literals(["api", "oauth"])),
|
||||
})
|
||||
|
||||
export const NotificationAction = Schema.Struct({
|
||||
actionText: Schema.String,
|
||||
actionURL: Schema.String,
|
||||
@@ -241,6 +246,7 @@ export const CloudSessionData = Schema.Struct({
|
||||
export const KiloGatewayPaths = {
|
||||
modes: `${root}/modes`,
|
||||
profile: `${root}/profile`,
|
||||
authStatus: `${root}/auth-status`,
|
||||
fim: `${root}/fim`,
|
||||
edit: `${root}/edit`,
|
||||
audioTranscriptions: `${root}/audio/transcriptions`,
|
||||
@@ -268,6 +274,17 @@ export const KiloGatewayApi = HttpApi.make("kilo")
|
||||
description: "Fetch user profile and organizations from Kilo Gateway",
|
||||
}),
|
||||
),
|
||||
HttpApiEndpoint.get("authStatus", KiloGatewayPaths.authStatus, {
|
||||
query: WorkspaceRoutingQuery,
|
||||
success: described(AuthStatus, "Kilo authentication status"),
|
||||
error: HttpApiError.BadRequest,
|
||||
}).annotateMerge(
|
||||
OpenApi.annotations({
|
||||
identifier: "kilo.authStatus",
|
||||
summary: "Get Kilo authentication status",
|
||||
description: "Check whether a locally stored Kilo credential can authenticate Gateway requests",
|
||||
}),
|
||||
),
|
||||
HttpApiEndpoint.get("modes", KiloGatewayPaths.modes, {
|
||||
query: WorkspaceRoutingQuery,
|
||||
success: described(OrganizationModes, "Organization modes list"),
|
||||
|
||||
@@ -73,6 +73,13 @@ export const kiloGatewayHandlers = HttpApiBuilder.group(InstanceHttpApi, "kilo",
|
||||
return { profile, balance, currentOrgId }
|
||||
})
|
||||
|
||||
const authStatus = Effect.fn("KiloGatewayHttpApi.authStatus")(function* () {
|
||||
const info = yield* auth.get("kilo").pipe(Effect.mapError(() => new HttpApiError.BadRequest({})))
|
||||
const type = getToken(info) && (info?.type === "api" || info?.type === "oauth") ? info.type : undefined
|
||||
if (!type) return { authenticated: false }
|
||||
return { authenticated: true, type }
|
||||
})
|
||||
|
||||
const proxyAuth = Effect.fn("KiloGatewayHttpApi.proxyAuth")(function* () {
|
||||
const info = yield* auth.get("kilo").pipe(Effect.mapError(() => new HttpApiError.Unauthorized({})))
|
||||
return {
|
||||
@@ -504,6 +511,7 @@ export const kiloGatewayHandlers = HttpApiBuilder.group(InstanceHttpApi, "kilo",
|
||||
|
||||
return handlers
|
||||
.handle("profile", profile)
|
||||
.handle("authStatus", authStatus)
|
||||
.handle("modes", modes)
|
||||
.handle("fim", fim)
|
||||
.handle("edit", edit)
|
||||
|
||||
@@ -132,6 +132,11 @@ export const kiloScenarios: Scenario[] = [
|
||||
http.protected.get("/indexing/models", "indexing.models").json(200, object),
|
||||
http.protected.get("/indexing/warnings", "indexing.warnings").json(200, array),
|
||||
http.protected.get("/kilo/profile", "kilo.profile").probe({ path: "/path" }).status(401),
|
||||
http.protected.get("/kilo/auth-status", "kilo.authStatus").json(200, (body) => {
|
||||
object(body)
|
||||
check(body.authenticated === false, "Kilo auth status should report signed out")
|
||||
check(body.type === undefined, "Kilo auth status should not expose a credential type while signed out")
|
||||
}),
|
||||
http.protected.get("/kilo/modes", "kilo.modes").json(200, (body) => {
|
||||
object(body)
|
||||
array(body.modes)
|
||||
|
||||
@@ -17,6 +17,7 @@ type Schema = {
|
||||
items?: Schema
|
||||
properties?: Record<string, Schema>
|
||||
type?: string
|
||||
enum?: string[]
|
||||
minLength?: number
|
||||
maxLength?: number
|
||||
pattern?: string
|
||||
@@ -165,6 +166,12 @@ describe("Kilo PublicApi OpenAPI contract", () => {
|
||||
expect(profile?.balance).toEqual({ anyOf: [expect.objectContaining({ type: "object" }), { type: "null" }] })
|
||||
expect(profile?.currentOrgId).toEqual({ anyOf: [{ type: "string" }, { type: "null" }] })
|
||||
|
||||
const auth = response(KiloGatewayPaths.authStatus)?.properties
|
||||
expect(auth).toEqual({
|
||||
authenticated: { type: "boolean" },
|
||||
type: { type: "string", enum: ["api", "oauth"] },
|
||||
})
|
||||
|
||||
const sessions = response(KiloGatewayPaths.cloudSessions)?.properties
|
||||
expect(sessions?.cliSessions?.items?.properties?.title).toEqual({
|
||||
anyOf: [{ type: "string" }, { type: "null" }],
|
||||
|
||||
@@ -84,6 +84,17 @@ function post(path: string, body: Record<string, unknown>) {
|
||||
}
|
||||
|
||||
describe("Kilo gateway HttpApi statuses", () => {
|
||||
it.live("reports locally stored API authentication without a Gateway request", () =>
|
||||
Effect.gen(function* () {
|
||||
yield* stub(() => Promise.reject(new Error("unexpected Gateway request")))
|
||||
|
||||
const response = yield* HttpClient.get(KiloGatewayPaths.authStatus)
|
||||
|
||||
expect(response.status).toBe(200)
|
||||
expect(yield* response.json).toEqual({ authenticated: true, type: "api" })
|
||||
}),
|
||||
)
|
||||
|
||||
it.live("preserves cloud session list rate limits", () =>
|
||||
Effect.gen(function* () {
|
||||
yield* stub(() => new Response("rate limited", { status: 429 }))
|
||||
|
||||
@@ -91,6 +91,8 @@ import type {
|
||||
InstanceDisposeResponses,
|
||||
KiloAudioTranscriptionsErrors,
|
||||
KiloAudioTranscriptionsResponses,
|
||||
KiloAuthStatusErrors,
|
||||
KiloAuthStatusResponses,
|
||||
KiloClawChatCredentialsResponses,
|
||||
KiloClawStatusErrors,
|
||||
KiloClawStatusResponses,
|
||||
@@ -6523,6 +6525,36 @@ export class Kilo extends HeyApiClient {
|
||||
})
|
||||
}
|
||||
|
||||
/**
|
||||
* Get Kilo authentication status
|
||||
*
|
||||
* Check whether a locally stored Kilo credential can authenticate Gateway requests
|
||||
*/
|
||||
public authStatus<ThrowOnError extends boolean = false>(
|
||||
parameters?: {
|
||||
directory?: string
|
||||
workspace?: string
|
||||
},
|
||||
options?: Options<never, ThrowOnError>,
|
||||
) {
|
||||
const params = buildClientParams(
|
||||
[parameters],
|
||||
[
|
||||
{
|
||||
args: [
|
||||
{ in: "query", key: "directory" },
|
||||
{ in: "query", key: "workspace" },
|
||||
],
|
||||
},
|
||||
],
|
||||
)
|
||||
return (options?.client ?? this.client).get<KiloAuthStatusResponses, KiloAuthStatusErrors, ThrowOnError>({
|
||||
url: "/kilo/auth-status",
|
||||
...options,
|
||||
...params,
|
||||
})
|
||||
}
|
||||
|
||||
/**
|
||||
* Get organization custom modes
|
||||
*
|
||||
|
||||
@@ -8837,6 +8837,37 @@ export type KiloProfileResponses = {
|
||||
|
||||
export type KiloProfileResponse = KiloProfileResponses[keyof KiloProfileResponses]
|
||||
|
||||
export type KiloAuthStatusData = {
|
||||
body?: never
|
||||
path?: never
|
||||
query?: {
|
||||
directory?: string
|
||||
workspace?: string
|
||||
}
|
||||
url: "/kilo/auth-status"
|
||||
}
|
||||
|
||||
export type KiloAuthStatusErrors = {
|
||||
/**
|
||||
* Bad request
|
||||
*/
|
||||
400: BadRequestError
|
||||
}
|
||||
|
||||
export type KiloAuthStatusError = KiloAuthStatusErrors[keyof KiloAuthStatusErrors]
|
||||
|
||||
export type KiloAuthStatusResponses = {
|
||||
/**
|
||||
* Kilo authentication status
|
||||
*/
|
||||
200: {
|
||||
authenticated: boolean
|
||||
type?: "api" | "oauth"
|
||||
}
|
||||
}
|
||||
|
||||
export type KiloAuthStatusResponse = KiloAuthStatusResponses[keyof KiloAuthStatusResponses]
|
||||
|
||||
export type KiloModesData = {
|
||||
body?: never
|
||||
path?: never
|
||||
|
||||
@@ -11382,6 +11382,72 @@
|
||||
]
|
||||
}
|
||||
},
|
||||
"/kilo/auth-status": {
|
||||
"get": {
|
||||
"tags": ["kilo"],
|
||||
"operationId": "kilo.authStatus",
|
||||
"parameters": [
|
||||
{
|
||||
"name": "directory",
|
||||
"in": "query",
|
||||
"schema": {
|
||||
"type": "string"
|
||||
},
|
||||
"required": false
|
||||
},
|
||||
{
|
||||
"name": "workspace",
|
||||
"in": "query",
|
||||
"schema": {
|
||||
"type": "string"
|
||||
},
|
||||
"required": false
|
||||
}
|
||||
],
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": "Kilo authentication status",
|
||||
"content": {
|
||||
"application/json": {
|
||||
"schema": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"authenticated": {
|
||||
"type": "boolean"
|
||||
},
|
||||
"type": {
|
||||
"type": "string",
|
||||
"enum": ["api", "oauth"]
|
||||
}
|
||||
},
|
||||
"required": ["authenticated"],
|
||||
"additionalProperties": false,
|
||||
"description": "Kilo authentication status"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"400": {
|
||||
"description": "Bad request",
|
||||
"content": {
|
||||
"application/json": {
|
||||
"schema": {
|
||||
"$ref": "#/components/schemas/BadRequestError"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"description": "Check whether a locally stored Kilo credential can authenticate Gateway requests",
|
||||
"summary": "Get Kilo authentication status",
|
||||
"x-codeSamples": [
|
||||
{
|
||||
"lang": "js",
|
||||
"source": "import { createKiloClient } from \"@kilocode/sdk\n\nconst client = createKiloClient()\nawait client.kilo.authStatus({\n ...\n})"
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
"/kilo/modes": {
|
||||
"get": {
|
||||
"tags": ["kilo"],
|
||||
|
||||
Reference in New Issue
Block a user