mirror of
https://github.com/Kilo-Org/kilocode.git
synced 2026-09-24 16:02:55 +08:00
refactor(vscode): simplify speech input wiring
This commit is contained in:
@@ -339,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
|
||||
@@ -362,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",
|
||||
}
|
||||
|
||||
@@ -436,16 +433,16 @@ export function createKiloRoutes(deps: KiloRoutesDeps) {
|
||||
}),
|
||||
),
|
||||
async (c: any) => {
|
||||
const auth = await getProxyAuth()
|
||||
if (!auth.auth) return c.json({ error: "Not authenticated with Kilo Gateway" }, 401)
|
||||
const proxy = await getProxyAuth()
|
||||
if (!proxy.auth) return c.json({ error: "Not authenticated with Kilo Gateway" }, 401)
|
||||
|
||||
if (!auth.token) return c.json({ error: "No valid token found" }, 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 ${auth.token}`,
|
||||
...buildKiloHeaders(undefined, { kilocodeOrganizationId: auth.organizationId }),
|
||||
Authorization: `Bearer ${proxy.token}`,
|
||||
...buildKiloHeaders(undefined, { kilocodeOrganizationId: proxy.organizationId }),
|
||||
[HEADER_FEATURE]: "vscode-extension",
|
||||
}
|
||||
|
||||
|
||||
@@ -783,7 +783,6 @@
|
||||
},
|
||||
"kilo-code.new.speechToText.model": {
|
||||
"type": "string",
|
||||
"default": "openai/gpt-4o-mini-transcribe",
|
||||
"description": "Model to use for experimental speech-to-text voice input. Requires Kilo Gateway."
|
||||
},
|
||||
"kilo-code.new.claudeCodeCompat": {
|
||||
|
||||
@@ -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()
|
||||
})
|
||||
})
|
||||
@@ -19,6 +19,7 @@ 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,
|
||||
@@ -33,8 +34,6 @@ import { useSpeechToText } from "../src/components/speech-to-text/useSpeechToTex
|
||||
import { convertToMentionPath } from "../src/utils/path-mentions"
|
||||
import { insertSpacedText } from "../src/components/chat/prompt-input-utils"
|
||||
import { BranchSelect, BranchSelectPopover } from "../src/components/shared/BranchSelect"
|
||||
import { KILO_PROVIDER_ID } from "../../src/shared/provider-model"
|
||||
import { getSpeechToTextModel } from "../../src/speech-to-text/models"
|
||||
|
||||
type VersionCount = 1 | 2 | 3 | 4
|
||||
const VERSION_OPTIONS: VersionCount[] = [1, 2, 3, 4]
|
||||
@@ -99,12 +98,8 @@ 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 = () =>
|
||||
settings()["speechToText.enabled"] === true &&
|
||||
provider.connected().includes(KILO_PROVIDER_ID) &&
|
||||
!config().disabled_providers?.includes(KILO_PROVIDER_ID) &&
|
||||
!!server.profileData()
|
||||
const speechModel = () => getSpeechToTextModel(String(settings()["speechToText.model"] ?? "")).id
|
||||
const canUseSpeech = () => canUseSpeechToText(settings(), config(), provider.connected(), server.profileData())
|
||||
const speechModel = () => selectedSpeechToTextModel(settings())
|
||||
|
||||
// Variant list for the currently selected model
|
||||
const variants = createMemo(() => {
|
||||
|
||||
@@ -23,6 +23,7 @@ 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"
|
||||
@@ -40,8 +41,6 @@ import { fileName, dirName, buildHighlightSegments, atEnd, insertSpacedText, isP
|
||||
import type { ReviewComment, TextPart } from "../../types/messages"
|
||||
import { formatReviewCommentsMarkdown } from "../../utils/review-comment-markdown"
|
||||
import { pendingDraftKey, scopeDraftKey, sessionDraftKey } from "../../utils/prompt-drafts"
|
||||
import { KILO_PROVIDER_ID } from "../../../../src/shared/provider-model"
|
||||
import { getSpeechToTextModel } from "../../../../src/speech-to-text/models"
|
||||
|
||||
// Per-session input text storage (module-level so it survives remounts)
|
||||
const drafts = new Map<string, string>()
|
||||
@@ -332,12 +331,8 @@ export const PromptInput: Component<PromptInputProps> = (props) => {
|
||||
|
||||
const isBusy = () => isPromptBusy(session.status(), !!props.suggesting?.(), !!props.questioning?.())
|
||||
const isDisabled = () => !server.isConnected()
|
||||
const canUseSpeech = () =>
|
||||
settings()["speechToText.enabled"] === true &&
|
||||
provider.connected().includes(KILO_PROVIDER_ID) &&
|
||||
!config().disabled_providers?.includes(KILO_PROVIDER_ID) &&
|
||||
!!server.profileData()
|
||||
const speechModel = () => getSpeechToTextModel(String(settings()["speechToText.model"] ?? "")).id
|
||||
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() && !speech.active() && !terminal.pending() && !git.pending() && !props.blocked?.()
|
||||
|
||||
@@ -10,8 +10,8 @@ import { useServer } from "../../context/server"
|
||||
import { useVSCode } from "../../context/vscode"
|
||||
import type { ExtensionMessage } from "../../types/messages"
|
||||
import SettingsRow from "./SettingsRow"
|
||||
import { KILO_PROVIDER_ID } from "../../../../src/shared/provider-model"
|
||||
import { DEFAULT_SPEECH_TO_TEXT_MODEL, getSpeechToTextModel } from "../../../../src/speech-to-text/models"
|
||||
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 {
|
||||
@@ -46,13 +46,8 @@ const ExperimentalTab: Component = () => {
|
||||
})
|
||||
|
||||
const experimental = createMemo(() => config().experimental ?? {})
|
||||
const kiloReady = createMemo(
|
||||
() =>
|
||||
provider.connected().includes(KILO_PROVIDER_ID) &&
|
||||
!config().disabled_providers?.includes(KILO_PROVIDER_ID) &&
|
||||
!!server.profileData(),
|
||||
)
|
||||
const speechModel = createMemo(() => getSpeechToTextModel(String(settings()["speechToText.model"] ?? "")).id)
|
||||
const kiloReady = createMemo(() => hasSpeechToTextAccess(config(), provider.connected(), server.profileData()))
|
||||
const speechModel = createMemo(() => selectedSpeechToTextModel(settings()))
|
||||
|
||||
const updateExperimental = (key: string, value: unknown) => {
|
||||
updateConfig({
|
||||
|
||||
@@ -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
|
||||
}
|
||||
@@ -72,23 +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") {
|
||||
const patch = {
|
||||
mergeSettings({
|
||||
"speechToText.enabled": message.settings.enabled,
|
||||
"speechToText.model": message.settings.model,
|
||||
}
|
||||
setSavedSettings((prev) => ({ ...prev, ...patch }))
|
||||
setSettings((prev) => ({ ...prev, ...patch, ...settingsDraft() }))
|
||||
})
|
||||
return
|
||||
}
|
||||
if (message.type === "configLoaded") {
|
||||
@@ -151,17 +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" })
|
||||
vscode.postMessage({ type: "requestSpeechToTextSettings" })
|
||||
requestInitialData()
|
||||
|
||||
const fallback = setTimeout(() => {
|
||||
if (loading()) {
|
||||
vscode.postMessage({ type: "requestConfig" })
|
||||
vscode.postMessage({ type: "requestAutocompleteSettings" })
|
||||
vscode.postMessage({ type: "requestSpeechToTextSettings" })
|
||||
requestInitialData()
|
||||
}
|
||||
}, 3000)
|
||||
|
||||
@@ -170,9 +173,7 @@ export const ConfigProvider: ParentComponent = (props) => {
|
||||
unsubReady()
|
||||
clearTimeout(fallback)
|
||||
if (loading()) {
|
||||
vscode.postMessage({ type: "requestConfig" })
|
||||
vscode.postMessage({ type: "requestAutocompleteSettings" })
|
||||
vscode.postMessage({ type: "requestSpeechToTextSettings" })
|
||||
requestInitialData()
|
||||
}
|
||||
})
|
||||
|
||||
|
||||
Reference in New Issue
Block a user