feat(vscode): add KiloClaw chat panel to VS Code extension

Integrate KiloClaw as a standalone editor panel in the VS Code extension,
enabling real-time chat with the KiloClaw bot via Stream Chat. The panel
follows the established Agent Manager pattern with proper panel
serialization, webview lifecycle management, and a race condition guard
for deferred backend connections.
This commit is contained in:
Catriel Müller
2026-03-31 00:54:57 -03:00
parent 28aebeb157
commit fd0930a61a
39 changed files with 2661 additions and 4 deletions
+1
View File
@@ -321,6 +321,7 @@
"quick-lru": "^7.0.0",
"simple-git": "3.35.2",
"solid-js": "^1.9.11",
"stream-chat": "9.38.0",
"uri-js": "^4.4.1",
"web-tree-sitter": "^0.24.7",
"yaml": "2.8.3",
+6
View File
@@ -188,6 +188,9 @@ async function main() {
"dist/agent-manager.js",
)
// Build KiloClaw webview (SolidJS, standalone chat panel)
const kiloClawCtx = await createBrowserWebviewContext("webview-ui/kiloclaw/index.tsx", "dist/kiloclaw.js")
// Build Diff Viewer webview (SolidJS, reuses Agent Manager diff components)
const diffViewerCtx = await createBrowserWebviewContext("webview-ui/diff-viewer/index.tsx", "dist/diff-viewer.js")
@@ -204,12 +207,14 @@ async function main() {
agentManagerCtx.watch(),
diffViewerCtx.watch(),
diffVirtualCtx.watch(),
kiloClawCtx.watch(),
])
} else {
await Promise.all([
extensionCtx.rebuild(),
webviewCtx.rebuild(),
agentManagerCtx.rebuild(),
kiloClawCtx.rebuild(),
diffViewerCtx.rebuild(),
diffVirtualCtx.rebuild(),
])
@@ -217,6 +222,7 @@ async function main() {
extensionCtx.dispose(),
webviewCtx.dispose(),
agentManagerCtx.dispose(),
kiloClawCtx.dispose(),
diffViewerCtx.dispose(),
diffVirtualCtx.dispose(),
])
+1
View File
@@ -5,6 +5,7 @@
"webview-ui/agent-manager/index.tsx",
"webview-ui/diff-viewer/index.tsx",
"webview-ui/diff-virtual/index.tsx",
"webview-ui/kiloclaw/index.tsx",
"webview-ui/src/index.tsx",
"src/**/__tests__/**/*.{ts,spec.ts}",
"src/**/*.test.ts",
+14 -3
View File
@@ -91,6 +91,11 @@
"title": "Agent Manager",
"icon": "$(circuit-board)"
},
{
"command": "kilo-code.new.kiloClawOpen",
"title": "KiloClaw",
"icon": "$(comment-discussion)"
},
{
"command": "kilo-code.new.marketplaceButtonClicked",
"title": "Marketplace",
@@ -356,19 +361,24 @@
"when": "view == kilo-code.SidebarProvider"
},
{
"command": "kilo-code.new.marketplaceButtonClicked",
"command": "kilo-code.new.kiloClawOpen",
"group": "navigation@3",
"when": "view == kilo-code.SidebarProvider"
},
{
"command": "kilo-code.new.profileButtonClicked",
"command": "kilo-code.new.marketplaceButtonClicked",
"group": "navigation@4",
"when": "view == kilo-code.SidebarProvider"
},
{
"command": "kilo-code.new.settingsButtonClicked",
"command": "kilo-code.new.profileButtonClicked",
"group": "navigation@5",
"when": "view == kilo-code.SidebarProvider"
},
{
"command": "kilo-code.new.settingsButtonClicked",
"group": "navigation@6",
"when": "view == kilo-code.SidebarProvider"
}
],
"scm/title": [
@@ -874,6 +884,7 @@
"quick-lru": "^7.0.0",
"simple-git": "3.35.2",
"solid-js": "^1.9.11",
"stream-chat": "9.38.0",
"uri-js": "^4.4.1",
"web-tree-sitter": "^0.24.7",
"yaml": "2.8.3",
+18
View File
@@ -2,6 +2,7 @@ import * as vscode from "vscode"
import { KiloProvider } from "./KiloProvider"
import { AgentManagerProvider } from "./agent-manager/AgentManagerProvider"
import { VscodeHost } from "./agent-manager/vscode-host"
import { KiloClawProvider } from "./kiloclaw/KiloClawProvider"
import { DiffViewerProvider } from "./DiffViewerProvider"
import { DiffVirtualProvider } from "./DiffVirtualProvider"
import { SettingsEditorProvider } from "./SettingsEditorProvider"
@@ -94,6 +95,10 @@ export function activate(context: vscode.ExtensionContext) {
if (process.platform === "darwin") skip.push("kilo-code.new.agentManager.runScript")
ensureCommandsSkipShell(skip)
// Create KiloClaw chat provider for editor panel
const kiloClawProvider = new KiloClawProvider(context.extensionUri, connectionService)
context.subscriptions.push(kiloClawProvider)
// Create Agent Manager provider for editor panel
const agentManagerHost = new VscodeHost(context.extensionUri, connectionService, context)
const agentManagerProvider = new AgentManagerProvider(agentManagerHost, connectionService)
@@ -117,6 +122,16 @@ export function activate(context: vscode.ExtensionContext) {
}),
)
// Register serializer so KiloClaw panel restores when VS Code restarts
context.subscriptions.push(
vscode.window.registerWebviewPanelSerializer(KiloClawProvider.viewType, {
deserializeWebviewPanel(panel: vscode.WebviewPanel) {
kiloClawProvider.restorePanel(panel)
return Promise.resolve()
},
}),
)
// Register serializer so "Open in Tab" restores when VS Code restarts
context.subscriptions.push(
vscode.window.registerWebviewPanelSerializer("kilo-code.new.TabPanel", {
@@ -211,6 +226,9 @@ export function activate(context: vscode.ExtensionContext) {
vscode.commands.registerCommand("kilo-code.new.marketplaceButtonClicked", (directory?: string) => {
settingsEditorProvider.openPanel("marketplace", undefined, directory)
}),
vscode.commands.registerCommand("kilo-code.new.kiloClawOpen", () => {
kiloClawProvider.openPanel()
}),
vscode.commands.registerCommand("kilo-code.new.historyButtonClicked", () => {
const tab = activeTabProvider()
if (tab) tab.postMessage({ type: "action", action: "historyButtonClicked" })
@@ -0,0 +1,357 @@
/**
* KiloClaw panel provider for the VS Code extension.
*
* Owns the Stream Chat WebSocket connection (in the extension host Node.js runtime)
* and relays messages to/from the webview via postMessage.
*/
import * as vscode from "vscode"
import { homedir } from "os"
import type { KiloConnectionService } from "../services/cli-backend"
import { buildWebviewHtml } from "../utils"
import { connect, history, presence, type ClawChatClient } from "./chat-client"
import type {
KiloClawInMessage,
KiloClawOutMessage,
KiloClawState,
ClawStatus,
ChatCredentials,
ChatMessage,
} from "./types"
const MAX_MESSAGES = 500
const STATUS_POLL_MS = 10_000
export class KiloClawProvider implements vscode.Disposable {
static readonly viewType = "kilo-code.new.KiloClawPanel"
private panel: vscode.WebviewPanel | null = null
private chat: ClawChatClient | null = null
private timer: ReturnType<typeof setInterval> | null = null
private subs: Array<() => void> = []
private messages: ChatMessage[] = []
private status: ClawStatus | null = null
private online = false
private connected = false
private disposed = false
private initializing = false
constructor(
private readonly uri: vscode.Uri,
private readonly connection: KiloConnectionService,
) {}
openPanel(): void {
if (this.panel) {
this.panel.reveal(vscode.ViewColumn.One)
return
}
const panel = vscode.window.createWebviewPanel(KiloClawProvider.viewType, "KiloClaw", vscode.ViewColumn.One, {
enableScripts: true,
retainContextWhenHidden: true,
localResourceRoots: [this.uri],
})
this.attach(panel)
}
/** Restore a serialized panel after VS Code restart. */
restorePanel(panel: vscode.WebviewPanel): void {
this.attach(panel)
}
dispose(): void {
this.disposed = true
this.cleanup()
this.panel?.dispose()
this.panel = null
}
// --- private ---
private attach(panel: vscode.WebviewPanel): void {
if (this.panel) {
this.cleanup()
this.panel.dispose()
}
this.panel = panel
panel.iconPath = {
light: vscode.Uri.joinPath(this.uri, "assets", "icons", "kilo-light.svg"),
dark: vscode.Uri.joinPath(this.uri, "assets", "icons", "kilo-dark.svg"),
}
panel.webview.options = {
enableScripts: true,
localResourceRoots: [this.uri],
}
panel.webview.html = buildWebviewHtml(panel.webview, {
scriptUri: panel.webview.asWebviewUri(vscode.Uri.joinPath(this.uri, "dist", "kiloclaw.js")),
styleUri: panel.webview.asWebviewUri(vscode.Uri.joinPath(this.uri, "dist", "kiloclaw.css")),
iconsBaseUri: panel.webview.asWebviewUri(vscode.Uri.joinPath(this.uri, "assets", "icons")),
title: "KiloClaw",
})
const msgSub = panel.webview.onDidReceiveMessage((msg: KiloClawInMessage) => this.onMessage(msg))
this.subs.push(() => msgSub.dispose())
const disposeSub = panel.onDidDispose(() => {
this.panel = null
this.cleanup()
})
this.subs.push(() => disposeSub.dispose())
// Pause status polling when the panel is not visible to avoid unnecessary HTTP traffic
const viewSub = panel.onDidChangeViewState(() => {
if (panel.visible) this.startPolling()
else this.stopPolling()
})
this.subs.push(() => viewSub.dispose())
// Subscribe to language changes broadcast by other KiloProvider instances
const unsub = this.connection.onLanguageChanged((locale) => {
this.post({ type: "kiloclaw.locale", locale })
})
this.subs.push(unsub)
}
private post(msg: KiloClawOutMessage): void {
this.panel?.webview.postMessage(msg)
}
private async onMessage(msg: KiloClawInMessage): Promise<void> {
switch (msg.type) {
case "kiloclaw.ready":
await this.init()
break
case "kiloclaw.send":
await this.sendChat(msg.text)
break
case "kiloclaw.openExternal": {
const uri = vscode.Uri.parse(msg.url)
if (uri.scheme === "https" || uri.scheme === "http") {
void vscode.env.openExternal(uri)
}
break
}
}
}
private get locale(): string {
const override = vscode.workspace.getConfiguration("kilo-code.new").get<string>("language")
return override || vscode.env.language
}
private async init(): Promise<void> {
if (this.initializing || this.disposed) return
this.initializing = true
// Track whether we deferred to waitForConnection — if so, keep
// `initializing` true so duplicate kiloclaw.ready messages are
// harmlessly ignored until the connection arrives.
let deferred = false
try {
this.post({ type: "kiloclaw.state", state: { phase: "loading", locale: this.locale } })
// Ensure the CLI backend is running before trying to get the client.
// On VS Code restart the backend hasn't started yet — connect() starts
// it lazily (no-op if already connected).
if (this.connection.getConnectionState() !== "connected") {
try {
const dir = vscode.workspace.workspaceFolders?.[0]?.uri.fsPath ?? homedir()
await this.connection.connect(dir)
} catch (err) {
// Connection failed now — wait for another provider to bring it up.
console.debug("[Kilo New] KiloClaw connect deferred:", (err as Error)?.message ?? err)
deferred = true
this.waitForConnection()
return
}
}
let client
try {
client = this.connection.getClient()
} catch (err) {
console.debug("[Kilo New] KiloClaw getClient deferred:", (err as Error)?.message ?? err)
deferred = true
this.waitForConnection()
return
}
// Fetch instance status
const res = await client.kilo.claw.status().catch(() => null)
if (!res?.data || (res.data as Record<string, unknown>).error) {
this.post({ type: "kiloclaw.state", state: { phase: "noInstance", locale: this.locale } })
return
}
const data = res.data as ClawStatus & { userId?: string }
if (!data.userId) {
this.post({ type: "kiloclaw.state", state: { phase: "noInstance", locale: this.locale } })
return
}
this.status = data
// Fetch chat credentials
const creds = await client.kilo.claw.chatCredentials().catch(() => null)
if (!creds?.data) {
this.post({ type: "kiloclaw.state", state: { phase: "needsUpgrade", locale: this.locale } })
return
}
const credentials = creds.data as ChatCredentials
// Connect to Stream Chat
try {
await this.connectChat(credentials)
} catch (err: unknown) {
const msg = err instanceof Error ? err.message : String(err)
console.error("[Kilo New] KiloClaw chat connect failed:", msg)
this.post({
type: "kiloclaw.state",
state: {
phase: "ready",
locale: this.locale,
status: this.status,
connected: false,
online: false,
messages: [],
},
})
this.post({ type: "kiloclaw.error", error: msg || "Failed to connect to chat" })
this.startPolling()
return
}
// Push ready state
const state: KiloClawState = {
phase: "ready",
locale: this.locale,
status: this.status,
connected: this.connected,
online: this.online,
messages: this.messages,
}
this.post({ type: "kiloclaw.state", state })
this.startPolling()
} finally {
if (!deferred) this.initializing = false
}
}
private async connectChat(creds: ChatCredentials): Promise<void> {
this.chat = await connect(creds)
// Load history
const bot = `bot-${creds.channelId.replace(/^default-/, "")}`
this.messages = history(this.chat.channel, bot)
this.online = presence(this.chat.channel, bot)
this.connected = true
// Subscribe to events and relay to webview
const unsub = this.chat.onMessage((msg) => {
this.messages = [...this.messages, msg]
if (this.messages.length > MAX_MESSAGES) {
this.messages = this.messages.slice(-MAX_MESSAGES)
}
this.post({ type: "kiloclaw.message", message: msg })
})
this.subs.push(unsub)
const unsubUpdated = this.chat.onMessageUpdated((msg) => {
const idx = this.messages.findIndex((m) => m.id === msg.id)
if (idx === -1) {
this.messages = [...this.messages, msg]
if (this.messages.length > MAX_MESSAGES) {
this.messages = this.messages.slice(-MAX_MESSAGES)
}
} else {
this.messages = this.messages.map((m, i) => (i === idx ? msg : m))
}
this.post({ type: "kiloclaw.messageUpdated", message: msg })
})
this.subs.push(unsubUpdated)
const unsubPresence = this.chat.onPresence((val) => {
this.online = val
this.post({ type: "kiloclaw.presence", online: val })
})
this.subs.push(unsubPresence)
}
private async sendChat(text: string): Promise<void> {
if (!this.chat) {
this.post({ type: "kiloclaw.error", error: "Chat not connected" })
return
}
try {
await this.chat.send(text)
} catch (err) {
console.error("[Kilo New] KiloClaw send failed:", err instanceof Error ? err.message : err)
this.post({ type: "kiloclaw.error", error: "Failed to send message" })
}
}
private startPolling(): void {
if (this.timer) return
this.timer = setInterval(() => void this.poll(), STATUS_POLL_MS)
}
private stopPolling(): void {
if (!this.timer) return
clearInterval(this.timer)
this.timer = null
}
private async poll(): Promise<void> {
try {
const client = this.connection.getClient()
const res = await client.kilo.claw.status()
if (res?.data) {
this.status = res.data as ClawStatus
this.post({ type: "kiloclaw.status", data: this.status })
}
} catch (err) {
console.debug("[Kilo New] KiloClaw poll failed:", (err as Error)?.message ?? err)
}
}
/** Subscribe to connection state changes and re-run init() once connected. */
private waitForConnection(): void {
const unsub = this.connection.onStateChange((state) => {
if (state === "connected" && !this.disposed) {
unsub()
this.initializing = false
void this.init()
}
})
this.subs.push(unsub)
}
private cleanup(): void {
for (const unsub of this.subs) unsub()
this.subs = []
if (this.timer) {
clearInterval(this.timer)
this.timer = null
}
if (this.chat) {
this.chat.disconnect().catch((err) => {
console.error("[Kilo New] KiloClaw disconnect failed:", err?.message ?? err)
})
this.chat = null
}
this.messages = []
this.connected = false
this.online = false
this.initializing = false
this.status = null
}
}
@@ -0,0 +1,92 @@
/**
* KiloClaw Stream Chat client wrapper for the VS Code extension host.
*
* Port of packages/opencode/src/kilocode/claw/client.ts adapted for Node.js.
* No Bun patches needed — the extension host runs in standard Node.js.
* stream-chat resolves to its Node.js CJS entry point automatically.
*/
import type { Channel, Event } from "stream-chat"
import type { ChatCredentials, ChatMessage } from "./types"
export type ClawChatClient = {
channel: Channel
disconnect: () => Promise<void>
send: (text: string) => Promise<void>
onMessage: (cb: (msg: ChatMessage) => void) => () => void
onMessageUpdated: (cb: (msg: ChatMessage) => void) => () => void
onPresence: (cb: (online: boolean) => void) => () => void
}
function botId(creds: ChatCredentials): string {
return `bot-${creds.channelId.replace(/^default-/, "")}`
}
function toMessage(raw: Record<string, unknown>, bot: string): ChatMessage {
const user = raw.user as Record<string, unknown> | undefined
const uid = (user?.id as string) ?? (raw.user_id as string) ?? ""
return {
id: (raw.id as string) ?? "",
text: (raw.text as string) ?? "",
user: uid,
created: raw.created_at ? new Date(raw.created_at as string).toISOString() : new Date().toISOString(),
bot: uid === bot,
}
}
export async function connect(creds: ChatCredentials): Promise<ClawChatClient> {
const { StreamChat } = await import("stream-chat")
// Use a fresh instance instead of the singleton to avoid stale state
// (cached channels, event listeners) when credentials rotate.
const client = new StreamChat(creds.apiKey)
await client.connectUser({ id: creds.userId }, creds.userToken)
const channel = client.channel("messaging", creds.channelId)
await channel.watch({ presence: true })
const bot = botId(creds)
return {
channel,
async disconnect() {
await client.disconnectUser()
},
async send(text: string) {
await channel.sendMessage({ text })
},
onMessage(cb) {
const handler = (event: Event) => {
if (event.message) cb(toMessage(event.message as unknown as Record<string, unknown>, bot))
}
channel.on("message.new", handler)
return () => channel.off("message.new", handler)
},
onMessageUpdated(cb) {
const handler = (event: Event) => {
if (event.message) cb(toMessage(event.message as unknown as Record<string, unknown>, bot))
}
channel.on("message.updated", handler)
return () => channel.off("message.updated", handler)
},
onPresence(cb) {
const handler = (event: Event) => {
if (event.user?.id === bot) {
cb(event.user.online ?? false)
}
}
client.on("user.presence.changed", handler)
return () => client.off("user.presence.changed", handler)
},
}
}
export function history(channel: Channel, bot: string): ChatMessage[] {
const state = channel.state.messages
return state.map((raw) => toMessage(raw as unknown as Record<string, unknown>, bot))
}
export function presence(channel: Channel, bot: string): boolean {
const member = channel.state.members?.[bot]
return !!member?.user?.online
}
@@ -0,0 +1,68 @@
/**
* KiloClaw VS Code extension message types.
*
* Defines the postMessage protocol between the extension host (Node.js)
* and the KiloClaw webview (SolidJS). The extension host owns all network
* connections (SDK + Stream Chat) and relays data to the webview.
*
* SYNC: Shared types (ClawStatus, ChatMessage, KiloClawState, KiloClawOutMessage)
* are mirrored in webview-ui/kiloclaw/lib/types.ts — keep both in sync.
*/
export type ClawStatus = {
status: "provisioned" | "starting" | "restarting" | "running" | "stopped" | "destroying" | null
sandboxId?: string
flyRegion?: string
machineSize?: { cpus: number; memory_mb: number }
openclawVersion?: string | null
lastStartedAt?: string | null
lastStoppedAt?: string | null
channelCount?: number
secretCount?: number
}
export type ChatCredentials = {
apiKey: string
userId: string
userToken: string
channelId: string
}
export type ChatMessage = {
id: string
text: string
user: string
created: string // ISO string (serializable via postMessage)
bot: boolean
}
// Full state snapshot pushed to the webview
// Every phase carries `locale` so the webview can resolve translations immediately.
export type KiloClawState =
| { phase: "loading"; locale: string }
| { phase: "noInstance"; locale: string }
| { phase: "needsUpgrade"; locale: string }
| {
phase: "ready"
locale: string
status: ClawStatus | null
connected: boolean
online: boolean
messages: ChatMessage[]
}
// Messages: Webview → Extension Host
export type KiloClawInMessage =
| { type: "kiloclaw.ready" }
| { type: "kiloclaw.send"; text: string }
| { type: "kiloclaw.openExternal"; url: string }
// Messages: Extension Host → Webview
export type KiloClawOutMessage =
| { type: "kiloclaw.state"; state: KiloClawState }
| { type: "kiloclaw.message"; message: ChatMessage }
| { type: "kiloclaw.messageUpdated"; message: ChatMessage }
| { type: "kiloclaw.presence"; online: boolean }
| { type: "kiloclaw.status"; data: ClawStatus | null }
| { type: "kiloclaw.locale"; locale: string }
| { type: "kiloclaw.error"; error: string }
@@ -0,0 +1,63 @@
// KiloClaw root component
import { Show, Switch, Match } from "solid-js"
import { ThemeProvider } from "@kilocode/kilo-ui/theme"
import { ClawProvider, useClaw } from "./context/claw"
import { KiloClawLanguageProvider, useKiloClawLanguage } from "./context/language"
import { ChatPanel } from "./components/ChatPanel"
import { StatusSidebar } from "./components/StatusSidebar"
import { SetupView } from "./components/SetupView"
import { UpgradeView } from "./components/UpgradeView"
function Content() {
const claw = useClaw()
const { t } = useKiloClawLanguage()
return (
<div class="kiloclaw-root">
<Switch>
<Match when={claw.phase() === "loading"}>
<div class="kiloclaw-center">
<div class="kiloclaw-loading">
<div class="kiloclaw-spinner" />
<span>{t("kiloClaw.loading")}</span>
</div>
</div>
</Match>
<Match when={claw.phase() === "noInstance"}>
<SetupView />
</Match>
<Match when={claw.phase() === "needsUpgrade"}>
<UpgradeView />
</Match>
<Match when={claw.phase() === "ready"}>
<div class="kiloclaw-layout">
<ChatPanel />
<StatusSidebar />
</div>
</Match>
</Switch>
<Show when={claw.error()}>
<div class="kiloclaw-toast">{claw.error()}</div>
</Show>
</div>
)
}
export function KiloClawApp() {
return (
<ThemeProvider>
<ClawProvider>
<LanguageBridge>
<Content />
</LanguageBridge>
</ClawProvider>
</ThemeProvider>
)
}
/** Bridges the claw context locale into the language provider. Must be below ClawProvider. */
function LanguageBridge(props: { children: any }) {
const claw = useClaw()
return <KiloClawLanguageProvider locale={claw.locale}>{props.children}</KiloClawLanguageProvider>
}
@@ -0,0 +1,105 @@
// KiloClaw chat panel — message list + input
import { createSignal, createEffect, For, Show, createMemo, onMount } from "solid-js"
import { useClaw } from "../context/claw"
import { useKiloClawLanguage } from "../context/language"
import { MessageBubble } from "./MessageBubble"
export function ChatPanel() {
const claw = useClaw()
const { t } = useKiloClawLanguage()
const [text, setText] = createSignal("")
let list!: HTMLDivElement
let input!: HTMLTextAreaElement
const disabled = createMemo(() => {
const s = claw.status()
return !s || s.status !== "running" || !claw.connected()
})
const placeholder = createMemo(() => {
if (!claw.connected()) return t("kiloClaw.chat.connecting")
const s = claw.status()
if (!s || s.status !== "running") return t("kiloClaw.chat.notRunning")
return t("kiloClaw.chat.placeholder")
})
// Auto-scroll to bottom when messages change
createEffect(() => {
claw.messages()
if (list) {
requestAnimationFrame(() => {
list.scrollTop = list.scrollHeight
})
}
})
// Focus input on mount
onMount(() => {
if (input && !disabled()) input.focus()
})
const submit = () => {
const val = text().trim()
if (!val || disabled()) return
claw.send(val)
setText("")
if (input) {
input.style.height = "auto"
}
}
const onKeyDown = (e: KeyboardEvent) => {
if (e.key === "Enter" && !e.shiftKey) {
e.preventDefault()
submit()
}
}
const onInput = (e: InputEvent) => {
const target = e.target as HTMLTextAreaElement
setText(target.value)
// Auto-resize
target.style.height = "auto"
target.style.height = Math.min(target.scrollHeight, 120) + "px"
}
return (
<div class="kiloclaw-chat">
{/* Header */}
<div class="kiloclaw-chat-header">
<div class="kiloclaw-chat-header-left">
<span class={`kiloclaw-dot ${claw.online() ? "kiloclaw-dot-online" : "kiloclaw-dot-offline"}`} />
<span class="kiloclaw-chat-header-title">
KiloClaw {claw.online() ? t("kiloClaw.chat.online") : t("kiloClaw.chat.offline")}
</span>
</div>
</div>
{/* Messages */}
<div class="kiloclaw-messages" ref={list}>
<Show when={claw.messages().length === 0 && claw.connected()}>
<div class="kiloclaw-empty">{t("kiloClaw.chat.empty")}</div>
</Show>
<For each={claw.messages()}>{(msg) => <MessageBubble message={msg} />}</For>
</div>
{/* Input */}
<div class="kiloclaw-input-wrap">
<textarea
ref={input}
class="kiloclaw-input"
placeholder={placeholder()}
disabled={disabled()}
value={text()}
onInput={onInput}
onKeyDown={onKeyDown}
rows={1}
/>
<button class="kiloclaw-send-btn" disabled={disabled() || !text().trim()} onClick={submit}>
{t("kiloClaw.chat.send")}
</button>
</div>
</div>
)
}
@@ -0,0 +1,31 @@
// Individual chat message bubble
import { Show, createMemo } from "solid-js"
import type { ChatMessage } from "../lib/types"
import { useKiloClawLanguage } from "../context/language"
function formatTime(iso: string): string {
const d = new Date(iso)
return d.toLocaleTimeString([], { hour: "2-digit", minute: "2-digit" })
}
export function MessageBubble(props: { message: ChatMessage }) {
const { t } = useKiloClawLanguage()
const empty = createMemo(() => !props.message.text || !props.message.text.trim())
return (
<div class={`kiloclaw-msg ${props.message.bot ? "kiloclaw-msg-bot" : "kiloclaw-msg-user"}`}>
<div class="kiloclaw-msg-header">
<span class="kiloclaw-msg-author">
{props.message.bot ? t("kiloClaw.message.bot") : t("kiloClaw.message.you")}
</span>
<span class="kiloclaw-msg-time">{formatTime(props.message.created)}</span>
</div>
<div class="kiloclaw-msg-body">
<Show when={!empty()} fallback={<span class="kiloclaw-msg-thinking">{t("kiloClaw.message.thinking")}</span>}>
{props.message.text}
</Show>
</div>
</div>
)
}
@@ -0,0 +1,28 @@
// KiloClaw setup view — shown when no instance is provisioned
import { useClaw } from "../context/claw"
import { useKiloClawLanguage } from "../context/language"
export function SetupView() {
const claw = useClaw()
const { t } = useKiloClawLanguage()
return (
<div class="kiloclaw-center">
<div class="kiloclaw-card">
<h2 class="kiloclaw-card-title">{t("kiloClaw.setup.title")}</h2>
<h3 class="kiloclaw-card-subtitle">{t("kiloClaw.setup.subtitle")}</h3>
<p class="kiloclaw-card-text">{t("kiloClaw.setup.description1")}</p>
<p class="kiloclaw-card-text">{t("kiloClaw.setup.description2")}</p>
<div class="kiloclaw-card-actions">
<button class="kiloclaw-link-btn" onClick={() => claw.openExternal("https://kilo.ai/kiloclaw")}>
{t("kiloClaw.setup.learnMore")}
</button>
<button class="kiloclaw-primary-btn" onClick={() => claw.openExternal("https://app.kilo.ai/claw")}>
{t("kiloClaw.setup.tryKiloClaw")}
</button>
</div>
</div>
</div>
)
}
@@ -0,0 +1,97 @@
// KiloClaw status sidebar — instance info, bot presence, details
import { Show, createMemo } from "solid-js"
import { useClaw } from "../context/claw"
import { useKiloClawLanguage } from "../context/language"
function dot(status: string | null | undefined): string {
if (!status) return "kiloclaw-dot-offline"
if (status === "running") return "kiloclaw-dot-online"
if (status === "starting" || status === "restarting") return "kiloclaw-dot-warning"
if (status === "destroying") return "kiloclaw-dot-error"
return "kiloclaw-dot-offline"
}
function uptime(started: string | null | undefined): string {
if (!started) return "\u2014"
const ms = Date.now() - new Date(started).getTime()
if (ms < 0) return "\u2014"
const seconds = Math.floor(ms / 1000)
const minutes = Math.floor(seconds / 60)
const hours = Math.floor(minutes / 60)
const days = Math.floor(hours / 24)
if (days > 0) return `${days}d ${hours % 24}h`
if (hours > 0) return `${hours}h ${minutes % 60}m`
if (minutes > 0) return `${minutes}m`
return `${seconds}s`
}
function capitalize(s: string | null | undefined, fallback: string): string {
if (!s) return fallback
return s.charAt(0).toUpperCase() + s.slice(1)
}
export function StatusSidebar() {
const claw = useClaw()
const { t } = useKiloClawLanguage()
const status = createMemo(() => claw.status())
return (
<div class="kiloclaw-sidebar">
<h3 class="kiloclaw-sidebar-title">{t("kiloClaw.sidebar.title")}</h3>
<Show when={status()}>
<div class="kiloclaw-sidebar-section">
<div class="kiloclaw-sidebar-label">{t("kiloClaw.sidebar.instance")}</div>
<div class="kiloclaw-sidebar-row">
<span class={`kiloclaw-dot ${dot(status()!.status)}`} />
<span>
{capitalize(status()!.status, t("kiloClaw.sidebar.unknown"))}
<Show when={status()!.status === "running"}>
<span class="kiloclaw-sidebar-muted"> {uptime(status()!.lastStartedAt)}</span>
</Show>
</span>
</div>
</div>
<div class="kiloclaw-sidebar-section">
<div class="kiloclaw-sidebar-label">{t("kiloClaw.sidebar.bot")}</div>
<div class="kiloclaw-sidebar-row">
<span class={`kiloclaw-dot ${claw.online() ? "kiloclaw-dot-online" : "kiloclaw-dot-offline"}`} />
<span>{claw.online() ? t("kiloClaw.chat.online") : t("kiloClaw.chat.offline")}</span>
</div>
</div>
<div class="kiloclaw-sidebar-section">
<div class="kiloclaw-sidebar-label">{t("kiloClaw.sidebar.details")}</div>
<div class="kiloclaw-sidebar-detail">
<span class="kiloclaw-sidebar-muted">{t("kiloClaw.sidebar.region")}</span>
<span>{status()!.flyRegion?.toUpperCase() ?? "\u2014"}</span>
</div>
<div class="kiloclaw-sidebar-detail">
<span class="kiloclaw-sidebar-muted">{t("kiloClaw.sidebar.version")}</span>
<span>{status()!.openclawVersion ?? "\u2014"}</span>
</div>
<Show
when={
status()!.channelCount !== null &&
status()!.channelCount !== undefined &&
(status()!.channelCount ?? 0) >= 1
}
>
<div class="kiloclaw-sidebar-detail">
<span class="kiloclaw-sidebar-muted">{t("kiloClaw.sidebar.channels")}</span>
<span>{status()!.channelCount}</span>
</div>
</Show>
</div>
</Show>
<Show when={!status()}>
<div class="kiloclaw-sidebar-section">
<span class="kiloclaw-sidebar-muted">{t("kiloClaw.sidebar.noData")}</span>
</div>
</Show>
</div>
)
}
@@ -0,0 +1,29 @@
// KiloClaw upgrade view — shown when instance needs upgrade for chat
import { useClaw } from "../context/claw"
import { useKiloClawLanguage } from "../context/language"
export function UpgradeView() {
const claw = useClaw()
const { t } = useKiloClawLanguage()
return (
<div class="kiloclaw-center">
<div class="kiloclaw-card">
<h2 class="kiloclaw-card-title">{t("kiloClaw.upgrade.title")}</h2>
<p class="kiloclaw-card-text">{t("kiloClaw.upgrade.description1")}</p>
<p class="kiloclaw-card-text">
{t("kiloClaw.upgrade.description2.before")}
<strong>{t("kiloClaw.upgrade.description2.bold")}</strong>
{t("kiloClaw.upgrade.description2.after")}
</p>
<div class="kiloclaw-card-actions">
<div />
<button class="kiloclaw-primary-btn" onClick={() => claw.openExternal("https://app.kilo.ai/claw")}>
{t("kiloClaw.upgrade.openDashboard")}
</button>
</div>
</div>
</div>
)
}
@@ -0,0 +1,124 @@
// KiloClaw state context
//
// All data arrives from the extension host via postMessage.
// The webview has no direct network access.
import { createContext, createSignal, onMount, onCleanup, useContext, type JSX } from "solid-js"
import type { ClawStatus, ChatMessage, KiloClawOutMessage } from "../lib/types"
type VSCodeAPI = {
postMessage(msg: unknown): void
getState(): unknown
setState(state: unknown): void
}
declare function acquireVsCodeApi(): VSCodeAPI
const vscode = acquireVsCodeApi()
const MAX_MESSAGES = 500
type Phase = "loading" | "noInstance" | "needsUpgrade" | "ready"
type ClawCtx = {
phase: () => Phase
locale: () => string | undefined
status: () => ClawStatus | null
messages: () => ChatMessage[]
online: () => boolean
connected: () => boolean
error: () => string | null
send: (text: string) => void
openExternal: (url: string) => void
}
const ClawContext = createContext<ClawCtx>()
export function ClawProvider(props: { children: JSX.Element }) {
const [phase, setPhase] = createSignal<Phase>("loading")
const [locale, setLocale] = createSignal<string | undefined>(undefined)
const [status, setStatus] = createSignal<ClawStatus | null>(null)
const [messages, setMessages] = createSignal<ChatMessage[]>([])
const [online, setOnline] = createSignal(false)
const [connected, setConnected] = createSignal(false)
const [error, setError] = createSignal<string | null>(null)
const handler = (event: MessageEvent) => {
const msg = event.data as KiloClawOutMessage
if (!msg?.type?.startsWith("kiloclaw.")) return
switch (msg.type) {
case "kiloclaw.state": {
const s = msg.state
setPhase(s.phase)
setLocale(s.locale)
if (s.phase === "ready") {
setStatus(s.status)
setConnected(s.connected)
setOnline(s.online)
setMessages(s.messages)
}
break
}
case "kiloclaw.message":
setMessages((prev) => {
const next = [...prev, msg.message]
return next.length > MAX_MESSAGES ? next.slice(-MAX_MESSAGES) : next
})
break
case "kiloclaw.messageUpdated": {
setMessages((prev) => {
const idx = prev.findIndex((m) => m.id === msg.message.id)
if (idx === -1) {
const next = [...prev, msg.message]
return next.length > MAX_MESSAGES ? next.slice(-MAX_MESSAGES) : next
}
return prev.map((m, i) => (i === idx ? msg.message : m))
})
break
}
case "kiloclaw.presence":
setOnline(msg.online)
break
case "kiloclaw.status":
setStatus(msg.data)
break
case "kiloclaw.locale":
setLocale(msg.locale)
break
case "kiloclaw.error":
setError(msg.error)
// Auto-clear after 5s
setTimeout(() => setError(null), 5000)
break
}
}
// Register listener immediately (before mount) to catch early pushes
window.addEventListener("message", handler)
onCleanup(() => window.removeEventListener("message", handler))
onMount(() => {
vscode.postMessage({ type: "kiloclaw.ready" })
})
const ctx: ClawCtx = {
phase,
locale,
status,
messages,
online,
connected,
error,
send: (text: string) => vscode.postMessage({ type: "kiloclaw.send", text }),
openExternal: (url: string) => vscode.postMessage({ type: "kiloclaw.openExternal", url }),
}
return <ClawContext.Provider value={ctx}>{props.children}</ClawContext.Provider>
}
export function useClaw(): ClawCtx {
const ctx = useContext(ClawContext)
if (!ctx) throw new Error("useClaw must be used within ClawProvider")
return ctx
}
@@ -0,0 +1,78 @@
// KiloClaw lightweight language context
//
// Self-contained i18n for the KiloClaw panel. Does not depend on
// VSCodeProvider/ServerProvider — locale comes from the extension host
// via the claw context, falling back to navigator.language then "en".
import { createContext, createMemo, useContext, type JSX } from "solid-js"
import { normalizeLocale, resolveTemplate } from "../../src/context/language-utils"
import type { Locale } from "../../src/context/language-utils"
import { dict as en } from "../i18n/en"
import { dict as ar } from "../i18n/ar"
import { dict as br } from "../i18n/br"
import { dict as bs } from "../i18n/bs"
import { dict as da } from "../i18n/da"
import { dict as de } from "../i18n/de"
import { dict as es } from "../i18n/es"
import { dict as fr } from "../i18n/fr"
import { dict as ja } from "../i18n/ja"
import { dict as ko } from "../i18n/ko"
import { dict as nl } from "../i18n/nl"
import { dict as no } from "../i18n/no"
import { dict as pl } from "../i18n/pl"
import { dict as ru } from "../i18n/ru"
import { dict as th } from "../i18n/th"
import { dict as tr } from "../i18n/tr"
import { dict as zh } from "../i18n/zh"
import { dict as zht } from "../i18n/zht"
const dicts: Record<Locale, Record<string, string>> = {
en,
ar: { ...en, ...ar },
br: { ...en, ...br },
bs: { ...en, ...bs },
da: { ...en, ...da },
de: { ...en, ...de },
es: { ...en, ...es },
fr: { ...en, ...fr },
ja: { ...en, ...ja },
ko: { ...en, ...ko },
nl: { ...en, ...nl },
no: { ...en, ...no },
pl: { ...en, ...pl },
ru: { ...en, ...ru },
th: { ...en, ...th },
tr: { ...en, ...tr },
zh: { ...en, ...zh },
zht: { ...en, ...zht },
}
type LanguageCtx = {
t: (key: string, params?: Record<string, string | number | boolean | undefined>) => string
}
const LanguageContext = createContext<LanguageCtx>()
export function KiloClawLanguageProvider(props: { locale: () => string | undefined; children: JSX.Element }) {
const resolved = createMemo<Locale>(() => {
const ext = props.locale()
if (ext) return normalizeLocale(ext)
if (typeof navigator !== "undefined" && navigator.language) return normalizeLocale(navigator.language)
return "en"
})
const dict = createMemo(() => dicts[resolved()] ?? dicts.en)
const t = (key: string, params?: Record<string, string | number | boolean | undefined>) => {
const text = dict()[key] ?? key
return resolveTemplate(text, params)
}
return <LanguageContext.Provider value={{ t }}>{props.children}</LanguageContext.Provider>
}
export function useKiloClawLanguage(): LanguageCtx {
const ctx = useContext(LanguageContext)
if (!ctx) throw new Error("useKiloClawLanguage must be used within KiloClawLanguageProvider")
return ctx
}
+35
View File
@@ -0,0 +1,35 @@
export const dict = {
"kiloClaw.loading": "جاري تحميل KiloClaw...",
"kiloClaw.chat.connecting": "جاري الاتصال...",
"kiloClaw.chat.notRunning": "المثيل لا يعمل",
"kiloClaw.chat.placeholder": "اكتب رسالة... (اضغط Enter للإرسال)",
"kiloClaw.chat.online": "متصل",
"kiloClaw.chat.offline": "غير متصل",
"kiloClaw.chat.empty": "لا توجد رسائل بعد. قُل مرحباً!",
"kiloClaw.chat.send": "إرسال",
"kiloClaw.message.bot": "KiloClaw",
"kiloClaw.message.you": "أنت",
"kiloClaw.message.thinking": "يفكر...",
"kiloClaw.setup.title": "KiloClaw",
"kiloClaw.setup.subtitle": "ذكاء اصطناعي شخصي للحياة اليومية",
"kiloClaw.setup.description1":
"يمنحك KiloClaw ذكاءً اصطناعياً شخصياً يقرأ البريد الإلكتروني، ويدير تقويمك، ويراقب مشاريعك، ويتواجد في Telegram و Slack — وأي شيء تستخدمه بالفعل.",
"kiloClaw.setup.description2": "لا يوجد تطبيق لتثبيته. لا توجد واجهة جديدة لتعلمها. فقط أرسل له رسالة كصديق.",
"kiloClaw.setup.learnMore": "معرفة المزيد",
"kiloClaw.setup.tryKiloClaw": "جرب KiloClaw",
"kiloClaw.upgrade.title": "تتطلب دردشة KiloClaw ترقية",
"kiloClaw.upgrade.description1": "تم إعداد هذا المثيل قبل تفعيل الدردشة.",
"kiloClaw.upgrade.description2.before": "استخدم زر ",
"kiloClaw.upgrade.description2.bold": "الترقية إلى الأحدث",
"kiloClaw.upgrade.description2.after": " في لوحة تحكم KiloClaw لتنشيط الدردشة في الوقت الفعلي مع الروبوت الخاص بك.",
"kiloClaw.upgrade.openDashboard": "فتح لوحة التحكم",
"kiloClaw.sidebar.title": "KiloClaw",
"kiloClaw.sidebar.instance": "المثيل",
"kiloClaw.sidebar.unknown": "غير معروف",
"kiloClaw.sidebar.bot": "الروبوت",
"kiloClaw.sidebar.details": "التفاصيل",
"kiloClaw.sidebar.region": "المنطقة",
"kiloClaw.sidebar.version": "الإصدار",
"kiloClaw.sidebar.channels": "القنوات",
"kiloClaw.sidebar.noData": "لا توجد بيانات للمثيل",
}
+36
View File
@@ -0,0 +1,36 @@
export const dict = {
"kiloClaw.loading": "Carregando KiloClaw...",
"kiloClaw.chat.connecting": "Conectando...",
"kiloClaw.chat.notRunning": "A instância não está em execução",
"kiloClaw.chat.placeholder": "Digite uma mensagem... (Enter para enviar)",
"kiloClaw.chat.online": "Online",
"kiloClaw.chat.offline": "Offline",
"kiloClaw.chat.empty": "Nenhuma mensagem ainda. Diga olá!",
"kiloClaw.chat.send": "Enviar",
"kiloClaw.message.bot": "KiloClaw",
"kiloClaw.message.you": "Você",
"kiloClaw.message.thinking": "Pensando...",
"kiloClaw.setup.title": "KiloClaw",
"kiloClaw.setup.subtitle": "IA pessoal para o dia a dia",
"kiloClaw.setup.description1":
"O KiloClaw oferece uma IA pessoal que lê e-mails, gerencia sua agenda, monitora seus projetos e vive no Telegram, Slack \u2014 ou qualquer ferramenta que você já use.",
"kiloClaw.setup.description2":
"Nenhum aplicativo para instalar. Nenhuma interface nova para aprender. Apenas envie mensagens como se fosse um amigo.",
"kiloClaw.setup.learnMore": "Saiba mais",
"kiloClaw.setup.tryKiloClaw": "Experimente o KiloClaw",
"kiloClaw.upgrade.title": "O Chat do KiloClaw requer uma atualização",
"kiloClaw.upgrade.description1": "Esta instância foi provisionada antes do chat ser ativado.",
"kiloClaw.upgrade.description2.before": "Use o botão ",
"kiloClaw.upgrade.description2.bold": "Atualizar para a Versão Mais Recente",
"kiloClaw.upgrade.description2.after": " no dashboard do KiloClaw para ativar o chat em tempo real com seu bot.",
"kiloClaw.upgrade.openDashboard": "Abrir Dashboard",
"kiloClaw.sidebar.title": "KiloClaw",
"kiloClaw.sidebar.instance": "Instância",
"kiloClaw.sidebar.unknown": "Desconhecido",
"kiloClaw.sidebar.bot": "Bot",
"kiloClaw.sidebar.details": "Detalhes",
"kiloClaw.sidebar.region": "Região",
"kiloClaw.sidebar.version": "Versão",
"kiloClaw.sidebar.channels": "Canais",
"kiloClaw.sidebar.noData": "Sem dados da instância",
}
+37
View File
@@ -0,0 +1,37 @@
export const dict = {
"kiloClaw.loading": "Učitavanje KiloClaw-a...",
"kiloClaw.chat.connecting": "Povezivanje...",
"kiloClaw.chat.notRunning": "Instanca nije pokrenuta",
"kiloClaw.chat.placeholder": "Unesite poruku... (Enter za slanje)",
"kiloClaw.chat.online": "Na mreži",
"kiloClaw.chat.offline": "Van mreže",
"kiloClaw.chat.empty": "Još uvijek nema poruka. Recite zdravo!",
"kiloClaw.chat.send": "Pošalji",
"kiloClaw.message.bot": "KiloClaw",
"kiloClaw.message.you": "Vi",
"kiloClaw.message.thinking": "Razmišljam...",
"kiloClaw.setup.title": "KiloClaw",
"kiloClaw.setup.subtitle": "Lična vještačka inteligencija za svakodnevni život",
"kiloClaw.setup.description1":
"KiloClaw vam pruža ličnu vještačku inteligenciju koja čita e-poštu, upravlja vašim kalendarom, nadgleda vaše projekte i živi u Telegramu, Slacku \u2014 šta god već koristite.",
"kiloClaw.setup.description2":
"Nema aplikacije za instaliranje. Nema novog interfejsa za učenje. Samo mu pošaljite poruku kao prijatelju.",
"kiloClaw.setup.learnMore": "Saznajte više",
"kiloClaw.setup.tryKiloClaw": "Isprobajte KiloClaw",
"kiloClaw.upgrade.title": "KiloClaw Chat zahtijeva nadogradnju",
"kiloClaw.upgrade.description1": "Ova instanca je obezbijeđena prije nego što je chat bio omogućen.",
"kiloClaw.upgrade.description2.before": "Upotrijebite dugme ",
"kiloClaw.upgrade.description2.bold": "Upgrade to Latest",
"kiloClaw.upgrade.description2.after":
" na KiloClaw kontrolnoj tabli da aktivirate chat u realnom vremenu sa vašim botom.",
"kiloClaw.upgrade.openDashboard": "Otvori kontrolnu tablu",
"kiloClaw.sidebar.title": "KiloClaw",
"kiloClaw.sidebar.instance": "Instanca",
"kiloClaw.sidebar.unknown": "Nepoznato",
"kiloClaw.sidebar.bot": "Bot",
"kiloClaw.sidebar.details": "Detalji",
"kiloClaw.sidebar.region": "Regija",
"kiloClaw.sidebar.version": "Verzija",
"kiloClaw.sidebar.channels": "Kanali",
"kiloClaw.sidebar.noData": "Nema podataka o instanci",
}
+37
View File
@@ -0,0 +1,37 @@
export const dict = {
"kiloClaw.loading": "Indlæser KiloClaw...",
"kiloClaw.chat.connecting": "Opretter forbindelse...",
"kiloClaw.chat.notRunning": "Instansen kører ikke",
"kiloClaw.chat.placeholder": "Skriv en besked... (Enter for at sende)",
"kiloClaw.chat.online": "Online",
"kiloClaw.chat.offline": "Offline",
"kiloClaw.chat.empty": "Ingen beskeder endnu. Sig hej!",
"kiloClaw.chat.send": "Send",
"kiloClaw.message.bot": "KiloClaw",
"kiloClaw.message.you": "Dig",
"kiloClaw.message.thinking": "Tænker...",
"kiloClaw.setup.title": "KiloClaw",
"kiloClaw.setup.subtitle": "Personlig AI til hverdagen",
"kiloClaw.setup.description1":
"KiloClaw giver dig en personlig AI, der læser e-mails, administrerer din kalender, overvåger dine projekter og bor i Telegram, Slack \u2014 uanset hvad du allerede bruger.",
"kiloClaw.setup.description2":
"Ingen app at installere. Ingen ny grænseflade at lære. Bare skriv til den som en ven.",
"kiloClaw.setup.learnMore": "Læs mere",
"kiloClaw.setup.tryKiloClaw": "Prøv KiloClaw",
"kiloClaw.upgrade.title": "KiloClaw Chat kræver en opgradering",
"kiloClaw.upgrade.description1": "Denne instans blev klargjort, før chat blev aktiveret.",
"kiloClaw.upgrade.description2.before": "Brug ",
"kiloClaw.upgrade.description2.bold": "Opgrader til seneste",
"kiloClaw.upgrade.description2.after":
"-knappen på KiloClaw-kontrolpanelet for at aktivere realtidschat med din bot.",
"kiloClaw.upgrade.openDashboard": "Åbn kontrolpanel",
"kiloClaw.sidebar.title": "KiloClaw",
"kiloClaw.sidebar.instance": "Instans",
"kiloClaw.sidebar.unknown": "Ukendt",
"kiloClaw.sidebar.bot": "Bot",
"kiloClaw.sidebar.details": "Detaljer",
"kiloClaw.sidebar.region": "Region",
"kiloClaw.sidebar.version": "Version",
"kiloClaw.sidebar.channels": "Kanaler",
"kiloClaw.sidebar.noData": "Ingen instansdata",
}
+36
View File
@@ -0,0 +1,36 @@
export const dict = {
"kiloClaw.loading": "Lade KiloClaw...",
"kiloClaw.chat.connecting": "Verbinde...",
"kiloClaw.chat.notRunning": "Instanz läuft nicht",
"kiloClaw.chat.placeholder": "Schreibe eine Nachricht... (Eingabetaste zum Senden)",
"kiloClaw.chat.online": "Online",
"kiloClaw.chat.offline": "Offline",
"kiloClaw.chat.empty": "Noch keine Nachrichten. Sag Hallo!",
"kiloClaw.chat.send": "Senden",
"kiloClaw.message.bot": "KiloClaw",
"kiloClaw.message.you": "Du",
"kiloClaw.message.thinking": "Denkt nach...",
"kiloClaw.setup.title": "KiloClaw",
"kiloClaw.setup.subtitle": "Persönliche KI für den Alltag",
"kiloClaw.setup.description1":
"KiloClaw gibt dir eine persönliche KI, die E-Mails liest, deinen Kalender verwaltet, deine Projekte überwacht und in Telegram, Slack lebt — was auch immer du bereits nutzt.",
"kiloClaw.setup.description2":
"Keine App zu installieren. Keine neue Oberfläche zu lernen. Schreib ihr einfach wie einem Freund.",
"kiloClaw.setup.learnMore": "Mehr erfahren",
"kiloClaw.setup.tryKiloClaw": "KiloClaw ausprobieren",
"kiloClaw.upgrade.title": "KiloClaw Chat erfordert ein Upgrade",
"kiloClaw.upgrade.description1": "Diese Instanz wurde bereitgestellt, bevor der Chat aktiviert wurde.",
"kiloClaw.upgrade.description2.before": "Verwende die Schaltfläche ",
"kiloClaw.upgrade.description2.bold": "Auf neueste Version aktualisieren",
"kiloClaw.upgrade.description2.after": " im KiloClaw Dashboard, um den Echtzeit-Chat mit deinem Bot zu aktivieren.",
"kiloClaw.upgrade.openDashboard": "Dashboard öffnen",
"kiloClaw.sidebar.title": "KiloClaw",
"kiloClaw.sidebar.instance": "Instanz",
"kiloClaw.sidebar.unknown": "Unbekannt",
"kiloClaw.sidebar.bot": "Bot",
"kiloClaw.sidebar.details": "Details",
"kiloClaw.sidebar.region": "Region",
"kiloClaw.sidebar.version": "Version",
"kiloClaw.sidebar.channels": "Kanäle",
"kiloClaw.sidebar.noData": "Keine Instanzdaten",
}
@@ -0,0 +1,46 @@
export const dict = {
// App
"kiloClaw.loading": "Loading KiloClaw...",
// Chat
"kiloClaw.chat.connecting": "Connecting...",
"kiloClaw.chat.notRunning": "Instance is not running",
"kiloClaw.chat.placeholder": "Type a message... (Enter to send)",
"kiloClaw.chat.online": "Online",
"kiloClaw.chat.offline": "Offline",
"kiloClaw.chat.empty": "No messages yet. Say hello!",
"kiloClaw.chat.send": "Send",
// Messages
"kiloClaw.message.bot": "KiloClaw",
"kiloClaw.message.you": "You",
"kiloClaw.message.thinking": "Thinking...",
// Setup
"kiloClaw.setup.title": "KiloClaw",
"kiloClaw.setup.subtitle": "Personal AI for everyday life",
"kiloClaw.setup.description1":
"KiloClaw gives you a personal AI that reads email, manages your calendar, monitors your projects, and lives in Telegram, Slack \u2014 whatever you already use.",
"kiloClaw.setup.description2": "No app to install. No new interface to learn. Just message it like a friend.",
"kiloClaw.setup.learnMore": "Learn more",
"kiloClaw.setup.tryKiloClaw": "Try KiloClaw",
// Upgrade
"kiloClaw.upgrade.title": "KiloClaw Chat requires an upgrade",
"kiloClaw.upgrade.description1": "This instance was provisioned before chat was enabled.",
"kiloClaw.upgrade.description2.before": "Use the ",
"kiloClaw.upgrade.description2.bold": "Upgrade to Latest",
"kiloClaw.upgrade.description2.after": " button on the KiloClaw dashboard to activate real-time chat with your bot.",
"kiloClaw.upgrade.openDashboard": "Open Dashboard",
// Sidebar
"kiloClaw.sidebar.title": "KiloClaw",
"kiloClaw.sidebar.instance": "Instance",
"kiloClaw.sidebar.unknown": "Unknown",
"kiloClaw.sidebar.bot": "Bot",
"kiloClaw.sidebar.details": "Details",
"kiloClaw.sidebar.region": "Region",
"kiloClaw.sidebar.version": "Version",
"kiloClaw.sidebar.channels": "Channels",
"kiloClaw.sidebar.noData": "No instance data",
}
+36
View File
@@ -0,0 +1,36 @@
export const dict = {
"kiloClaw.loading": "Cargando KiloClaw...",
"kiloClaw.chat.connecting": "Conectando...",
"kiloClaw.chat.notRunning": "La instancia no está en ejecución",
"kiloClaw.chat.placeholder": "Escribe un mensaje... (Enter para enviar)",
"kiloClaw.chat.online": "En línea",
"kiloClaw.chat.offline": "Desconectado",
"kiloClaw.chat.empty": "Aún no hay mensajes. ¡Di hola!",
"kiloClaw.chat.send": "Enviar",
"kiloClaw.message.bot": "KiloClaw",
"kiloClaw.message.you": "Tú",
"kiloClaw.message.thinking": "Pensando...",
"kiloClaw.setup.title": "KiloClaw",
"kiloClaw.setup.subtitle": "IA personal para la vida diaria",
"kiloClaw.setup.description1":
"KiloClaw te ofrece una IA personal que lee correos, gestiona tu calendario, monitorea tus proyectos y vive en Telegram, Slack \u2014 lo que sea que ya uses.",
"kiloClaw.setup.description2":
"No hay que instalar ninguna aplicación. No hay que aprender una nueva interfaz. Solo envíale mensajes como si fuera un amigo.",
"kiloClaw.setup.learnMore": "Más información",
"kiloClaw.setup.tryKiloClaw": "Probar KiloClaw",
"kiloClaw.upgrade.title": "El chat de KiloClaw requiere una actualización",
"kiloClaw.upgrade.description1": "Esta instancia se aprovisionó antes de que se habilitara el chat.",
"kiloClaw.upgrade.description2.before": "Usa el botón ",
"kiloClaw.upgrade.description2.bold": "Actualizar a la última versión",
"kiloClaw.upgrade.description2.after": " en el panel de KiloClaw para activar el chat en tiempo real con tu bot.",
"kiloClaw.upgrade.openDashboard": "Abrir panel",
"kiloClaw.sidebar.title": "KiloClaw",
"kiloClaw.sidebar.instance": "Instancia",
"kiloClaw.sidebar.unknown": "Desconocido",
"kiloClaw.sidebar.bot": "Bot",
"kiloClaw.sidebar.details": "Detalles",
"kiloClaw.sidebar.region": "Región",
"kiloClaw.sidebar.version": "Versión",
"kiloClaw.sidebar.channels": "Canales",
"kiloClaw.sidebar.noData": "Sin datos de la instancia",
}
+37
View File
@@ -0,0 +1,37 @@
export const dict = {
"kiloClaw.loading": "Chargement de KiloClaw...",
"kiloClaw.chat.connecting": "Connexion en cours...",
"kiloClaw.chat.notRunning": "L'instance n'est pas en cours d'exécution",
"kiloClaw.chat.placeholder": "Tapez un message... (Entrée pour envoyer)",
"kiloClaw.chat.online": "En ligne",
"kiloClaw.chat.offline": "Hors ligne",
"kiloClaw.chat.empty": "Aucun message pour le moment. Dites bonjour !",
"kiloClaw.chat.send": "Envoyer",
"kiloClaw.message.bot": "KiloClaw",
"kiloClaw.message.you": "Vous",
"kiloClaw.message.thinking": "Réflexion...",
"kiloClaw.setup.title": "KiloClaw",
"kiloClaw.setup.subtitle": "Une IA personnelle pour la vie de tous les jours",
"kiloClaw.setup.description1":
"KiloClaw vous offre une IA personnelle qui lit vos e-mails, gère votre calendrier, surveille vos projets et vit dans Telegram, Slack \u2014 ou tout ce que vous utilisez déjà.",
"kiloClaw.setup.description2":
"Aucune application à installer. Aucune nouvelle interface à apprendre. Envoyez-lui simplement des messages comme à un ami.",
"kiloClaw.setup.learnMore": "En savoir plus",
"kiloClaw.setup.tryKiloClaw": "Essayer KiloClaw",
"kiloClaw.upgrade.title": "KiloClaw Chat nécessite une mise à niveau",
"kiloClaw.upgrade.description1": "Cette instance a été provisionnée avant l'activation du chat.",
"kiloClaw.upgrade.description2.before": "Utilisez le bouton ",
"kiloClaw.upgrade.description2.bold": "Mettre à niveau vers la dernière version",
"kiloClaw.upgrade.description2.after":
" sur le tableau de bord KiloClaw pour activer le chat en temps réel avec votre bot.",
"kiloClaw.upgrade.openDashboard": "Ouvrir le tableau de bord",
"kiloClaw.sidebar.title": "KiloClaw",
"kiloClaw.sidebar.instance": "Instance",
"kiloClaw.sidebar.unknown": "Inconnu",
"kiloClaw.sidebar.bot": "Bot",
"kiloClaw.sidebar.details": "Détails",
"kiloClaw.sidebar.region": "Région",
"kiloClaw.sidebar.version": "Version",
"kiloClaw.sidebar.channels": "Canaux",
"kiloClaw.sidebar.noData": "Aucune donnée d'instance",
}
+36
View File
@@ -0,0 +1,36 @@
export const dict = {
"kiloClaw.loading": "KiloClaw を読み込んでいます...",
"kiloClaw.chat.connecting": "接続中...",
"kiloClaw.chat.notRunning": "インスタンスが実行されていません",
"kiloClaw.chat.placeholder": "メッセージを入力... (Enter で送信)",
"kiloClaw.chat.online": "オンライン",
"kiloClaw.chat.offline": "オフライン",
"kiloClaw.chat.empty": "まだメッセージがありません。挨拶してみましょう!",
"kiloClaw.chat.send": "送信",
"kiloClaw.message.bot": "KiloClaw",
"kiloClaw.message.you": "あなた",
"kiloClaw.message.thinking": "考え中...",
"kiloClaw.setup.title": "KiloClaw",
"kiloClaw.setup.subtitle": "日常生活のためのパーソナル AI",
"kiloClaw.setup.description1":
"KiloClaw は、メールを読み、カレンダーを管理し、プロジェクトを監視し、Telegram や Slack など普段使っているツールで動作するパーソナル AI を提供します。",
"kiloClaw.setup.description2":
"アプリのインストール不要。新しいインターフェースの学習不要。友達にメッセージするように話しかけるだけ。",
"kiloClaw.setup.learnMore": "詳しくはこちら",
"kiloClaw.setup.tryKiloClaw": "KiloClaw を試す",
"kiloClaw.upgrade.title": "KiloClaw チャットにはアップグレードが必要です",
"kiloClaw.upgrade.description1": "このインスタンスはチャットが有効になる前にプロビジョニングされました。",
"kiloClaw.upgrade.description2.before": "KiloClaw ダッシュボードの",
"kiloClaw.upgrade.description2.bold": "最新版にアップグレード",
"kiloClaw.upgrade.description2.after": "ボタンを使用して、ボットとのリアルタイムチャットを有効にしてください。",
"kiloClaw.upgrade.openDashboard": "ダッシュボードを開く",
"kiloClaw.sidebar.title": "KiloClaw",
"kiloClaw.sidebar.instance": "インスタンス",
"kiloClaw.sidebar.unknown": "不明",
"kiloClaw.sidebar.bot": "ボット",
"kiloClaw.sidebar.details": "詳細",
"kiloClaw.sidebar.region": "リージョン",
"kiloClaw.sidebar.version": "バージョン",
"kiloClaw.sidebar.channels": "チャンネル",
"kiloClaw.sidebar.noData": "インスタンスデータがありません",
}
+36
View File
@@ -0,0 +1,36 @@
export const dict = {
"kiloClaw.loading": "KiloClaw 로딩 중...",
"kiloClaw.chat.connecting": "연결 중...",
"kiloClaw.chat.notRunning": "인스턴스가 실행 중이 아닙니다",
"kiloClaw.chat.placeholder": "메시지를 입력하세요... (Enter로 전송)",
"kiloClaw.chat.online": "온라인",
"kiloClaw.chat.offline": "오프라인",
"kiloClaw.chat.empty": "아직 메시지가 없습니다. 인사해 보세요!",
"kiloClaw.chat.send": "전송",
"kiloClaw.message.bot": "KiloClaw",
"kiloClaw.message.you": "나",
"kiloClaw.message.thinking": "생각 중...",
"kiloClaw.setup.title": "KiloClaw",
"kiloClaw.setup.subtitle": "일상을 위한 개인 AI",
"kiloClaw.setup.description1":
"KiloClaw는 이메일을 읽고, 캘린더를 관리하고, 프로젝트를 모니터링하며, Telegram, Slack 등 이미 사용 중인 도구에서 작동하는 개인 AI를 제공합니다.",
"kiloClaw.setup.description2":
"설치할 앱이 없습니다. 배울 새로운 인터페이스도 없습니다. 친구에게 메시지하듯 대화하세요.",
"kiloClaw.setup.learnMore": "자세히 보기",
"kiloClaw.setup.tryKiloClaw": "KiloClaw 사용해 보기",
"kiloClaw.upgrade.title": "KiloClaw 채팅에 업그레이드가 필요합니다",
"kiloClaw.upgrade.description1": "이 인스턴스는 채팅이 활성화되기 전에 프로비저닝되었습니다.",
"kiloClaw.upgrade.description2.before": "KiloClaw 대시보드에서 ",
"kiloClaw.upgrade.description2.bold": "최신 버전으로 업그레이드",
"kiloClaw.upgrade.description2.after": " 버튼을 사용하여 봇과의 실시간 채팅을 활성화하세요.",
"kiloClaw.upgrade.openDashboard": "대시보드 열기",
"kiloClaw.sidebar.title": "KiloClaw",
"kiloClaw.sidebar.instance": "인스턴스",
"kiloClaw.sidebar.unknown": "알 수 없음",
"kiloClaw.sidebar.bot": "봇",
"kiloClaw.sidebar.details": "상세 정보",
"kiloClaw.sidebar.region": "리전",
"kiloClaw.sidebar.version": "버전",
"kiloClaw.sidebar.channels": "채널",
"kiloClaw.sidebar.noData": "인스턴스 데이터 없음",
}
+36
View File
@@ -0,0 +1,36 @@
export const dict = {
"kiloClaw.loading": "KiloClaw laden...",
"kiloClaw.chat.connecting": "Verbinden...",
"kiloClaw.chat.notRunning": "Instantie is niet actief",
"kiloClaw.chat.placeholder": "Typ een bericht... (Enter om te verzenden)",
"kiloClaw.chat.online": "Online",
"kiloClaw.chat.offline": "Offline",
"kiloClaw.chat.empty": "Nog geen berichten. Zeg hallo!",
"kiloClaw.chat.send": "Verzenden",
"kiloClaw.message.bot": "KiloClaw",
"kiloClaw.message.you": "Jij",
"kiloClaw.message.thinking": "Aan het nadenken...",
"kiloClaw.setup.title": "KiloClaw",
"kiloClaw.setup.subtitle": "Persoonlijke AI voor het dagelijks leven",
"kiloClaw.setup.description1":
"KiloClaw biedt je een persoonlijke AI die e-mails leest, je agenda beheert, je projecten bewaakt, en actief is in Telegram, Slack \u2014 of wat je al gebruikt.",
"kiloClaw.setup.description2":
"Geen app om te installeren. Geen nieuwe interface om te leren. Stuur gewoon een berichtje zoals je naar een vriend zou doen.",
"kiloClaw.setup.learnMore": "Meer informatie",
"kiloClaw.setup.tryKiloClaw": "Probeer KiloClaw",
"kiloClaw.upgrade.title": "KiloClaw Chat vereist een upgrade",
"kiloClaw.upgrade.description1": "Deze instantie is ingericht voordat chat was ingeschakeld.",
"kiloClaw.upgrade.description2.before": "Gebruik de knop ",
"kiloClaw.upgrade.description2.bold": "Upgrade to Latest",
"kiloClaw.upgrade.description2.after": " op het KiloClaw-dashboard om realtime chat met je bot te activeren.",
"kiloClaw.upgrade.openDashboard": "Dashboard openen",
"kiloClaw.sidebar.title": "KiloClaw",
"kiloClaw.sidebar.instance": "Instantie",
"kiloClaw.sidebar.unknown": "Onbekend",
"kiloClaw.sidebar.bot": "Bot",
"kiloClaw.sidebar.details": "Details",
"kiloClaw.sidebar.region": "Regio",
"kiloClaw.sidebar.version": "Versie",
"kiloClaw.sidebar.channels": "Kanalen",
"kiloClaw.sidebar.noData": "Geen instantiegegevens",
}
+36
View File
@@ -0,0 +1,36 @@
export const dict = {
"kiloClaw.loading": "Laster KiloClaw...",
"kiloClaw.chat.connecting": "Kobler til...",
"kiloClaw.chat.notRunning": "Instansen kjører ikke",
"kiloClaw.chat.placeholder": "Skriv en melding... (Enter for å sende)",
"kiloClaw.chat.online": "Pålogget",
"kiloClaw.chat.offline": "Frakoblet",
"kiloClaw.chat.empty": "Ingen meldinger ennå. Si hei!",
"kiloClaw.chat.send": "Send",
"kiloClaw.message.bot": "KiloClaw",
"kiloClaw.message.you": "Du",
"kiloClaw.message.thinking": "Tenker...",
"kiloClaw.setup.title": "KiloClaw",
"kiloClaw.setup.subtitle": "Personlig AI for hverdagen",
"kiloClaw.setup.description1":
"KiloClaw gir deg en personlig AI som leser e-post, administrerer kalenderen din, overvåker prosjektene dine og lever i Telegram, Slack \u2014 uansett hva du allerede bruker.",
"kiloClaw.setup.description2":
"Ingen app å installere. Ingen nytt grensesnitt å lære. Bare skriv til den som en venn.",
"kiloClaw.setup.learnMore": "Lær mer",
"kiloClaw.setup.tryKiloClaw": "Prøv KiloClaw",
"kiloClaw.upgrade.title": "KiloClaw Chat krever en oppgradering",
"kiloClaw.upgrade.description1": "Denne instansen ble klargjort før chat ble aktivert.",
"kiloClaw.upgrade.description2.before": "Bruk ",
"kiloClaw.upgrade.description2.bold": "Oppgrader til nyeste",
"kiloClaw.upgrade.description2.after": "-knappen på KiloClaw-dashbordet for å aktivere sanntidschat med boten din.",
"kiloClaw.upgrade.openDashboard": "Åpne dashbord",
"kiloClaw.sidebar.title": "KiloClaw",
"kiloClaw.sidebar.instance": "Instans",
"kiloClaw.sidebar.unknown": "Ukjent",
"kiloClaw.sidebar.bot": "Bot",
"kiloClaw.sidebar.details": "Detaljer",
"kiloClaw.sidebar.region": "Region",
"kiloClaw.sidebar.version": "Versjon",
"kiloClaw.sidebar.channels": "Kanaler",
"kiloClaw.sidebar.noData": "Ingen instansdata",
}
+36
View File
@@ -0,0 +1,36 @@
export const dict = {
"kiloClaw.loading": "Ładowanie KiloClaw...",
"kiloClaw.chat.connecting": "Łączenie...",
"kiloClaw.chat.notRunning": "Instancja nie jest uruchomiona",
"kiloClaw.chat.placeholder": "Wpisz wiadomość... (Enter, aby wysłać)",
"kiloClaw.chat.online": "Dostępny",
"kiloClaw.chat.offline": "Niedostępny",
"kiloClaw.chat.empty": "Brak wiadomości. Przywitaj się!",
"kiloClaw.chat.send": "Wyślij",
"kiloClaw.message.bot": "KiloClaw",
"kiloClaw.message.you": "Ty",
"kiloClaw.message.thinking": "Myślenie...",
"kiloClaw.setup.title": "KiloClaw",
"kiloClaw.setup.subtitle": "Osobista SI na co dzień",
"kiloClaw.setup.description1":
"KiloClaw zapewnia osobistą sztuczną inteligencję, która czyta e-maile, zarządza kalendarzem, monitoruje projekty i jest dostępna w Telegramie, Slacku \u2014 czyli tam, gdzie już jesteś.",
"kiloClaw.setup.description2":
"Żadnych aplikacji do instalacji. Żadnego nowego interfejsu do nauki. Po prostu pisz do niej jak do przyjaciela.",
"kiloClaw.setup.learnMore": "Dowiedz się więcej",
"kiloClaw.setup.tryKiloClaw": "Wypróbuj KiloClaw",
"kiloClaw.upgrade.title": "KiloClaw Chat wymaga aktualizacji",
"kiloClaw.upgrade.description1": "Ta instancja została utworzona przed włączeniem czatu.",
"kiloClaw.upgrade.description2.before": "Użyj przycisku ",
"kiloClaw.upgrade.description2.bold": "Zaktualizuj do najnowszej wersji",
"kiloClaw.upgrade.description2.after": " w panelu KiloClaw, aby aktywować czat w czasie rzeczywistym z botem.",
"kiloClaw.upgrade.openDashboard": "Otwórz panel",
"kiloClaw.sidebar.title": "KiloClaw",
"kiloClaw.sidebar.instance": "Instancja",
"kiloClaw.sidebar.unknown": "Nieznany",
"kiloClaw.sidebar.bot": "Bot",
"kiloClaw.sidebar.details": "Szczegóły",
"kiloClaw.sidebar.region": "Region",
"kiloClaw.sidebar.version": "Wersja",
"kiloClaw.sidebar.channels": "Kanały",
"kiloClaw.sidebar.noData": "Brak danych instancji",
}
+37
View File
@@ -0,0 +1,37 @@
export const dict = {
"kiloClaw.loading": "Загрузка KiloClaw...",
"kiloClaw.chat.connecting": "Подключение...",
"kiloClaw.chat.notRunning": "Экземпляр не запущен",
"kiloClaw.chat.placeholder": "Введите сообщение... (Enter для отправки)",
"kiloClaw.chat.online": "В сети",
"kiloClaw.chat.offline": "Не в сети",
"kiloClaw.chat.empty": "Пока нет сообщений. Поздоровайтесь!",
"kiloClaw.chat.send": "Отправить",
"kiloClaw.message.bot": "KiloClaw",
"kiloClaw.message.you": "Вы",
"kiloClaw.message.thinking": "Думает...",
"kiloClaw.setup.title": "KiloClaw",
"kiloClaw.setup.subtitle": "Персональный ИИ для повседневной жизни",
"kiloClaw.setup.description1":
"KiloClaw предоставляет вам персональный ИИ, который читает электронную почту, управляет календарем, следит за вашими проектами и живет в Telegram, Slack \u2014 во всем, что вы уже используете.",
"kiloClaw.setup.description2":
"Не нужно устанавливать приложение. Не нужно изучать новый интерфейс. Просто напишите ему, как другу.",
"kiloClaw.setup.learnMore": "Узнать больше",
"kiloClaw.setup.tryKiloClaw": "Попробовать KiloClaw",
"kiloClaw.upgrade.title": "Чат KiloClaw требует обновления",
"kiloClaw.upgrade.description1": "Этот экземпляр был развернут до того, как был включен чат.",
"kiloClaw.upgrade.description2.before": "Используйте кнопку ",
"kiloClaw.upgrade.description2.bold": "Upgrade to Latest",
"kiloClaw.upgrade.description2.after":
" на панели управления KiloClaw, чтобы активировать чат в реальном времени с вашим ботом.",
"kiloClaw.upgrade.openDashboard": "Открыть панель управления",
"kiloClaw.sidebar.title": "KiloClaw",
"kiloClaw.sidebar.instance": "Экземпляр",
"kiloClaw.sidebar.unknown": "Неизвестно",
"kiloClaw.sidebar.bot": "Бот",
"kiloClaw.sidebar.details": "Детали",
"kiloClaw.sidebar.region": "Регион",
"kiloClaw.sidebar.version": "Версия",
"kiloClaw.sidebar.channels": "Каналы",
"kiloClaw.sidebar.noData": "Нет данных об экземпляре",
}
+35
View File
@@ -0,0 +1,35 @@
export const dict = {
"kiloClaw.loading": "กำลังโหลด KiloClaw...",
"kiloClaw.chat.connecting": "กำลังเชื่อมต่อ...",
"kiloClaw.chat.notRunning": "อินสแตนซ์ไม่ได้ทำงานอยู่",
"kiloClaw.chat.placeholder": "พิมพ์ข้อความ... (กด Enter เพื่อส่ง)",
"kiloClaw.chat.online": "ออนไลน์",
"kiloClaw.chat.offline": "ออฟไลน์",
"kiloClaw.chat.empty": "ยังไม่มีข้อความ ทักทายสิ!",
"kiloClaw.chat.send": "ส่ง",
"kiloClaw.message.bot": "KiloClaw",
"kiloClaw.message.you": "คุณ",
"kiloClaw.message.thinking": "กำลังคิด...",
"kiloClaw.setup.title": "KiloClaw",
"kiloClaw.setup.subtitle": "AI ส่วนตัวสำหรับชีวิตประจำวัน",
"kiloClaw.setup.description1":
"KiloClaw ให้ AI ส่วนตัวที่อ่านอีเมล จัดการปฏิทิน ตรวจสอบโปรเจกต์ของคุณ และอยู่ใน Telegram, Slack \u2014 หรืออะไรก็ตามที่คุณใช้อยู่แล้ว",
"kiloClaw.setup.description2": "ไม่ต้องติดตั้งแอป ไม่ต้องเรียนรู้อินเทอร์เฟซใหม่ แค่ส่งข้อความคุยเหมือนเป็นเพื่อน",
"kiloClaw.setup.learnMore": "เรียนรู้เพิ่มเติม",
"kiloClaw.setup.tryKiloClaw": "ลองใช้ KiloClaw",
"kiloClaw.upgrade.title": "KiloClaw Chat ต้องได้รับการอัปเกรด",
"kiloClaw.upgrade.description1": "อินสแตนซ์นี้ถูกสร้างขึ้นก่อนที่จะเปิดใช้งานแชท",
"kiloClaw.upgrade.description2.before": "ใช้ปุ่ม ",
"kiloClaw.upgrade.description2.bold": "อัปเกรดเป็นเวอร์ชันล่าสุด",
"kiloClaw.upgrade.description2.after": " บนแดชบอร์ด KiloClaw เพื่อเปิดใช้งานแชทแบบเรียลไทม์กับบอทของคุณ",
"kiloClaw.upgrade.openDashboard": "เปิดแดชบอร์ด",
"kiloClaw.sidebar.title": "KiloClaw",
"kiloClaw.sidebar.instance": "อินสแตนซ์",
"kiloClaw.sidebar.unknown": "ไม่ทราบ",
"kiloClaw.sidebar.bot": "บอท",
"kiloClaw.sidebar.details": "รายละเอียด",
"kiloClaw.sidebar.region": "ภูมิภาค",
"kiloClaw.sidebar.version": "เวอร์ชัน",
"kiloClaw.sidebar.channels": "ช่องทาง",
"kiloClaw.sidebar.noData": "ไม่มีข้อมูลอินสแตนซ์",
}
+37
View File
@@ -0,0 +1,37 @@
export const dict = {
"kiloClaw.loading": "KiloClaw yükleniyor...",
"kiloClaw.chat.connecting": "Bağlanıyor...",
"kiloClaw.chat.notRunning": "Örnek çalışmıyor",
"kiloClaw.chat.placeholder": "Bir mesaj yazın... (Göndermek için Enter'a basın)",
"kiloClaw.chat.online": "Çevrimiçi",
"kiloClaw.chat.offline": "Çevrimdışı",
"kiloClaw.chat.empty": "Henüz mesaj yok. Merhaba deyin!",
"kiloClaw.chat.send": "Gönder",
"kiloClaw.message.bot": "KiloClaw",
"kiloClaw.message.you": "Siz",
"kiloClaw.message.thinking": "Düşünüyor...",
"kiloClaw.setup.title": "KiloClaw",
"kiloClaw.setup.subtitle": "Günlük yaşam için kişisel yapay zeka",
"kiloClaw.setup.description1":
"KiloClaw, e-postalarınızı okuyan, takviminizi yöneten, projelerinizi izleyen ve Telegram, Slack \u2014 halihazırda ne kullanıyorsanız orada yaşayan kişisel bir yapay zeka sunar.",
"kiloClaw.setup.description2":
"Yüklenecek bir uygulama yok. Öğrenilecek yeni bir arayüz yok. Sadece bir arkadaşınız gibi mesajlaşın.",
"kiloClaw.setup.learnMore": "Daha fazla bilgi edinin",
"kiloClaw.setup.tryKiloClaw": "KiloClaw'ı Deneyin",
"kiloClaw.upgrade.title": "KiloClaw Sohbeti yükseltme gerektiriyor",
"kiloClaw.upgrade.description1": "Bu örnek, sohbet etkinleştirilmeden önce oluşturulmuş.",
"kiloClaw.upgrade.description2.before":
"Botunuzla gerçek zamanlı sohbeti etkinleştirmek için KiloClaw kontrol panelindeki ",
"kiloClaw.upgrade.description2.bold": "Upgrade to Latest",
"kiloClaw.upgrade.description2.after": " düğmesini kullanın.",
"kiloClaw.upgrade.openDashboard": "Kontrol Panelini Aç",
"kiloClaw.sidebar.title": "KiloClaw",
"kiloClaw.sidebar.instance": "Örnek",
"kiloClaw.sidebar.unknown": "Bilinmiyor",
"kiloClaw.sidebar.bot": "Bot",
"kiloClaw.sidebar.details": "Ayrıntılar",
"kiloClaw.sidebar.region": "Bölge",
"kiloClaw.sidebar.version": "Sürüm",
"kiloClaw.sidebar.channels": "Kanallar",
"kiloClaw.sidebar.noData": "Örnek verisi yok",
}
+35
View File
@@ -0,0 +1,35 @@
export const dict = {
"kiloClaw.loading": "正在加载 KiloClaw...",
"kiloClaw.chat.connecting": "连接中...",
"kiloClaw.chat.notRunning": "实例未运行",
"kiloClaw.chat.placeholder": "输入消息... (按 Enter 键发送)",
"kiloClaw.chat.online": "在线",
"kiloClaw.chat.offline": "离线",
"kiloClaw.chat.empty": "暂无消息。打个招呼吧!",
"kiloClaw.chat.send": "发送",
"kiloClaw.message.bot": "KiloClaw",
"kiloClaw.message.you": "你",
"kiloClaw.message.thinking": "思考中...",
"kiloClaw.setup.title": "KiloClaw",
"kiloClaw.setup.subtitle": "日常生活中的个人 AI",
"kiloClaw.setup.description1":
"KiloClaw 为你提供一个个人 AI,可以阅读邮件、管理日历、监控项目,并且存在于 Telegram、Slack \u2014 任何你已经在使用的平台中。",
"kiloClaw.setup.description2": "无需安装应用。无需学习新界面。只需像朋友一样跟它发消息。",
"kiloClaw.setup.learnMore": "了解更多",
"kiloClaw.setup.tryKiloClaw": "尝试 KiloClaw",
"kiloClaw.upgrade.title": "KiloClaw 聊天需要升级",
"kiloClaw.upgrade.description1": "此实例是在启用聊天功能之前配置的。",
"kiloClaw.upgrade.description2.before": "使用 KiloClaw 仪表板上的 ",
"kiloClaw.upgrade.description2.bold": "升级到最新版本",
"kiloClaw.upgrade.description2.after": " 按钮来激活与你的机器人的实时聊天。",
"kiloClaw.upgrade.openDashboard": "打开仪表板",
"kiloClaw.sidebar.title": "KiloClaw",
"kiloClaw.sidebar.instance": "实例",
"kiloClaw.sidebar.unknown": "未知",
"kiloClaw.sidebar.bot": "机器人",
"kiloClaw.sidebar.details": "详情",
"kiloClaw.sidebar.region": "区域",
"kiloClaw.sidebar.version": "版本",
"kiloClaw.sidebar.channels": "频道",
"kiloClaw.sidebar.noData": "无实例数据",
}
+35
View File
@@ -0,0 +1,35 @@
export const dict = {
"kiloClaw.loading": "載入 KiloClaw 中...",
"kiloClaw.chat.connecting": "連線中...",
"kiloClaw.chat.notRunning": "執行個體未在執行",
"kiloClaw.chat.placeholder": "輸入訊息... (按 Enter 傳送)",
"kiloClaw.chat.online": "上線",
"kiloClaw.chat.offline": "離線",
"kiloClaw.chat.empty": "尚無訊息。打個招呼吧!",
"kiloClaw.chat.send": "傳送",
"kiloClaw.message.bot": "KiloClaw",
"kiloClaw.message.you": "你",
"kiloClaw.message.thinking": "思考中...",
"kiloClaw.setup.title": "KiloClaw",
"kiloClaw.setup.subtitle": "日常生活中的個人 AI",
"kiloClaw.setup.description1":
"KiloClaw 為您提供專屬的個人 AI,它可以閱讀電子郵件、管理行事曆、監控專案,並存在於 Telegram、Slack \u2014 或任何您已經在使用的應用程式中。",
"kiloClaw.setup.description2": "無須安裝應用程式。無須學習新介面。就像朋友一樣傳訊息給它即可。",
"kiloClaw.setup.learnMore": "了解更多",
"kiloClaw.setup.tryKiloClaw": "試用 KiloClaw",
"kiloClaw.upgrade.title": "KiloClaw 聊天功能需要升級",
"kiloClaw.upgrade.description1": "此執行個體是在啟用聊天功能之前所建立的。",
"kiloClaw.upgrade.description2.before": "使用 KiloClaw 儀表板上的 ",
"kiloClaw.upgrade.description2.bold": "升級至最新版",
"kiloClaw.upgrade.description2.after": " 按鈕,以啟用與您的機器人進行即時聊天的功能。",
"kiloClaw.upgrade.openDashboard": "開啟儀表板",
"kiloClaw.sidebar.title": "KiloClaw",
"kiloClaw.sidebar.instance": "執行個體",
"kiloClaw.sidebar.unknown": "未知",
"kiloClaw.sidebar.bot": "機器人",
"kiloClaw.sidebar.details": "詳細資訊",
"kiloClaw.sidebar.region": "地區",
"kiloClaw.sidebar.version": "版本",
"kiloClaw.sidebar.channels": "頻道",
"kiloClaw.sidebar.noData": "無執行個體資料",
}
@@ -0,0 +1,11 @@
// KiloClaw SolidJS webview entry point
import { render } from "solid-js/web"
import "@kilocode/kilo-ui/styles"
import "./kiloclaw.css"
import { KiloClawApp } from "./KiloClawApp"
const root = document.getElementById("root")
if (root) {
render(() => <KiloClawApp />, root)
}
@@ -0,0 +1,381 @@
/* KiloClaw webview styles */
/* Root */
.kiloclaw-root {
display: flex;
flex-direction: column;
height: 100%;
position: relative;
overflow: hidden;
}
/* Main layout: chat (left) + sidebar (right) */
.kiloclaw-layout {
display: flex;
flex-direction: row;
height: 100%;
overflow: hidden;
}
/* Center layout for setup/upgrade/loading */
.kiloclaw-center {
display: flex;
align-items: center;
justify-content: center;
height: 100%;
padding: 24px;
}
/* Loading */
.kiloclaw-loading {
display: flex;
flex-direction: column;
align-items: center;
gap: 12px;
color: var(--vscode-descriptionForeground);
}
.kiloclaw-spinner {
width: 24px;
height: 24px;
border: 2px solid var(--vscode-progressBar-background, #0078d4);
border-top-color: transparent;
border-radius: 50%;
animation: kiloclaw-spin 0.8s linear infinite;
}
@keyframes kiloclaw-spin {
to {
transform: rotate(360deg);
}
}
/* Card (setup/upgrade views) */
.kiloclaw-card {
max-width: 480px;
width: 100%;
padding: 32px;
border-radius: 8px;
background: var(--vscode-editorWidget-background, var(--vscode-editor-background));
border: 1px solid var(--vscode-editorWidget-border, var(--vscode-panel-border));
}
.kiloclaw-card-title {
margin: 0 0 4px;
font-size: 18px;
font-weight: 600;
color: var(--vscode-foreground);
}
.kiloclaw-card-subtitle {
margin: 0 0 16px;
font-size: 14px;
font-weight: 600;
color: var(--vscode-foreground);
}
.kiloclaw-card-text {
margin: 0 0 12px;
font-size: 13px;
line-height: 1.5;
color: var(--vscode-descriptionForeground);
}
.kiloclaw-card-actions {
display: flex;
gap: 12px;
margin-top: 20px;
align-items: center;
justify-content: space-between;
}
.kiloclaw-link-btn {
background: none;
border: none;
color: var(--vscode-textLink-foreground);
cursor: pointer;
font-size: 13px;
padding: 6px 0;
text-decoration: underline;
}
.kiloclaw-link-btn:hover {
color: var(--vscode-textLink-activeForeground);
}
.kiloclaw-primary-btn {
background: var(--vscode-button-background);
color: var(--vscode-button-foreground);
border: none;
border-radius: 4px;
padding: 8px 20px;
font-size: 13px;
cursor: pointer;
font-weight: 500;
}
.kiloclaw-primary-btn:hover {
background: var(--vscode-button-hoverBackground);
}
/* Chat panel */
.kiloclaw-chat {
flex: 1;
display: flex;
flex-direction: column;
min-width: 0;
overflow: hidden;
}
/* Chat header */
.kiloclaw-chat-header {
display: flex;
align-items: center;
justify-content: space-between;
padding: 10px 16px;
border-bottom: 1px solid var(--vscode-panel-border);
flex-shrink: 0;
}
.kiloclaw-chat-header-left {
display: flex;
align-items: center;
gap: 8px;
}
.kiloclaw-chat-header-title {
font-size: 13px;
font-weight: 500;
color: var(--vscode-foreground);
}
/* Status dots */
.kiloclaw-dot {
display: inline-block;
width: 8px;
height: 8px;
border-radius: 50%;
flex-shrink: 0;
}
.kiloclaw-dot-online {
background: var(--vscode-testing-iconPassed, #73c991);
}
.kiloclaw-dot-offline {
background: var(--vscode-descriptionForeground, #888);
}
.kiloclaw-dot-warning {
background: var(--vscode-editorWarning-foreground, #cca700);
}
.kiloclaw-dot-error {
background: var(--vscode-editorError-foreground, #f14c4c);
}
/* Messages area */
.kiloclaw-messages {
flex: 1;
overflow-y: auto;
padding: 16px;
display: flex;
flex-direction: column;
gap: 4px;
}
.kiloclaw-empty {
color: var(--vscode-descriptionForeground);
font-size: 13px;
text-align: center;
padding: 40px 0;
}
/* Message bubble */
.kiloclaw-msg {
padding: 8px 12px;
border-radius: 6px;
max-width: 85%;
font-size: 13px;
line-height: 1.5;
}
.kiloclaw-msg-user {
align-self: flex-end;
background: var(--vscode-button-background);
color: var(--vscode-button-foreground);
border-bottom-right-radius: 2px;
}
.kiloclaw-msg-bot {
align-self: flex-start;
background: var(--vscode-editorWidget-background, var(--vscode-editor-background));
color: var(--vscode-foreground);
border: 1px solid var(--vscode-panel-border);
border-bottom-left-radius: 2px;
}
.kiloclaw-msg-header {
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: 4px;
gap: 12px;
}
.kiloclaw-msg-author {
font-weight: 600;
font-size: 11px;
opacity: 0.8;
}
.kiloclaw-msg-time {
font-size: 10px;
opacity: 0.6;
}
.kiloclaw-msg-body {
white-space: pre-wrap;
word-break: break-word;
}
.kiloclaw-msg-thinking {
color: var(--vscode-descriptionForeground);
font-style: italic;
}
/* Input area */
.kiloclaw-input-wrap {
display: flex;
align-items: flex-end;
gap: 8px;
padding: 12px 16px;
border-top: 1px solid var(--vscode-panel-border);
flex-shrink: 0;
}
.kiloclaw-input {
flex: 1;
resize: none;
border: 1px solid var(--vscode-input-border, var(--vscode-panel-border));
border-radius: 4px;
background: var(--vscode-input-background);
color: var(--vscode-input-foreground);
padding: 8px 12px;
font-size: 13px;
font-family: var(--vscode-font-family);
line-height: 1.4;
max-height: 120px;
overflow-y: auto;
}
.kiloclaw-input:focus {
outline: none;
border-color: var(--vscode-focusBorder);
}
.kiloclaw-input:disabled {
opacity: 0.5;
cursor: not-allowed;
}
.kiloclaw-input::placeholder {
color: var(--vscode-input-placeholderForeground);
}
.kiloclaw-send-btn {
background: var(--vscode-button-background);
color: var(--vscode-button-foreground);
border: none;
border-radius: 4px;
padding: 8px 16px;
font-size: 13px;
cursor: pointer;
font-weight: 500;
flex-shrink: 0;
}
.kiloclaw-send-btn:hover:not(:disabled) {
background: var(--vscode-button-hoverBackground);
}
.kiloclaw-send-btn:disabled {
opacity: 0.5;
cursor: not-allowed;
}
/* Status sidebar */
.kiloclaw-sidebar {
width: 260px;
flex-shrink: 0;
border-left: 1px solid var(--vscode-panel-border);
padding: 16px;
overflow-y: auto;
background: var(--vscode-sideBar-background, var(--vscode-editor-background));
}
.kiloclaw-sidebar-title {
margin: 0 0 16px;
font-size: 14px;
font-weight: 600;
color: var(--vscode-foreground);
}
.kiloclaw-sidebar-section {
margin-bottom: 16px;
}
.kiloclaw-sidebar-label {
font-size: 11px;
font-weight: 600;
text-transform: uppercase;
letter-spacing: 0.5px;
color: var(--vscode-descriptionForeground);
margin-bottom: 6px;
}
.kiloclaw-sidebar-row {
display: flex;
align-items: center;
gap: 8px;
font-size: 13px;
color: var(--vscode-foreground);
}
.kiloclaw-sidebar-detail {
display: flex;
justify-content: space-between;
font-size: 13px;
color: var(--vscode-foreground);
padding: 2px 0;
}
.kiloclaw-sidebar-muted {
color: var(--vscode-descriptionForeground);
}
/* Toast notification */
.kiloclaw-toast {
position: absolute;
bottom: 16px;
left: 50%;
transform: translateX(-50%);
background: var(--vscode-editorError-foreground, #f14c4c);
color: #fff;
padding: 8px 16px;
border-radius: 4px;
font-size: 13px;
z-index: 100;
animation: kiloclaw-fadeIn 0.2s ease;
}
@keyframes kiloclaw-fadeIn {
from {
opacity: 0;
transform: translateX(-50%) translateY(8px);
}
to {
opacity: 1;
transform: translateX(-50%) translateY(0);
}
}
@@ -0,0 +1,51 @@
/**
* KiloClaw webview types.
*
* Mirrors the extension host types for use in the SolidJS webview.
* All data arrives via postMessage — no direct network access.
*
* SYNC: These types are mirrored from src/kiloclaw/types.ts — keep both in sync.
*/
export type ClawStatus = {
status: "provisioned" | "starting" | "restarting" | "running" | "stopped" | "destroying" | null
sandboxId?: string
flyRegion?: string
machineSize?: { cpus: number; memory_mb: number }
openclawVersion?: string | null
lastStartedAt?: string | null
lastStoppedAt?: string | null
channelCount?: number
secretCount?: number
}
export type ChatMessage = {
id: string
text: string
user: string
created: string
bot: boolean
}
export type KiloClawState =
| { phase: "loading"; locale: string }
| { phase: "noInstance"; locale: string }
| { phase: "needsUpgrade"; locale: string }
| {
phase: "ready"
locale: string
status: ClawStatus | null
connected: boolean
online: boolean
messages: ChatMessage[]
}
// Messages: Extension Host -> Webview
export type KiloClawOutMessage =
| { type: "kiloclaw.state"; state: KiloClawState }
| { type: "kiloclaw.message"; message: ChatMessage }
| { type: "kiloclaw.messageUpdated"; message: ChatMessage }
| { type: "kiloclaw.presence"; online: boolean }
| { type: "kiloclaw.status"; data: ClawStatus | null }
| { type: "kiloclaw.locale"; locale: string }
| { type: "kiloclaw.error"; error: string }
@@ -12,5 +12,5 @@
"esModuleInterop": true,
"skipLibCheck": true
},
"include": ["src/**/*", "agent-manager/**/*"]
"include": ["src/**/*", "agent-manager/**/*", "kiloclaw/**/*"]
}
@@ -0,0 +1,446 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
<svg version="1.1" id="Layer_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px" width="384px" height="86px" viewBox="0 0 384 86" enable-background="new 0 0 384 86" xml:space="preserve"> <image id="image0" width="384" height="86" x="0" y="0"
xlink:href="data:image/png;base64,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
6CoAAAAASUVORK5CYII=" />
</svg>

After

Width:  |  Height:  |  Size: 34 KiB