Files
CodeNomad/packages/server/src/server/routes/settings.ts
Shantur Rathore 1233121a13 feat(speech): add prompt voice input (#249)
## Summary
- add server-backed speech capabilities and transcription endpoints plus
UI settings for speech configuration
- add push-to-talk prompt voice input with microphone controls,
transcription insertion, and browser capability gating
- keep prompt controls aligned by restoring right-side nav placement and
moving the mic beside the expand control
2026-03-25 14:08:11 +00:00

85 lines
3.1 KiB
TypeScript

import { FastifyInstance } from "fastify"
import { z } from "zod"
import { probeBinaryVersion } from "../../workspaces/runtime"
import type { SettingsService } from "../../settings/service"
import type { Logger } from "../../logger"
import { sanitizeConfigDoc, sanitizeConfigOwner } from "../../settings/public-config"
interface RouteDeps {
settings: SettingsService
logger: Logger
}
const ValidateBinarySchema = z.object({
path: z.string(),
})
function validateBinaryPath(binaryPath: string): { valid: boolean; version?: string; error?: string } {
const result = probeBinaryVersion(binaryPath)
return { valid: result.valid, version: result.version, error: result.error }
}
export function registerSettingsRoutes(app: FastifyInstance, deps: RouteDeps) {
// Full-document access
app.get("/api/storage/config", async () => sanitizeConfigDoc(deps.settings.getDoc("config")))
app.patch("/api/storage/config", async (request, reply) => {
try {
return sanitizeConfigDoc(deps.settings.mergePatchDoc("config", request.body ?? {}))
} catch (error) {
reply.code(400)
return { error: error instanceof Error ? error.message : "Invalid patch" }
}
})
app.get<{ Params: { owner: string } }>("/api/storage/config/:owner", async (request) => {
return sanitizeConfigOwner(request.params.owner, deps.settings.getOwner("config", request.params.owner))
})
app.patch<{ Params: { owner: string } }>("/api/storage/config/:owner", async (request, reply) => {
try {
return sanitizeConfigOwner(
request.params.owner,
deps.settings.mergePatchOwner("config", request.params.owner, request.body ?? {}),
)
} catch (error) {
reply.code(400)
return { error: error instanceof Error ? error.message : "Invalid patch" }
}
})
app.get("/api/storage/state", async () => deps.settings.getDoc("state"))
app.patch("/api/storage/state", async (request, reply) => {
try {
return deps.settings.mergePatchDoc("state", request.body ?? {})
} catch (error) {
reply.code(400)
return { error: error instanceof Error ? error.message : "Invalid patch" }
}
})
app.get<{ Params: { owner: string } }>("/api/storage/state/:owner", async (request) => {
return deps.settings.getOwner("state", request.params.owner)
})
app.patch<{ Params: { owner: string } }>("/api/storage/state/:owner", async (request, reply) => {
try {
return deps.settings.mergePatchOwner("state", request.params.owner, request.body ?? {})
} catch (error) {
reply.code(400)
return { error: error instanceof Error ? error.message : "Invalid patch" }
}
})
// Binary validation helper (used by UI when adding binaries)
app.post("/api/storage/binaries/validate", async (request, reply) => {
try {
const body = ValidateBinarySchema.parse(request.body ?? {})
return validateBinaryPath(body.path)
} catch (error) {
deps.logger.warn({ err: error }, "Failed to validate binary")
reply.code(400)
return { valid: false, error: error instanceof Error ? error.message : "Invalid request" }
}
})
}