mirror of
https://github.com/Kilo-Org/kilocode.git
synced 2026-09-24 16:02:55 +08:00
Merge pull request #10163 from Kilo-Org/disco-trapezoid
fix(jetbrains): address history review gaps + add cloud repo-only filter
This commit is contained in:
@@ -43,6 +43,7 @@ openApiGenerate {
|
||||
"anyOf<>" to "kotlin.Any",
|
||||
"number" to "kotlin.Double",
|
||||
"decimal" to "kotlin.Double",
|
||||
"integer" to "kotlin.Long",
|
||||
))
|
||||
openapiNormalizer.set(mapOf(
|
||||
"SIMPLIFY_ANYOF_STRING_AND_ENUM_STRING" to "true",
|
||||
|
||||
+41
-8
@@ -21,6 +21,7 @@ import kotlinx.coroutines.flow.asStateFlow
|
||||
import kotlinx.coroutines.flow.update
|
||||
import kotlinx.coroutines.launch
|
||||
import kotlinx.serialization.json.JsonPrimitive
|
||||
import okhttp3.HttpUrl.Companion.toHttpUrl
|
||||
import okhttp3.MediaType.Companion.toMediaType
|
||||
import okhttp3.OkHttpClient
|
||||
import okhttp3.Request
|
||||
@@ -156,6 +157,36 @@ class KiloBackendSessionManager(
|
||||
directories.remove(id)
|
||||
}
|
||||
|
||||
/**
|
||||
* Rename a session by sending `PATCH /session/{id}?directory={dir}` with `{"title":"..."}`.
|
||||
*
|
||||
* Uses raw HTTP because the generated Kotlin client is build-time only and
|
||||
* this repo already uses raw HTTP for session create and cloud operations.
|
||||
*/
|
||||
fun rename(id: String, dir: String, title: String): SessionDto {
|
||||
val h = http ?: throw IllegalStateException("Session manager not started")
|
||||
val url = base ?: throw IllegalStateException("Session manager not started")
|
||||
val json = """{"title":"${escape(title)}"}"""
|
||||
val patch = url.toHttpUrl().newBuilder()
|
||||
.addPathSegment("session")
|
||||
.addPathSegment(id)
|
||||
.addQueryParameter("directory", dir)
|
||||
.build()
|
||||
val request = Request.Builder()
|
||||
.url(patch)
|
||||
.method("PATCH", json.toRequestBody("application/json".toMediaType()))
|
||||
.build()
|
||||
|
||||
h.newCall(request).execute().use { response ->
|
||||
val raw = response.body?.string()
|
||||
if (!response.isSuccessful) {
|
||||
log.warn("Session rename failed: HTTP ${response.code}, body=$raw")
|
||||
throw RuntimeException("Session rename failed: HTTP ${response.code} — $raw")
|
||||
}
|
||||
return KiloCliDataParser.parseSession(raw!!)
|
||||
}
|
||||
}
|
||||
|
||||
fun cloudSessions(dir: String, cursor: String?, limit: Int, gitUrl: String?): CloudSessionListDto {
|
||||
val h = http ?: throw IllegalStateException("Session manager not started")
|
||||
val url = base ?: throw IllegalStateException("Session manager not started")
|
||||
@@ -238,9 +269,9 @@ class KiloBackendSessionManager(
|
||||
),
|
||||
summary = s.summary?.let {
|
||||
SessionSummaryDto(
|
||||
additions = it.additions.toInt(),
|
||||
deletions = it.deletions.toInt(),
|
||||
files = it.files.toInt(),
|
||||
additions = it.additions.safeInt(),
|
||||
deletions = it.deletions.safeInt(),
|
||||
files = it.files.safeInt(),
|
||||
)
|
||||
},
|
||||
)
|
||||
@@ -259,9 +290,9 @@ class KiloBackendSessionManager(
|
||||
),
|
||||
summary = s.summary?.let {
|
||||
SessionSummaryDto(
|
||||
additions = it.additions.toInt(),
|
||||
deletions = it.deletions.toInt(),
|
||||
files = it.files.toInt(),
|
||||
additions = it.additions.safeInt(),
|
||||
deletions = it.deletions.safeInt(),
|
||||
files = it.files.safeInt(),
|
||||
)
|
||||
},
|
||||
)
|
||||
@@ -269,8 +300,8 @@ class KiloBackendSessionManager(
|
||||
private fun statusDto(s: SessionStatus) = SessionStatusDto(
|
||||
type = s.type.value,
|
||||
message = s.message.ifBlank { null },
|
||||
attempt = s.attempt.toInt(),
|
||||
next = s.next.toLong(),
|
||||
attempt = s.attempt.safeInt(),
|
||||
next = s.next,
|
||||
requestID = s.requestID.ifBlank { null },
|
||||
)
|
||||
|
||||
@@ -288,4 +319,6 @@ class KiloBackendSessionManager(
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun Long.safeInt() = coerceIn(Int.MIN_VALUE.toLong(), Int.MAX_VALUE.toLong()).toInt()
|
||||
}
|
||||
|
||||
+8
-6
@@ -200,8 +200,8 @@ object KiloCliDataParser {
|
||||
val file = d.str("file") ?: return@mapNotNull null
|
||||
DiffFileDto(
|
||||
file = file,
|
||||
additions = d.long("additions")?.toInt() ?: 0,
|
||||
deletions = d.long("deletions")?.toInt() ?: 0,
|
||||
additions = d.long("additions")?.safeInt() ?: 0,
|
||||
deletions = d.long("deletions")?.safeInt() ?: 0,
|
||||
patch = d.str("patch"),
|
||||
)
|
||||
} ?: emptyList()
|
||||
@@ -537,9 +537,9 @@ object KiloCliDataParser {
|
||||
),
|
||||
summary = summary?.let {
|
||||
SessionSummaryDto(
|
||||
additions = it.long("additions")?.toInt() ?: 0,
|
||||
deletions = it.long("deletions")?.toInt() ?: 0,
|
||||
files = it.long("files")?.toInt() ?: 0,
|
||||
additions = it.long("additions")?.safeInt() ?: 0,
|
||||
deletions = it.long("deletions")?.safeInt() ?: 0,
|
||||
files = it.long("files")?.safeInt() ?: 0,
|
||||
)
|
||||
},
|
||||
)
|
||||
@@ -564,7 +564,7 @@ object KiloCliDataParser {
|
||||
return SessionStatusDto(
|
||||
type = type,
|
||||
message = st.str("message"),
|
||||
attempt = st.long("attempt")?.toInt(),
|
||||
attempt = st.long("attempt")?.safeInt(),
|
||||
next = st.long("next"),
|
||||
requestID = st.str("requestID"),
|
||||
)
|
||||
@@ -679,6 +679,8 @@ private fun JsonObject.num(key: String): Double? =
|
||||
private fun JsonObject.long(key: String): Long? =
|
||||
this[key]?.jsonPrimitive?.longOrNull
|
||||
|
||||
private fun Long.safeInt() = coerceIn(Int.MIN_VALUE.toLong(), Int.MAX_VALUE.toLong()).toInt()
|
||||
|
||||
private fun JsonObject?.map(key: String): Map<String, String> {
|
||||
val obj = this?.get(key)?.jsonObject ?: return emptyMap()
|
||||
return obj.entries.mapNotNull { (name, value) ->
|
||||
|
||||
+5
@@ -71,6 +71,11 @@ class KiloSessionRpcApiImpl : KiloSessionRpcApi {
|
||||
workspaces.get(dir).deleteSession(id)
|
||||
}
|
||||
|
||||
override suspend fun rename(id: String, directory: String, title: String): ai.kilocode.rpc.dto.SessionDto {
|
||||
val dir = sessions.getDirectory(id, directory)
|
||||
return sessions.rename(id, dir, title)
|
||||
}
|
||||
|
||||
override suspend fun cloudSessions(directory: String, cursor: String?, limit: Int, gitUrl: String?): CloudSessionListDto =
|
||||
sessions.cloudSessions(directory, cursor, limit, gitUrl)
|
||||
|
||||
|
||||
+157
@@ -20,6 +20,7 @@ import kotlin.test.AfterTest
|
||||
import kotlin.test.Test
|
||||
import kotlin.test.assertEquals
|
||||
import kotlin.test.assertFailsWith
|
||||
import kotlin.test.assertFalse
|
||||
import kotlin.test.assertNotNull
|
||||
import kotlin.test.assertTrue
|
||||
|
||||
@@ -472,6 +473,134 @@ class KiloBackendSessionManagerTest {
|
||||
assertNotNull(result)
|
||||
}
|
||||
|
||||
// ------ Session rename ------
|
||||
|
||||
@Test
|
||||
fun `rename patches session title and returns updated session`() = runBlocking {
|
||||
mock.sessionRenameResponse = """{
|
||||
"id": "ses_1",
|
||||
"slug": "s1",
|
||||
"projectID": "prj_test",
|
||||
"directory": "/test",
|
||||
"title": "New Name",
|
||||
"version": "1.0.0",
|
||||
"time": {"created": 1000, "updated": 2000}
|
||||
}"""
|
||||
val app = setup()
|
||||
ready(app)
|
||||
|
||||
val session = app.sessions.rename("ses_1", "/test", "New Name")
|
||||
|
||||
assertEquals("ses_1", session.id)
|
||||
assertEquals("New Name", session.title)
|
||||
val path = mock.lastSessionRenamePath ?: error("missing rename request")
|
||||
assertTrue(path.startsWith("/session/ses_1?"), "Expected /session/ses_1?... got $path")
|
||||
assertTrue(path.contains("directory=%2Ftest"), "Expected directory=/test in $path")
|
||||
assertEquals("PATCH", mock.lastSessionRenameMethod)
|
||||
assertEquals("""{"title":"New Name"}""", mock.lastSessionRenameBody)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `rename response preserves directory parentId summary and timestamps`() = runBlocking {
|
||||
mock.sessionRenameResponse = """{
|
||||
"id": "ses_1",
|
||||
"slug": "s1",
|
||||
"projectID": "prj_test",
|
||||
"directory": "/worktree/path",
|
||||
"title": "Renamed",
|
||||
"version": "2.0.0",
|
||||
"time": {"created": 1000, "updated": 9999},
|
||||
"parentID": "ses_parent",
|
||||
"summary": {"additions": 5, "deletions": 3, "files": 2}
|
||||
}"""
|
||||
val app = setup()
|
||||
ready(app)
|
||||
|
||||
val session = app.sessions.rename("ses_1", "/test", "Renamed")
|
||||
|
||||
assertEquals("/worktree/path", session.directory)
|
||||
assertEquals("ses_parent", session.parentID)
|
||||
assertEquals(1000.0, session.time.created)
|
||||
assertEquals(9999.0, session.time.updated)
|
||||
assertNotNull(session.summary)
|
||||
assertEquals(5, session.summary!!.additions)
|
||||
assertEquals(3, session.summary!!.deletions)
|
||||
assertEquals(2, session.summary!!.files)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `rename url-encodes session id and directory for special characters`() = runBlocking {
|
||||
// Session IDs/directories may contain spaces, slashes, plus signs, and ampersands
|
||||
val app = setup()
|
||||
ready(app)
|
||||
|
||||
app.sessions.rename("ses_a/b c", "/my dir/project", "New Name")
|
||||
|
||||
val path = mock.lastSessionRenamePath ?: error("missing rename request")
|
||||
assertTrue(path.startsWith("/session/ses_a%2Fb%20c?"), "Expected encoded session path in $path")
|
||||
assertTrue(path.contains("directory=%2Fmy%20dir%2Fproject"), "Expected encoded directory in $path")
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `rename url-encodes ampersand plus and query separators`() = runBlocking {
|
||||
val app = setup()
|
||||
ready(app)
|
||||
|
||||
app.sessions.rename("ses_a+b&c?d", "/path?a=1&b=2", "Title")
|
||||
|
||||
val path: String = mock.lastSessionRenamePath ?: error("missing rename request")
|
||||
val bare = path.substringBefore("?")
|
||||
val query = path.substringAfter("?", "")
|
||||
assertTrue(path.contains("/session/ses_a+b&c%3Fd?"), "Unexpected encoded session id: $path")
|
||||
assertFalse(query.contains("/path?a=1&b=2"), "Directory must be encoded as one query value: $query")
|
||||
assertTrue(query.contains("directory=%2Fpath%3Fa%3D1%26b%3D2"), "Unexpected encoded directory: $query")
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `rename encodes title in json body`() = runBlocking {
|
||||
val app = setup()
|
||||
ready(app)
|
||||
|
||||
app.sessions.rename("ses_1", "/test", "Has \"quotes\" and \\ backslash")
|
||||
|
||||
assertEquals("""{"title":"Has \"quotes\" and \\ backslash"}""", mock.lastSessionRenameBody)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `rename encodes title control characters in json body`() = runBlocking {
|
||||
val app = setup()
|
||||
ready(app)
|
||||
|
||||
app.sessions.rename("ses_1", "/test", "Line\nTab\tReturn\rBell\u0007")
|
||||
|
||||
assertEquals("""{"title":"Line\nTab\tReturn\rBell\u0007"}""", mock.lastSessionRenameBody)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `rename surfaces server failure`() = runBlocking {
|
||||
mock.sessionRenameStatus = 500
|
||||
mock.sessionRenameResponse = """{"error":"boom"}"""
|
||||
val app = setup()
|
||||
ready(app)
|
||||
|
||||
val err = assertFailsWith<RuntimeException> {
|
||||
app.sessions.rename("ses_1", "/test", "New Name")
|
||||
}
|
||||
|
||||
assertTrue(err.message.orEmpty().contains("HTTP 500"))
|
||||
assertTrue(err.message.orEmpty().contains("boom"))
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `rename throws before start`() = runBlocking {
|
||||
val app = setup()
|
||||
// Don't connect — manager is not started
|
||||
|
||||
assertFailsWith<IllegalStateException> {
|
||||
app.sessions.rename("ses_1", "/test", "Title")
|
||||
}
|
||||
}
|
||||
|
||||
// ------ Session with summary ------
|
||||
|
||||
@Test
|
||||
@@ -496,4 +625,32 @@ class KiloBackendSessionManagerTest {
|
||||
assertEquals(7, session.summary!!.deletions)
|
||||
assertEquals(3, session.summary!!.files)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `status and summary long values clamp to shared dto int range`() = runBlocking {
|
||||
mock.sessions = """[{
|
||||
"id": "ses_big",
|
||||
"slug": "big",
|
||||
"projectID": "prj",
|
||||
"directory": "/d",
|
||||
"title": "Big",
|
||||
"version": "1",
|
||||
"time": {"created": 1, "updated": 1},
|
||||
"summary": {"additions": 2147483648, "deletions": 9223372036854775807, "files": 3}
|
||||
}]"""
|
||||
mock.sessionStatuses = """{
|
||||
"ses_big": {"type":"retry","attempt":2147483648,"message":"retrying","next":9223372036854775807,"requestID":"req"}
|
||||
}"""
|
||||
val app = setup()
|
||||
ready(app)
|
||||
|
||||
val result = app.sessions.list("/d")
|
||||
val session = result.sessions[0]
|
||||
val status = result.statuses["ses_big"] ?: error("missing status")
|
||||
|
||||
assertEquals(Int.MAX_VALUE, session.summary?.additions)
|
||||
assertEquals(Int.MAX_VALUE, session.summary?.deletions)
|
||||
assertEquals(Int.MAX_VALUE, status.attempt)
|
||||
assertEquals(Long.MAX_VALUE, status.next)
|
||||
}
|
||||
}
|
||||
|
||||
+81
-3
@@ -39,7 +39,7 @@ class GeneratedApiModelSerializationTest {
|
||||
|
||||
@Test
|
||||
fun `empty provider list`() {
|
||||
val src = """{"all":[],"default":{},"connected":[]}"""
|
||||
val src = """{"all":[],"default":{},"connected":[],"failed":[]}"""
|
||||
val obj = json.decodeFromString<ProviderList200Response>(src)
|
||||
assertTrue(obj.all.isEmpty())
|
||||
assertTrue(obj.default.isEmpty())
|
||||
@@ -81,7 +81,8 @@ class GeneratedApiModelSerializationTest {
|
||||
}
|
||||
}],
|
||||
"default": {"code": "anthropic/claude-4"},
|
||||
"connected": ["anthropic"]
|
||||
"connected": ["anthropic"],
|
||||
"failed": []
|
||||
}"""
|
||||
val obj = json.decodeFromString<ProviderList200Response>(src)
|
||||
assertEquals(1, obj.all.size)
|
||||
@@ -132,7 +133,8 @@ class GeneratedApiModelSerializationTest {
|
||||
}
|
||||
}],
|
||||
"default": {},
|
||||
"connected": []
|
||||
"connected": [],
|
||||
"failed": []
|
||||
}"""
|
||||
val obj = json.decodeFromString<ProviderList200Response>(src)
|
||||
val model = obj.all[0].models["free-model"]!!
|
||||
@@ -149,12 +151,22 @@ class GeneratedApiModelSerializationTest {
|
||||
"all": [],
|
||||
"default": {},
|
||||
"connected": [],
|
||||
"failed": [],
|
||||
"future_field": "value"
|
||||
}"""
|
||||
val obj = json.decodeFromString<ProviderList200Response>(src)
|
||||
assertTrue(obj.all.isEmpty())
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `provider list defaults omitted collection fields`() {
|
||||
val obj = json.decodeFromString<ProviderList200Response>("{}")
|
||||
assertTrue(obj.all.isEmpty())
|
||||
assertTrue(obj.default.isEmpty())
|
||||
assertTrue(obj.connected.isEmpty())
|
||||
assertTrue(obj.failed.isEmpty())
|
||||
}
|
||||
|
||||
// ------ Agent ------
|
||||
|
||||
@Test
|
||||
@@ -287,4 +299,70 @@ class GeneratedApiModelSerializationTest {
|
||||
val list = json.decodeFromString<List<AppSkills200ResponseInner>>("[]")
|
||||
assertTrue(list.isEmpty())
|
||||
}
|
||||
|
||||
// ------ ProviderList200Response.failed non-empty ------
|
||||
|
||||
@Test
|
||||
fun `provider list failed field handles non-empty list`() {
|
||||
val src = """{
|
||||
"all": [],
|
||||
"default": {},
|
||||
"connected": [],
|
||||
"failed": ["openai", "anthropic"]
|
||||
}"""
|
||||
val obj = json.decodeFromString<ProviderList200Response>(src)
|
||||
assertEquals(listOf("openai", "anthropic"), obj.failed)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `provider list failed field handles single failed provider`() {
|
||||
val src = """{"all":[],"default":{},"connected":[],"failed":["openai"]}"""
|
||||
val obj = json.decodeFromString<ProviderList200Response>(src)
|
||||
assertEquals(listOf("openai"), obj.failed)
|
||||
}
|
||||
|
||||
// ------ Model limits with realistic large values ------
|
||||
|
||||
@Test
|
||||
fun `model limit context and output deserialize with large values`() {
|
||||
val src = """{
|
||||
"all": [{
|
||||
"id": "p",
|
||||
"name": "Provider",
|
||||
"source": "api",
|
||||
"env": [],
|
||||
"options": {},
|
||||
"models": {
|
||||
"big-model": {
|
||||
"id": "big-model",
|
||||
"providerID": "p",
|
||||
"name": "Big Model",
|
||||
"api": {"id": "p", "url": "", "npm": ""},
|
||||
"capabilities": {
|
||||
"temperature": true,
|
||||
"reasoning": false,
|
||||
"attachment": false,
|
||||
"toolcall": true,
|
||||
"input": {"text": true, "audio": false, "image": false, "video": false, "pdf": false},
|
||||
"output": {"text": true, "audio": false, "image": false, "video": false, "pdf": false},
|
||||
"interleaved": {"field":"reasoning_content"}
|
||||
},
|
||||
"cost": {"input": 0, "output": 0, "cache": {"read": 0, "write": 0}},
|
||||
"limit": {"context": 1000000, "output": 32000},
|
||||
"status": "active",
|
||||
"options": {},
|
||||
"headers": {},
|
||||
"release_date": "2025-01-01"
|
||||
}
|
||||
}
|
||||
}],
|
||||
"default": {},
|
||||
"connected": [],
|
||||
"failed": []
|
||||
}"""
|
||||
val obj = json.decodeFromString<ProviderList200Response>(src)
|
||||
val model = obj.all[0].models["big-model"]!!
|
||||
assertEquals(1000000.0, model.limit.context)
|
||||
assertEquals(32000.0, model.limit.output)
|
||||
}
|
||||
}
|
||||
|
||||
+30
@@ -836,6 +836,21 @@ class KiloCliDataParserTest {
|
||||
assertEquals(5000L, result.status.next)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `parseChatEvent - session status clamps large attempt`() {
|
||||
val data = globalEvent("""
|
||||
"type": "session.status",
|
||||
"properties": {
|
||||
"sessionID": "ses_1",
|
||||
"status": {"type": "retry", "message": "Retrying...", "attempt": 2147483648, "next": 9223372036854775807}
|
||||
}
|
||||
""")
|
||||
|
||||
val result = KiloCliDataParser.parseChatEvent("session.status", data) as ChatEventDto.SessionStatusChanged
|
||||
assertEquals(Int.MAX_VALUE, result.status.attempt)
|
||||
assertEquals(Long.MAX_VALUE, result.status.next)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `parseChatEvent - session status offline with requestID`() {
|
||||
val data = globalEvent("""
|
||||
@@ -970,6 +985,21 @@ class KiloCliDataParserTest {
|
||||
assertEquals(3, result.diff[0].additions)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `parseChatEvent - session diff clamps large counts`() {
|
||||
val data = globalEvent("""
|
||||
"type": "session.diff",
|
||||
"properties": {
|
||||
"sessionID": "ses_1",
|
||||
"diff": [{"file": "src/A.kt", "additions": 2147483648, "deletions": 9223372036854775807, "patch": "@@ ..."}]
|
||||
}
|
||||
""")
|
||||
|
||||
val result = KiloCliDataParser.parseChatEvent("session.diff", data) as ChatEventDto.SessionDiffChanged
|
||||
assertEquals(Int.MAX_VALUE, result.diff[0].additions)
|
||||
assertEquals(Int.MAX_VALUE, result.diff[0].deletions)
|
||||
}
|
||||
|
||||
// ================================================================
|
||||
// parseChatEvent — part with callID
|
||||
// ================================================================
|
||||
|
||||
+27
-3
@@ -23,7 +23,7 @@ class ProjectModelSerializationTest {
|
||||
|
||||
@Test
|
||||
fun `empty provider list`() {
|
||||
val src = """{"all":[],"default":{},"connected":[]}"""
|
||||
val src = """{"all":[],"default":{},"connected":[],"failed":[]}"""
|
||||
val obj = json.decodeFromString<ProviderList200Response>(src)
|
||||
assertTrue(obj.all.isEmpty())
|
||||
assertTrue(obj.default.isEmpty())
|
||||
@@ -64,7 +64,8 @@ class ProjectModelSerializationTest {
|
||||
}
|
||||
}],
|
||||
"default": {"code": "anthropic/claude-4"},
|
||||
"connected": ["anthropic"]
|
||||
"connected": ["anthropic"],
|
||||
"failed": []
|
||||
}"""
|
||||
val obj = json.decodeFromString<ProviderList200Response>(src)
|
||||
assertEquals(1, obj.all.size)
|
||||
@@ -115,7 +116,8 @@ class ProjectModelSerializationTest {
|
||||
}
|
||||
}],
|
||||
"default": {},
|
||||
"connected": []
|
||||
"connected": [],
|
||||
"failed": []
|
||||
}"""
|
||||
val obj = json.decodeFromString<ProviderList200Response>(src)
|
||||
val model = obj.all[0].models["free-model"]!!
|
||||
@@ -132,12 +134,20 @@ class ProjectModelSerializationTest {
|
||||
"all": [],
|
||||
"default": {},
|
||||
"connected": [],
|
||||
"failed": [],
|
||||
"future_field": "value"
|
||||
}"""
|
||||
val obj = json.decodeFromString<ProviderList200Response>(src)
|
||||
assertTrue(obj.all.isEmpty())
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `provider list defaults omitted failed field`() {
|
||||
val src = """{"all":[],"default":{},"connected":[]}"""
|
||||
val obj = json.decodeFromString<ProviderList200Response>(src)
|
||||
assertTrue(obj.failed.isEmpty())
|
||||
}
|
||||
|
||||
// ------ Agent ------
|
||||
|
||||
@Test
|
||||
@@ -270,4 +280,18 @@ class ProjectModelSerializationTest {
|
||||
val list = json.decodeFromString<List<AppSkills200ResponseInner>>("[]")
|
||||
assertTrue(list.isEmpty())
|
||||
}
|
||||
|
||||
// ------ ProviderList200Response.failed non-empty ------
|
||||
|
||||
@Test
|
||||
fun `provider list failed field handles non-empty list`() {
|
||||
val src = """{
|
||||
"all": [],
|
||||
"default": {},
|
||||
"connected": [],
|
||||
"failed": ["openai", "gemini"]
|
||||
}"""
|
||||
val obj = json.decodeFromString<ProviderList200Response>(src)
|
||||
assertEquals(listOf("openai", "gemini"), obj.failed)
|
||||
}
|
||||
}
|
||||
|
||||
+75
-10
@@ -26,7 +26,7 @@ class SessionModelSerializationTest {
|
||||
"directory": "/test/project",
|
||||
"title": "My Session",
|
||||
"version": "1.0.0",
|
||||
"time": {"created": 1000.0, "updated": 2000.0}
|
||||
"time": {"created": 1000, "updated": 2000}
|
||||
}"""
|
||||
val obj = json.decodeFromString<Session>(src)
|
||||
assertEquals("ses_abc", obj.id)
|
||||
@@ -34,8 +34,8 @@ class SessionModelSerializationTest {
|
||||
assertEquals("prj_123", obj.projectID)
|
||||
assertEquals("/test/project", obj.directory)
|
||||
assertEquals("My Session", obj.title)
|
||||
assertEquals(1000.0, obj.time.created)
|
||||
assertEquals(2000.0, obj.time.updated)
|
||||
assertEquals(1000L, obj.time.created)
|
||||
assertEquals(2000L, obj.time.updated)
|
||||
assertNull(obj.parentID)
|
||||
assertNull(obj.summary)
|
||||
}
|
||||
@@ -49,14 +49,14 @@ class SessionModelSerializationTest {
|
||||
"directory": "/d",
|
||||
"title": "T",
|
||||
"version": "1.0.0",
|
||||
"time": {"created": 1.0, "updated": 2.0},
|
||||
"time": {"created": 1, "updated": 2},
|
||||
"summary": {"additions": 10, "deletions": 5, "files": 3}
|
||||
}"""
|
||||
val obj = json.decodeFromString<Session>(src)
|
||||
assertNotNull(obj.summary)
|
||||
assertEquals(10.0, obj.summary!!.additions)
|
||||
assertEquals(5.0, obj.summary!!.deletions)
|
||||
assertEquals(3.0, obj.summary!!.files)
|
||||
assertEquals(10, obj.summary!!.additions)
|
||||
assertEquals(5, obj.summary!!.deletions)
|
||||
assertEquals(3, obj.summary!!.files)
|
||||
}
|
||||
|
||||
@Test
|
||||
@@ -68,7 +68,7 @@ class SessionModelSerializationTest {
|
||||
"directory": "/d",
|
||||
"title": "Fork",
|
||||
"version": "1.0.0",
|
||||
"time": {"created": 1.0, "updated": 2.0, "archived": 3000.0},
|
||||
"time": {"created": 1, "updated": 2, "archived": 3000.0},
|
||||
"parentID": "ses_parent"
|
||||
}"""
|
||||
val obj = json.decodeFromString<Session>(src)
|
||||
@@ -132,9 +132,9 @@ class SessionModelSerializationTest {
|
||||
val src = """{"type":"retry","attempt":2,"message":"Rate limited","next":1500,"requestID":"req_1"}"""
|
||||
val obj = json.decodeFromString<SessionStatus>(src)
|
||||
assertEquals(SessionStatus.Type.RETRY, obj.type)
|
||||
assertEquals(2.0, obj.attempt)
|
||||
assertEquals(2L, obj.attempt)
|
||||
assertEquals("Rate limited", obj.message)
|
||||
assertEquals(1500.0, obj.next)
|
||||
assertEquals(1500L, obj.next)
|
||||
}
|
||||
|
||||
@Test
|
||||
@@ -162,4 +162,69 @@ class SessionModelSerializationTest {
|
||||
val map = json.decodeFromString<Map<String, SessionStatus>>("{}")
|
||||
assertTrue(map.isEmpty())
|
||||
}
|
||||
|
||||
// ------ Large integer (Long) handling ------
|
||||
|
||||
@Test
|
||||
fun `Session time created and updated deserialize as Long for values larger than Int MAX_VALUE`() {
|
||||
val big = Int.MAX_VALUE.toLong() + 12345L
|
||||
val src = """{
|
||||
"id": "ses_big",
|
||||
"slug": "big",
|
||||
"projectID": "prj",
|
||||
"directory": "/d",
|
||||
"title": "Big Times",
|
||||
"version": "1",
|
||||
"time": {"created": $big, "updated": ${big + 1L}}
|
||||
}"""
|
||||
val obj = json.decodeFromString<Session>(src)
|
||||
assertEquals(big, obj.time.created)
|
||||
assertEquals(big + 1L, obj.time.updated)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `Session time millisecond epoch timestamps deserialize correctly`() {
|
||||
// Realistic millisecond timestamp: 2026-01-01T00:00:00Z = 1767139200000
|
||||
val ms = 1767139200000L
|
||||
val src = """{
|
||||
"id": "ses_ms",
|
||||
"slug": "ms",
|
||||
"projectID": "prj",
|
||||
"directory": "/d",
|
||||
"title": "MS Times",
|
||||
"version": "1",
|
||||
"time": {"created": $ms, "updated": $ms}
|
||||
}"""
|
||||
val obj = json.decodeFromString<Session>(src)
|
||||
assertEquals(ms, obj.time.created)
|
||||
assertEquals(ms, obj.time.updated)
|
||||
assertTrue(obj.time.created > Int.MAX_VALUE)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `Session time defaults to 0L when fields are omitted`() {
|
||||
// The generated model has default = 0L for created and updated
|
||||
val obj = ai.kilocode.jetbrains.api.model.SessionTime()
|
||||
assertEquals(0L, obj.created)
|
||||
assertEquals(0L, obj.updated)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `SessionStatus attempt and next deserialize as Long for large values`() {
|
||||
val bigAttempt = Int.MAX_VALUE.toLong() + 5L
|
||||
val bigNext = Int.MAX_VALUE.toLong() + 99999L
|
||||
val src = """{"type":"retry","attempt":$bigAttempt,"message":"Retrying","next":$bigNext,"requestID":"req_1"}"""
|
||||
val obj = json.decodeFromString<SessionStatus>(src)
|
||||
assertEquals(SessionStatus.Type.RETRY, obj.type)
|
||||
assertEquals(bigAttempt, obj.attempt)
|
||||
assertEquals(bigNext, obj.next)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `SessionStatus defaults attempt and next to 0L when omitted`() {
|
||||
val src = """{"type":"idle","message":"","requestID":""}"""
|
||||
val obj = json.decodeFromString<SessionStatus>(src)
|
||||
assertEquals(0L, obj.attempt)
|
||||
assertEquals(0L, obj.next)
|
||||
}
|
||||
}
|
||||
|
||||
+14
-3
@@ -41,7 +41,7 @@ class MockCliServer : AutoCloseable {
|
||||
@Volatile var notificationsStatus = 200
|
||||
|
||||
// Project-scoped REST responses
|
||||
@Volatile var providers = """{"all":[],"default":{},"connected":[]}"""
|
||||
@Volatile var providers = """{"all":[],"default":{},"connected":[],"failed":[]}"""
|
||||
@Volatile var agents = "[]"
|
||||
@Volatile var commands = "[]"
|
||||
@Volatile var skills = "[]"
|
||||
@@ -72,6 +72,11 @@ class MockCliServer : AutoCloseable {
|
||||
@Volatile var summarizeStatus = 200
|
||||
@Volatile var lastSummarizePath: String? = null
|
||||
@Volatile var lastSummarizeBody: String? = null
|
||||
@Volatile var sessionRenameStatus = 200
|
||||
@Volatile var sessionRenameResponse = """{"id":"ses_test","slug":"test","projectID":"prj_test","directory":"/test","title":"Renamed","version":"1.0.0","time":{"created":1000,"updated":2000}}"""
|
||||
@Volatile var lastSessionRenamePath: String? = null
|
||||
@Volatile var lastSessionRenameBody: String? = null
|
||||
@Volatile var lastSessionRenameMethod: String? = null
|
||||
|
||||
/** Configurable delay for all endpoint responses (ms). 0 = no delay. */
|
||||
@Volatile var responseDelay: Long = 0
|
||||
@@ -239,10 +244,16 @@ class MockCliServer : AutoCloseable {
|
||||
bare == "/session/status" -> respond(output, sessionStatusesStatus, sessionStatuses)
|
||||
bare == "/session" && method == "GET" -> respond(output, sessionsStatus, sessions)
|
||||
bare == "/session" && method == "POST" -> respond(output, sessionCreateStatus, sessionCreate)
|
||||
bare.matches(Regex("/session/ses_[^/]+")) && method == "GET" ->
|
||||
bare.matches(Regex("/session/ses_.+")) && !bare.contains("/summarize") && method == "GET" ->
|
||||
respond(output, sessionGetStatus, sessionCreate)
|
||||
bare.matches(Regex("/session/ses_[^/]+")) && method == "DELETE" ->
|
||||
bare.matches(Regex("/session/ses_.+")) && !bare.contains("/summarize") && method == "DELETE" ->
|
||||
respond(output, sessionDeleteStatus, "true")
|
||||
bare.matches(Regex("/session/ses_.+")) && !bare.contains("/summarize") && method == "PATCH" -> {
|
||||
lastSessionRenamePath = path
|
||||
lastSessionRenameBody = body
|
||||
lastSessionRenameMethod = method
|
||||
respond(output, sessionRenameStatus, sessionRenameResponse)
|
||||
}
|
||||
bare.matches(Regex("/session/ses_[^/]+/summarize")) && method == "POST" -> {
|
||||
lastSummarizePath = path
|
||||
lastSummarizeBody = body
|
||||
|
||||
@@ -218,7 +218,7 @@ abstract class FixGeneratedApiTask : DefaultTask() {
|
||||
}
|
||||
}
|
||||
|
||||
// Fix 7: Default values for non-nullable primitives in model data classes.
|
||||
// Fix 7: Default values for non-nullable primitives and collections in model data classes.
|
||||
// The CLI API may omit fields that the OpenAPI spec marks as required
|
||||
// (e.g. `attachment`, `reasoning` on dynamically added models).
|
||||
// Add Kotlin defaults so kotlinx.serialization doesn't throw
|
||||
@@ -230,6 +230,9 @@ abstract class FixGeneratedApiTask : DefaultTask() {
|
||||
Regex("""(val \w+:\s*kotlin\.Boolean)(,|\n)""") to { m: MatchResult ->
|
||||
"${m.groupValues[1]} = false${m.groupValues[2]}"
|
||||
},
|
||||
Regex("""(val \w+:\s*kotlin\.Long)(,|\n)""") to { m: MatchResult ->
|
||||
"${m.groupValues[1]} = 0L${m.groupValues[2]}"
|
||||
},
|
||||
Regex("""(val \w+:\s*kotlin\.Int)(,|\n)""") to { m: MatchResult ->
|
||||
"${m.groupValues[1]} = 0${m.groupValues[2]}"
|
||||
},
|
||||
@@ -239,6 +242,12 @@ abstract class FixGeneratedApiTask : DefaultTask() {
|
||||
Regex("""(val \w+:\s*kotlin\.String)(,|\n)""") to { m: MatchResult ->
|
||||
"${m.groupValues[1]} = \"\"${m.groupValues[2]}"
|
||||
},
|
||||
Regex("""(val \w+:\s*kotlin\.collections\.List<[^>]+>)(,|\n)""") to { m: MatchResult ->
|
||||
"${m.groupValues[1]} = emptyList()${m.groupValues[2]}"
|
||||
},
|
||||
Regex("""(val \w+:\s*kotlin\.collections\.Map<[^>]+>)(,|\n)""") to { m: MatchResult ->
|
||||
"${m.groupValues[1]} = emptyMap()${m.groupValues[2]}"
|
||||
},
|
||||
)
|
||||
for ((pattern, transform) in primitiveDefaults) {
|
||||
if (pattern.containsMatchIn(text)) {
|
||||
|
||||
+47
@@ -0,0 +1,47 @@
|
||||
package ai.kilocode.client.actions
|
||||
|
||||
import ai.kilocode.client.plugin.KiloBundle
|
||||
import ai.kilocode.client.session.history.HistoryDataKeys
|
||||
import ai.kilocode.client.session.SessionManager
|
||||
import com.intellij.openapi.actionSystem.ActionUpdateThread
|
||||
import com.intellij.openapi.actionSystem.AnAction
|
||||
import com.intellij.openapi.actionSystem.AnActionEvent
|
||||
import com.intellij.openapi.project.Project
|
||||
import com.intellij.openapi.ui.Messages
|
||||
|
||||
class DeleteSessionAction : AnAction() {
|
||||
/** Overridable in tests to avoid showing a real modal dialog. */
|
||||
internal var confirm: (project: Project?, msg: String) -> Boolean = { project, msg ->
|
||||
Messages.showYesNoDialog(
|
||||
project,
|
||||
msg,
|
||||
KiloBundle.message("history.delete.confirm.title"),
|
||||
Messages.getWarningIcon(),
|
||||
) == Messages.YES
|
||||
}
|
||||
|
||||
override fun getActionUpdateThread() = ActionUpdateThread.EDT
|
||||
|
||||
override fun update(e: AnActionEvent) {
|
||||
val selection = e.getData(HistoryDataKeys.SELECTION)
|
||||
val manager = e.getData(SessionManager.KEY)
|
||||
e.presentation.isEnabledAndVisible = manager != null &&
|
||||
selection != null &&
|
||||
selection.selectedLocal.isNotEmpty()
|
||||
}
|
||||
|
||||
override fun actionPerformed(e: AnActionEvent) {
|
||||
val selection = e.getData(HistoryDataKeys.SELECTION) ?: return
|
||||
val controller = e.getData(HistoryDataKeys.CONTROLLER) ?: return
|
||||
val items = selection.selectedLocal.filter { !controller.deleting(it) }
|
||||
if (items.isEmpty()) return
|
||||
|
||||
val msg = if (items.size == 1)
|
||||
KiloBundle.message("history.delete.confirm.message", ai.kilocode.client.session.history.title(items[0]))
|
||||
else
|
||||
KiloBundle.message("history.delete.confirm.message.multiple", items.size)
|
||||
|
||||
if (!confirm(e.project, msg)) return
|
||||
items.forEach { controller.delete(it) }
|
||||
}
|
||||
}
|
||||
+26
@@ -0,0 +1,26 @@
|
||||
package ai.kilocode.client.actions
|
||||
|
||||
import ai.kilocode.client.session.history.HistoryDataKeys
|
||||
import ai.kilocode.client.session.SessionManager
|
||||
import com.intellij.openapi.actionSystem.ActionUpdateThread
|
||||
import com.intellij.openapi.actionSystem.AnAction
|
||||
import com.intellij.openapi.actionSystem.AnActionEvent
|
||||
|
||||
class OpenSessionAction : AnAction() {
|
||||
override fun getActionUpdateThread() = ActionUpdateThread.EDT
|
||||
|
||||
override fun update(e: AnActionEvent) {
|
||||
val selection = e.getData(HistoryDataKeys.SELECTION)
|
||||
val manager = e.getData(SessionManager.KEY)
|
||||
e.presentation.isEnabledAndVisible = manager != null &&
|
||||
selection != null &&
|
||||
(selection.selectedLocal.size == 1 || selection.cloudItems.size == 1)
|
||||
}
|
||||
|
||||
override fun actionPerformed(e: AnActionEvent) {
|
||||
val selection = e.getData(HistoryDataKeys.SELECTION) ?: return
|
||||
val controller = e.getData(HistoryDataKeys.CONTROLLER) ?: return
|
||||
selection.selectedLocal.singleOrNull()?.let { controller.open(it); return }
|
||||
selection.cloudItems.singleOrNull()?.let { controller.open(it) }
|
||||
}
|
||||
}
|
||||
+47
@@ -0,0 +1,47 @@
|
||||
package ai.kilocode.client.actions
|
||||
|
||||
import ai.kilocode.client.plugin.KiloBundle
|
||||
import ai.kilocode.client.session.history.HistoryDataKeys
|
||||
import ai.kilocode.client.session.history.title
|
||||
import ai.kilocode.client.session.SessionManager
|
||||
import com.intellij.openapi.actionSystem.ActionUpdateThread
|
||||
import com.intellij.openapi.actionSystem.AnAction
|
||||
import com.intellij.openapi.actionSystem.AnActionEvent
|
||||
import com.intellij.openapi.project.Project
|
||||
import com.intellij.openapi.ui.Messages
|
||||
|
||||
class RenameSessionAction : AnAction() {
|
||||
/** Overridable in tests to avoid showing a real modal dialog. */
|
||||
internal var input: (project: Project?, current: String) -> String? = { project, current ->
|
||||
Messages.showInputDialog(
|
||||
project,
|
||||
KiloBundle.message("history.rename.prompt"),
|
||||
KiloBundle.message("history.rename.title"),
|
||||
null,
|
||||
current,
|
||||
null,
|
||||
)
|
||||
}
|
||||
|
||||
override fun getActionUpdateThread() = ActionUpdateThread.EDT
|
||||
|
||||
override fun update(e: AnActionEvent) {
|
||||
val selection = e.getData(HistoryDataKeys.SELECTION)
|
||||
val manager = e.getData(SessionManager.KEY)
|
||||
e.presentation.isEnabledAndVisible = manager != null &&
|
||||
selection != null &&
|
||||
selection.selectedLocal.size == 1
|
||||
}
|
||||
|
||||
override fun actionPerformed(e: AnActionEvent) {
|
||||
val selection = e.getData(HistoryDataKeys.SELECTION) ?: return
|
||||
val controller = e.getData(HistoryDataKeys.CONTROLLER) ?: return
|
||||
val item = selection.selectedLocal.singleOrNull() ?: return
|
||||
|
||||
val current = title(item)
|
||||
val newTitle = input(e.project, current)?.trim() ?: return
|
||||
|
||||
if (newTitle.isBlank() || newTitle == current) return
|
||||
controller.rename(item, newTitle)
|
||||
}
|
||||
}
|
||||
+6
@@ -124,6 +124,12 @@ class KiloSessionService internal constructor(
|
||||
list(dir)
|
||||
}
|
||||
|
||||
suspend fun renameSession(id: String, dir: String, newTitle: String): ai.kilocode.rpc.dto.SessionDto {
|
||||
val session = call { rename(id, dir, newTitle) }
|
||||
_sessions.value = _sessions.value.map { if (it.id == id) session else it }
|
||||
return session
|
||||
}
|
||||
|
||||
suspend fun cloudSessions(dir: String, cursor: String?, limit: Int, gitUrl: String?): CloudSessionListDto =
|
||||
call { cloudSessions(dir, cursor, limit, gitUrl) }
|
||||
|
||||
|
||||
+1
-1
@@ -113,7 +113,7 @@ class SessionSidePanelManager(
|
||||
deleted = this::removeSession,
|
||||
)
|
||||
Disposer.register(this) { cs.cancel() }
|
||||
return HistoryPanel(this, controller, nav = this::back).component
|
||||
return HistoryPanel(this, controller, nav = this::back, manager = this).component
|
||||
}
|
||||
|
||||
private fun back() {
|
||||
|
||||
+34
@@ -0,0 +1,34 @@
|
||||
package ai.kilocode.client.session.history
|
||||
|
||||
import java.io.File
|
||||
import java.util.concurrent.CompletableFuture
|
||||
import java.util.concurrent.TimeUnit
|
||||
|
||||
/**
|
||||
* Resolves the origin remote URL for a given directory by running
|
||||
* `git remote get-url origin` as a subprocess.
|
||||
*
|
||||
* Returns null when the directory is not a git repo, has no origin remote,
|
||||
* or the command fails for any reason.
|
||||
*
|
||||
* Overridable in tests via [resolve] parameter.
|
||||
*/
|
||||
internal fun resolveGitRemoteUrl(dir: String): String? = runCatching {
|
||||
val proc = ProcessBuilder("git", "remote", "get-url", "origin")
|
||||
.directory(File(dir))
|
||||
.redirectErrorStream(true)
|
||||
.start()
|
||||
|
||||
val text = CompletableFuture.supplyAsync {
|
||||
proc.inputStream.bufferedReader().use { it.readText() }
|
||||
}
|
||||
val done = proc.waitFor(5, TimeUnit.SECONDS)
|
||||
if (!done) {
|
||||
proc.destroyForcibly()
|
||||
return@runCatching null
|
||||
}
|
||||
|
||||
val out = text.get(1, TimeUnit.SECONDS).trim()
|
||||
val code = proc.exitValue()
|
||||
if (code == 0 && out.isNotEmpty()) out else null
|
||||
}.getOrNull()
|
||||
+75
-9
@@ -8,7 +8,11 @@ import ai.kilocode.rpc.dto.CloudSessionDto
|
||||
import ai.kilocode.rpc.dto.SessionDto
|
||||
import com.intellij.openapi.application.ApplicationManager
|
||||
import kotlinx.coroutines.CoroutineScope
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.launch
|
||||
import kotlinx.coroutines.sync.Mutex
|
||||
import kotlinx.coroutines.sync.withLock
|
||||
import kotlinx.coroutines.withContext
|
||||
|
||||
class HistoryController(
|
||||
private val sessions: KiloSessionService,
|
||||
@@ -16,21 +20,43 @@ class HistoryController(
|
||||
private val cs: CoroutineScope,
|
||||
open: (SessionRef) -> Unit = {},
|
||||
private val deleted: (String) -> Unit = {},
|
||||
private val gitUrlProvider: () -> String? = { resolveGitRemoteUrl(workspace.directory) },
|
||||
) {
|
||||
companion object {
|
||||
const val CLOUD_LIMIT = 150
|
||||
const val CLOUD_LIMIT = 50
|
||||
}
|
||||
|
||||
val local = HistoryModel<LocalHistoryItem>()
|
||||
val cloud = CloudHistoryModel()
|
||||
|
||||
/** Resolved once on first cloud load; null means no remote found. Written from IO, read on EDT. */
|
||||
@Volatile
|
||||
var gitUrl: String? = null
|
||||
private set
|
||||
|
||||
@Volatile
|
||||
private var resolved = false
|
||||
private val lock = Mutex()
|
||||
|
||||
/** Whether to filter cloud history by the current repository. */
|
||||
var repoOnly: Boolean = false
|
||||
private set
|
||||
|
||||
/** Notified on EDT when [repoOnly] changes (e.g. to update checkbox state). */
|
||||
var onRepoOnlyChanged: ((Boolean) -> Unit)? = null
|
||||
|
||||
private fun updateRepoOnly(value: Boolean) {
|
||||
if (repoOnly == value) return
|
||||
repoOnly = value
|
||||
edt { onRepoOnlyChanged?.invoke(value) }
|
||||
}
|
||||
|
||||
private val deleting = mutableSetOf<String>()
|
||||
private val opener = open
|
||||
private var git: String? = null
|
||||
|
||||
fun reload(gitUrl: String? = null) {
|
||||
fun reload() {
|
||||
reloadLocal()
|
||||
reloadCloud(gitUrl)
|
||||
reloadCloud()
|
||||
}
|
||||
|
||||
fun reloadLocal() {
|
||||
@@ -46,8 +72,7 @@ class HistoryController(
|
||||
}
|
||||
}
|
||||
|
||||
fun reloadCloud(gitUrl: String? = null) {
|
||||
git = gitUrl
|
||||
fun reloadCloud() {
|
||||
loadCloud(reset = true)
|
||||
}
|
||||
|
||||
@@ -56,14 +81,20 @@ class HistoryController(
|
||||
loadCloud(reset = false)
|
||||
}
|
||||
|
||||
fun applyRepoOnly(value: Boolean) {
|
||||
updateRepoOnly(value)
|
||||
edt { reloadCloud() }
|
||||
}
|
||||
|
||||
fun delete(item: LocalHistoryItem) {
|
||||
edt {
|
||||
if (item.id in deleting) return@edt
|
||||
deleting.add(item.id)
|
||||
local.refresh()
|
||||
val dir = item.directory ?: workspace.directory
|
||||
cs.launch {
|
||||
try {
|
||||
sessions.deleteSession(item.id, item.directory ?: workspace.directory)
|
||||
sessions.deleteSession(item.id, dir)
|
||||
edt {
|
||||
deleting.remove(item.id)
|
||||
local.remove(item.id)
|
||||
@@ -79,6 +110,18 @@ class HistoryController(
|
||||
}
|
||||
}
|
||||
|
||||
fun rename(item: LocalHistoryItem, title: String) {
|
||||
val dir = item.directory ?: workspace.directory
|
||||
cs.launch {
|
||||
try {
|
||||
val updated = sessions.renameSession(item.id, dir, title)
|
||||
edt { local.update(LocalHistoryItem(updated)) }
|
||||
} catch (e: Exception) {
|
||||
edt { local.fail(e.message ?: KiloBundle.message("history.error.local.rename")) }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fun deleting(item: LocalHistoryItem): Boolean = item.id in deleting
|
||||
|
||||
fun open(item: LocalHistoryItem) {
|
||||
@@ -91,11 +134,12 @@ class HistoryController(
|
||||
|
||||
private fun loadCloud(reset: Boolean) {
|
||||
val cursor = cloud.cursor.takeUnless { reset }
|
||||
val gitUrl = git
|
||||
edt { cloud.start(reset) }
|
||||
cs.launch {
|
||||
val url = resolveUrlIfNeeded()
|
||||
val filter = if (repoOnly) url else null
|
||||
try {
|
||||
val result = sessions.cloudSessions(workspace.directory, cursor, CLOUD_LIMIT, gitUrl)
|
||||
val result = sessions.cloudSessions(workspace.directory, cursor, CLOUD_LIMIT, filter)
|
||||
val items = result.sessions.map(::cloudItem)
|
||||
edt {
|
||||
if (reset) cloud.replace(items, result.nextCursor)
|
||||
@@ -106,6 +150,28 @@ class HistoryController(
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Resolves [gitUrl] on first cloud load. Subsequent calls return the cached value.
|
||||
* Also enables [repoOnly] by default when a URL is found the first time.
|
||||
*
|
||||
* Must be called from a coroutine. Writes [gitUrl] directly (volatile) and then
|
||||
* propagates state updates to EDT via [edt].
|
||||
*/
|
||||
private suspend fun resolveUrlIfNeeded(): String? {
|
||||
if (resolved) return gitUrl
|
||||
return lock.withLock {
|
||||
if (resolved) return@withLock gitUrl
|
||||
val url = withContext(Dispatchers.IO) {
|
||||
gitUrlProvider()
|
||||
}
|
||||
// Write gitUrl directly (volatile) so it is visible before EDT callbacks fire.
|
||||
gitUrl = url
|
||||
resolved = true
|
||||
if (url != null) updateRepoOnly(true)
|
||||
url
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun edt(block: () -> Unit) {
|
||||
|
||||
+16
@@ -0,0 +1,16 @@
|
||||
package ai.kilocode.client.session.history
|
||||
|
||||
import com.intellij.openapi.actionSystem.DataKey
|
||||
|
||||
data class HistorySelection(
|
||||
val source: HistorySource,
|
||||
val localItems: List<LocalHistoryItem>,
|
||||
val cloudItems: List<CloudHistoryItem>,
|
||||
) {
|
||||
val selectedLocal: List<LocalHistoryItem> get() = if (source == HistorySource.LOCAL) localItems else emptyList()
|
||||
}
|
||||
|
||||
object HistoryDataKeys {
|
||||
val SELECTION: DataKey<HistorySelection> = DataKey.create("ai.kilocode.client.session.history.HistorySelection")
|
||||
val CONTROLLER: DataKey<HistoryController> = DataKey.create("ai.kilocode.client.session.history.HistoryController")
|
||||
}
|
||||
+10
-20
@@ -3,7 +3,6 @@ package ai.kilocode.client.session.history
|
||||
import ai.kilocode.client.session.ui.PickerRow
|
||||
import ai.kilocode.client.ui.UiStyle
|
||||
import com.intellij.icons.AllIcons
|
||||
import com.intellij.ui.ExperimentalUI
|
||||
import com.intellij.ui.GroupHeaderSeparator
|
||||
import com.intellij.ui.SimpleColoredComponent
|
||||
import com.intellij.ui.SimpleTextAttributes
|
||||
@@ -20,7 +19,7 @@ import javax.swing.JPanel
|
||||
import javax.swing.ListCellRenderer
|
||||
import javax.swing.SwingConstants
|
||||
|
||||
private const val DELETE_CLICK_AREA_WIDTH = 32
|
||||
private const val DELETE_AREA_WIDTH = 32
|
||||
|
||||
internal open class HistoryRenderer<T : HistoryItem>(
|
||||
private val model: HistoryModel<T>,
|
||||
@@ -31,14 +30,14 @@ internal open class HistoryRenderer<T : HistoryItem>(
|
||||
private val empty: Icon = EmptyIcon.create(icon)
|
||||
|
||||
fun isDeleteClick(list: JList<*>, bounds: Rectangle, point: Point): Boolean {
|
||||
val width = JBUI.scale(DELETE_CLICK_AREA_WIDTH)
|
||||
val inset = deleteInset(list)
|
||||
if (list.componentOrientation.isLeftToRight) {
|
||||
val right = bounds.x + bounds.width - inset
|
||||
return point.x in (right - width)..right
|
||||
val width = JBUI.scale(DELETE_AREA_WIDTH)
|
||||
return if (list.componentOrientation.isLeftToRight) {
|
||||
val right = bounds.x + bounds.width
|
||||
point.x in (right - width)..right
|
||||
} else {
|
||||
val left = bounds.x
|
||||
point.x in left..(left + width)
|
||||
}
|
||||
val left = bounds.x + inset
|
||||
return point.x in left..(left + width)
|
||||
}
|
||||
|
||||
fun section(items: List<HistoryItem>, index: Int): String? {
|
||||
@@ -48,13 +47,6 @@ internal open class HistoryRenderer<T : HistoryItem>(
|
||||
if (current == previous) return null
|
||||
return HistoryTime.title(current)
|
||||
}
|
||||
|
||||
private fun deleteInset(list: JList<*>): Int {
|
||||
if (!ExperimentalUI.isNewUI()) return 0
|
||||
val inner = JBUI.CurrentTheme.Popup.Selection.innerInsets()
|
||||
val edge = JBUI.CurrentTheme.Popup.Selection.LEFT_RIGHT_INSET.get()
|
||||
return edge + if (list.componentOrientation.isLeftToRight) inner.right else inner.left
|
||||
}
|
||||
}
|
||||
|
||||
private val sep = GroupHeaderSeparator(JBUI.CurrentTheme.Popup.separatorLabelInsets())
|
||||
@@ -75,7 +67,7 @@ internal open class HistoryRenderer<T : HistoryItem>(
|
||||
}
|
||||
private val row = JPanel(BorderLayout()).apply {
|
||||
add(main, BorderLayout.CENTER)
|
||||
add(del, BorderLayout.EAST)
|
||||
if (deletable) add(del, BorderLayout.EAST)
|
||||
}
|
||||
private val wrap = PickerRow()
|
||||
|
||||
@@ -114,13 +106,11 @@ internal open class HistoryRenderer<T : HistoryItem>(
|
||||
)
|
||||
time.text = value?.let(HistoryTime::relative).orEmpty()
|
||||
time.foreground = weak
|
||||
del.icon = if (deletable && selected) icon else empty
|
||||
if (deletable) del.icon = if (selected) icon else empty
|
||||
|
||||
top.invalidate()
|
||||
return this
|
||||
}
|
||||
|
||||
fun deleteVisible(): Boolean = del.icon === icon
|
||||
}
|
||||
|
||||
internal class LocalHistoryRenderer(model: HistoryModel<LocalHistoryItem>) : HistoryRenderer<LocalHistoryItem>(model, deletable = true)
|
||||
|
||||
+1
-2
@@ -6,10 +6,9 @@ import java.awt.event.MouseEvent
|
||||
|
||||
internal fun title(item: HistoryItem): String = item.title.takeIf { it.isNotBlank() } ?: KiloBundle.message("history.untitled")
|
||||
|
||||
internal fun <T> clicked(list: JBList<T>, e: MouseEvent): T? {
|
||||
internal fun <T> itemAt(list: JBList<T>, e: MouseEvent): T? {
|
||||
val row = list.locationToIndex(e.point)
|
||||
val box = row.takeIf { it >= 0 }?.let { list.getCellBounds(it, it) } ?: return null
|
||||
if (!box.contains(e.point)) return null
|
||||
list.selectedIndex = row
|
||||
return list.model.getElementAt(row)
|
||||
}
|
||||
|
||||
+6
-2
@@ -48,6 +48,12 @@ open class HistoryModel<T : HistoryItem> : AbstractListModel<T>() {
|
||||
filter()
|
||||
}
|
||||
|
||||
fun update(item: T) {
|
||||
if (all.none { it.id == item.id }) return
|
||||
all = HistoryTime.sorted(all.map { if (it.id == item.id) item else it })
|
||||
filter()
|
||||
}
|
||||
|
||||
fun fail(message: String) {
|
||||
loading = false
|
||||
loaded = true
|
||||
@@ -60,8 +66,6 @@ open class HistoryModel<T : HistoryItem> : AbstractListModel<T>() {
|
||||
filter()
|
||||
}
|
||||
|
||||
fun hasFilter(): Boolean = query.isNotEmpty()
|
||||
|
||||
fun refresh() {
|
||||
val end = size.coerceAtLeast(1) - 1
|
||||
fireContentsChanged(this, 0, end)
|
||||
|
||||
+116
-55
@@ -1,20 +1,28 @@
|
||||
package ai.kilocode.client.session.history
|
||||
|
||||
import ai.kilocode.client.plugin.KiloBundle
|
||||
import ai.kilocode.client.session.SessionManager
|
||||
import ai.kilocode.client.session.ui.LoadingPanel
|
||||
import ai.kilocode.client.session.ui.style.SessionEditorStyle
|
||||
import ai.kilocode.client.ui.UiStyle
|
||||
import com.intellij.icons.AllIcons
|
||||
import com.intellij.ide.ui.LafManagerListener
|
||||
import com.intellij.ide.ui.laf.darcula.ui.DarculaButtonUI
|
||||
import com.intellij.openapi.application.ApplicationManager
|
||||
import com.intellij.openapi.Disposable
|
||||
import com.intellij.openapi.ui.Messages
|
||||
import com.intellij.openapi.actionSystem.ActionGroup
|
||||
import com.intellij.openapi.actionSystem.ActionManager
|
||||
import com.intellij.openapi.actionSystem.ActionPlaces
|
||||
import com.intellij.openapi.actionSystem.AnAction
|
||||
import com.intellij.openapi.actionSystem.AnActionEvent
|
||||
import com.intellij.openapi.actionSystem.DataProvider
|
||||
import com.intellij.openapi.actionSystem.Presentation
|
||||
import com.intellij.openapi.actionSystem.impl.ActionButton
|
||||
import com.intellij.openapi.util.Disposer
|
||||
import com.intellij.ui.DocumentAdapter
|
||||
import com.intellij.ui.ListUtil
|
||||
import com.intellij.ui.PopupHandler
|
||||
import com.intellij.ui.SearchTextField
|
||||
import com.intellij.ui.ScrollingUtil
|
||||
import com.intellij.ui.components.JBCheckBox
|
||||
import com.intellij.ui.components.JBList
|
||||
import com.intellij.ui.components.JBScrollPane
|
||||
import com.intellij.ui.tabs.JBTabs
|
||||
@@ -49,14 +57,19 @@ import javax.swing.event.ListDataListener
|
||||
class HistoryPanel(
|
||||
parent: Disposable,
|
||||
private val controller: HistoryController,
|
||||
private val gitUrl: () -> String? = { null },
|
||||
private val nav: () -> Unit = {},
|
||||
) : BorderLayoutPanel(), Disposable {
|
||||
private val manager: SessionManager? = null,
|
||||
) : BorderLayoutPanel(), Disposable, DataProvider {
|
||||
private val localSearch = search(controller.local)
|
||||
private val cloudSearch = search(controller.cloud)
|
||||
private val localList = localList()
|
||||
private val cloudList = cloudList()
|
||||
private val more = LoadMoreButton()
|
||||
private val repoOnly = JBCheckBox(KiloBundle.message("history.cloud.repo.only"), true).apply {
|
||||
isVisible = false
|
||||
border = JBUI.Borders.emptyLeft(UiStyle.Gap.lg())
|
||||
addActionListener { controller.applyRepoOnly(isSelected) }
|
||||
}
|
||||
private val localPanel = panel(localSearch, localList)
|
||||
private val cloudPanel = panel(cloudSearch, cloudList, more)
|
||||
private val cards = CardLayout()
|
||||
@@ -89,6 +102,9 @@ class HistoryPanel(
|
||||
bind(localList, controller.local)
|
||||
bind(cloudList, controller.cloud)
|
||||
bindTheme()
|
||||
controller.onRepoOnlyChanged = { value ->
|
||||
repoOnly.isSelected = value
|
||||
}
|
||||
addHierarchyListener { e ->
|
||||
if (e.changeFlags and HierarchyEvent.SHOWING_CHANGED.toLong() == 0L) return@addHierarchyListener
|
||||
if (isShowing && stale) {
|
||||
@@ -111,7 +127,7 @@ class HistoryPanel(
|
||||
fun refresh() {
|
||||
stale = false
|
||||
updateTheme()
|
||||
controller.reload(gitUrl())
|
||||
controller.reload()
|
||||
}
|
||||
|
||||
private fun bindTheme() {
|
||||
@@ -162,19 +178,30 @@ class HistoryPanel(
|
||||
)
|
||||
}
|
||||
|
||||
private fun back() = BorderLayoutPanel().apply {
|
||||
add(JButton(KiloBundle.message("history.back"), AllIcons.Actions.Back).apply {
|
||||
putClientProperty(DarculaButtonUI.DEFAULT_STYLE_KEY, true)
|
||||
isFocusable = false
|
||||
private fun back(): BorderLayoutPanel {
|
||||
val label = KiloBundle.message("history.back")
|
||||
val action = object : AnAction(label, null, AllIcons.Actions.Back) {
|
||||
override fun actionPerformed(e: AnActionEvent) = nav()
|
||||
}
|
||||
val presentation = Presentation(label).apply { icon = AllIcons.Actions.Back }
|
||||
val btn = ActionButton(action, presentation, ActionPlaces.TOOLBAR, JBUI.size(16)).apply {
|
||||
cursor = Cursor.getPredefinedCursor(Cursor.HAND_CURSOR)
|
||||
addActionListener { nav() }
|
||||
}, BorderLayout.WEST)
|
||||
border = JBUI.Borders.emptyRight(UiStyle.Gap.lg())
|
||||
}
|
||||
return BorderLayoutPanel().apply {
|
||||
add(btn, BorderLayout.WEST)
|
||||
border = JBUI.Borders.emptyRight(UiStyle.Gap.lg())
|
||||
}
|
||||
}
|
||||
|
||||
private fun panel(search: SearchTextField, list: JList<out HistoryItem>, footer: JComponent? = null): JComponent {
|
||||
return BorderLayoutPanel().apply {
|
||||
add(search, BorderLayout.NORTH)
|
||||
val north = BorderLayoutPanel().apply {
|
||||
add(search, BorderLayout.CENTER)
|
||||
if (list === cloudList) {
|
||||
add(repoOnly, BorderLayout.SOUTH)
|
||||
}
|
||||
}
|
||||
add(north, BorderLayout.NORTH)
|
||||
add(JBScrollPane(list).apply {
|
||||
border = JBUI.Borders.empty()
|
||||
viewportBorder = JBUI.Borders.empty()
|
||||
@@ -188,21 +215,22 @@ class HistoryPanel(
|
||||
}
|
||||
|
||||
private fun localList() = JBList(controller.local).apply {
|
||||
selectionMode = ListSelectionModel.SINGLE_SELECTION
|
||||
isFocusable = false
|
||||
selectionMode = ListSelectionModel.MULTIPLE_INTERVAL_SELECTION
|
||||
isFocusable = true
|
||||
cellRenderer = LocalHistoryRenderer(controller.local)
|
||||
cursor = Cursor.getPredefinedCursor(Cursor.HAND_CURSOR)
|
||||
emptyText.text = KiloBundle.message("history.empty")
|
||||
addMouseListener(object : MouseAdapter() {
|
||||
override fun mouseReleased(e: MouseEvent) {
|
||||
if (!UIUtil.isActionClick(e, MouseEvent.MOUSE_RELEASED, true)) return
|
||||
val item = clicked(this@apply, e) ?: return
|
||||
if (deleteClick(this@apply, e)) {
|
||||
override fun mouseClicked(e: MouseEvent) {
|
||||
val row = locationToIndex(e.point)
|
||||
val box = row.takeIf { it >= 0 }?.let { getCellBounds(it, it) } ?: return
|
||||
if (!box.contains(e.point)) return
|
||||
if (e.clickCount == 1 && HistoryRenderer.isDeleteClick(this@apply, box, e.point)) {
|
||||
val item = model.getElementAt(row)
|
||||
confirm(item)
|
||||
e.consume()
|
||||
return
|
||||
} else if (e.clickCount == 2) {
|
||||
selectedValue?.let(::activate)
|
||||
}
|
||||
activate(item)
|
||||
}
|
||||
})
|
||||
registerKeyboardAction(
|
||||
@@ -210,20 +238,19 @@ class HistoryPanel(
|
||||
KeyStroke.getKeyStroke(KeyEvent.VK_ENTER, 0),
|
||||
JComponent.WHEN_FOCUSED,
|
||||
)
|
||||
ListUtil.installAutoSelectOnMouseMove(this)
|
||||
installContextMenu(this)
|
||||
ScrollingUtil.installActions(this)
|
||||
}
|
||||
|
||||
private fun cloudList() = JBList(controller.cloud).apply {
|
||||
selectionMode = ListSelectionModel.SINGLE_SELECTION
|
||||
isFocusable = false
|
||||
isFocusable = true
|
||||
cellRenderer = CloudHistoryRenderer(controller.cloud)
|
||||
cursor = Cursor.getPredefinedCursor(Cursor.HAND_CURSOR)
|
||||
emptyText.text = KiloBundle.message("history.empty")
|
||||
addMouseListener(object : MouseAdapter() {
|
||||
override fun mouseReleased(e: MouseEvent) {
|
||||
if (!UIUtil.isActionClick(e, MouseEvent.MOUSE_RELEASED, true)) return
|
||||
clicked(this@apply, e)?.let(::activate)
|
||||
override fun mouseClicked(e: MouseEvent) {
|
||||
if (e.clickCount == 2) selectedValue?.let(::activate)
|
||||
}
|
||||
})
|
||||
registerKeyboardAction(
|
||||
@@ -231,7 +258,7 @@ class HistoryPanel(
|
||||
KeyStroke.getKeyStroke(KeyEvent.VK_ENTER, 0),
|
||||
JComponent.WHEN_FOCUSED,
|
||||
)
|
||||
ListUtil.installAutoSelectOnMouseMove(this)
|
||||
installContextMenu(this)
|
||||
ScrollingUtil.installActions(this)
|
||||
}
|
||||
|
||||
@@ -253,6 +280,7 @@ class HistoryPanel(
|
||||
syncList(cloudList, controller.cloud)
|
||||
more.isEnabled = controller.cloud.cursor != null && !controller.cloud.loading
|
||||
more.isVisible = controller.cloud.cursor != null || controller.cloud.loading
|
||||
repoOnly.isVisible = controller.gitUrl != null
|
||||
cards.show(body, if (loading()) CARD_LOAD else CARD_TABS)
|
||||
revalidate()
|
||||
repaint()
|
||||
@@ -272,13 +300,6 @@ class HistoryPanel(
|
||||
}
|
||||
}
|
||||
|
||||
private fun deleteClick(list: JBList<LocalHistoryItem>, e: MouseEvent): Boolean {
|
||||
val row = list.locationToIndex(e.point)
|
||||
val box = row.takeIf { it >= 0 }?.let { list.getCellBounds(it, it) } ?: return false
|
||||
if (!box.contains(e.point)) return false
|
||||
return HistoryRenderer.isDeleteClick(list, box, e.point)
|
||||
}
|
||||
|
||||
private fun activate(item: HistoryItem) {
|
||||
when (item) {
|
||||
is LocalHistoryItem -> controller.open(item)
|
||||
@@ -286,18 +307,54 @@ class HistoryPanel(
|
||||
}
|
||||
}
|
||||
|
||||
override fun getData(dataId: String): Any? {
|
||||
if (SessionManager.KEY.`is`(dataId)) return manager
|
||||
if (HistoryDataKeys.CONTROLLER.`is`(dataId)) return controller
|
||||
if (HistoryDataKeys.SELECTION.`is`(dataId)) {
|
||||
val source = selectedSource()
|
||||
val local = if (source == HistorySource.LOCAL) localList.selectedValuesList.filterIsInstance<LocalHistoryItem>() else emptyList()
|
||||
val cloud = if (source == HistorySource.CLOUD) cloudList.selectedValuesList.filterIsInstance<CloudHistoryItem>() else emptyList()
|
||||
return HistorySelection(source, local, cloud)
|
||||
}
|
||||
return null
|
||||
}
|
||||
|
||||
private fun installContextMenu(list: JBList<out HistoryItem>) {
|
||||
val group = ActionManager.getInstance().getAction("Kilo.History.ContextMenu")
|
||||
if (group is ActionGroup) {
|
||||
PopupHandler.installPopupMenu(list, group, ActionPlaces.POPUP)
|
||||
}
|
||||
}
|
||||
|
||||
private fun confirm(item: LocalHistoryItem) {
|
||||
if (controller.deleting(item)) return
|
||||
val result = Messages.showYesNoDialog(
|
||||
val result = com.intellij.openapi.ui.Messages.showYesNoDialog(
|
||||
this,
|
||||
KiloBundle.message("history.delete.confirm.message", title(item)),
|
||||
KiloBundle.message("history.delete.confirm.title"),
|
||||
Messages.getWarningIcon(),
|
||||
com.intellij.openapi.ui.Messages.getWarningIcon(),
|
||||
)
|
||||
if (result != Messages.YES) return
|
||||
if (result != com.intellij.openapi.ui.Messages.YES) return
|
||||
controller.delete(item)
|
||||
}
|
||||
|
||||
internal fun confirmDelete(items: List<LocalHistoryItem>) {
|
||||
val active = items.filter { !controller.deleting(it) }
|
||||
if (active.isEmpty()) return
|
||||
val msg = if (active.size == 1)
|
||||
KiloBundle.message("history.delete.confirm.message", title(active[0]))
|
||||
else
|
||||
KiloBundle.message("history.delete.confirm.message.multiple", active.size)
|
||||
val result = com.intellij.openapi.ui.Messages.showYesNoDialog(
|
||||
this,
|
||||
msg,
|
||||
KiloBundle.message("history.delete.confirm.title"),
|
||||
com.intellij.openapi.ui.Messages.getWarningIcon(),
|
||||
)
|
||||
if (result != com.intellij.openapi.ui.Messages.YES) return
|
||||
active.forEach { controller.delete(it) }
|
||||
}
|
||||
|
||||
internal fun itemCount() = activeModel().size
|
||||
|
||||
internal fun selectedSource() = if (tabs.selectedInfo === cloudInfo) HistorySource.CLOUD else HistorySource.LOCAL
|
||||
@@ -306,29 +363,38 @@ class HistoryPanel(
|
||||
activeList().selectedIndex = index
|
||||
}
|
||||
|
||||
internal fun selectIndices(vararg indices: Int) {
|
||||
activeList().selectedIndices = indices
|
||||
}
|
||||
|
||||
internal fun selectedIndex() = activeList().selectedIndex
|
||||
|
||||
internal fun listFocusable() = activeList().isFocusable
|
||||
|
||||
internal fun listSelectionMode() = activeList().selectionMode
|
||||
|
||||
internal fun loadMoreFocusable() = more.isFocusable
|
||||
|
||||
internal fun listCursor() = activeList().cursor.type
|
||||
|
||||
internal fun backText(): String? {
|
||||
val view = activeInfo().foreSideComponent ?: return null
|
||||
return UIUtil.uiTraverser(view).filter(JButton::class.java).firstOrNull()?.text
|
||||
return UIUtil.uiTraverser(view).filter(ActionButton::class.java).firstOrNull()?.presentation?.text
|
||||
}
|
||||
|
||||
internal fun backCursor(): Int? {
|
||||
val view = activeInfo().foreSideComponent ?: return null
|
||||
return UIUtil.uiTraverser(view).filter(JButton::class.java).firstOrNull()?.cursor?.type
|
||||
return UIUtil.uiTraverser(view).filter(ActionButton::class.java).firstOrNull()?.cursor?.type
|
||||
}
|
||||
|
||||
internal fun clickBack() {
|
||||
val view = activeInfo().foreSideComponent ?: return
|
||||
UIUtil.uiTraverser(view).filter(JButton::class.java).firstOrNull()?.doClick()
|
||||
UIUtil.uiTraverser(view).filter(ActionButton::class.java).firstOrNull()?.click()
|
||||
}
|
||||
|
||||
internal fun clickDelete() {
|
||||
localList.selectedValue?.let(controller::delete)
|
||||
val items = localList.selectedValuesList.filterIsInstance<LocalHistoryItem>()
|
||||
items.forEach { controller.delete(it) }
|
||||
}
|
||||
|
||||
internal fun clickCloud() {
|
||||
@@ -354,16 +420,12 @@ class HistoryPanel(
|
||||
return items.indices.mapNotNull { HistoryRenderer.section(items, it) }
|
||||
}
|
||||
|
||||
internal fun deleteVisible(index: Int, selected: Boolean = true): Boolean {
|
||||
val item = controller.local.getElementAt(index)
|
||||
val view = localList.cellRenderer.getListCellRendererComponent(localList, item, index, selected, false)
|
||||
return view is HistoryRenderer<*> && view.deleteVisible()
|
||||
}
|
||||
internal fun repoOnlyVisible() = repoOnly.isVisible
|
||||
|
||||
internal fun cloudDeleteVisible(index: Int, selected: Boolean = true): Boolean {
|
||||
val item = controller.cloud.getElementAt(index)
|
||||
val view = cloudList.cellRenderer.getListCellRendererComponent(cloudList, item, index, selected, false)
|
||||
return view is HistoryRenderer<*> && view.deleteVisible()
|
||||
internal fun repoOnlySelected() = repoOnly.isSelected
|
||||
|
||||
internal fun clickRepoOnly() {
|
||||
repoOnly.doClick()
|
||||
}
|
||||
|
||||
private fun activeList(): JBList<out HistoryItem> = if (tabs.selectedInfo === cloudInfo) cloudList else localList
|
||||
@@ -385,15 +447,14 @@ class HistoryPanel(
|
||||
}
|
||||
|
||||
override fun dispose() {
|
||||
// no-op
|
||||
controller.onRepoOnlyChanged = null
|
||||
}
|
||||
|
||||
private class LoadMoreButton : JButton(KiloBundle.message("history.cloud.load.more")) {
|
||||
private var over = false
|
||||
|
||||
init {
|
||||
isFocusable = false
|
||||
setRequestFocusEnabled(false)
|
||||
isFocusable = true
|
||||
isContentAreaFilled = false
|
||||
isBorderPainted = false
|
||||
isOpaque = false
|
||||
|
||||
+2
-2
@@ -35,8 +35,8 @@ internal object HistoryTime {
|
||||
val today = Instant.ofEpochMilli(now).atZone(zone).toLocalDate()
|
||||
if (date == today) return HistorySection.TODAY
|
||||
if (date == today.minusDays(1)) return HistorySection.YESTERDAY
|
||||
if (date.isAfter(today.minusDays(7)) && date.isBefore(today)) return HistorySection.WEEK
|
||||
if (date.isAfter(today.minusDays(30)) && date.isBefore(today)) return HistorySection.MONTH
|
||||
if (!date.isBefore(today.minusDays(7)) && date.isBefore(today)) return HistorySection.WEEK
|
||||
if (!date.isBefore(today.minusDays(30)) && date.isBefore(today)) return HistorySection.MONTH
|
||||
return HistorySection.OLDER
|
||||
}
|
||||
|
||||
|
||||
+2
-2
@@ -4,7 +4,7 @@ import ai.kilocode.client.plugin.KiloBundle
|
||||
import ai.kilocode.client.session.SessionRef
|
||||
import ai.kilocode.client.session.history.HistoryTime
|
||||
import ai.kilocode.client.session.history.LocalHistoryItem
|
||||
import ai.kilocode.client.session.history.clicked
|
||||
import ai.kilocode.client.session.history.itemAt
|
||||
import ai.kilocode.client.session.history.title
|
||||
import ai.kilocode.client.session.ui.style.SessionEditorStyle
|
||||
import ai.kilocode.client.session.ui.style.SessionEditorStyleTarget
|
||||
@@ -71,7 +71,7 @@ class EmptySessionPanel(
|
||||
emptyText.clear()
|
||||
addMouseListener(object : MouseAdapter() {
|
||||
override fun mouseClicked(e: MouseEvent) {
|
||||
val item = clicked(this@apply, e) ?: return
|
||||
val item = itemAt(this@apply, e) ?: return
|
||||
controller.openSession(SessionRef.Local(item.session))
|
||||
}
|
||||
|
||||
|
||||
+1
-1
@@ -108,7 +108,7 @@ class SessionHeaderPanel(
|
||||
add(output)
|
||||
add(Box.createHorizontalStrut(UiStyle.Gap.sm()))
|
||||
add(cacheRead)
|
||||
add(Box.createHorizontalStrut(UiStyle.Gap.small()))
|
||||
add(Box.createHorizontalStrut(UiStyle.Gap.sm()))
|
||||
add(cacheWrite)
|
||||
}
|
||||
private val todoRow = JPanel(FlowLayout(FlowLayout.LEFT, UiStyle.Gap.md(), 0)).apply {
|
||||
|
||||
@@ -58,5 +58,25 @@
|
||||
<keyboard-shortcut keymap="$default"
|
||||
first-keystroke="alt S"/>
|
||||
</action>
|
||||
|
||||
<action id="Kilo.Session.Open"
|
||||
class="ai.kilocode.client.actions.OpenSessionAction">
|
||||
<keyboard-shortcut keymap="$default" first-keystroke="ENTER"/>
|
||||
</action>
|
||||
|
||||
<action id="Kilo.Session.Rename"
|
||||
class="ai.kilocode.client.actions.RenameSessionAction"
|
||||
use-shortcut-of="RenameElement"/>
|
||||
|
||||
<action id="Kilo.Session.Delete"
|
||||
class="ai.kilocode.client.actions.DeleteSessionAction"
|
||||
use-shortcut-of="$Delete"/>
|
||||
|
||||
<group id="Kilo.History.ContextMenu">
|
||||
<reference ref="Kilo.Session.Open"/>
|
||||
<separator/>
|
||||
<reference ref="Kilo.Session.Rename"/>
|
||||
<reference ref="Kilo.Session.Delete"/>
|
||||
</group>
|
||||
</actions>
|
||||
</idea-plugin>
|
||||
|
||||
+22
-11
@@ -14,21 +14,21 @@ session.tab.new=New Session
|
||||
session.tab.untitled=Untitled Session
|
||||
|
||||
session.permission.title=Permission request
|
||||
session.permission.meta=Tool: {0} \u2022 Patterns: {1}
|
||||
session.permission.meta=Tool: {0} • Patterns: {1}
|
||||
session.permission.allow=Allow
|
||||
session.permission.deny=Deny
|
||||
session.question.dismiss=Dismiss
|
||||
|
||||
session.status.considering=Considering next steps\u2026
|
||||
session.status.thinking=Thinking\u2026
|
||||
session.status.writing=Writing response\u2026
|
||||
session.status.delegating=Delegating work\u2026
|
||||
session.status.planning=Planning\u2026
|
||||
session.status.gathering=Gathering context\u2026
|
||||
session.status.searching.codebase=Searching codebase\u2026
|
||||
session.status.searching.web=Searching web\u2026
|
||||
session.status.editing=Making edits\u2026
|
||||
session.status.commands=Running commands\u2026
|
||||
session.status.considering=Considering next steps…
|
||||
session.status.thinking=Thinking…
|
||||
session.status.writing=Writing response…
|
||||
session.status.delegating=Delegating work…
|
||||
session.status.planning=Planning…
|
||||
session.status.gathering=Gathering context…
|
||||
session.status.searching.codebase=Searching codebase…
|
||||
session.status.searching.web=Searching web…
|
||||
session.status.editing=Making edits…
|
||||
session.status.commands=Running commands…
|
||||
|
||||
session.part.reasoning=Reasoning
|
||||
session.part.compaction=context compacted
|
||||
@@ -92,6 +92,9 @@ history.untitled=Untitled
|
||||
history.delete.text=Delete
|
||||
history.delete.confirm.title=Delete session?
|
||||
history.delete.confirm.message=Delete "{0}" from local history?
|
||||
history.delete.confirm.message.multiple=Delete {0} sessions from local history?
|
||||
history.rename.title=Rename Session
|
||||
history.rename.prompt=New session name:
|
||||
history.cloud.load.more=Load more
|
||||
history.cloud.repo.only=Only this repository
|
||||
history.group.today=Today
|
||||
@@ -108,6 +111,7 @@ history.time.years={0}y ago
|
||||
history.error.local=Failed to load local history
|
||||
history.error.cloud=Failed to load cloud history
|
||||
history.error.local.delete=Failed to delete session
|
||||
history.error.local.rename=Failed to rename session
|
||||
history.error.cloud.delete=Cloud sessions cannot be deleted yet
|
||||
|
||||
action.Kilo.Settings.text=Settings
|
||||
@@ -126,3 +130,10 @@ action.Kilo.Restart.text=Restart Kilo
|
||||
action.Kilo.Restart.description=Kill and restart the CLI process
|
||||
action.Kilo.Reinstall.text=Reinstall Kilo
|
||||
action.Kilo.Reinstall.description=Re-extract the CLI binary and restart
|
||||
action.Kilo.Session.Open.text=Open
|
||||
action.Kilo.Session.Open.description=Open the selected session
|
||||
action.Kilo.Session.Rename.text=Rename
|
||||
action.Kilo.Session.Rename.description=Rename the selected session
|
||||
action.Kilo.Session.Delete.text=Delete
|
||||
action.Kilo.Session.Delete.description=Delete the selected session(s)
|
||||
action.Kilo.History.ContextMenu.text=History Actions
|
||||
|
||||
+139
@@ -0,0 +1,139 @@
|
||||
session.connection.connecting=جاري التحميل…
|
||||
session.connection.error.app=فشل الاتصال
|
||||
session.connection.error.workspace=فشل تحميل مساحة العمل
|
||||
session.connection.error.unknown=خطأ غير معروف
|
||||
session.connection.retry=إعادة المحاولة
|
||||
session.connection.warning.config=تحذيرات التكوين
|
||||
|
||||
session.empty.welcome=Kilo Code هو مساعد برمجة بالذكاء الاصطناعي. اطلب منه بناء ميزات أو إصلاح أخطاء أو شرح قاعدة الكود.
|
||||
session.empty.loading=جاري التحميل…
|
||||
session.empty.recent=الحديثة
|
||||
session.showHistory=عرض السجل
|
||||
session.scroll.bottom=التمرير إلى الأسفل
|
||||
session.tab.new=جلسة جديدة
|
||||
session.tab.untitled=جلسة بدون عنوان
|
||||
|
||||
session.permission.title=طلب إذن
|
||||
session.permission.meta=الأداة: {0} • الأنماط: {1}
|
||||
session.permission.allow=سماح
|
||||
session.permission.deny=رفض
|
||||
session.question.dismiss=رفض
|
||||
|
||||
session.status.considering=جار التفكير في الخطوات التالية…
|
||||
session.status.thinking=جار التفكير…
|
||||
session.status.writing=جاري كتابة الرد…
|
||||
session.status.delegating=جار تفويض العمل…
|
||||
session.status.planning=جار التخطيط…
|
||||
session.status.gathering=جار جمع السياق…
|
||||
session.status.searching.codebase=جار البحث في قاعدة الكود…
|
||||
session.status.searching.web=جار البحث على الويب…
|
||||
session.status.editing=جاري التحرير…
|
||||
session.status.commands=جاري تنفيذ الأوامر…
|
||||
|
||||
session.part.reasoning=الاستدلال
|
||||
session.part.compaction=السياق مضغوط
|
||||
session.part.tool.copy=نسخ
|
||||
session.part.tool.error=خطأ
|
||||
session.part.tool.pending=معلق
|
||||
session.part.tool.read=قراءة
|
||||
session.part.tool.running=قيد التشغيل
|
||||
session.part.tool.shell=Shell
|
||||
session.part.tool.truncated=المخرجات مختصرة في المعاينة المسبقة. المخرجات الكاملة لا تزال في بيانات الجلسة.
|
||||
|
||||
session.error.prompt=فشل الطلب
|
||||
session.error.compact=فشل ضغط الجلسة
|
||||
session.error.unknown=خطأ غير معروف
|
||||
|
||||
session.header.tokens=الرموز
|
||||
session.header.tokens.description=الرموز المستخدمة في آخر رد للمساعد: الإدخال، الإخراج، كتابات المخزن المؤقت وقراءاته.
|
||||
session.header.input=إدخال {0}
|
||||
session.header.output=إخراج {0}
|
||||
session.header.cache.write=كتابة مخزن مؤقت {0}
|
||||
session.header.cache.read=قراءة مخزن مؤقت {0}
|
||||
session.header.compact=ضغط الجلسة
|
||||
session.header.compact.description=تلخيص الجلسة لتقليل حجم السياق
|
||||
session.header.expand=عرض مقاييس الجلسة
|
||||
session.header.collapse=إخفاء مقاييس الجلسة
|
||||
session.header.todos.progress={0}/{1} مهام مكتملة
|
||||
session.header.todos.done=جميع {0} مهام مكتملة
|
||||
session.header.context.tooltip.percent={0} رمز ({1}٪ من السياق)
|
||||
session.header.context.tooltip.tokens={0} رمز
|
||||
session.header.context.used={0} / {1} رمز مستخدم
|
||||
session.header.context.reserved={0} محجوز للإخراج
|
||||
session.header.context.available={0} متاح
|
||||
|
||||
prompt.placeholder=اكتب رسالة…
|
||||
prompt.placeholder.with.shortcuts=اكتب رسالة… ({0} للإرسال، {1} لسطر جديد)
|
||||
prompt.placeholder.with.send=اكتب رسالة… ({0} للإرسال)
|
||||
prompt.placeholder.with.newline=اكتب رسالة… ({0} لسطر جديد)
|
||||
prompt.button.send=إرسال
|
||||
prompt.button.stop=إيقاف
|
||||
prompt.button.send.tooltip.stop=للإيقاف، اضغط {0}
|
||||
mode.picker.tooltip=اختيار الوضع
|
||||
mode.picker.deprecated=مهجور
|
||||
model.picker.tooltip=اختيار النموذج
|
||||
model.picker.recommended=موصى به
|
||||
model.picker.favorites=المفضلة
|
||||
model.picker.search=بحث عن نماذج
|
||||
model.picker.no.matches=لا توجد نماذج مطابقة
|
||||
model.picker.favorite.add=إضافة إلى المفضلة
|
||||
model.picker.favorite.remove=إزالة من المفضلة
|
||||
model.picker.free=مجاني
|
||||
model.picker.reset=إعادة ضبط النموذج للافتراضي
|
||||
reasoning.picker.tooltip=اختيار مستوى الاستدلال
|
||||
|
||||
history.tab.local=محلي
|
||||
history.tab.cloud=سحابي
|
||||
history.back=رجوع
|
||||
history.search.placeholder=بحث في الجلسات
|
||||
history.empty=لا توجد جلسات
|
||||
history.loading=جاري التحميل…
|
||||
history.untitled=بدون عنوان
|
||||
history.delete.text=حذف
|
||||
history.delete.confirm.title=حذف الجلسة؟
|
||||
history.delete.confirm.message=حذف "{0}" من السجل المحلي؟
|
||||
history.delete.confirm.message.multiple=حذف {0} جلسات من السجل المحلي؟
|
||||
history.rename.title=إعادة تسمية الجلسة
|
||||
history.rename.prompt=اسم الجلسة الجديد:
|
||||
history.cloud.load.more=تحميل المزيد
|
||||
history.cloud.repo.only=هذا المستودع فقط
|
||||
history.group.today=اليوم
|
||||
history.group.yesterday=أمس
|
||||
history.group.week=هذا الأسبوع
|
||||
history.group.month=هذا الشهر
|
||||
history.group.older=أقدم
|
||||
history.time.moments=للتو
|
||||
history.time.minutes=منذ {0} دقيقة
|
||||
history.time.hours=منذ {0} ساعة
|
||||
history.time.days=منذ {0} يوم
|
||||
history.time.months=منذ {0} شهر
|
||||
history.time.years=منذ {0} سنة
|
||||
history.error.local=فشل تحميل السجل المحلي
|
||||
history.error.cloud=فشل تحميل سجل السحابة
|
||||
history.error.local.delete=فشل حذف الجلسة
|
||||
history.error.local.rename=فشل إعادة تسمية الجلسة
|
||||
history.error.cloud.delete=لا يمكن حذف جلسات السحابة بعد
|
||||
|
||||
action.Kilo.Settings.text=الإعدادات
|
||||
action.Kilo.Settings.description=إعدادات Kilo Code
|
||||
action.Kilo.NewSession.text=جلسة جديدة
|
||||
action.Kilo.NewSession.description=بدء جلسة Kilo جديدة
|
||||
action.Kilo.History.text=السجل
|
||||
action.Kilo.History.description=عرض سجل الجلسات
|
||||
action.Kilo.SendPrompt.text=إرسال الطلب
|
||||
action.Kilo.SendPrompt.description=إرسال طلب Kilo الحالي
|
||||
action.Kilo.StopSession.text=إيقاف الجلسة
|
||||
action.Kilo.StopSession.description=إيقاف جلسة Kilo الحالية
|
||||
action.Kilo.SettingsGroup.text=الإعدادات
|
||||
action.Kilo.SettingsGroup.description=إعدادات Kilo Code
|
||||
action.Kilo.Restart.text=إعادة تشغيل Kilo
|
||||
action.Kilo.Restart.description=إنهاء عملية CLI وإعادة تشغيلها
|
||||
action.Kilo.Reinstall.text=إعادة تثبيت Kilo
|
||||
action.Kilo.Reinstall.description=استخراج CLI مجدداً وإعادة التشغيل
|
||||
action.Kilo.Session.Open.text=فتح
|
||||
action.Kilo.Session.Open.description=فتح الجلسة المحددة
|
||||
action.Kilo.Session.Rename.text=إعادة تسمية
|
||||
action.Kilo.Session.Rename.description=إعادة تسمية الجلسة المحددة
|
||||
action.Kilo.Session.Delete.text=حذف
|
||||
action.Kilo.Session.Delete.description=حذف الجلسة (الجلسات) المحددة
|
||||
action.Kilo.History.ContextMenu.text=إجراءات السجل
|
||||
+139
@@ -0,0 +1,139 @@
|
||||
session.connection.connecting=Učitavanje…
|
||||
session.connection.error.app=Greška pri spajanju
|
||||
session.connection.error.workspace=Greška pri učitavanju radnog prostora
|
||||
session.connection.error.unknown=Nepoznata greška
|
||||
session.connection.retry=Pokušaj ponovo
|
||||
session.connection.warning.config=Upozorenja konfiguracije
|
||||
|
||||
session.empty.welcome=Kilo Code je AI asistent za kodiranje. Zatražite od njega da gradi funkcije, ispravlja greške ili objašnjava vašu bazu koda.
|
||||
session.empty.loading=Učitavanje…
|
||||
session.empty.recent=NEDAVNO
|
||||
session.showHistory=Prikaži historiju
|
||||
session.scroll.bottom=Skrolaj na dno
|
||||
session.tab.new=Nova sesija
|
||||
session.tab.untitled=Sesija bez naslova
|
||||
|
||||
session.permission.title=Zahtjev za dozvolu
|
||||
session.permission.meta=Alat: {0} • Uzorci: {1}
|
||||
session.permission.allow=Dozvoli
|
||||
session.permission.deny=Odbij
|
||||
session.question.dismiss=Zatvori
|
||||
|
||||
session.status.considering=Razmatranje sljedećih koraka…
|
||||
session.status.thinking=Razmišljanje…
|
||||
session.status.writing=Pisanje odgovora…
|
||||
session.status.delegating=Delegiranje posla…
|
||||
session.status.planning=Planiranje…
|
||||
session.status.gathering=Prikupljanje konteksta…
|
||||
session.status.searching.codebase=Pretraživanje baze koda…
|
||||
session.status.searching.web=Pretraživanje weba…
|
||||
session.status.editing=Uređivanje…
|
||||
session.status.commands=Pokretanje komandi…
|
||||
|
||||
session.part.reasoning=Rezonovanje
|
||||
session.part.compaction=kontekst komprimovan
|
||||
session.part.tool.copy=Kopiraj
|
||||
session.part.tool.error=Greška
|
||||
session.part.tool.pending=Na čekanju
|
||||
session.part.tool.read=Čita
|
||||
session.part.tool.running=Pokrenuto
|
||||
session.part.tool.shell=Shell
|
||||
session.part.tool.truncated=Izlaz skraćen u pregledu. Potpuni izlaz ostaje u podacima sesije.
|
||||
|
||||
session.error.prompt=Greška upita
|
||||
session.error.compact=Komprimiranje sesije neuspješno
|
||||
session.error.unknown=Nepoznata greška
|
||||
|
||||
session.header.tokens=Tokeni
|
||||
session.header.tokens.description=Tokeni korišteni u posljednjem odgovoru asistenta: ulaz, izlaz, pisanja u keš i čitanja iz keša.
|
||||
session.header.input=ulaz {0}
|
||||
session.header.output=izlaz {0}
|
||||
session.header.cache.write=pisanje u keš {0}
|
||||
session.header.cache.read=čitanje iz keša {0}
|
||||
session.header.compact=Komprimiraj sesiju
|
||||
session.header.compact.description=Sažmite sesiju kako biste smanjili veličinu konteksta
|
||||
session.header.expand=Prikaži metrike sesije
|
||||
session.header.collapse=Sakrij metrike sesije
|
||||
session.header.todos.progress={0}/{1} zadataka završeno
|
||||
session.header.todos.done=Svih {0} zadataka završeno
|
||||
session.header.context.tooltip.percent={0} tokena ({1}% konteksta)
|
||||
session.header.context.tooltip.tokens={0} tokena
|
||||
session.header.context.used={0} / {1} tokena korišteno
|
||||
session.header.context.reserved={0} rezervisano za izlaz
|
||||
session.header.context.available={0} dostupno
|
||||
|
||||
prompt.placeholder=Upišite poruku…
|
||||
prompt.placeholder.with.shortcuts=Upišite poruku… ({0} za slanje, {1} za novi red)
|
||||
prompt.placeholder.with.send=Upišite poruku… ({0} za slanje)
|
||||
prompt.placeholder.with.newline=Upišite poruku… ({0} za novi red)
|
||||
prompt.button.send=Pošalji
|
||||
prompt.button.stop=Zaustavi
|
||||
prompt.button.send.tooltip.stop=Da zaustavite, pritisnite {0}
|
||||
mode.picker.tooltip=Odaberi način rada
|
||||
mode.picker.deprecated=zastarjelo
|
||||
model.picker.tooltip=Odaberi model
|
||||
model.picker.recommended=Preporučeno
|
||||
model.picker.favorites=Omiljeni
|
||||
model.picker.search=Pretraži modele
|
||||
model.picker.no.matches=Nema odgovarajućih modela
|
||||
model.picker.favorite.add=Dodaj u omiljene
|
||||
model.picker.favorite.remove=Ukloni iz omiljenih
|
||||
model.picker.free=Besplatno
|
||||
model.picker.reset=Vrati model na zadano
|
||||
reasoning.picker.tooltip=Odaberi razinu rezoniranja
|
||||
|
||||
history.tab.local=Lokalno
|
||||
history.tab.cloud=Oblak
|
||||
history.back=Nazad
|
||||
history.search.placeholder=Pretraži sesije
|
||||
history.empty=Nema sesija
|
||||
history.loading=Učitavanje…
|
||||
history.untitled=Bez naslova
|
||||
history.delete.text=Obriši
|
||||
history.delete.confirm.title=Obrisati sesiju?
|
||||
history.delete.confirm.message=Obrisati "{0}" iz lokalne historije?
|
||||
history.delete.confirm.message.multiple=Obrisati {0} sesija iz lokalne historije?
|
||||
history.rename.title=Preimenuj sesiju
|
||||
history.rename.prompt=Novi naziv sesije:
|
||||
history.cloud.load.more=Učitaj više
|
||||
history.cloud.repo.only=Samo ovaj repozitorij
|
||||
history.group.today=Danas
|
||||
history.group.yesterday=Jučer
|
||||
history.group.week=Ove sedmice
|
||||
history.group.month=Ovog mjeseca
|
||||
history.group.older=Starije
|
||||
history.time.moments=Upravo sada
|
||||
history.time.minutes=Prije {0} min
|
||||
history.time.hours=Prije {0} h
|
||||
history.time.days=Prije {0} d
|
||||
history.time.months=Prije {0} mj
|
||||
history.time.years=Prije {0} g
|
||||
history.error.local=Učitavanje lokalne historije neuspješno
|
||||
history.error.cloud=Učitavanje historije iz oblaka neuspješno
|
||||
history.error.local.delete=Brisanje sesije neuspješno
|
||||
history.error.local.rename=Preimenovanje sesije neuspješno
|
||||
history.error.cloud.delete=Sesije iz oblaka još uvijek nije moguće brisati
|
||||
|
||||
action.Kilo.Settings.text=Postavke
|
||||
action.Kilo.Settings.description=Postavke Kilo Code
|
||||
action.Kilo.NewSession.text=Nova sesija
|
||||
action.Kilo.NewSession.description=Pokrenite novu Kilo sesiju
|
||||
action.Kilo.History.text=Historija
|
||||
action.Kilo.History.description=Prikaži historiju sesija
|
||||
action.Kilo.SendPrompt.text=Pošalji upit
|
||||
action.Kilo.SendPrompt.description=Pošalji trenutni Kilo upit
|
||||
action.Kilo.StopSession.text=Zaustavi sesiju
|
||||
action.Kilo.StopSession.description=Zaustavi trenutnu Kilo sesiju
|
||||
action.Kilo.SettingsGroup.text=Postavke
|
||||
action.Kilo.SettingsGroup.description=Postavke Kilo Code
|
||||
action.Kilo.Restart.text=Ponovo pokrenite Kilo
|
||||
action.Kilo.Restart.description=Ugasite i ponovo pokrenite CLI proces
|
||||
action.Kilo.Reinstall.text=Ponovo instalirajte Kilo
|
||||
action.Kilo.Reinstall.description=Ponovo raspakirajte CLI binarnu datoteku i ponovo pokrenite
|
||||
action.Kilo.Session.Open.text=Otvori
|
||||
action.Kilo.Session.Open.description=Otvorite odabranu sesiju
|
||||
action.Kilo.Session.Rename.text=Preimenuj
|
||||
action.Kilo.Session.Rename.description=Preimenujte odabranu sesiju
|
||||
action.Kilo.Session.Delete.text=Obriši
|
||||
action.Kilo.Session.Delete.description=Obrišite odabrane sesije
|
||||
action.Kilo.History.ContextMenu.text=Akcije historije
|
||||
+139
@@ -0,0 +1,139 @@
|
||||
session.connection.connecting=Indlæser…
|
||||
session.connection.error.app=Forbindelsesfejl
|
||||
session.connection.error.workspace=Fejl ved indlæsning af arbejdsområde
|
||||
session.connection.error.unknown=Ukendt fejl
|
||||
session.connection.retry=Prøv igen
|
||||
session.connection.warning.config=Konfigurationsadvarsler
|
||||
|
||||
session.empty.welcome=Kilo Code er en AI-kodningsassistent. Bed den om at bygge funktioner, rette fejl eller forklare din kodebase.
|
||||
session.empty.loading=Indlæser…
|
||||
session.empty.recent=SENESTE
|
||||
session.showHistory=Vis historik
|
||||
session.scroll.bottom=Rul til bunden
|
||||
session.tab.new=Ny session
|
||||
session.tab.untitled=Unavngivet session
|
||||
|
||||
session.permission.title=Tilladelsesanmodning
|
||||
session.permission.meta=Værktøj: {0} • Mønstre: {1}
|
||||
session.permission.allow=Tillad
|
||||
session.permission.deny=Afvis
|
||||
session.question.dismiss=Luk
|
||||
|
||||
session.status.considering=Overvejer næste skridt…
|
||||
session.status.thinking=Tænker…
|
||||
session.status.writing=Skriver svar…
|
||||
session.status.delegating=Delegerer arbejde…
|
||||
session.status.planning=Planlagger…
|
||||
session.status.gathering=Indsamler kontekst…
|
||||
session.status.searching.codebase=Søger i kodebasen…
|
||||
session.status.searching.web=Søger på nettet…
|
||||
session.status.editing=Foretager redigeringer…
|
||||
session.status.commands=Kører kommandoer…
|
||||
|
||||
session.part.reasoning=Ræsonnering
|
||||
session.part.compaction=kontekst komprimeret
|
||||
session.part.tool.copy=Kopiér
|
||||
session.part.tool.error=Fejl
|
||||
session.part.tool.pending=Afventer
|
||||
session.part.tool.read=Læs
|
||||
session.part.tool.running=Kører
|
||||
session.part.tool.shell=Shell
|
||||
session.part.tool.truncated=Output afkortet i forhåndsvisning. Fuldt output forbliver i sessionsdata.
|
||||
|
||||
session.error.prompt=Prompt mislykkedes
|
||||
session.error.compact=Sessionskomprimering mislykkedes
|
||||
session.error.unknown=Ukendt fejl
|
||||
|
||||
session.header.tokens=Tokens
|
||||
session.header.tokens.description=Tokens brugt af det seneste assistentsvar: input, output, cache-skrivninger og cache-læsninger.
|
||||
session.header.input=ind {0}
|
||||
session.header.output=ud {0}
|
||||
session.header.cache.write=cache-skrivning {0}
|
||||
session.header.cache.read=cache-læsning {0}
|
||||
session.header.compact=Komprimer session
|
||||
session.header.compact.description=Opsummer sessionen for at reducere kontekstsstørrelsen
|
||||
session.header.expand=Vis sessionsmålinger
|
||||
session.header.collapse=Skjul sessionsmålinger
|
||||
session.header.todos.progress={0}/{1} opgaver færdige
|
||||
session.header.todos.done=Alle {0} opgaver færdige
|
||||
session.header.context.tooltip.percent={0} tokens ({1}% af konteksten)
|
||||
session.header.context.tooltip.tokens={0} tokens
|
||||
session.header.context.used={0} / {1} tokens brugt
|
||||
session.header.context.reserved={0} reserveret til output
|
||||
session.header.context.available={0} tilgængelige
|
||||
|
||||
prompt.placeholder=Skriv en besked…
|
||||
prompt.placeholder.with.shortcuts=Skriv en besked… ({0} for at sende, {1} for ny linje)
|
||||
prompt.placeholder.with.send=Skriv en besked… ({0} for at sende)
|
||||
prompt.placeholder.with.newline=Skriv en besked… ({0} for ny linje)
|
||||
prompt.button.send=Send
|
||||
prompt.button.stop=Stop
|
||||
prompt.button.send.tooltip.stop=Tryk på {0} for at stoppe
|
||||
mode.picker.tooltip=Vælg tilstand
|
||||
mode.picker.deprecated=forældet
|
||||
model.picker.tooltip=Vælg model
|
||||
model.picker.recommended=Anbefalet
|
||||
model.picker.favorites=Favoritter
|
||||
model.picker.search=Søg efter modeller
|
||||
model.picker.no.matches=Ingen matchende modeller
|
||||
model.picker.favorite.add=Føj til favoritter
|
||||
model.picker.favorite.remove=Fjern fra favoritter
|
||||
model.picker.free=Gratis
|
||||
model.picker.reset=Nulstil model til standard
|
||||
reasoning.picker.tooltip=Vælg ræsonneringsindsats
|
||||
|
||||
history.tab.local=Lokal
|
||||
history.tab.cloud=Sky
|
||||
history.back=Tilbage
|
||||
history.search.placeholder=Søg i sessioner
|
||||
history.empty=Ingen sessioner
|
||||
history.loading=Indlæser…
|
||||
history.untitled=Unavngivet
|
||||
history.delete.text=Slet
|
||||
history.delete.confirm.title=Slet session?
|
||||
history.delete.confirm.message=Slet "{0}" fra lokal historik?
|
||||
history.delete.confirm.message.multiple=Slet {0} sessioner fra lokal historik?
|
||||
history.rename.title=Omdøb session
|
||||
history.rename.prompt=Nyt sessionsnavn:
|
||||
history.cloud.load.more=Indlæs mere
|
||||
history.cloud.repo.only=Kun dette arkiv
|
||||
history.group.today=I dag
|
||||
history.group.yesterday=I går
|
||||
history.group.week=Denne uge
|
||||
history.group.month=Denne måned
|
||||
history.group.older=Ældre
|
||||
history.time.moments=Lige nu
|
||||
history.time.minutes={0} min siden
|
||||
history.time.hours={0} t siden
|
||||
history.time.days={0} d siden
|
||||
history.time.months={0} mdr siden
|
||||
history.time.years={0} år siden
|
||||
history.error.local=Kunne ikke indlæse lokal historik
|
||||
history.error.cloud=Kunne ikke indlæse skyhistorik
|
||||
history.error.local.delete=Kunne ikke slette session
|
||||
history.error.local.rename=Kunne ikke omdøbe session
|
||||
history.error.cloud.delete=Skysessioner kan endnu ikke slettes
|
||||
|
||||
action.Kilo.Settings.text=Indstillinger
|
||||
action.Kilo.Settings.description=Kilo Code-indstillinger
|
||||
action.Kilo.NewSession.text=Ny session
|
||||
action.Kilo.NewSession.description=Start en ny Kilo-session
|
||||
action.Kilo.History.text=Historik
|
||||
action.Kilo.History.description=Vis sessionshistorik
|
||||
action.Kilo.SendPrompt.text=Send prompt
|
||||
action.Kilo.SendPrompt.description=Send den aktuelle Kilo-prompt
|
||||
action.Kilo.StopSession.text=Stop session
|
||||
action.Kilo.StopSession.description=Stop den aktuelle Kilo-session
|
||||
action.Kilo.SettingsGroup.text=Indstillinger
|
||||
action.Kilo.SettingsGroup.description=Kilo Code-indstillinger
|
||||
action.Kilo.Restart.text=Genstart Kilo
|
||||
action.Kilo.Restart.description=Afslut og genstart CLI-processen
|
||||
action.Kilo.Reinstall.text=Geninstaller Kilo
|
||||
action.Kilo.Reinstall.description=Udpak CLI-binærfilen igen og genstart
|
||||
action.Kilo.Session.Open.text=Åbn
|
||||
action.Kilo.Session.Open.description=Åbn den valgte session
|
||||
action.Kilo.Session.Rename.text=Omdøb
|
||||
action.Kilo.Session.Rename.description=Omdøb den valgte session
|
||||
action.Kilo.Session.Delete.text=Slet
|
||||
action.Kilo.Session.Delete.description=Slet den/de valgte session(er)
|
||||
action.Kilo.History.ContextMenu.text=Historikhandlinger
|
||||
+139
@@ -0,0 +1,139 @@
|
||||
session.connection.connecting=Wird geladen…
|
||||
session.connection.error.app=Verbindung fehlgeschlagen
|
||||
session.connection.error.workspace=Workspace-Laden fehlgeschlagen
|
||||
session.connection.error.unknown=Unbekannter Fehler
|
||||
session.connection.retry=Erneut versuchen
|
||||
session.connection.warning.config=Konfigurationswarnungen
|
||||
|
||||
session.empty.welcome=Kilo Code ist ein KI-Coding-Assistent. Bitten Sie ihn, Funktionen zu erstellen, Fehler zu beheben oder Ihre Codebasis zu erklären.
|
||||
session.empty.loading=Wird geladen…
|
||||
session.empty.recent=ZULETZT
|
||||
session.showHistory=Verlauf anzeigen
|
||||
session.scroll.bottom=Zum Ende scrollen
|
||||
session.tab.new=Neue Sitzung
|
||||
session.tab.untitled=Unbenannte Sitzung
|
||||
|
||||
session.permission.title=Berechtigungsanfrage
|
||||
session.permission.meta=Werkzeug: {0} • Muster: {1}
|
||||
session.permission.allow=Erlauben
|
||||
session.permission.deny=Ablehnen
|
||||
session.question.dismiss=Schließen
|
||||
|
||||
session.status.considering=Nächste Schritte abwägen…
|
||||
session.status.thinking=Nachdenken…
|
||||
session.status.writing=Antwort schreiben…
|
||||
session.status.delegating=Arbeit delegieren…
|
||||
session.status.planning=Planen…
|
||||
session.status.gathering=Kontext sammeln…
|
||||
session.status.searching.codebase=Codebasis durchsuchen…
|
||||
session.status.searching.web=Web durchsuchen…
|
||||
session.status.editing=Änderungen vornehmen…
|
||||
session.status.commands=Befehle ausführen…
|
||||
|
||||
session.part.reasoning=Denken
|
||||
session.part.compaction=Kontext komprimiert
|
||||
session.part.tool.copy=Kopieren
|
||||
session.part.tool.error=Fehler
|
||||
session.part.tool.pending=Ausstehend
|
||||
session.part.tool.read=Lesen
|
||||
session.part.tool.running=Läuft
|
||||
session.part.tool.shell=Shell
|
||||
session.part.tool.truncated=Ausgabe in der Vorschau gekürzt. Vollständige Ausgabe verbleibt in den Sitzungsdaten.
|
||||
|
||||
session.error.prompt=Anfrage fehlgeschlagen
|
||||
session.error.compact=Sitzungskomprimierung fehlgeschlagen
|
||||
session.error.unknown=Unbekannter Fehler
|
||||
|
||||
session.header.tokens=Token
|
||||
session.header.tokens.description=Von der letzten Assistentenantwort verwendete Token: Eingabe, Ausgabe, Cache-Schreibvorgänge und Cache-Lesevorgänge.
|
||||
session.header.input=ein {0}
|
||||
session.header.output=aus {0}
|
||||
session.header.cache.write=Cache-Schreiben {0}
|
||||
session.header.cache.read=Cache-Lesen {0}
|
||||
session.header.compact=Sitzung komprimieren
|
||||
session.header.compact.description=Sitzung zusammenfassen, um die Kontextgröße zu reduzieren
|
||||
session.header.expand=Sitzungsmetriken anzeigen
|
||||
session.header.collapse=Sitzungsmetriken ausblenden
|
||||
session.header.todos.progress={0}/{1} Aufgaben abgeschlossen
|
||||
session.header.todos.done=Alle {0} Aufgaben abgeschlossen
|
||||
session.header.context.tooltip.percent={0} Token ({1}% des Kontexts)
|
||||
session.header.context.tooltip.tokens={0} Token
|
||||
session.header.context.used={0} / {1} Token verwendet
|
||||
session.header.context.reserved={0} für Ausgabe reserviert
|
||||
session.header.context.available={0} verfügbar
|
||||
|
||||
prompt.placeholder=Nachricht eingeben…
|
||||
prompt.placeholder.with.shortcuts=Nachricht eingeben… ({0} zum Senden, {1} für neue Zeile)
|
||||
prompt.placeholder.with.send=Nachricht eingeben… ({0} zum Senden)
|
||||
prompt.placeholder.with.newline=Nachricht eingeben… ({0} für neue Zeile)
|
||||
prompt.button.send=Senden
|
||||
prompt.button.stop=Stopp
|
||||
prompt.button.send.tooltip.stop=Zum Stoppen drücken Sie {0}
|
||||
mode.picker.tooltip=Modus auswählen
|
||||
mode.picker.deprecated=veraltet
|
||||
model.picker.tooltip=Modell auswählen
|
||||
model.picker.recommended=Empfohlen
|
||||
model.picker.favorites=Favoriten
|
||||
model.picker.search=Modelle suchen
|
||||
model.picker.no.matches=Keine passenden Modelle
|
||||
model.picker.favorite.add=Zu Favoriten hinzufügen
|
||||
model.picker.favorite.remove=Aus Favoriten entfernen
|
||||
model.picker.free=Kostenlos
|
||||
model.picker.reset=Modell auf Standard zurücksetzen
|
||||
reasoning.picker.tooltip=Denkanstrengung auswählen
|
||||
|
||||
history.tab.local=Lokal
|
||||
history.tab.cloud=Cloud
|
||||
history.back=Zurück
|
||||
history.search.placeholder=Sitzungen suchen
|
||||
history.empty=Keine Sitzungen
|
||||
history.loading=Wird geladen…
|
||||
history.untitled=Unbenannt
|
||||
history.delete.text=Löschen
|
||||
history.delete.confirm.title=Sitzung löschen?
|
||||
history.delete.confirm.message="{0}" aus dem lokalen Verlauf löschen?
|
||||
history.delete.confirm.message.multiple={0} Sitzungen aus dem lokalen Verlauf löschen?
|
||||
history.rename.title=Sitzung umbenennen
|
||||
history.rename.prompt=Neuer Sitzungsname:
|
||||
history.cloud.load.more=Mehr laden
|
||||
history.cloud.repo.only=Nur dieses Repository
|
||||
history.group.today=Heute
|
||||
history.group.yesterday=Gestern
|
||||
history.group.week=Diese Woche
|
||||
history.group.month=Dieser Monat
|
||||
history.group.older=Älter
|
||||
history.time.moments=Gerade eben
|
||||
history.time.minutes={0} Min. zuvor
|
||||
history.time.hours={0} Std. zuvor
|
||||
history.time.days={0} Tage zuvor
|
||||
history.time.months={0} Mon. zuvor
|
||||
history.time.years={0} J. zuvor
|
||||
history.error.local=Lokaler Verlauf konnte nicht geladen werden
|
||||
history.error.cloud=Cloud-Verlauf konnte nicht geladen werden
|
||||
history.error.local.delete=Sitzung konnte nicht gelöscht werden
|
||||
history.error.local.rename=Sitzung konnte nicht umbenannt werden
|
||||
history.error.cloud.delete=Cloud-Sitzungen können noch nicht gelöscht werden
|
||||
|
||||
action.Kilo.Settings.text=Einstellungen
|
||||
action.Kilo.Settings.description=Kilo Code Einstellungen
|
||||
action.Kilo.NewSession.text=Neue Sitzung
|
||||
action.Kilo.NewSession.description=Neue Kilo-Sitzung starten
|
||||
action.Kilo.History.text=Verlauf
|
||||
action.Kilo.History.description=Sitzungsverlauf anzeigen
|
||||
action.Kilo.SendPrompt.text=Anfrage senden
|
||||
action.Kilo.SendPrompt.description=Aktuelle Kilo-Anfrage senden
|
||||
action.Kilo.StopSession.text=Sitzung stoppen
|
||||
action.Kilo.StopSession.description=Aktuelle Kilo-Sitzung stoppen
|
||||
action.Kilo.SettingsGroup.text=Einstellungen
|
||||
action.Kilo.SettingsGroup.description=Kilo Code Einstellungen
|
||||
action.Kilo.Restart.text=Kilo neu starten
|
||||
action.Kilo.Restart.description=CLI-Prozess beenden und neu starten
|
||||
action.Kilo.Reinstall.text=Kilo neu installieren
|
||||
action.Kilo.Reinstall.description=CLI-Binärdatei neu extrahieren und neu starten
|
||||
action.Kilo.Session.Open.text=Öffnen
|
||||
action.Kilo.Session.Open.description=Ausgewählte Sitzung öffnen
|
||||
action.Kilo.Session.Rename.text=Umbenennen
|
||||
action.Kilo.Session.Rename.description=Ausgewählte Sitzung umbenennen
|
||||
action.Kilo.Session.Delete.text=Löschen
|
||||
action.Kilo.Session.Delete.description=Ausgewählte Sitzung(en) löschen
|
||||
action.Kilo.History.ContextMenu.text=Verlaufsaktionen
|
||||
+139
@@ -0,0 +1,139 @@
|
||||
session.connection.connecting=Cargando…
|
||||
session.connection.error.app=Error de conexión
|
||||
session.connection.error.workspace=Error al cargar el espacio de trabajo
|
||||
session.connection.error.unknown=Error desconocido
|
||||
session.connection.retry=Reintentar
|
||||
session.connection.warning.config=Advertencias de configuración
|
||||
|
||||
session.empty.welcome=Kilo Code es un asistente de codificación con IA. Pida que construya funciones, corrija errores o explique su base de código.
|
||||
session.empty.loading=Cargando…
|
||||
session.empty.recent=RECIENTE
|
||||
session.showHistory=Mostrar historial
|
||||
session.scroll.bottom=Desplazarse al final
|
||||
session.tab.new=Nueva sesión
|
||||
session.tab.untitled=Sesión sin título
|
||||
|
||||
session.permission.title=Solicitud de permiso
|
||||
session.permission.meta=Herramienta: {0} • Patrones: {1}
|
||||
session.permission.allow=Permitir
|
||||
session.permission.deny=Denegar
|
||||
session.question.dismiss=Descartar
|
||||
|
||||
session.status.considering=Considerando los próximos pasos…
|
||||
session.status.thinking=Pensando…
|
||||
session.status.writing=Escribiendo respuesta…
|
||||
session.status.delegating=Delegando trabajo…
|
||||
session.status.planning=Planificando…
|
||||
session.status.gathering=Recopilando contexto…
|
||||
session.status.searching.codebase=Buscando en la base de código…
|
||||
session.status.searching.web=Buscando en la web…
|
||||
session.status.editing=Realizando ediciones…
|
||||
session.status.commands=Ejecutando comandos…
|
||||
|
||||
session.part.reasoning=Razonamiento
|
||||
session.part.compaction=contexto compactado
|
||||
session.part.tool.copy=Copiar
|
||||
session.part.tool.error=Error
|
||||
session.part.tool.pending=Pendiente
|
||||
session.part.tool.read=Leer
|
||||
session.part.tool.running=Ejecutando
|
||||
session.part.tool.shell=Shell
|
||||
session.part.tool.truncated=Salida truncada en la vista previa. La salida completa permanece en los datos de la sesión.
|
||||
|
||||
session.error.prompt=Error en el mensaje
|
||||
session.error.compact=Error al compactar la sesión
|
||||
session.error.unknown=Error desconocido
|
||||
|
||||
session.header.tokens=Tokens
|
||||
session.header.tokens.description=Tokens utilizados por la última respuesta del asistente: entrada, salida, escrituras en caché y lecturas en caché.
|
||||
session.header.input=entrada {0}
|
||||
session.header.output=salida {0}
|
||||
session.header.cache.write=escritura caché {0}
|
||||
session.header.cache.read=lectura caché {0}
|
||||
session.header.compact=Compactar sesión
|
||||
session.header.compact.description=Resumir la sesión para reducir el tamaño del contexto
|
||||
session.header.expand=Mostrar métricas de sesión
|
||||
session.header.collapse=Ocultar métricas de sesión
|
||||
session.header.todos.progress={0}/{1} tareas completadas
|
||||
session.header.todos.done=Todas las {0} tareas completadas
|
||||
session.header.context.tooltip.percent={0} tokens ({1}% del contexto)
|
||||
session.header.context.tooltip.tokens={0} tokens
|
||||
session.header.context.used={0} / {1} tokens utilizados
|
||||
session.header.context.reserved={0} reservados para la salida
|
||||
session.header.context.available={0} disponibles
|
||||
|
||||
prompt.placeholder=Escribe un mensaje…
|
||||
prompt.placeholder.with.shortcuts=Escribe un mensaje… ({0} para enviar, {1} para nueva línea)
|
||||
prompt.placeholder.with.send=Escribe un mensaje… ({0} para enviar)
|
||||
prompt.placeholder.with.newline=Escribe un mensaje… ({0} para nueva línea)
|
||||
prompt.button.send=Enviar
|
||||
prompt.button.stop=Detener
|
||||
prompt.button.send.tooltip.stop=Para detener, presiona {0}
|
||||
mode.picker.tooltip=Seleccionar modo
|
||||
mode.picker.deprecated=obsoleto
|
||||
model.picker.tooltip=Seleccionar modelo
|
||||
model.picker.recommended=Recomendado
|
||||
model.picker.favorites=Favoritos
|
||||
model.picker.search=Buscar modelos
|
||||
model.picker.no.matches=No hay modelos coincidentes
|
||||
model.picker.favorite.add=Añadir a favoritos
|
||||
model.picker.favorite.remove=Eliminar de favoritos
|
||||
model.picker.free=Gratis
|
||||
model.picker.reset=Restablecer modelo a predeterminado
|
||||
reasoning.picker.tooltip=Seleccionar esfuerzo de razonamiento
|
||||
|
||||
history.tab.local=Local
|
||||
history.tab.cloud=Nube
|
||||
history.back=Atrás
|
||||
history.search.placeholder=Buscar sesiones
|
||||
history.empty=Sin sesiones
|
||||
history.loading=Cargando…
|
||||
history.untitled=Sin título
|
||||
history.delete.text=Eliminar
|
||||
history.delete.confirm.title=¿Eliminar sesión?
|
||||
history.delete.confirm.message=¿Eliminar "{0}" del historial local?
|
||||
history.delete.confirm.message.multiple=¿Eliminar {0} sesiones del historial local?
|
||||
history.rename.title=Renombrar sesión
|
||||
history.rename.prompt=Nuevo nombre de sesión:
|
||||
history.cloud.load.more=Cargar más
|
||||
history.cloud.repo.only=Solo este repositorio
|
||||
history.group.today=Hoy
|
||||
history.group.yesterday=Ayer
|
||||
history.group.week=Esta semana
|
||||
history.group.month=Este mes
|
||||
history.group.older=Más antiguo
|
||||
history.time.moments=Hace un momento
|
||||
history.time.minutes=Hace {0} min
|
||||
history.time.hours=Hace {0} h
|
||||
history.time.days=Hace {0} d
|
||||
history.time.months=Hace {0} mes(es)
|
||||
history.time.years=Hace {0} año(s)
|
||||
history.error.local=Error al cargar el historial local
|
||||
history.error.cloud=Error al cargar el historial en la nube
|
||||
history.error.local.delete=Error al eliminar la sesión
|
||||
history.error.local.rename=Error al renombrar la sesión
|
||||
history.error.cloud.delete=Las sesiones en la nube no se pueden eliminar aún
|
||||
|
||||
action.Kilo.Settings.text=Configuración
|
||||
action.Kilo.Settings.description=Configuración de Kilo Code
|
||||
action.Kilo.NewSession.text=Nueva sesión
|
||||
action.Kilo.NewSession.description=Iniciar una nueva sesión de Kilo
|
||||
action.Kilo.History.text=Historial
|
||||
action.Kilo.History.description=Mostrar el historial de sesiones
|
||||
action.Kilo.SendPrompt.text=Enviar mensaje
|
||||
action.Kilo.SendPrompt.description=Enviar el mensaje actual de Kilo
|
||||
action.Kilo.StopSession.text=Detener sesión
|
||||
action.Kilo.StopSession.description=Detener la sesión actual de Kilo
|
||||
action.Kilo.SettingsGroup.text=Configuración
|
||||
action.Kilo.SettingsGroup.description=Configuración de Kilo Code
|
||||
action.Kilo.Restart.text=Reiniciar Kilo
|
||||
action.Kilo.Restart.description=Terminar y reiniciar el proceso CLI
|
||||
action.Kilo.Reinstall.text=Reinstalar Kilo
|
||||
action.Kilo.Reinstall.description=Reextraer el binario CLI y reiniciar
|
||||
action.Kilo.Session.Open.text=Abrir
|
||||
action.Kilo.Session.Open.description=Abrir la sesión seleccionada
|
||||
action.Kilo.Session.Rename.text=Renombrar
|
||||
action.Kilo.Session.Rename.description=Renombrar la sesión seleccionada
|
||||
action.Kilo.Session.Delete.text=Eliminar
|
||||
action.Kilo.Session.Delete.description=Eliminar la(s) sesión(es) seleccionada(s)
|
||||
action.Kilo.History.ContextMenu.text=Acciones del historial
|
||||
+139
@@ -0,0 +1,139 @@
|
||||
session.connection.connecting=Chargement…
|
||||
session.connection.error.app=Échec de la connexion
|
||||
session.connection.error.workspace=Échec du chargement de l'espace de travail
|
||||
session.connection.error.unknown=Erreur inconnue
|
||||
session.connection.retry=Réessayer
|
||||
session.connection.warning.config=Avertissements de configuration
|
||||
|
||||
session.empty.welcome=Kilo Code est un assistant de codage IA. Demandez-lui de créer des fonctionnalités, corriger des bugs ou expliquer votre base de code.
|
||||
session.empty.loading=Chargement…
|
||||
session.empty.recent=RÉCENT
|
||||
session.showHistory=Afficher l'historique
|
||||
session.scroll.bottom=Faire défiler vers le bas
|
||||
session.tab.new=Nouvelle session
|
||||
session.tab.untitled=Session sans titre
|
||||
|
||||
session.permission.title=Demande de permission
|
||||
session.permission.meta=Outil : {0} • Modèles : {1}
|
||||
session.permission.allow=Autoriser
|
||||
session.permission.deny=Refuser
|
||||
session.question.dismiss=Ignorer
|
||||
|
||||
session.status.considering=Considération des prochaines étapes…
|
||||
session.status.thinking=Réflexion…
|
||||
session.status.writing=Rédaction de la réponse…
|
||||
session.status.delegating=Délégation du travail…
|
||||
session.status.planning=Planification…
|
||||
session.status.gathering=Collecte du contexte…
|
||||
session.status.searching.codebase=Recherche dans la base de code…
|
||||
session.status.searching.web=Recherche sur le web…
|
||||
session.status.editing=Modifications en cours…
|
||||
session.status.commands=Exécution des commandes…
|
||||
|
||||
session.part.reasoning=Raisonnement
|
||||
session.part.compaction=contexte compacté
|
||||
session.part.tool.copy=Copier
|
||||
session.part.tool.error=Erreur
|
||||
session.part.tool.pending=En attente
|
||||
session.part.tool.read=Lire
|
||||
session.part.tool.running=En cours
|
||||
session.part.tool.shell=Shell
|
||||
session.part.tool.truncated=Sortie tronquée dans l'aperçu. La sortie complète reste dans les données de session.
|
||||
|
||||
session.error.prompt=Échec de l'invite
|
||||
session.error.compact=Échec de la compaction de session
|
||||
session.error.unknown=Erreur inconnue
|
||||
|
||||
session.header.tokens=Tokens
|
||||
session.header.tokens.description=Tokens utilisés par la dernière réponse de l'assistant : entrée, sortie, écritures en cache et lectures en cache.
|
||||
session.header.input=entrée {0}
|
||||
session.header.output=sortie {0}
|
||||
session.header.cache.write=écriture cache {0}
|
||||
session.header.cache.read=lecture cache {0}
|
||||
session.header.compact=Compacter la session
|
||||
session.header.compact.description=Résumer la session pour réduire la taille du contexte
|
||||
session.header.expand=Afficher les métriques de session
|
||||
session.header.collapse=Masquer les métriques de session
|
||||
session.header.todos.progress={0}/{1} tâches terminées
|
||||
session.header.todos.done=Toutes les {0} tâches terminées
|
||||
session.header.context.tooltip.percent={0} tokens ({1} % du contexte)
|
||||
session.header.context.tooltip.tokens={0} tokens
|
||||
session.header.context.used={0} / {1} tokens utilisés
|
||||
session.header.context.reserved={0} réservés pour la sortie
|
||||
session.header.context.available={0} disponibles
|
||||
|
||||
prompt.placeholder=Saisir un message…
|
||||
prompt.placeholder.with.shortcuts=Saisir un message… ({0} pour envoyer, {1} pour nouvelle ligne)
|
||||
prompt.placeholder.with.send=Saisir un message… ({0} pour envoyer)
|
||||
prompt.placeholder.with.newline=Saisir un message… ({0} pour nouvelle ligne)
|
||||
prompt.button.send=Envoyer
|
||||
prompt.button.stop=Arrêter
|
||||
prompt.button.send.tooltip.stop=Pour arrêter, appuyez sur {0}
|
||||
mode.picker.tooltip=Sélectionner le mode
|
||||
mode.picker.deprecated=obsolète
|
||||
model.picker.tooltip=Sélectionner le modèle
|
||||
model.picker.recommended=Recommandé
|
||||
model.picker.favorites=Favoris
|
||||
model.picker.search=Rechercher des modèles
|
||||
model.picker.no.matches=Aucun modèle correspondant
|
||||
model.picker.favorite.add=Ajouter aux favoris
|
||||
model.picker.favorite.remove=Retirer des favoris
|
||||
model.picker.free=Gratuit
|
||||
model.picker.reset=Réinitialiser le modèle par défaut
|
||||
reasoning.picker.tooltip=Sélectionner l'effort de raisonnement
|
||||
|
||||
history.tab.local=Local
|
||||
history.tab.cloud=Cloud
|
||||
history.back=Retour
|
||||
history.search.placeholder=Rechercher des sessions
|
||||
history.empty=Aucune session
|
||||
history.loading=Chargement…
|
||||
history.untitled=Sans titre
|
||||
history.delete.text=Supprimer
|
||||
history.delete.confirm.title=Supprimer la session ?
|
||||
history.delete.confirm.message=Supprimer « {0} » de l'historique local ?
|
||||
history.delete.confirm.message.multiple=Supprimer {0} sessions de l'historique local ?
|
||||
history.rename.title=Renommer la session
|
||||
history.rename.prompt=Nouveau nom de session :
|
||||
history.cloud.load.more=Charger plus
|
||||
history.cloud.repo.only=Ce dépôt uniquement
|
||||
history.group.today=Aujourd'hui
|
||||
history.group.yesterday=Hier
|
||||
history.group.week=Cette semaine
|
||||
history.group.month=Ce mois
|
||||
history.group.older=Plus ancien
|
||||
history.time.moments=À l'instant
|
||||
history.time.minutes=Il y a {0} min
|
||||
history.time.hours=Il y a {0} h
|
||||
history.time.days=Il y a {0} j
|
||||
history.time.months=Il y a {0} mois
|
||||
history.time.years=Il y a {0} an(s)
|
||||
history.error.local=Échec du chargement de l'historique local
|
||||
history.error.cloud=Échec du chargement de l'historique cloud
|
||||
history.error.local.delete=Échec de la suppression de la session
|
||||
history.error.local.rename=Échec du renommage de la session
|
||||
history.error.cloud.delete=Les sessions cloud ne peuvent pas encore être supprimées
|
||||
|
||||
action.Kilo.Settings.text=Paramètres
|
||||
action.Kilo.Settings.description=Paramètres de Kilo Code
|
||||
action.Kilo.NewSession.text=Nouvelle session
|
||||
action.Kilo.NewSession.description=Démarrer une nouvelle session Kilo
|
||||
action.Kilo.History.text=Historique
|
||||
action.Kilo.History.description=Afficher l'historique des sessions
|
||||
action.Kilo.SendPrompt.text=Envoyer l'invite
|
||||
action.Kilo.SendPrompt.description=Envoyer l'invite Kilo actuelle
|
||||
action.Kilo.StopSession.text=Arrêter la session
|
||||
action.Kilo.StopSession.description=Arrêter la session Kilo actuelle
|
||||
action.Kilo.SettingsGroup.text=Paramètres
|
||||
action.Kilo.SettingsGroup.description=Paramètres de Kilo Code
|
||||
action.Kilo.Restart.text=Redémarrer Kilo
|
||||
action.Kilo.Restart.description=Tuer et redémarrer le processus CLI
|
||||
action.Kilo.Reinstall.text=Réinstaller Kilo
|
||||
action.Kilo.Reinstall.description=Réextraire le binaire CLI et redémarrer
|
||||
action.Kilo.Session.Open.text=Ouvrir
|
||||
action.Kilo.Session.Open.description=Ouvrir la session sélectionnée
|
||||
action.Kilo.Session.Rename.text=Renommer
|
||||
action.Kilo.Session.Rename.description=Renommer la session sélectionnée
|
||||
action.Kilo.Session.Delete.text=Supprimer
|
||||
action.Kilo.Session.Delete.description=Supprimer la ou les sessions sélectionnées
|
||||
action.Kilo.History.ContextMenu.text=Actions d'historique
|
||||
+139
@@ -0,0 +1,139 @@
|
||||
session.connection.connecting=読み込み中…
|
||||
session.connection.error.app=接続に失敗しました
|
||||
session.connection.error.workspace=ワークスペースの読み込みに失敗しました
|
||||
session.connection.error.unknown=不明なエラー
|
||||
session.connection.retry=再試行
|
||||
session.connection.warning.config=設定の警告
|
||||
|
||||
session.empty.welcome=Kilo CodeはAIコーディングアシスタントです。機能の作成、バグの修正、またはコードベースの説明を依頼できます。
|
||||
session.empty.loading=読み込み中…
|
||||
session.empty.recent=最近
|
||||
session.showHistory=履歴を表示
|
||||
session.scroll.bottom=一番下にスクロール
|
||||
session.tab.new=新しいセッション
|
||||
session.tab.untitled=名前なしのセッション
|
||||
|
||||
session.permission.title=権限リクエスト
|
||||
session.permission.meta=ツール:{0} • パターン:{1}
|
||||
session.permission.allow=許可
|
||||
session.permission.deny=拒否
|
||||
session.question.dismiss=閉じる
|
||||
|
||||
session.status.considering=次のステップを検討中…
|
||||
session.status.thinking=考え中…
|
||||
session.status.writing=回答を作成中…
|
||||
session.status.delegating=作業を委任中…
|
||||
session.status.planning=計画中…
|
||||
session.status.gathering=コンテキストを収集中…
|
||||
session.status.searching.codebase=コードベースを検索中…
|
||||
session.status.searching.web=ウェブを検索中…
|
||||
session.status.editing=編集中…
|
||||
session.status.commands=コマンドを実行中…
|
||||
|
||||
session.part.reasoning=推論
|
||||
session.part.compaction=コンテキスト圧縮済み
|
||||
session.part.tool.copy=コピー
|
||||
session.part.tool.error=エラー
|
||||
session.part.tool.pending=保留中
|
||||
session.part.tool.read=読み取り
|
||||
session.part.tool.running=実行中
|
||||
session.part.tool.shell=シェル
|
||||
session.part.tool.truncated=プレビューでは出力が切り詰められています。完全な出力はセッションデータに残っています。
|
||||
|
||||
session.error.prompt=プロンプトが失敗しました
|
||||
session.error.compact=セッション圧縮が失敗しました
|
||||
session.error.unknown=不明なエラー
|
||||
|
||||
session.header.tokens=トークン
|
||||
session.header.tokens.description=最新のアシスタントの回答で使用されたトークン:入力、出力、キャッシュ書き込み、キャッシュ読み取り。
|
||||
session.header.input=入力 {0}
|
||||
session.header.output=出力 {0}
|
||||
session.header.cache.write=キャッシュ書き込み {0}
|
||||
session.header.cache.read=キャッシュ読み取り {0}
|
||||
session.header.compact=セッションを圧縮
|
||||
session.header.compact.description=セッションを要約してコンテキストサイズを削減
|
||||
session.header.expand=セッションメトリクスを表示
|
||||
session.header.collapse=セッションメトリクスを非表示
|
||||
session.header.todos.progress={0}/{1}個のタスク完了
|
||||
session.header.todos.done=全{0}個のタスク完了
|
||||
session.header.context.tooltip.percent={0}トークン(コンテキストの{1}%)
|
||||
session.header.context.tooltip.tokens={0}トークン
|
||||
session.header.context.used={0} / {1}トークン使用中
|
||||
session.header.context.reserved={0}は出力用に予約済み
|
||||
session.header.context.available={0}利用可能
|
||||
|
||||
prompt.placeholder=メッセージを入力…
|
||||
prompt.placeholder.with.shortcuts=メッセージを入力…({0}で送信、{1}で改行)
|
||||
prompt.placeholder.with.send=メッセージを入力…({0}で送信)
|
||||
prompt.placeholder.with.newline=メッセージを入力…({0}で改行)
|
||||
prompt.button.send=送信
|
||||
prompt.button.stop=停止
|
||||
prompt.button.send.tooltip.stop=停止するには{0}を押してください
|
||||
mode.picker.tooltip=モードを選択
|
||||
mode.picker.deprecated=非推奨
|
||||
model.picker.tooltip=モデルを選択
|
||||
model.picker.recommended=おすすめ
|
||||
model.picker.favorites=お気に入り
|
||||
model.picker.search=モデルを検索
|
||||
model.picker.no.matches=一致するモデルなし
|
||||
model.picker.favorite.add=お気に入りに追加
|
||||
model.picker.favorite.remove=お気に入りから削除
|
||||
model.picker.free=無料
|
||||
model.picker.reset=モデルをデフォルトにリセット
|
||||
reasoning.picker.tooltip=推論努力を選択
|
||||
|
||||
history.tab.local=ローカル
|
||||
history.tab.cloud=クラウド
|
||||
history.back=戻る
|
||||
history.search.placeholder=セッションを検索
|
||||
history.empty=セッションなし
|
||||
history.loading=読み込み中…
|
||||
history.untitled=名前なし
|
||||
history.delete.text=削除
|
||||
history.delete.confirm.title=セッションを削除しますか?
|
||||
history.delete.confirm.message=「{0}」をローカル履歴から削除しますか?
|
||||
history.delete.confirm.message.multiple={0}個のセッションをローカル履歴から削除しますか?
|
||||
history.rename.title=セッションを変名
|
||||
history.rename.prompt=新しいセッション名:
|
||||
history.cloud.load.more=さらに読み込む
|
||||
history.cloud.repo.only=このリポジトリのみ
|
||||
history.group.today=今日
|
||||
history.group.yesterday=昨日
|
||||
history.group.week=今週
|
||||
history.group.month=今月
|
||||
history.group.older=さらに古い
|
||||
history.time.moments=たった今
|
||||
history.time.minutes={0}分前
|
||||
history.time.hours={0}時間前
|
||||
history.time.days={0}日前
|
||||
history.time.months={0}か月前
|
||||
history.time.years={0}年前
|
||||
history.error.local=ローカル履歴の読み込みに失敗しました
|
||||
history.error.cloud=クラウド履歴の読み込みに失敗しました
|
||||
history.error.local.delete=セッションの削除に失敗しました
|
||||
history.error.local.rename=セッションの変名に失敗しました
|
||||
history.error.cloud.delete=クラウドセッションはまだ削除できません
|
||||
|
||||
action.Kilo.Settings.text=設定
|
||||
action.Kilo.Settings.description=Kilo Codeの設定
|
||||
action.Kilo.NewSession.text=新しいセッション
|
||||
action.Kilo.NewSession.description=新しいKiloセッションを開始
|
||||
action.Kilo.History.text=履歴
|
||||
action.Kilo.History.description=セッション履歴を表示
|
||||
action.Kilo.SendPrompt.text=プロンプトを送信
|
||||
action.Kilo.SendPrompt.description=現在のKiloプロンプトを送信
|
||||
action.Kilo.StopSession.text=セッションを停止
|
||||
action.Kilo.StopSession.description=現在のKiloセッションを停止
|
||||
action.Kilo.SettingsGroup.text=設定
|
||||
action.Kilo.SettingsGroup.description=Kilo Codeの設定
|
||||
action.Kilo.Restart.text=Kiloを再起動
|
||||
action.Kilo.Restart.description=CLIプロセスを終了して再起動
|
||||
action.Kilo.Reinstall.text=Kiloを再インストール
|
||||
action.Kilo.Reinstall.description=CLIバイナリを再展開して再起動
|
||||
action.Kilo.Session.Open.text=開く
|
||||
action.Kilo.Session.Open.description=選択したセッションを開く
|
||||
action.Kilo.Session.Rename.text=変名
|
||||
action.Kilo.Session.Rename.description=選択したセッションを変名
|
||||
action.Kilo.Session.Delete.text=削除
|
||||
action.Kilo.Session.Delete.description=選択したセッションを削除
|
||||
action.Kilo.History.ContextMenu.text=履歴アクション
|
||||
+139
@@ -0,0 +1,139 @@
|
||||
session.connection.connecting=로딩 중…
|
||||
session.connection.error.app=연결 실패
|
||||
session.connection.error.workspace=워크스페이스 로딩 실패
|
||||
session.connection.error.unknown=알 수 없는 오류
|
||||
session.connection.retry=다시 시도
|
||||
session.connection.warning.config=구성 경고
|
||||
|
||||
session.empty.welcome=Kilo Code는 AI 코딩 어시스턴트입니다. 기능 구축, 버그 수정, 코드베이스 설명을 요청하세요.
|
||||
session.empty.loading=로딩 중…
|
||||
session.empty.recent=최근
|
||||
session.showHistory=기록 보기
|
||||
session.scroll.bottom=맨 아래로 스크롤
|
||||
session.tab.new=새 세션
|
||||
session.tab.untitled=제목 없는 세션
|
||||
|
||||
session.permission.title=권한 요청
|
||||
session.permission.meta=도구: {0} • 패턴: {1}
|
||||
session.permission.allow=허용
|
||||
session.permission.deny=거부
|
||||
session.question.dismiss=닫기
|
||||
|
||||
session.status.considering=다음 단계 고려 중…
|
||||
session.status.thinking=생각 중…
|
||||
session.status.writing=응답 작성 중…
|
||||
session.status.delegating=작업 위임 중…
|
||||
session.status.planning=계획 중…
|
||||
session.status.gathering=컨텍스트 수집 중…
|
||||
session.status.searching.codebase=코드베이스 검색 중…
|
||||
session.status.searching.web=웹 검색 중…
|
||||
session.status.editing=편집 중…
|
||||
session.status.commands=명령 실행 중…
|
||||
|
||||
session.part.reasoning=추론
|
||||
session.part.compaction=컨텍스트 압축됨
|
||||
session.part.tool.copy=복사
|
||||
session.part.tool.error=오류
|
||||
session.part.tool.pending=대기 중
|
||||
session.part.tool.read=읽기
|
||||
session.part.tool.running=실행 중
|
||||
session.part.tool.shell=셸
|
||||
session.part.tool.truncated=미리보기에서 출력이 잘렸습니다. 전체 출력은 세션 데이터에 남아 있습니다.
|
||||
|
||||
session.error.prompt=프롬프트 실패
|
||||
session.error.compact=세션 압축 실패
|
||||
session.error.unknown=알 수 없는 오류
|
||||
|
||||
session.header.tokens=토큰
|
||||
session.header.tokens.description=마지막 어시스턴트 응답에서 사용된 토큰: 입력, 출력, 캐시 쓰기, 캐시 읽기.
|
||||
session.header.input=입력 {0}
|
||||
session.header.output=출력 {0}
|
||||
session.header.cache.write=캐시 쓰기 {0}
|
||||
session.header.cache.read=캐시 읽기 {0}
|
||||
session.header.compact=세션 압축
|
||||
session.header.compact.description=컨텍스트 크기를 줄이기 위해 세션 요약
|
||||
session.header.expand=세션 메트릭 표시
|
||||
session.header.collapse=세션 메트릭 숨기기
|
||||
session.header.todos.progress={0}/{1} 할 일 완료
|
||||
session.header.todos.done=모든 {0}개 할 일 완료
|
||||
session.header.context.tooltip.percent={0} 토큰 (컨텍스트의 {1}%)
|
||||
session.header.context.tooltip.tokens={0} 토큰
|
||||
session.header.context.used={0} / {1} 토큰 사용 중
|
||||
session.header.context.reserved={0} 출력용 예약됨
|
||||
session.header.context.available={0} 사용 가능
|
||||
|
||||
prompt.placeholder=메시지를 입력하세요…
|
||||
prompt.placeholder.with.shortcuts=메시지를 입력하세요… ({0} 전송, {1} 새 줄)
|
||||
prompt.placeholder.with.send=메시지를 입력하세요… ({0} 전송)
|
||||
prompt.placeholder.with.newline=메시지를 입력하세요… ({0} 새 줄)
|
||||
prompt.button.send=전송
|
||||
prompt.button.stop=정지
|
||||
prompt.button.send.tooltip.stop=정지하려면 {0}을 누르세요
|
||||
mode.picker.tooltip=모드 선택
|
||||
mode.picker.deprecated=사용 중단
|
||||
model.picker.tooltip=모델 선택
|
||||
model.picker.recommended=추천
|
||||
model.picker.favorites=즐겨찾기
|
||||
model.picker.search=모델 검색
|
||||
model.picker.no.matches=일치하는 모델 없음
|
||||
model.picker.favorite.add=즐겨찾기에 추가
|
||||
model.picker.favorite.remove=즐겨찾기에서 제거
|
||||
model.picker.free=무료
|
||||
model.picker.reset=모델을 기본값으로 재설정
|
||||
reasoning.picker.tooltip=추론 노력 선택
|
||||
|
||||
history.tab.local=로컈
|
||||
history.tab.cloud=클라우드
|
||||
history.back=뒤로
|
||||
history.search.placeholder=세션 검색
|
||||
history.empty=세션 없음
|
||||
history.loading=로딩 중…
|
||||
history.untitled=제목 없음
|
||||
history.delete.text=삭제
|
||||
history.delete.confirm.title=세션을 삭제하시겠습니까?
|
||||
history.delete.confirm.message=로컈 기록에서 "{0}"을 삭제하시겠습니까?
|
||||
history.delete.confirm.message.multiple=로컈 기록에서 {0}개의 세션을 삭제하시겠습니까?
|
||||
history.rename.title=세션 이름 바꾸기
|
||||
history.rename.prompt=새 세션 이름:
|
||||
history.cloud.load.more=더 불러오기
|
||||
history.cloud.repo.only=이 리포지터리만
|
||||
history.group.today=오늘
|
||||
history.group.yesterday=어제
|
||||
history.group.week=이번 주
|
||||
history.group.month=이번 달
|
||||
history.group.older=더 오래된
|
||||
history.time.moments=방금
|
||||
history.time.minutes={0}분 전
|
||||
history.time.hours={0}시간 전
|
||||
history.time.days={0}일 전
|
||||
history.time.months={0}개월 전
|
||||
history.time.years={0}년 전
|
||||
history.error.local=로컈 기록을 로드하지 못했습니다
|
||||
history.error.cloud=클라우드 기록을 로드하지 못했습니다
|
||||
history.error.local.delete=세션을 삭제하지 못했습니다
|
||||
history.error.local.rename=세션의 이름을 바꾸지 못했습니다
|
||||
history.error.cloud.delete=클라우드 세션은 아직 삭제할 수 없습니다
|
||||
|
||||
action.Kilo.Settings.text=설정
|
||||
action.Kilo.Settings.description=Kilo Code 설정
|
||||
action.Kilo.NewSession.text=새 세션
|
||||
action.Kilo.NewSession.description=새 Kilo 세션 시작
|
||||
action.Kilo.History.text=기록
|
||||
action.Kilo.History.description=세션 기록 표시
|
||||
action.Kilo.SendPrompt.text=프롬프트 전송
|
||||
action.Kilo.SendPrompt.description=현재 Kilo 프롬프트 전송
|
||||
action.Kilo.StopSession.text=세션 정지
|
||||
action.Kilo.StopSession.description=현재 Kilo 세션 정지
|
||||
action.Kilo.SettingsGroup.text=설정
|
||||
action.Kilo.SettingsGroup.description=Kilo Code 설정
|
||||
action.Kilo.Restart.text=Kilo 재시작
|
||||
action.Kilo.Restart.description=CLI 프로세스 종료 후 재시작
|
||||
action.Kilo.Reinstall.text=Kilo 재설치
|
||||
action.Kilo.Reinstall.description=CLI 바이너리 재추출 후 재시작
|
||||
action.Kilo.Session.Open.text=열기
|
||||
action.Kilo.Session.Open.description=선택한 세션 열기
|
||||
action.Kilo.Session.Rename.text=이름 바꾸기
|
||||
action.Kilo.Session.Rename.description=선택한 세션 이름 바꾸기
|
||||
action.Kilo.Session.Delete.text=삭제
|
||||
action.Kilo.Session.Delete.description=선택한 세션 삭제
|
||||
action.Kilo.History.ContextMenu.text=기록 작업
|
||||
+139
@@ -0,0 +1,139 @@
|
||||
session.connection.connecting=Laden…
|
||||
session.connection.error.app=Verbinding mislukt
|
||||
session.connection.error.workspace=Werkruimte laden mislukt
|
||||
session.connection.error.unknown=Onbekende fout
|
||||
session.connection.retry=Opnieuw proberen
|
||||
session.connection.warning.config=Configuratiewaarschuwingen
|
||||
|
||||
session.empty.welcome=Kilo Code is een AI-codeerassistent. Vraag het om functies te bouwen, bugs te repareren of uw codebase uit te leggen.
|
||||
session.empty.loading=Laden…
|
||||
session.empty.recent=RECENT
|
||||
session.showHistory=Geschiedenis weergeven
|
||||
session.scroll.bottom=Naar beneden scrollen
|
||||
session.tab.new=Nieuwe sessie
|
||||
session.tab.untitled=Naamloze sessie
|
||||
|
||||
session.permission.title=Toestemmingsverzoek
|
||||
session.permission.meta=Hulpmiddel: {0} • Patronen: {1}
|
||||
session.permission.allow=Toestaan
|
||||
session.permission.deny=Weigeren
|
||||
session.question.dismiss=Sluiten
|
||||
|
||||
session.status.considering=Volgende stappen overwegen…
|
||||
session.status.thinking=Nadenken…
|
||||
session.status.writing=Antwoord schrijven…
|
||||
session.status.delegating=Werk delegeren…
|
||||
session.status.planning=Plannen…
|
||||
session.status.gathering=Context verzamelen…
|
||||
session.status.searching.codebase=Codebase doorzoeken…
|
||||
session.status.searching.web=Web doorzoeken…
|
||||
session.status.editing=Bewerkingen uitvoeren…
|
||||
session.status.commands=Opdrachten uitvoeren…
|
||||
|
||||
session.part.reasoning=Redenering
|
||||
session.part.compaction=context gecomprimeerd
|
||||
session.part.tool.copy=Kopiëren
|
||||
session.part.tool.error=Fout
|
||||
session.part.tool.pending=In afwachting
|
||||
session.part.tool.read=Lezen
|
||||
session.part.tool.running=Actief
|
||||
session.part.tool.shell=Shell
|
||||
session.part.tool.truncated=Uitvoer ingekort in voorvertoning. Volledige uitvoer blijft beschikbaar in sessiegegevens.
|
||||
|
||||
session.error.prompt=Prompt mislukt
|
||||
session.error.compact=Sessiecomprimering mislukt
|
||||
session.error.unknown=Onbekende fout
|
||||
|
||||
session.header.tokens=Tokens
|
||||
session.header.tokens.description=Tokens gebruikt door de laatste assistent-reactie: invoer, uitvoer, cache-schrijfacties en cache-leesacties.
|
||||
session.header.input=in {0}
|
||||
session.header.output=uit {0}
|
||||
session.header.cache.write=cache schrijven {0}
|
||||
session.header.cache.read=cache lezen {0}
|
||||
session.header.compact=Sessie comprimeren
|
||||
session.header.compact.description=Sessie samenvatten om de contextgrootte te verkleinen
|
||||
session.header.expand=Sessiestatistieken weergeven
|
||||
session.header.collapse=Sessiestatistieken verbergen
|
||||
session.header.todos.progress={0}/{1} taken voltooid
|
||||
session.header.todos.done=Alle {0} taken voltooid
|
||||
session.header.context.tooltip.percent={0} tokens ({1}% van de context)
|
||||
session.header.context.tooltip.tokens={0} tokens
|
||||
session.header.context.used={0} / {1} tokens gebruikt
|
||||
session.header.context.reserved={0} gereserveerd voor uitvoer
|
||||
session.header.context.available={0} beschikbaar
|
||||
|
||||
prompt.placeholder=Typ een bericht…
|
||||
prompt.placeholder.with.shortcuts=Typ een bericht… ({0} om te verzenden, {1} voor nieuwe regel)
|
||||
prompt.placeholder.with.send=Typ een bericht… ({0} om te verzenden)
|
||||
prompt.placeholder.with.newline=Typ een bericht… ({0} voor nieuwe regel)
|
||||
prompt.button.send=Verzenden
|
||||
prompt.button.stop=Stoppen
|
||||
prompt.button.send.tooltip.stop=Druk op {0} om te stoppen
|
||||
mode.picker.tooltip=Modus selecteren
|
||||
mode.picker.deprecated=verouderd
|
||||
model.picker.tooltip=Model selecteren
|
||||
model.picker.recommended=Aanbevolen
|
||||
model.picker.favorites=Favorieten
|
||||
model.picker.search=Modellen zoeken
|
||||
model.picker.no.matches=Geen overeenkomende modellen
|
||||
model.picker.favorite.add=Toevoegen aan favorieten
|
||||
model.picker.favorite.remove=Verwijderen uit favorieten
|
||||
model.picker.free=Gratis
|
||||
model.picker.reset=Model terugzetten naar standaard
|
||||
reasoning.picker.tooltip=Redeerniveau selecteren
|
||||
|
||||
history.tab.local=Lokaal
|
||||
history.tab.cloud=Cloud
|
||||
history.back=Terug
|
||||
history.search.placeholder=Sessies zoeken
|
||||
history.empty=Geen sessies
|
||||
history.loading=Laden…
|
||||
history.untitled=Naamloos
|
||||
history.delete.text=Verwijderen
|
||||
history.delete.confirm.title=Sessie verwijderen?
|
||||
history.delete.confirm.message="{0}" verwijderen uit lokale geschiedenis?
|
||||
history.delete.confirm.message.multiple={0} sessies verwijderen uit lokale geschiedenis?
|
||||
history.rename.title=Sessie hernoemen
|
||||
history.rename.prompt=Nieuwe sessienaam:
|
||||
history.cloud.load.more=Meer laden
|
||||
history.cloud.repo.only=Alleen deze repository
|
||||
history.group.today=Vandaag
|
||||
history.group.yesterday=Gisteren
|
||||
history.group.week=Deze week
|
||||
history.group.month=Deze maand
|
||||
history.group.older=Ouder
|
||||
history.time.moments=Zojuist
|
||||
history.time.minutes={0} min geleden
|
||||
history.time.hours={0} u geleden
|
||||
history.time.days={0} d geleden
|
||||
history.time.months={0} mnd geleden
|
||||
history.time.years={0} jr geleden
|
||||
history.error.local=Laden van lokale geschiedenis mislukt
|
||||
history.error.cloud=Laden van cloudgeschiedenis mislukt
|
||||
history.error.local.delete=Verwijderen van sessie mislukt
|
||||
history.error.local.rename=Hernoemen van sessie mislukt
|
||||
history.error.cloud.delete=Cloudsessies kunnen nog niet worden verwijderd
|
||||
|
||||
action.Kilo.Settings.text=Instellingen
|
||||
action.Kilo.Settings.description=Kilo Code-instellingen
|
||||
action.Kilo.NewSession.text=Nieuwe sessie
|
||||
action.Kilo.NewSession.description=Een nieuwe Kilo-sessie starten
|
||||
action.Kilo.History.text=Geschiedenis
|
||||
action.Kilo.History.description=Sessiegeschiedenis weergeven
|
||||
action.Kilo.SendPrompt.text=Prompt verzenden
|
||||
action.Kilo.SendPrompt.description=Huidige Kilo-prompt verzenden
|
||||
action.Kilo.StopSession.text=Sessie stoppen
|
||||
action.Kilo.StopSession.description=Huidige Kilo-sessie stoppen
|
||||
action.Kilo.SettingsGroup.text=Instellingen
|
||||
action.Kilo.SettingsGroup.description=Kilo Code-instellingen
|
||||
action.Kilo.Restart.text=Kilo herstarten
|
||||
action.Kilo.Restart.description=CLI-proces beëindigen en herstarten
|
||||
action.Kilo.Reinstall.text=Kilo herinstalleren
|
||||
action.Kilo.Reinstall.description=CLI-binair opnieuw uitpakken en herstarten
|
||||
action.Kilo.Session.Open.text=Openen
|
||||
action.Kilo.Session.Open.description=Geselecteerde sessie openen
|
||||
action.Kilo.Session.Rename.text=Hernoemen
|
||||
action.Kilo.Session.Rename.description=Geselecteerde sessie hernoemen
|
||||
action.Kilo.Session.Delete.text=Verwijderen
|
||||
action.Kilo.Session.Delete.description=Geselecteerde sessie(s) verwijderen
|
||||
action.Kilo.History.ContextMenu.text=Geschiedenisacties
|
||||
+139
@@ -0,0 +1,139 @@
|
||||
session.connection.connecting=Laster…
|
||||
session.connection.error.app=Tilkoblingsfeil
|
||||
session.connection.error.workspace=Feil ved lasting av arbeidsområde
|
||||
session.connection.error.unknown=Ukjent feil
|
||||
session.connection.retry=Prøv igjen
|
||||
session.connection.warning.config=Konfigurasjonsadvarsler
|
||||
|
||||
session.empty.welcome=Kilo Code er en AI-kodingsassistent. Be den om å bygge funksjoner, fikse feil eller forklare kodebasen din.
|
||||
session.empty.loading=Laster…
|
||||
session.empty.recent=NYLIGE
|
||||
session.showHistory=Vis historikk
|
||||
session.scroll.bottom=Rull til bunnen
|
||||
session.tab.new=Ny økt
|
||||
session.tab.untitled=Uten tittel
|
||||
|
||||
session.permission.title=Tillatelsesforespørsel
|
||||
session.permission.meta=Verktøy: {0} • Mønstre: {1}
|
||||
session.permission.allow=Tillat
|
||||
session.permission.deny=Avslå
|
||||
session.question.dismiss=Lukk
|
||||
|
||||
session.status.considering=Vurderer neste steg…
|
||||
session.status.thinking=Tenker…
|
||||
session.status.writing=Skriver svar…
|
||||
session.status.delegating=Delegerer arbeid…
|
||||
session.status.planning=Planlegger…
|
||||
session.status.gathering=Samler kontekst…
|
||||
session.status.searching.codebase=Søker i kodebasen…
|
||||
session.status.searching.web=Søker på nettet…
|
||||
session.status.editing=Gjør redigeringer…
|
||||
session.status.commands=Kjører kommandoer…
|
||||
|
||||
session.part.reasoning=Resonnering
|
||||
session.part.compaction=kontekst komprimert
|
||||
session.part.tool.copy=Kopier
|
||||
session.part.tool.error=Feil
|
||||
session.part.tool.pending=Venter
|
||||
session.part.tool.read=Les
|
||||
session.part.tool.running=Kjører
|
||||
session.part.tool.shell=Shell
|
||||
session.part.tool.truncated=Utdata avkortet i forhåndsvisning. Fullstendig utdata finnes fortsatt i øktdata.
|
||||
|
||||
session.error.prompt=Forespørsel mislyktes
|
||||
session.error.compact=Komprimer økt mislyktes
|
||||
session.error.unknown=Ukjent feil
|
||||
|
||||
session.header.tokens=Tokens
|
||||
session.header.tokens.description=Tokens brukt av siste assistentsvar: inndata, utdata, cache-skrivninger og cache-lesninger.
|
||||
session.header.input=inn {0}
|
||||
session.header.output=ut {0}
|
||||
session.header.cache.write=cache-skriving {0}
|
||||
session.header.cache.read=cache-lesing {0}
|
||||
session.header.compact=Komprimer økt
|
||||
session.header.compact.description=Oppsummer økten for å redusere kontekststørrelsen
|
||||
session.header.expand=Vis øktberegninger
|
||||
session.header.collapse=Skjul øktberegninger
|
||||
session.header.todos.progress={0}/{1} oppgaver fullført
|
||||
session.header.todos.done=Alle {0} oppgaver fullført
|
||||
session.header.context.tooltip.percent={0} tokens ({1}% av konteksten)
|
||||
session.header.context.tooltip.tokens={0} tokens
|
||||
session.header.context.used={0} / {1} tokens brukt
|
||||
session.header.context.reserved={0} reservert for utdata
|
||||
session.header.context.available={0} tilgjengelig
|
||||
|
||||
prompt.placeholder=Skriv en melding…
|
||||
prompt.placeholder.with.shortcuts=Skriv en melding… ({0} for å sende, {1} for ny linje)
|
||||
prompt.placeholder.with.send=Skriv en melding… ({0} for å sende)
|
||||
prompt.placeholder.with.newline=Skriv en melding… ({0} for ny linje)
|
||||
prompt.button.send=Send
|
||||
prompt.button.stop=Stopp
|
||||
prompt.button.send.tooltip.stop=Trykk {0} for å stoppe
|
||||
mode.picker.tooltip=Velg modus
|
||||
mode.picker.deprecated=utdatert
|
||||
model.picker.tooltip=Velg modell
|
||||
model.picker.recommended=Anbefalt
|
||||
model.picker.favorites=Favoritter
|
||||
model.picker.search=Søk etter modeller
|
||||
model.picker.no.matches=Ingen samsvarende modeller
|
||||
model.picker.favorite.add=Legg til favoritter
|
||||
model.picker.favorite.remove=Fjern fra favoritter
|
||||
model.picker.free=Gratis
|
||||
model.picker.reset=Tilbakestill modell til standard
|
||||
reasoning.picker.tooltip=Velg resonnementsnivå
|
||||
|
||||
history.tab.local=Lokal
|
||||
history.tab.cloud=Sky
|
||||
history.back=Tilbake
|
||||
history.search.placeholder=Søk i økter
|
||||
history.empty=Ingen økter
|
||||
history.loading=Laster…
|
||||
history.untitled=Uten tittel
|
||||
history.delete.text=Slett
|
||||
history.delete.confirm.title=Slette økt?
|
||||
history.delete.confirm.message=Slette "{0}" fra lokal historikk?
|
||||
history.delete.confirm.message.multiple=Slette {0} økter fra lokal historikk?
|
||||
history.rename.title=Gi økten nytt navn
|
||||
history.rename.prompt=Nytt øktnavn:
|
||||
history.cloud.load.more=Last inn mer
|
||||
history.cloud.repo.only=Bare dette depotet
|
||||
history.group.today=I dag
|
||||
history.group.yesterday=I går
|
||||
history.group.week=Denne uken
|
||||
history.group.month=Denne måneden
|
||||
history.group.older=Eldre
|
||||
history.time.moments=Akkurat nå
|
||||
history.time.minutes={0} min siden
|
||||
history.time.hours={0} t siden
|
||||
history.time.days={0} d siden
|
||||
history.time.months={0} mnd siden
|
||||
history.time.years={0} år siden
|
||||
history.error.local=Kunne ikke laste lokal historikk
|
||||
history.error.cloud=Kunne ikke laste sky-historikk
|
||||
history.error.local.delete=Kunne ikke slette økt
|
||||
history.error.local.rename=Kunne ikke gi økten nytt navn
|
||||
history.error.cloud.delete=Sky-økter kan ikke slettes ennå
|
||||
|
||||
action.Kilo.Settings.text=Innstillinger
|
||||
action.Kilo.Settings.description=Kilo Code-innstillinger
|
||||
action.Kilo.NewSession.text=Ny økt
|
||||
action.Kilo.NewSession.description=Start en ny Kilo-økt
|
||||
action.Kilo.History.text=Historikk
|
||||
action.Kilo.History.description=Vis økthistorikk
|
||||
action.Kilo.SendPrompt.text=Send forespørsel
|
||||
action.Kilo.SendPrompt.description=Send gjeldende Kilo-forespørsel
|
||||
action.Kilo.StopSession.text=Stopp økt
|
||||
action.Kilo.StopSession.description=Stopp gjeldende Kilo-økt
|
||||
action.Kilo.SettingsGroup.text=Innstillinger
|
||||
action.Kilo.SettingsGroup.description=Kilo Code-innstillinger
|
||||
action.Kilo.Restart.text=Start Kilo på nytt
|
||||
action.Kilo.Restart.description=Avslutt og start CLI-prosessen på nytt
|
||||
action.Kilo.Reinstall.text=Installer Kilo på nytt
|
||||
action.Kilo.Reinstall.description=Pakk ut CLI-binærfilen på nytt og start på nytt
|
||||
action.Kilo.Session.Open.text=Åpne
|
||||
action.Kilo.Session.Open.description=Åpne valgt økt
|
||||
action.Kilo.Session.Rename.text=Gi nytt navn
|
||||
action.Kilo.Session.Rename.description=Gi valgt økt nytt navn
|
||||
action.Kilo.Session.Delete.text=Slett
|
||||
action.Kilo.Session.Delete.description=Slett valgte økt(er)
|
||||
action.Kilo.History.ContextMenu.text=Historikkhandlinger
|
||||
+139
@@ -0,0 +1,139 @@
|
||||
session.connection.connecting=Ładowanie…
|
||||
session.connection.error.app=Błąd połączenia
|
||||
session.connection.error.workspace=Błąd ładowania obszaru roboczego
|
||||
session.connection.error.unknown=Nieznany błąd
|
||||
session.connection.retry=Spróbuj ponownie
|
||||
session.connection.warning.config=Ostrzeżenia konfiguracji
|
||||
|
||||
session.empty.welcome=Kilo Code to asystent kodowania AI. Poproś go o tworzenie funkcji, naprawianie błędów lub wyjaśnianie bazy kodu.
|
||||
session.empty.loading=Ładowanie…
|
||||
session.empty.recent=OSTATNIE
|
||||
session.showHistory=Pokaż historię
|
||||
session.scroll.bottom=Przewiń na dół
|
||||
session.tab.new=Nowa sesja
|
||||
session.tab.untitled=Sesja bez tytułu
|
||||
|
||||
session.permission.title=Prośba o uprawnienia
|
||||
session.permission.meta=Narzędzie: {0} • Wzorce: {1}
|
||||
session.permission.allow=Zezwól
|
||||
session.permission.deny=Odmów
|
||||
session.question.dismiss=Odrzuć
|
||||
|
||||
session.status.considering=Rozważanie następnych kroków…
|
||||
session.status.thinking=Myślenie…
|
||||
session.status.writing=Pisanie odpowiedzi…
|
||||
session.status.delegating=Delegowanie pracy…
|
||||
session.status.planning=Planowanie…
|
||||
session.status.gathering=Zbieranie kontekstu…
|
||||
session.status.searching.codebase=Przeszukiwanie bazy kodu…
|
||||
session.status.searching.web=Przeszukiwanie sieci…
|
||||
session.status.editing=Dokonywanie edycji…
|
||||
session.status.commands=Uruchamianie poleceń…
|
||||
|
||||
session.part.reasoning=Rozumowanie
|
||||
session.part.compaction=kontekst skompresowany
|
||||
session.part.tool.copy=Kopiuj
|
||||
session.part.tool.error=Błąd
|
||||
session.part.tool.pending=Oczekuje
|
||||
session.part.tool.read=Odczyt
|
||||
session.part.tool.running=Uruchomione
|
||||
session.part.tool.shell=Powłoka
|
||||
session.part.tool.truncated=Wyjście skrócone w podglądzie. Pełne wyjście pozostaje w danych sesji.
|
||||
|
||||
session.error.prompt=Błąd monitu
|
||||
session.error.compact=Błąd kompresji sesji
|
||||
session.error.unknown=Nieznany błąd
|
||||
|
||||
session.header.tokens=Tokeny
|
||||
session.header.tokens.description=Tokeny użyte przez ostatnią odpowiedź asystenta: wejściowe, wyjściowe, zapisy do bufora i odczyty z bufora.
|
||||
session.header.input=we {0}
|
||||
session.header.output=wy {0}
|
||||
session.header.cache.write=zapis bufora {0}
|
||||
session.header.cache.read=odczyt bufora {0}
|
||||
session.header.compact=Kompresuj sesję
|
||||
session.header.compact.description=Podsumuj sesję, aby zmniejszyć rozmiar kontekstu
|
||||
session.header.expand=Pokaż metryki sesji
|
||||
session.header.collapse=Ukryj metryki sesji
|
||||
session.header.todos.progress={0}/{1} zadań ukończono
|
||||
session.header.todos.done=Wszystkie {0} zadań ukończono
|
||||
session.header.context.tooltip.percent={0} tokenów ({1}% kontekstu)
|
||||
session.header.context.tooltip.tokens={0} tokenów
|
||||
session.header.context.used={0} / {1} tokenów użito
|
||||
session.header.context.reserved={0} zarezerwowane dla wyjścia
|
||||
session.header.context.available={0} dostępne
|
||||
|
||||
prompt.placeholder=Wpisz wiadomość…
|
||||
prompt.placeholder.with.shortcuts=Wpisz wiadomość… ({0} aby wyslać, {1} nowa linia)
|
||||
prompt.placeholder.with.send=Wpisz wiadomość… ({0} aby wyslać)
|
||||
prompt.placeholder.with.newline=Wpisz wiadomość… ({0} nowa linia)
|
||||
prompt.button.send=Wyślij
|
||||
prompt.button.stop=Zatrzymaj
|
||||
prompt.button.send.tooltip.stop=Aby zatrzymać, naciśnij {0}
|
||||
mode.picker.tooltip=Wybierz tryb
|
||||
mode.picker.deprecated=przestarzały
|
||||
model.picker.tooltip=Wybierz model
|
||||
model.picker.recommended=Zalecane
|
||||
model.picker.favorites=Ulubione
|
||||
model.picker.search=Szukaj modeli
|
||||
model.picker.no.matches=Brak pasujących modeli
|
||||
model.picker.favorite.add=Dodaj do ulubionych
|
||||
model.picker.favorite.remove=Usuń z ulubionych
|
||||
model.picker.free=Bezpłatny
|
||||
model.picker.reset=Zresetuj model do domyślnego
|
||||
reasoning.picker.tooltip=Wybierz poziom rozumowania
|
||||
|
||||
history.tab.local=Lokalne
|
||||
history.tab.cloud=Chmura
|
||||
history.back=Wstecz
|
||||
history.search.placeholder=Szukaj sesji
|
||||
history.empty=Brak sesji
|
||||
history.loading=Ładowanie…
|
||||
history.untitled=Bez tytułu
|
||||
history.delete.text=Usuń
|
||||
history.delete.confirm.title=Usunąć sesję?
|
||||
history.delete.confirm.message=Usunąć "{0}" z lokalnej historii?
|
||||
history.delete.confirm.message.multiple=Usunąć {0} sesji z lokalnej historii?
|
||||
history.rename.title=Zmień nazwę sesji
|
||||
history.rename.prompt=Nowa nazwa sesji:
|
||||
history.cloud.load.more=Załaduj więcej
|
||||
history.cloud.repo.only=Tylko to repozytorium
|
||||
history.group.today=Dzisiaj
|
||||
history.group.yesterday=Wczoraj
|
||||
history.group.week=Ten tydzień
|
||||
history.group.month=Ten miesiąc
|
||||
history.group.older=Starsze
|
||||
history.time.moments=Przed chwilą
|
||||
history.time.minutes={0} min temu
|
||||
history.time.hours={0} godz temu
|
||||
history.time.days={0} dni temu
|
||||
history.time.months={0} mies temu
|
||||
history.time.years={0} lat temu
|
||||
history.error.local=Nie udało się załadować lokalnej historii
|
||||
history.error.cloud=Nie udało się załadować historii w chmurze
|
||||
history.error.local.delete=Nie udało się usunąć sesji
|
||||
history.error.local.rename=Nie udało się zmienić nazwy sesji
|
||||
history.error.cloud.delete=Sesji w chmurze nie można jeszcze usuwać
|
||||
|
||||
action.Kilo.Settings.text=Ustawienia
|
||||
action.Kilo.Settings.description=Ustawienia Kilo Code
|
||||
action.Kilo.NewSession.text=Nowa sesja
|
||||
action.Kilo.NewSession.description=Rozpocznij nową sesję Kilo
|
||||
action.Kilo.History.text=Historia
|
||||
action.Kilo.History.description=Pokaż historię sesji
|
||||
action.Kilo.SendPrompt.text=Wyślij monit
|
||||
action.Kilo.SendPrompt.description=Wyślij bieżący monit Kilo
|
||||
action.Kilo.StopSession.text=Zatrzymaj sesję
|
||||
action.Kilo.StopSession.description=Zatrzymaj bieżącą sesję Kilo
|
||||
action.Kilo.SettingsGroup.text=Ustawienia
|
||||
action.Kilo.SettingsGroup.description=Ustawienia Kilo Code
|
||||
action.Kilo.Restart.text=Uruchom ponownie Kilo
|
||||
action.Kilo.Restart.description=Zakończ i uruchom ponownie proces CLI
|
||||
action.Kilo.Reinstall.text=Zainstaluj ponownie Kilo
|
||||
action.Kilo.Reinstall.description=Ponownie wyodrębnij plik binarny CLI i uruchom ponownie
|
||||
action.Kilo.Session.Open.text=Otwórz
|
||||
action.Kilo.Session.Open.description=Otwórz wybraną sesję
|
||||
action.Kilo.Session.Rename.text=Zmień nazwę
|
||||
action.Kilo.Session.Rename.description=Zmień nazwę wybranej sesji
|
||||
action.Kilo.Session.Delete.text=Usuń
|
||||
action.Kilo.Session.Delete.description=Usuń wybraną sesję (wybrane sesje)
|
||||
action.Kilo.History.ContextMenu.text=Akcje historii
|
||||
+139
@@ -0,0 +1,139 @@
|
||||
session.connection.connecting=Carregando…
|
||||
session.connection.error.app=Falha na conexão
|
||||
session.connection.error.workspace=Falha ao carregar o espaço de trabalho
|
||||
session.connection.error.unknown=Erro desconhecido
|
||||
session.connection.retry=Tentar novamente
|
||||
session.connection.warning.config=Avisos de configuração
|
||||
|
||||
session.empty.welcome=Kilo Code é um assistente de codificação com IA. Peça que construa funcionalidades, corrija bugs ou explique sua base de código.
|
||||
session.empty.loading=Carregando…
|
||||
session.empty.recent=RECENTE
|
||||
session.showHistory=Mostrar histórico
|
||||
session.scroll.bottom=Rolar para o fim
|
||||
session.tab.new=Nova sessão
|
||||
session.tab.untitled=Sessão sem título
|
||||
|
||||
session.permission.title=Solicitação de permissão
|
||||
session.permission.meta=Ferramenta: {0} • Padrões: {1}
|
||||
session.permission.allow=Permitir
|
||||
session.permission.deny=Negar
|
||||
session.question.dismiss=Dispensar
|
||||
|
||||
session.status.considering=Considerando os próximos passos…
|
||||
session.status.thinking=Pensando…
|
||||
session.status.writing=Escrevendo resposta…
|
||||
session.status.delegating=Delegando trabalho…
|
||||
session.status.planning=Planejando…
|
||||
session.status.gathering=Coletando contexto…
|
||||
session.status.searching.codebase=Pesquisando na base de código…
|
||||
session.status.searching.web=Pesquisando na web…
|
||||
session.status.editing=Realizando edições…
|
||||
session.status.commands=Executando comandos…
|
||||
|
||||
session.part.reasoning=Raciocínio
|
||||
session.part.compaction=contexto compactado
|
||||
session.part.tool.copy=Copiar
|
||||
session.part.tool.error=Erro
|
||||
session.part.tool.pending=Pendente
|
||||
session.part.tool.read=Ler
|
||||
session.part.tool.running=Executando
|
||||
session.part.tool.shell=Shell
|
||||
session.part.tool.truncated=Saída truncada na pré-visualização. A saída completa permanece nos dados da sessão.
|
||||
|
||||
session.error.prompt=Falha no prompt
|
||||
session.error.compact=Falha ao compactar a sessão
|
||||
session.error.unknown=Erro desconhecido
|
||||
|
||||
session.header.tokens=Tokens
|
||||
session.header.tokens.description=Tokens usados pela última resposta do assistente: entrada, saída, escritas em cache e leituras em cache.
|
||||
session.header.input=entrada {0}
|
||||
session.header.output=saída {0}
|
||||
session.header.cache.write=escrita em cache {0}
|
||||
session.header.cache.read=leitura em cache {0}
|
||||
session.header.compact=Compactar sessão
|
||||
session.header.compact.description=Resumir a sessão para reduzir o tamanho do contexto
|
||||
session.header.expand=Exibir métricas da sessão
|
||||
session.header.collapse=Ocultar métricas da sessão
|
||||
session.header.todos.progress={0}/{1} tarefas concluídas
|
||||
session.header.todos.done=Todas as {0} tarefas concluídas
|
||||
session.header.context.tooltip.percent={0} tokens ({1}% do contexto)
|
||||
session.header.context.tooltip.tokens={0} tokens
|
||||
session.header.context.used={0} / {1} tokens utilizados
|
||||
session.header.context.reserved={0} reservados para saída
|
||||
session.header.context.available={0} disponíveis
|
||||
|
||||
prompt.placeholder=Digite uma mensagem…
|
||||
prompt.placeholder.with.shortcuts=Digite uma mensagem… ({0} para enviar, {1} para nova linha)
|
||||
prompt.placeholder.with.send=Digite uma mensagem… ({0} para enviar)
|
||||
prompt.placeholder.with.newline=Digite uma mensagem… ({0} para nova linha)
|
||||
prompt.button.send=Enviar
|
||||
prompt.button.stop=Parar
|
||||
prompt.button.send.tooltip.stop=Para parar, pressione {0}
|
||||
mode.picker.tooltip=Selecionar modo
|
||||
mode.picker.deprecated=obsoleto
|
||||
model.picker.tooltip=Selecionar modelo
|
||||
model.picker.recommended=Recomendado
|
||||
model.picker.favorites=Favoritos
|
||||
model.picker.search=Pesquisar modelos
|
||||
model.picker.no.matches=Nenhum modelo correspondente
|
||||
model.picker.favorite.add=Adicionar aos favoritos
|
||||
model.picker.favorite.remove=Remover dos favoritos
|
||||
model.picker.free=Gratuito
|
||||
model.picker.reset=Redefinir modelo para o padrão
|
||||
reasoning.picker.tooltip=Selecionar esforço de raciocínio
|
||||
|
||||
history.tab.local=Local
|
||||
history.tab.cloud=Nuvem
|
||||
history.back=Voltar
|
||||
history.search.placeholder=Pesquisar sessões
|
||||
history.empty=Nenhuma sessão
|
||||
history.loading=Carregando…
|
||||
history.untitled=Sem título
|
||||
history.delete.text=Excluir
|
||||
history.delete.confirm.title=Excluir sessão?
|
||||
history.delete.confirm.message=Excluir "{0}" do histórico local?
|
||||
history.delete.confirm.message.multiple=Excluir {0} sessões do histórico local?
|
||||
history.rename.title=Renomear sessão
|
||||
history.rename.prompt=Novo nome da sessão:
|
||||
history.cloud.load.more=Carregar mais
|
||||
history.cloud.repo.only=Somente este repositório
|
||||
history.group.today=Hoje
|
||||
history.group.yesterday=Ontem
|
||||
history.group.week=Esta semana
|
||||
history.group.month=Este mês
|
||||
history.group.older=Mais antigo
|
||||
history.time.moments=Agora mesmo
|
||||
history.time.minutes=Há {0} min
|
||||
history.time.hours=Há {0} h
|
||||
history.time.days=Há {0} d
|
||||
history.time.months=Há {0} mês(es)
|
||||
history.time.years=Há {0} ano(s)
|
||||
history.error.local=Falha ao carregar o histórico local
|
||||
history.error.cloud=Falha ao carregar o histórico na nuvem
|
||||
history.error.local.delete=Falha ao excluir a sessão
|
||||
history.error.local.rename=Falha ao renomear a sessão
|
||||
history.error.cloud.delete=As sessões na nuvem ainda não podem ser excluídas
|
||||
|
||||
action.Kilo.Settings.text=Configurações
|
||||
action.Kilo.Settings.description=Configurações do Kilo Code
|
||||
action.Kilo.NewSession.text=Nova sessão
|
||||
action.Kilo.NewSession.description=Iniciar uma nova sessão do Kilo
|
||||
action.Kilo.History.text=Histórico
|
||||
action.Kilo.History.description=Exibir histórico de sessões
|
||||
action.Kilo.SendPrompt.text=Enviar prompt
|
||||
action.Kilo.SendPrompt.description=Enviar o prompt atual do Kilo
|
||||
action.Kilo.StopSession.text=Parar sessão
|
||||
action.Kilo.StopSession.description=Parar a sessão atual do Kilo
|
||||
action.Kilo.SettingsGroup.text=Configurações
|
||||
action.Kilo.SettingsGroup.description=Configurações do Kilo Code
|
||||
action.Kilo.Restart.text=Reiniciar Kilo
|
||||
action.Kilo.Restart.description=Encerrar e reiniciar o processo CLI
|
||||
action.Kilo.Reinstall.text=Reinstalar Kilo
|
||||
action.Kilo.Reinstall.description=Reextrair o binário CLI e reiniciar
|
||||
action.Kilo.Session.Open.text=Abrir
|
||||
action.Kilo.Session.Open.description=Abrir a sessão selecionada
|
||||
action.Kilo.Session.Rename.text=Renomear
|
||||
action.Kilo.Session.Rename.description=Renomear a sessão selecionada
|
||||
action.Kilo.Session.Delete.text=Excluir
|
||||
action.Kilo.Session.Delete.description=Excluir a(s) sessão(es) selecionada(s)
|
||||
action.Kilo.History.ContextMenu.text=Ações do histórico
|
||||
+139
@@ -0,0 +1,139 @@
|
||||
session.connection.connecting=Загрузка…
|
||||
session.connection.error.app=Ошибка подключения
|
||||
session.connection.error.workspace=Ошибка загрузки рабочей области
|
||||
session.connection.error.unknown=Неизвестная ошибка
|
||||
session.connection.retry=Повторить
|
||||
session.connection.warning.config=Предупреждения конфигурации
|
||||
|
||||
session.empty.welcome=Kilo Code — это AI-ассистент по программированию. Попросите его разработать функции, исправить ошибки или объяснить кодовую базу.
|
||||
session.empty.loading=Загрузка…
|
||||
session.empty.recent=НЕДАВНИЕ
|
||||
session.showHistory=Показать историю
|
||||
session.scroll.bottom=Прокрутить вниз
|
||||
session.tab.new=Новая сессия
|
||||
session.tab.untitled=Незаголовок сессия
|
||||
|
||||
session.permission.title=Запрос разрешения
|
||||
session.permission.meta=Инструмент: {0} • Шаблоны: {1}
|
||||
session.permission.allow=Разрешить
|
||||
session.permission.deny=Отказать
|
||||
session.question.dismiss=Отклонить
|
||||
|
||||
session.status.considering=Обдумываю следующие шаги…
|
||||
session.status.thinking=Размышляю…
|
||||
session.status.writing=Пишу ответ…
|
||||
session.status.delegating=Передаю задание…
|
||||
session.status.planning=Планирую…
|
||||
session.status.gathering=Сбор контекста…
|
||||
session.status.searching.codebase=Поиск в кодовой базе…
|
||||
session.status.searching.web=Поиск в интернете…
|
||||
session.status.editing=Вношу изменения…
|
||||
session.status.commands=Выполняю команды…
|
||||
|
||||
session.part.reasoning=Рассуждение
|
||||
session.part.compaction=контекст сжат
|
||||
session.part.tool.copy=Копировать
|
||||
session.part.tool.error=Ошибка
|
||||
session.part.tool.pending=Ожидание
|
||||
session.part.tool.read=Чтение
|
||||
session.part.tool.running=Выполняется
|
||||
session.part.tool.shell=Shell
|
||||
session.part.tool.truncated=Вывод усечён в предпросмотре. Полный вывод сохраняется в данных сессии.
|
||||
|
||||
session.error.prompt=Ошибка запроса
|
||||
session.error.compact=Ошибка сжатия сессии
|
||||
session.error.unknown=Неизвестная ошибка
|
||||
|
||||
session.header.tokens=Токены
|
||||
session.header.tokens.description=Токены, использованные последним ответом ассистента: входные, исходные, запись в кэш и чтение из кэша.
|
||||
session.header.input=вход {0}
|
||||
session.header.output=выход {0}
|
||||
session.header.cache.write=запись в кэш {0}
|
||||
session.header.cache.read=чтение из кэша {0}
|
||||
session.header.compact=Сжать сессию
|
||||
session.header.compact.description=Суммировать сессию для уменьшения размера контекста
|
||||
session.header.expand=Показать метрики сессии
|
||||
session.header.collapse=Скрыть метрики сессии
|
||||
session.header.todos.progress={0}/{1} задач выполнено
|
||||
session.header.todos.done=Все {0} задач выполнены
|
||||
session.header.context.tooltip.percent={0} токенов ({1}% контекста)
|
||||
session.header.context.tooltip.tokens={0} токенов
|
||||
session.header.context.used={0} / {1} токенов использовано
|
||||
session.header.context.reserved={0} зарезервировано для вывода
|
||||
session.header.context.available={0} доступно
|
||||
|
||||
prompt.placeholder=Введите сообщение…
|
||||
prompt.placeholder.with.shortcuts=Введите сообщение… ({0} — отправить, {1} — новая строка)
|
||||
prompt.placeholder.with.send=Введите сообщение… ({0} — отправить)
|
||||
prompt.placeholder.with.newline=Введите сообщение… ({0} — новая строка)
|
||||
prompt.button.send=Отправить
|
||||
prompt.button.stop=Остановить
|
||||
prompt.button.send.tooltip.stop=Для остановки нажмите {0}
|
||||
mode.picker.tooltip=Выбрать режим
|
||||
mode.picker.deprecated=устаревший
|
||||
model.picker.tooltip=Выбрать модель
|
||||
model.picker.recommended=Рекомендуемые
|
||||
model.picker.favorites=Избранное
|
||||
model.picker.search=Поиск моделей
|
||||
model.picker.no.matches=Нет подходящих моделей
|
||||
model.picker.favorite.add=Добавить в избранное
|
||||
model.picker.favorite.remove=Удалить из избранного
|
||||
model.picker.free=Бесплатно
|
||||
model.picker.reset=Сбросить модель на по умолчанию
|
||||
reasoning.picker.tooltip=Выбрать уровень рассуждения
|
||||
|
||||
history.tab.local=Локальная
|
||||
history.tab.cloud=Облако
|
||||
history.back=Назад
|
||||
history.search.placeholder=Поиск сессий
|
||||
history.empty=Нет сессий
|
||||
history.loading=Загрузка…
|
||||
history.untitled=Без названия
|
||||
history.delete.text=Удалить
|
||||
history.delete.confirm.title=Удалить сессию?
|
||||
history.delete.confirm.message=Удалить «{0}» из локальной истории?
|
||||
history.delete.confirm.message.multiple=Удалить {0} сессий из локальной истории?
|
||||
history.rename.title=Переименовать сессию
|
||||
history.rename.prompt=Новое имя сессии:
|
||||
history.cloud.load.more=Загрузить ещё
|
||||
history.cloud.repo.only=Только этот репозиторий
|
||||
history.group.today=Сегодня
|
||||
history.group.yesterday=Вчера
|
||||
history.group.week=На этой неделе
|
||||
history.group.month=В этом месяце
|
||||
history.group.older=Старше
|
||||
history.time.moments=Только что
|
||||
history.time.minutes={0} мин назад
|
||||
history.time.hours={0} ч назад
|
||||
history.time.days={0} д назад
|
||||
history.time.months={0} мес назад
|
||||
history.time.years={0} л назад
|
||||
history.error.local=Ошибка загрузки локальной истории
|
||||
history.error.cloud=Ошибка загрузки облачной истории
|
||||
history.error.local.delete=Ошибка удаления сессии
|
||||
history.error.local.rename=Ошибка переименования сессии
|
||||
history.error.cloud.delete=Облачные сессии пока нельзя удалить
|
||||
|
||||
action.Kilo.Settings.text=Настройки
|
||||
action.Kilo.Settings.description=Настройки Kilo Code
|
||||
action.Kilo.NewSession.text=Новая сессия
|
||||
action.Kilo.NewSession.description=Запустить новую сессию Kilo
|
||||
action.Kilo.History.text=История
|
||||
action.Kilo.History.description=Показать историю сессий
|
||||
action.Kilo.SendPrompt.text=Отправить запрос
|
||||
action.Kilo.SendPrompt.description=Отправить текущий запрос Kilo
|
||||
action.Kilo.StopSession.text=Остановить сессию
|
||||
action.Kilo.StopSession.description=Остановить текущую сессию Kilo
|
||||
action.Kilo.SettingsGroup.text=Настройки
|
||||
action.Kilo.SettingsGroup.description=Настройки Kilo Code
|
||||
action.Kilo.Restart.text=Перезапустить Kilo
|
||||
action.Kilo.Restart.description=Завершить и перезапустить CLI-процесс
|
||||
action.Kilo.Reinstall.text=Переустановить Kilo
|
||||
action.Kilo.Reinstall.description=Переизвлечь CLI-бинарник и перезапустить
|
||||
action.Kilo.Session.Open.text=Открыть
|
||||
action.Kilo.Session.Open.description=Открыть выбранную сессию
|
||||
action.Kilo.Session.Rename.text=Переименовать
|
||||
action.Kilo.Session.Rename.description=Переименовать выбранную сессию
|
||||
action.Kilo.Session.Delete.text=Удалить
|
||||
action.Kilo.Session.Delete.description=Удалить выбранную(-ые) сессию(-ии)
|
||||
action.Kilo.History.ContextMenu.text=Действия с историей
|
||||
+139
@@ -0,0 +1,139 @@
|
||||
session.connection.connecting=กำลังโหลด…
|
||||
session.connection.error.app=การเชื่อมต่อล้มเหลว
|
||||
session.connection.error.workspace=โหลดพื้นที่ทำงานล้มเหลว
|
||||
session.connection.error.unknown=ข้อผิดพลาดที่ไม่ทราบ
|
||||
session.connection.retry=ลองอีกครั้ง
|
||||
session.connection.warning.config=คำเตือนการกำหนดค่า
|
||||
|
||||
session.empty.welcome=Kilo Code คือผู้ช่วยเขียนโค้ด AI ขอให้สร้างฟีเจอร์แก้สันข้อผิดพลาด หรืออธิบายโค้ดเบสของคุณ
|
||||
session.empty.loading=กำลังโหลด…
|
||||
session.empty.recent=ล่าสุด
|
||||
session.showHistory=แสดงประวัติ
|
||||
session.scroll.bottom=เลื่อนไปด้านล่าง
|
||||
session.tab.new=เซสชันใหม่
|
||||
session.tab.untitled=เซสชันไม่มีชื่อ
|
||||
|
||||
session.permission.title=ขอสิทธิ์
|
||||
session.permission.meta=เครื่องมือ: {0} • รูปแบบ: {1}
|
||||
session.permission.allow=อนุญาต
|
||||
session.permission.deny=ปฏิเสธ
|
||||
session.question.dismiss=ปิด
|
||||
|
||||
session.status.considering=กำลังพิจารณาขั้นตอนถัดไป…
|
||||
session.status.thinking=กำลังคิด…
|
||||
session.status.writing=กำลังเขียนคำตอบ…
|
||||
session.status.delegating=กำลังมอบหมายงาน…
|
||||
session.status.planning=กำลังวางแผน…
|
||||
session.status.gathering=กำลังรวบรวมบริบท…
|
||||
session.status.searching.codebase=กำลังค้นหาในโค้ดเบส…
|
||||
session.status.searching.web=กำลังค้นหาบนเว็บ…
|
||||
session.status.editing=กำลังแก้ไข…
|
||||
session.status.commands=กำลังเรียกใช้คำสั่ง…
|
||||
|
||||
session.part.reasoning=การใช้เหตุผล
|
||||
session.part.compaction=บริบทถูกบีบอัดแล้ว
|
||||
session.part.tool.copy=คัดลอก
|
||||
session.part.tool.error=ข้อผิดพลาด
|
||||
session.part.tool.pending=รอดำเนินการ
|
||||
session.part.tool.read=อ่าน
|
||||
session.part.tool.running=กำลังทำงาน
|
||||
session.part.tool.shell=Shell
|
||||
session.part.tool.truncated=ผลลัพธ์ถูกตัดทอนในส่วนตัวอย่าง ผลลัพธ์ทั้งหมดยังคงอยู่ในข้อมูลเซสชัน
|
||||
|
||||
session.error.prompt=คำขอล้มเหลว
|
||||
session.error.compact=การบีบอัดเซสชันล้มเหลว
|
||||
session.error.unknown=ข้อผิดพลาดที่ไม่ทราบ
|
||||
|
||||
session.header.tokens=โทเคน
|
||||
session.header.tokens.description=โทเคนที่ใช้โดยคำตอบล่าสุดของผู้ช่วย: อินพุต เอาต์พุต การเขียนแคช และการอ่านแคช
|
||||
session.header.input=อินพุต {0}
|
||||
session.header.output=เอาต์พุต {0}
|
||||
session.header.cache.write=เขียนแคช {0}
|
||||
session.header.cache.read=อ่านแคช {0}
|
||||
session.header.compact=บีบอัดเซสชัน
|
||||
session.header.compact.description=สรุปเซสชันเพื่อลดขนาดบริบท
|
||||
session.header.expand=แสดงเมตริกส์เซสชัน
|
||||
session.header.collapse=ซ่อนเมตริกส์เซสชัน
|
||||
session.header.todos.progress={0}/{1} งานเสร็จสิ้นแล้ว
|
||||
session.header.todos.done=ครบทุก {0} งานเสร็จสิ้นแล้ว
|
||||
session.header.context.tooltip.percent={0} โทเคน ({1}% ของบริบท)
|
||||
session.header.context.tooltip.tokens={0} โทเคน
|
||||
session.header.context.used=ใช้แล้ว {0} / {1} โทเคน
|
||||
session.header.context.reserved={0} สำรองไว้สำหรับเอาต์พุต
|
||||
session.header.context.available={0} พร้อมใช้งาน
|
||||
|
||||
prompt.placeholder=พิมพ์ข้อความ…
|
||||
prompt.placeholder.with.shortcuts=พิมพ์ข้อความ… ({0} เพื่อส่ง {1} สำหรับบรรทัดใหม่)
|
||||
prompt.placeholder.with.send=พิมพ์ข้อความ… ({0} เพื่อส่ง)
|
||||
prompt.placeholder.with.newline=พิมพ์ข้อความ… ({0} สำหรับบรรทัดใหม่)
|
||||
prompt.button.send=ส่ง
|
||||
prompt.button.stop=หยุด
|
||||
prompt.button.send.tooltip.stop=กด {0} เพื่อหยุด
|
||||
mode.picker.tooltip=เลือกโหมด
|
||||
mode.picker.deprecated=ล้าสมัย
|
||||
model.picker.tooltip=เลือกโมเดล
|
||||
model.picker.recommended=แนะนำ
|
||||
model.picker.favorites=รายการโปรด
|
||||
model.picker.search=ค้นหาโมเดล
|
||||
model.picker.no.matches=ไม่พบโมเดลที่ตรงกัน
|
||||
model.picker.favorite.add=เพิ่มในรายการโปรด
|
||||
model.picker.favorite.remove=นำออกจากรายการโปรด
|
||||
model.picker.free=ฟรี
|
||||
model.picker.reset=รีเซ็ตโมเดลเป็นค่าเริ่มต้น
|
||||
reasoning.picker.tooltip=เลือกระดับการใช้เหตุผล
|
||||
|
||||
history.tab.local=เครื่องนี้
|
||||
history.tab.cloud=คลาวด์
|
||||
history.back=ย้อนกลับ
|
||||
history.search.placeholder=ค้นหาเซสชัน
|
||||
history.empty=ไม่มีเซสชัน
|
||||
history.loading=กำลังโหลด…
|
||||
history.untitled=ไม่มีชื่อ
|
||||
history.delete.text=ลบ
|
||||
history.delete.confirm.title=ลบเซสชัน?
|
||||
history.delete.confirm.message=ลบ "{0}" ออกจากประวัติในเครื่อง?
|
||||
history.delete.confirm.message.multiple=ลบ {0} เซสชันออกจากประวัติในเครื่อง?
|
||||
history.rename.title=เปลี่ยนชื่อเซสชัน
|
||||
history.rename.prompt=ชื่อเซสชันใหม่:
|
||||
history.cloud.load.more=โหลดเพิ่มเติม
|
||||
history.cloud.repo.only=เฉพาะรีโปซิทอรีนี้
|
||||
history.group.today=วันนี้
|
||||
history.group.yesterday=เมื่อวาน
|
||||
history.group.week=สัปดาห์นี้
|
||||
history.group.month=เดือนนี้
|
||||
history.group.older=เก่ากว่า
|
||||
history.time.moments=เมื่อกี้นี้
|
||||
history.time.minutes={0} นาทีที่แล้ว
|
||||
history.time.hours={0} ชั่วโมงที่แล้ว
|
||||
history.time.days={0} วันที่แล้ว
|
||||
history.time.months={0} เดือนที่แล้ว
|
||||
history.time.years={0} ปีที่แล้ว
|
||||
history.error.local=ไม่สามารถโหลดประวัติในเครื่อง
|
||||
history.error.cloud=ไม่สามารถโหลดประวัติคลาวด์
|
||||
history.error.local.delete=ไม่สามารถลบเซสชัน
|
||||
history.error.local.rename=ไม่สามารถเปลี่ยนชื่อเซสชัน
|
||||
history.error.cloud.delete=ยังไม่สามารถลบเซสชันคลาวด์ได้
|
||||
|
||||
action.Kilo.Settings.text=การตั้งค่า
|
||||
action.Kilo.Settings.description=การตั้งค่า Kilo Code
|
||||
action.Kilo.NewSession.text=เซสชันใหม่
|
||||
action.Kilo.NewSession.description=เริ่มเซสชัน Kilo ใหม่
|
||||
action.Kilo.History.text=ประวัติ
|
||||
action.Kilo.History.description=แสดงประวัติเซสชัน
|
||||
action.Kilo.SendPrompt.text=ส่งคำขอ
|
||||
action.Kilo.SendPrompt.description=ส่งคำขอ Kilo ปัจจุบัน
|
||||
action.Kilo.StopSession.text=หยุดเซสชัน
|
||||
action.Kilo.StopSession.description=หยุดเซสชัน Kilo ปัจจุบัน
|
||||
action.Kilo.SettingsGroup.text=การตั้งค่า
|
||||
action.Kilo.SettingsGroup.description=การตั้งค่า Kilo Code
|
||||
action.Kilo.Restart.text=รีสตาร์ต Kilo
|
||||
action.Kilo.Restart.description=สิ้นสุดและรีสตาร์ตกระบวนการ CLI
|
||||
action.Kilo.Reinstall.text=ติดตั้ง Kilo ใหม่
|
||||
action.Kilo.Reinstall.description=แตกไฟล์ไบนารี CLI อีกครั้งและรีสตาร์ต
|
||||
action.Kilo.Session.Open.text=เปิด
|
||||
action.Kilo.Session.Open.description=เปิดเซสชันที่เลือก
|
||||
action.Kilo.Session.Rename.text=เปลี่ยนชื่อ
|
||||
action.Kilo.Session.Rename.description=เปลี่ยนชื่อเซสชันที่เลือก
|
||||
action.Kilo.Session.Delete.text=ลบ
|
||||
action.Kilo.Session.Delete.description=ลบเซสชันที่เลือก
|
||||
action.Kilo.History.ContextMenu.text=การดำเนินการประวัติ
|
||||
+139
@@ -0,0 +1,139 @@
|
||||
session.connection.connecting=Yükleniyor…
|
||||
session.connection.error.app=Bağlantı hatası
|
||||
session.connection.error.workspace=Çalışma alanı yüklenemedi
|
||||
session.connection.error.unknown=Bilinmeyen hata
|
||||
session.connection.retry=Tekrar dene
|
||||
session.connection.warning.config=Yapılandırma uyarıları
|
||||
|
||||
session.empty.welcome=Kilo Code, bir yapay zeka kodlama asistanıdır. Özellik oluşturmasını, hata düzeltirlmesi veya kod tabanınızı açıklamasını isteyin.
|
||||
session.empty.loading=Yükleniyor…
|
||||
session.empty.recent=SON
|
||||
session.showHistory=Geçmişi göster
|
||||
session.scroll.bottom=En alta kaýr
|
||||
session.tab.new=Yeni oturum
|
||||
session.tab.untitled=Başlıksız oturum
|
||||
|
||||
session.permission.title=İzin isteği
|
||||
session.permission.meta=Araç: {0} • Desenler: {1}
|
||||
session.permission.allow=İzin ver
|
||||
session.permission.deny=Reddet
|
||||
session.question.dismiss=Kapat
|
||||
|
||||
session.status.considering=Sonraki adımlar düşünülüyor…
|
||||
session.status.thinking=Düşünülüyor…
|
||||
session.status.writing=Yanıt yazılıyor…
|
||||
session.status.delegating=İş devrediliyor…
|
||||
session.status.planning=Plan hazırlanıyor…
|
||||
session.status.gathering=Bağlam toplanıyor…
|
||||
session.status.searching.codebase=Kod tabanı aranıyor…
|
||||
session.status.searching.web=Web aranıyor…
|
||||
session.status.editing=Düzenleme yapılıyor…
|
||||
session.status.commands=Komutlar çalıştırılıyor…
|
||||
|
||||
session.part.reasoning=Muhakeme
|
||||
session.part.compaction=bağlam sıkıştırıldı
|
||||
session.part.tool.copy=Kopyala
|
||||
session.part.tool.error=Hata
|
||||
session.part.tool.pending=Bekliyor
|
||||
session.part.tool.read=Oku
|
||||
session.part.tool.running=Çalışıyor
|
||||
session.part.tool.shell=Kabuk
|
||||
session.part.tool.truncated=Önizlemede çıktı kısaltıldı. Tam çıktı oturum verilerinde kalıyor.
|
||||
|
||||
session.error.prompt=İstem hatası
|
||||
session.error.compact=Oturum sıkıştırma hatası
|
||||
session.error.unknown=Bilinmeyen hata
|
||||
|
||||
session.header.tokens=Jeton
|
||||
session.header.tokens.description=Son asistan yanıtında kullanılan jetonlar: giriş, çıkış, önbelleğe yazma ve önbellekten okuma.
|
||||
session.header.input=giriş {0}
|
||||
session.header.output=çıkış {0}
|
||||
session.header.cache.write=önbelleğe yazma {0}
|
||||
session.header.cache.read=önbellekten okuma {0}
|
||||
session.header.compact=Oturumu sıkıştır
|
||||
session.header.compact.description=Bağlam boyutunu azaltmak için oturumu özetle
|
||||
session.header.expand=Oturum metriklerini göster
|
||||
session.header.collapse=Oturum metriklerini gizle
|
||||
session.header.todos.progress={0}/{1} görev tamamlandı
|
||||
session.header.todos.done=Tüm {0} görev tamamlandı
|
||||
session.header.context.tooltip.percent={0} jeton (bağlamın %{1}’i)
|
||||
session.header.context.tooltip.tokens={0} jeton
|
||||
session.header.context.used={0} / {1} jeton kullanıldı
|
||||
session.header.context.reserved={0} çıkış için ayrıldı
|
||||
session.header.context.available={0} kullanılabilir
|
||||
|
||||
prompt.placeholder=Bir mesaj yazın…
|
||||
prompt.placeholder.with.shortcuts=Bir mesaj yazın… ({0} göndermek için, {1} yeni satır için)
|
||||
prompt.placeholder.with.send=Bir mesaj yazın… ({0} göndermek için)
|
||||
prompt.placeholder.with.newline=Bir mesaj yazın… ({0} yeni satır için)
|
||||
prompt.button.send=Gönder
|
||||
prompt.button.stop=Durdur
|
||||
prompt.button.send.tooltip.stop=Durdurmak için {0} basın
|
||||
mode.picker.tooltip=Mod seç
|
||||
mode.picker.deprecated=geçersiz
|
||||
model.picker.tooltip=Model seç
|
||||
model.picker.recommended=Önerilen
|
||||
model.picker.favorites=Favoriler
|
||||
model.picker.search=Model ara
|
||||
model.picker.no.matches=Eşleşen model yok
|
||||
model.picker.favorite.add=Favorilere ekle
|
||||
model.picker.favorite.remove=Favorilerden çıkar
|
||||
model.picker.free=Ücretsiz
|
||||
model.picker.reset=Modeli varsayılana sıfırla
|
||||
reasoning.picker.tooltip=Muhakeme çabası seç
|
||||
|
||||
history.tab.local=Yerel
|
||||
history.tab.cloud=Bulut
|
||||
history.back=Geri
|
||||
history.search.placeholder=Oturum ara
|
||||
history.empty=Oturum yok
|
||||
history.loading=Yükleniyor…
|
||||
history.untitled=Başlıksız
|
||||
history.delete.text=Sil
|
||||
history.delete.confirm.title=Oturum silinsin mi?
|
||||
history.delete.confirm.message="{0}" yerel geçmişten silinsin mi?
|
||||
history.delete.confirm.message.multiple={0} oturum yerel geçmişten silinsin mi?
|
||||
history.rename.title=Oturumu yeniden adlandır
|
||||
history.rename.prompt=Yeni oturum adı:
|
||||
history.cloud.load.more=Daha fazla yükle
|
||||
history.cloud.repo.only=Yalnızca bu depo
|
||||
history.group.today=Bugün
|
||||
history.group.yesterday=Dün
|
||||
history.group.week=Bu hafta
|
||||
history.group.month=Bu ay
|
||||
history.group.older=Daha eski
|
||||
history.time.moments=Az önce
|
||||
history.time.minutes={0} dk önce
|
||||
history.time.hours={0} sa önce
|
||||
history.time.days={0} g önce
|
||||
history.time.months={0} ay önce
|
||||
history.time.years={0} yıl önce
|
||||
history.error.local=Yerel geçmiş yüklenemedi
|
||||
history.error.cloud=Bulut geçmişi yüklenemedi
|
||||
history.error.local.delete=Oturum silinemedi
|
||||
history.error.local.rename=Oturum yeniden adlandırılamadı
|
||||
history.error.cloud.delete=Bulut oturumları henüz silinemiyor
|
||||
|
||||
action.Kilo.Settings.text=Ayarlar
|
||||
action.Kilo.Settings.description=Kilo Code ayarları
|
||||
action.Kilo.NewSession.text=Yeni oturum
|
||||
action.Kilo.NewSession.description=Yeni bir Kilo oturumu başlat
|
||||
action.Kilo.History.text=Geçmiş
|
||||
action.Kilo.History.description=Oturum geçmişini göster
|
||||
action.Kilo.SendPrompt.text=İstem gönder
|
||||
action.Kilo.SendPrompt.description=Geçerli Kilo istemini gönder
|
||||
action.Kilo.StopSession.text=Oturumu durdur
|
||||
action.Kilo.StopSession.description=Geçerli Kilo oturumunu durdur
|
||||
action.Kilo.SettingsGroup.text=Ayarlar
|
||||
action.Kilo.SettingsGroup.description=Kilo Code ayarları
|
||||
action.Kilo.Restart.text=Kilo’yu yeniden başlat
|
||||
action.Kilo.Restart.description=CLI sürecini sonlandır ve yeniden başlat
|
||||
action.Kilo.Reinstall.text=Kilo’yu yeniden yükle
|
||||
action.Kilo.Reinstall.description=CLI ikili dosyasını yeniden çıkar ve yeniden başlat
|
||||
action.Kilo.Session.Open.text=Aç
|
||||
action.Kilo.Session.Open.description=Seçilen oturumu aç
|
||||
action.Kilo.Session.Rename.text=Yeniden adlandır
|
||||
action.Kilo.Session.Rename.description=Seçilen oturumu yeniden adlandır
|
||||
action.Kilo.Session.Delete.text=Sil
|
||||
action.Kilo.Session.Delete.description=Seçilen oturumları sil
|
||||
action.Kilo.History.ContextMenu.text=Geçmiş eylemleri
|
||||
+139
@@ -0,0 +1,139 @@
|
||||
session.connection.connecting=Завантаження…
|
||||
session.connection.error.app=Помилка з'єднання
|
||||
session.connection.error.workspace=Помилка завантаження робочого простору
|
||||
session.connection.error.unknown=Невідома помилка
|
||||
session.connection.retry=Спробувати знову
|
||||
session.connection.warning.config=Попередження конфігурації
|
||||
|
||||
session.empty.welcome=Kilo Code — це AI-асистент для програмування. Попросіть його створити функції, виправити помилки або пояснити ваш код.
|
||||
session.empty.loading=Завантаження…
|
||||
session.empty.recent=НЕДАВНІ
|
||||
session.showHistory=Показати історію
|
||||
session.scroll.bottom=Прокрутити донизу
|
||||
session.tab.new=Нова сесія
|
||||
session.tab.untitled=Сесія без назви
|
||||
|
||||
session.permission.title=Запит дозволу
|
||||
session.permission.meta=Інструмент: {0} • Шаблони: {1}
|
||||
session.permission.allow=Дозволити
|
||||
session.permission.deny=Заборонити
|
||||
session.question.dismiss=Закрити
|
||||
|
||||
session.status.considering=Обмірковую наступні кроки…
|
||||
session.status.thinking=Міркую…
|
||||
session.status.writing=Пишу відповідь…
|
||||
session.status.delegating=Делегую роботу…
|
||||
session.status.planning=Планую…
|
||||
session.status.gathering=Збираю контекст…
|
||||
session.status.searching.codebase=Шукаю в кодовій базі…
|
||||
session.status.searching.web=Шукаю в інтернеті…
|
||||
session.status.editing=Вношу зміни…
|
||||
session.status.commands=Виконую команди…
|
||||
|
||||
session.part.reasoning=Міркування
|
||||
session.part.compaction=контекст стиснуто
|
||||
session.part.tool.copy=Копіювати
|
||||
session.part.tool.error=Помилка
|
||||
session.part.tool.pending=Очікується
|
||||
session.part.tool.read=Читання
|
||||
session.part.tool.running=Виконується
|
||||
session.part.tool.shell=Shell
|
||||
session.part.tool.truncated=Вивід у попередньому перегляді усічено. Повний вивід зберігається в даних сесії.
|
||||
|
||||
session.error.prompt=Помилка запиту
|
||||
session.error.compact=Помилка стиснення сесії
|
||||
session.error.unknown=Невідома помилка
|
||||
|
||||
session.header.tokens=Токени
|
||||
session.header.tokens.description=Токени, використані останній відповід䍎ю асистента: вхід, вихід, запис у кеш і читання з кешу.
|
||||
session.header.input=вхід {0}
|
||||
session.header.output=вихід {0}
|
||||
session.header.cache.write=запис у кеш {0}
|
||||
session.header.cache.read=читання з кешу {0}
|
||||
session.header.compact=Стиснути сесію
|
||||
session.header.compact.description=Стиснути сесію, щоб зменшити розмір контексту
|
||||
session.header.expand=Показати метрики сесії
|
||||
session.header.collapse=Приховати метрики сесії
|
||||
session.header.todos.progress={0}/{1} завдань виконано
|
||||
session.header.todos.done=Усі {0} завдань виконано
|
||||
session.header.context.tooltip.percent={0} токенів ({1}% контексту)
|
||||
session.header.context.tooltip.tokens={0} токенів
|
||||
session.header.context.used={0} / {1} токенів використано
|
||||
session.header.context.reserved={0} зарезервовано для виходу
|
||||
session.header.context.available={0} доступно
|
||||
|
||||
prompt.placeholder=Введіть повідомлення…
|
||||
prompt.placeholder.with.shortcuts=Введіть повідомлення… ({0} — надіслати, {1} — новий рядок)
|
||||
prompt.placeholder.with.send=Введіть повідомлення… ({0} — надіслати)
|
||||
prompt.placeholder.with.newline=Введіть повідомлення… ({0} — новий рядок)
|
||||
prompt.button.send=Надіслати
|
||||
prompt.button.stop=Зупинити
|
||||
prompt.button.send.tooltip.stop=Щоб зупинити, натисніть {0}
|
||||
mode.picker.tooltip=Обрати режим
|
||||
mode.picker.deprecated=застарілий
|
||||
model.picker.tooltip=Обрати модель
|
||||
model.picker.recommended=Рекомендовані
|
||||
model.picker.favorites=Улюблені
|
||||
model.picker.search=Пошук моделей
|
||||
model.picker.no.matches=Немає відповідних моделей
|
||||
model.picker.favorite.add=Додати до улюблених
|
||||
model.picker.favorite.remove=Видалити з улюблених
|
||||
model.picker.free=Безкоштовно
|
||||
model.picker.reset=Скинути модель до стандартної
|
||||
reasoning.picker.tooltip=Обрати рівень міркування
|
||||
|
||||
history.tab.local=Локальна
|
||||
history.tab.cloud=Хмара
|
||||
history.back=Назад
|
||||
history.search.placeholder=Пошук сесій
|
||||
history.empty=Немає сесій
|
||||
history.loading=Завантаження…
|
||||
history.untitled=Без назви
|
||||
history.delete.text=Видалити
|
||||
history.delete.confirm.title=Видалити сесію?
|
||||
history.delete.confirm.message=Видалити «{0}» з локальної історії?
|
||||
history.delete.confirm.message.multiple=Видалити {0} сесій з локальної історії?
|
||||
history.rename.title=Перейменувати сесію
|
||||
history.rename.prompt=Нова назва сесії:
|
||||
history.cloud.load.more=Завантажити більше
|
||||
history.cloud.repo.only=Тільки цей репозиторій
|
||||
history.group.today=Сьогодні
|
||||
history.group.yesterday=Вчора
|
||||
history.group.week=Цього тижня
|
||||
history.group.month=Цього місяця
|
||||
history.group.older=Старіше
|
||||
history.time.moments=Щойно
|
||||
history.time.minutes={0} хв тому
|
||||
history.time.hours={0} год тому
|
||||
history.time.days={0} дн тому
|
||||
history.time.months={0} міс тому
|
||||
history.time.years={0} р тому
|
||||
history.error.local=Помилка завантаження локальної історії
|
||||
history.error.cloud=Помилка завантаження хмарної історії
|
||||
history.error.local.delete=Помилка видалення сесії
|
||||
history.error.local.rename=Помилка перейменування сесії
|
||||
history.error.cloud.delete=Хмарні сесії поки не можна видалити
|
||||
|
||||
action.Kilo.Settings.text=Налаштування
|
||||
action.Kilo.Settings.description=Налаштування Kilo Code
|
||||
action.Kilo.NewSession.text=Нова сесія
|
||||
action.Kilo.NewSession.description=Розпочати нову сесію Kilo
|
||||
action.Kilo.History.text=Історія
|
||||
action.Kilo.History.description=Показати історію сесій
|
||||
action.Kilo.SendPrompt.text=Надіслати запит
|
||||
action.Kilo.SendPrompt.description=Надіслати поточний запит Kilo
|
||||
action.Kilo.StopSession.text=Зупинити сесію
|
||||
action.Kilo.StopSession.description=Зупинити поточну сесію Kilo
|
||||
action.Kilo.SettingsGroup.text=Налаштування
|
||||
action.Kilo.SettingsGroup.description=Налаштування Kilo Code
|
||||
action.Kilo.Restart.text=Перезапустити Kilo
|
||||
action.Kilo.Restart.description=Завершити і перезапустити CLI-процес
|
||||
action.Kilo.Reinstall.text=Перевстановити Kilo
|
||||
action.Kilo.Reinstall.description=Перерозпакувати CLI-бінарний файл і перезапустити
|
||||
action.Kilo.Session.Open.text=Відкрити
|
||||
action.Kilo.Session.Open.description=Відкрити вибрану сесію
|
||||
action.Kilo.Session.Rename.text=Перейменувати
|
||||
action.Kilo.Session.Rename.description=Перейменувати вибрану сесію
|
||||
action.Kilo.Session.Delete.text=Видалити
|
||||
action.Kilo.Session.Delete.description=Видалити вибрану(-і) сесію(-ї)
|
||||
action.Kilo.History.ContextMenu.text=Дії з історією
|
||||
+139
@@ -0,0 +1,139 @@
|
||||
session.connection.connecting=加载中…
|
||||
session.connection.error.app=连接失败
|
||||
session.connection.error.workspace=工作区加载失败
|
||||
session.connection.error.unknown=未知错误
|
||||
session.connection.retry=重试
|
||||
session.connection.warning.config=配置警告
|
||||
|
||||
session.empty.welcome=Kilo Code 是一个 AI 编程助手。可请它构建功能、修复错误或解释您的代码库。
|
||||
session.empty.loading=加载中…
|
||||
session.empty.recent=最近
|
||||
session.showHistory=显示历史
|
||||
session.scroll.bottom=滚动到底部
|
||||
session.tab.new=新建会话
|
||||
session.tab.untitled=无标题会话
|
||||
|
||||
session.permission.title=权限请求
|
||||
session.permission.meta=工具:{0} • 模式:{1}
|
||||
session.permission.allow=允许
|
||||
session.permission.deny=拒绝
|
||||
session.question.dismiss=关闭
|
||||
|
||||
session.status.considering=正在考虑下一步…
|
||||
session.status.thinking=思考中…
|
||||
session.status.writing=正在编写回复…
|
||||
session.status.delegating=正在委派工作…
|
||||
session.status.planning=规划中…
|
||||
session.status.gathering=收集上下文…
|
||||
session.status.searching.codebase=搜索代码库…
|
||||
session.status.searching.web=搜索网页…
|
||||
session.status.editing=正在编辑…
|
||||
session.status.commands=运行命令…
|
||||
|
||||
session.part.reasoning=推理
|
||||
session.part.compaction=上下文已压缩
|
||||
session.part.tool.copy=复制
|
||||
session.part.tool.error=错误
|
||||
session.part.tool.pending=待处理
|
||||
session.part.tool.read=读取
|
||||
session.part.tool.running=运行中
|
||||
session.part.tool.shell=Shell
|
||||
session.part.tool.truncated=预览中的输出已截断。完整输出仍保留在会话数据中。
|
||||
|
||||
session.error.prompt=提示失败
|
||||
session.error.compact=会话压缩失败
|
||||
session.error.unknown=未知错误
|
||||
|
||||
session.header.tokens=令牌
|
||||
session.header.tokens.description=最近一次助手回复中使用的令牌:输入、输出、缓存写入和缓存读取。
|
||||
session.header.input=输入 {0}
|
||||
session.header.output=输出 {0}
|
||||
session.header.cache.write=缓存写入 {0}
|
||||
session.header.cache.read=缓存读取 {0}
|
||||
session.header.compact=压缩会话
|
||||
session.header.compact.description=汇总会话以减少上下文大小
|
||||
session.header.expand=显示会话指标
|
||||
session.header.collapse=隐藏会话指标
|
||||
session.header.todos.progress={0}/{1} 个任务已完成
|
||||
session.header.todos.done=全部 {0} 个任务已完成
|
||||
session.header.context.tooltip.percent={0} 令牌(上下文的 {1}%)
|
||||
session.header.context.tooltip.tokens={0} 令牌
|
||||
session.header.context.used=已使用 {0} / {1} 令牌
|
||||
session.header.context.reserved={0} 为输出预留
|
||||
session.header.context.available={0} 可用
|
||||
|
||||
prompt.placeholder=输入消息…
|
||||
prompt.placeholder.with.shortcuts=输入消息…({0} 发送,{1} 换行)
|
||||
prompt.placeholder.with.send=输入消息…({0} 发送)
|
||||
prompt.placeholder.with.newline=输入消息…({0} 换行)
|
||||
prompt.button.send=发送
|
||||
prompt.button.stop=停止
|
||||
prompt.button.send.tooltip.stop=按 {0} 停止
|
||||
mode.picker.tooltip=选择模式
|
||||
mode.picker.deprecated=已弃用
|
||||
model.picker.tooltip=选择模型
|
||||
model.picker.recommended=推荐
|
||||
model.picker.favorites=收藏夹
|
||||
model.picker.search=搜索模型
|
||||
model.picker.no.matches=无匹配模型
|
||||
model.picker.favorite.add=添加到收藏夹
|
||||
model.picker.favorite.remove=从收藏夹移除
|
||||
model.picker.free=免费
|
||||
model.picker.reset=重置模型为默认
|
||||
reasoning.picker.tooltip=选择推理力度
|
||||
|
||||
history.tab.local=本地
|
||||
history.tab.cloud=云端
|
||||
history.back=返回
|
||||
history.search.placeholder=搜索会话
|
||||
history.empty=暂无会话
|
||||
history.loading=加载中…
|
||||
history.untitled=无标题
|
||||
history.delete.text=删除
|
||||
history.delete.confirm.title=删除会话?
|
||||
history.delete.confirm.message=从本地历史中删除“{0}”?
|
||||
history.delete.confirm.message.multiple=从本地历史中删除 {0} 个会话?
|
||||
history.rename.title=重命名会话
|
||||
history.rename.prompt=新会话名称:
|
||||
history.cloud.load.more=加载更多
|
||||
history.cloud.repo.only=仅此仓库
|
||||
history.group.today=今天
|
||||
history.group.yesterday=昨天
|
||||
history.group.week=本周
|
||||
history.group.month=本月
|
||||
history.group.older=更早以前
|
||||
history.time.moments=刚刚
|
||||
history.time.minutes={0} 分钟前
|
||||
history.time.hours={0} 小时前
|
||||
history.time.days={0} 天前
|
||||
history.time.months={0} 个月前
|
||||
history.time.years={0} 年前
|
||||
history.error.local=无法加载本地历史
|
||||
history.error.cloud=无法加载云端历史
|
||||
history.error.local.delete=删除会话失败
|
||||
history.error.local.rename=重命名会话失败
|
||||
history.error.cloud.delete=云端会话暂无法删除
|
||||
|
||||
action.Kilo.Settings.text=设置
|
||||
action.Kilo.Settings.description=Kilo Code 设置
|
||||
action.Kilo.NewSession.text=新建会话
|
||||
action.Kilo.NewSession.description=开始新的 Kilo 会话
|
||||
action.Kilo.History.text=历史
|
||||
action.Kilo.History.description=显示会话历史
|
||||
action.Kilo.SendPrompt.text=发送提示
|
||||
action.Kilo.SendPrompt.description=发送当前 Kilo 提示
|
||||
action.Kilo.StopSession.text=停止会话
|
||||
action.Kilo.StopSession.description=停止当前 Kilo 会话
|
||||
action.Kilo.SettingsGroup.text=设置
|
||||
action.Kilo.SettingsGroup.description=Kilo Code 设置
|
||||
action.Kilo.Restart.text=重启 Kilo
|
||||
action.Kilo.Restart.description=终止并重启 CLI 进程
|
||||
action.Kilo.Reinstall.text=重新安装 Kilo
|
||||
action.Kilo.Reinstall.description=重新解压 CLI 二进制文件并重启
|
||||
action.Kilo.Session.Open.text=打开
|
||||
action.Kilo.Session.Open.description=打开所选会话
|
||||
action.Kilo.Session.Rename.text=重命名
|
||||
action.Kilo.Session.Rename.description=重命名所选会话
|
||||
action.Kilo.Session.Delete.text=删除
|
||||
action.Kilo.Session.Delete.description=删除所选会话
|
||||
action.Kilo.History.ContextMenu.text=历史操作
|
||||
+139
@@ -0,0 +1,139 @@
|
||||
session.connection.connecting=載入中…
|
||||
session.connection.error.app=連線失敗
|
||||
session.connection.error.workspace=工作區載入失敗
|
||||
session.connection.error.unknown=未知預設
|
||||
session.connection.retry=重試
|
||||
session.connection.warning.config=設定警告
|
||||
|
||||
session.empty.welcome=Kilo Code 是 AI 程式輔助。可請它建置功能、修復錯誤或解釋您的程式程式庫。
|
||||
session.empty.loading=載入中…
|
||||
session.empty.recent=最近
|
||||
session.showHistory=顯示歷史
|
||||
session.scroll.bottom=滾動到底部
|
||||
session.tab.new=新建工作階段
|
||||
session.tab.untitled=未命名的工作階段
|
||||
|
||||
session.permission.title=權限請求
|
||||
session.permission.meta=工具:{0} • 樣式:{1}
|
||||
session.permission.allow=允許
|
||||
session.permission.deny=拒絕
|
||||
session.question.dismiss=關閉
|
||||
|
||||
session.status.considering=考慮下一步…
|
||||
session.status.thinking=思考中…
|
||||
session.status.writing=正在撰寫回覆…
|
||||
session.status.delegating=正在委派工作…
|
||||
session.status.planning=計劃中…
|
||||
session.status.gathering=收集上下文…
|
||||
session.status.searching.codebase=搜尋程式程式庫…
|
||||
session.status.searching.web=搜尋網頁…
|
||||
session.status.editing=進行編輯…
|
||||
session.status.commands=執行指令…
|
||||
|
||||
session.part.reasoning=推理
|
||||
session.part.compaction=上下文已壓縮
|
||||
session.part.tool.copy=複製
|
||||
session.part.tool.error=錯誤
|
||||
session.part.tool.pending=待處理
|
||||
session.part.tool.read=讀取
|
||||
session.part.tool.running=執行中
|
||||
session.part.tool.shell=Shell
|
||||
session.part.tool.truncated=預覽中的輸出已截斷。完整輸出仍保留在工作階段資料中。
|
||||
|
||||
session.error.prompt=提示失敗
|
||||
session.error.compact=工作階段壓縮失敗
|
||||
session.error.unknown=未知錯誤
|
||||
|
||||
session.header.tokens=記號
|
||||
session.header.tokens.description=最新助手回覆使用的記號:輸入、輸出、快取寫入和快取讀取。
|
||||
session.header.input=輸入 {0}
|
||||
session.header.output=輸出 {0}
|
||||
session.header.cache.write=快取寫入 {0}
|
||||
session.header.cache.read=快取讀取 {0}
|
||||
session.header.compact=壓縮工作階段
|
||||
session.header.compact.description=摘要工作階段以減少上下文大小
|
||||
session.header.expand=顯示工作階段度量
|
||||
session.header.collapse=隱藏工作階段度量
|
||||
session.header.todos.progress={0}/{1} 個待辦事項已完成
|
||||
session.header.todos.done=全部 {0} 個待辦事項已完成
|
||||
session.header.context.tooltip.percent={0} 記號(上下文的 {1}%)
|
||||
session.header.context.tooltip.tokens={0} 記號
|
||||
session.header.context.used=已使用 {0} / {1} 記號
|
||||
session.header.context.reserved={0} 為輸出預留
|
||||
session.header.context.available={0} 可用
|
||||
|
||||
prompt.placeholder=輸入訊息…
|
||||
prompt.placeholder.with.shortcuts=輸入訊息…({0} 發送,{1} 換行)
|
||||
prompt.placeholder.with.send=輸入訊息…({0} 發送)
|
||||
prompt.placeholder.with.newline=輸入訊息…({0} 換行)
|
||||
prompt.button.send=發送
|
||||
prompt.button.stop=停止
|
||||
prompt.button.send.tooltip.stop=按 {0} 停止
|
||||
mode.picker.tooltip=選擇模式
|
||||
mode.picker.deprecated=已棄用
|
||||
model.picker.tooltip=選擇模型
|
||||
model.picker.recommended=推薦
|
||||
model.picker.favorites=收藏夾
|
||||
model.picker.search=搜尋模型
|
||||
model.picker.no.matches=無匹配模型
|
||||
model.picker.favorite.add=新增至收藏夾
|
||||
model.picker.favorite.remove=從收藏夾移除
|
||||
model.picker.free=免費
|
||||
model.picker.reset=重設模型為預設
|
||||
reasoning.picker.tooltip=選擇推理力度
|
||||
|
||||
history.tab.local=本機
|
||||
history.tab.cloud=雲端
|
||||
history.back=返回
|
||||
history.search.placeholder=搜尋工作階段
|
||||
history.empty=無工作階段
|
||||
history.loading=載入中…
|
||||
history.untitled=未命名
|
||||
history.delete.text=刪除
|
||||
history.delete.confirm.title=刪除工作階段?
|
||||
history.delete.confirm.message=從本機歷史中刪除「{0}」?
|
||||
history.delete.confirm.message.multiple=從本機歷史中刪除 {0} 個工作階段?
|
||||
history.rename.title=重命名工作階段
|
||||
history.rename.prompt=新工作階段名稱:
|
||||
history.cloud.load.more=載入更多
|
||||
history.cloud.repo.only=僅此儲存庫
|
||||
history.group.today=今天
|
||||
history.group.yesterday=昨天
|
||||
history.group.week=本週
|
||||
history.group.month=本月
|
||||
history.group.older=更早以前
|
||||
history.time.moments=剛剛
|
||||
history.time.minutes={0} 分鐘前
|
||||
history.time.hours={0} 小時前
|
||||
history.time.days={0} 天前
|
||||
history.time.months={0} 個月前
|
||||
history.time.years={0} 年前
|
||||
history.error.local=無法載入本機歷史
|
||||
history.error.cloud=無法載入雲端歷史
|
||||
history.error.local.delete=刪除工作階段失敗
|
||||
history.error.local.rename=重命名工作階段失敗
|
||||
history.error.cloud.delete=雲端工作階段暫時無法刪除
|
||||
|
||||
action.Kilo.Settings.text=設定
|
||||
action.Kilo.Settings.description=Kilo Code 設定
|
||||
action.Kilo.NewSession.text=新建工作階段
|
||||
action.Kilo.NewSession.description=開始新的 Kilo 工作階段
|
||||
action.Kilo.History.text=歷史
|
||||
action.Kilo.History.description=顯示工作階段歷史
|
||||
action.Kilo.SendPrompt.text=發送提示
|
||||
action.Kilo.SendPrompt.description=發送目前的 Kilo 提示
|
||||
action.Kilo.StopSession.text=停止工作階段
|
||||
action.Kilo.StopSession.description=停止目前的 Kilo 工作階段
|
||||
action.Kilo.SettingsGroup.text=設定
|
||||
action.Kilo.SettingsGroup.description=Kilo Code 設定
|
||||
action.Kilo.Restart.text=重啟 Kilo
|
||||
action.Kilo.Restart.description=終止並重啟 CLI 程序
|
||||
action.Kilo.Reinstall.text=重新安裝 Kilo
|
||||
action.Kilo.Reinstall.description=重新解壓縮 CLI 二進位檔案並重啟
|
||||
action.Kilo.Session.Open.text=開啟
|
||||
action.Kilo.Session.Open.description=開啟所選工作階段
|
||||
action.Kilo.Session.Rename.text=重命名
|
||||
action.Kilo.Session.Rename.description=重命名所選工作階段
|
||||
action.Kilo.Session.Delete.text=刪除
|
||||
action.Kilo.Session.Delete.description=刪除所選工作階段
|
||||
action.Kilo.History.ContextMenu.text=歷史操作
|
||||
+488
@@ -0,0 +1,488 @@
|
||||
package ai.kilocode.client.actions
|
||||
|
||||
import ai.kilocode.client.app.KiloSessionService
|
||||
import ai.kilocode.client.app.KiloWorkspaceService
|
||||
import ai.kilocode.client.app.Workspace
|
||||
import ai.kilocode.client.plugin.KiloBundle
|
||||
import ai.kilocode.client.session.SessionManager
|
||||
import ai.kilocode.client.session.SessionRef
|
||||
import ai.kilocode.client.session.history.CloudHistoryItem
|
||||
import ai.kilocode.client.session.history.HistoryController
|
||||
import ai.kilocode.client.session.history.HistoryDataKeys
|
||||
import ai.kilocode.client.session.history.HistorySelection
|
||||
import ai.kilocode.client.session.history.HistorySource
|
||||
import ai.kilocode.client.session.history.LocalHistoryItem
|
||||
import ai.kilocode.client.testing.FakeSessionRpcApi
|
||||
import ai.kilocode.client.testing.FakeWorkspaceRpcApi
|
||||
import ai.kilocode.rpc.dto.CloudSessionDto
|
||||
import ai.kilocode.rpc.dto.KiloWorkspaceStateDto
|
||||
import ai.kilocode.rpc.dto.KiloWorkspaceStatusDto
|
||||
import ai.kilocode.rpc.dto.SessionDto
|
||||
import ai.kilocode.rpc.dto.SessionTimeDto
|
||||
import com.intellij.openapi.actionSystem.AnActionEvent
|
||||
import com.intellij.openapi.actionSystem.CommonDataKeys
|
||||
import com.intellij.openapi.actionSystem.DataContext
|
||||
import com.intellij.openapi.actionSystem.Presentation
|
||||
import com.intellij.openapi.application.ApplicationManager
|
||||
import com.intellij.openapi.util.Disposer
|
||||
import com.intellij.testFramework.fixtures.BasePlatformTestCase
|
||||
import com.intellij.util.ui.UIUtil
|
||||
import kotlinx.coroutines.CoroutineScope
|
||||
import kotlinx.coroutines.SupervisorJob
|
||||
import kotlinx.coroutines.cancel
|
||||
import kotlinx.coroutines.delay
|
||||
import kotlinx.coroutines.runBlocking
|
||||
import kotlinx.coroutines.withTimeout
|
||||
|
||||
@Suppress("UnstableApiUsage")
|
||||
class HistorySessionActionsTest : BasePlatformTestCase() {
|
||||
private lateinit var scope: CoroutineScope
|
||||
private lateinit var rpc: FakeSessionRpcApi
|
||||
private lateinit var sessions: KiloSessionService
|
||||
private lateinit var workspace: Workspace
|
||||
private lateinit var controller: HistoryController
|
||||
private lateinit var manager: FakeManager
|
||||
|
||||
override fun setUp() {
|
||||
super.setUp()
|
||||
scope = CoroutineScope(SupervisorJob())
|
||||
rpc = FakeSessionRpcApi()
|
||||
sessions = KiloSessionService(project, scope, rpc)
|
||||
val workspaces = KiloWorkspaceService(scope, FakeWorkspaceRpcApi().also {
|
||||
it.state.value = KiloWorkspaceStateDto(status = KiloWorkspaceStatusDto.READY)
|
||||
})
|
||||
workspace = workspaces.workspace("/test")
|
||||
controller = HistoryController(sessions, workspace, scope)
|
||||
manager = FakeManager()
|
||||
}
|
||||
|
||||
override fun tearDown() {
|
||||
try {
|
||||
scope.cancel()
|
||||
} finally {
|
||||
super.tearDown()
|
||||
}
|
||||
}
|
||||
|
||||
// ------ OpenSessionAction.update ------
|
||||
|
||||
fun `test open action enabled for single local selection`() {
|
||||
val action = OpenSessionAction()
|
||||
val local = localItem("ses_1")
|
||||
val event = event(action, manager, selection(HistorySource.LOCAL, listOf(local)), controller)
|
||||
|
||||
action.update(event)
|
||||
|
||||
assertTrue(event.presentation.isEnabledAndVisible)
|
||||
}
|
||||
|
||||
fun `test open action enabled for single cloud selection`() {
|
||||
val action = OpenSessionAction()
|
||||
val item = cloudItem("cloud_1")
|
||||
val event = event(action, manager, selection(HistorySource.CLOUD, emptyList(), listOf(item)), controller)
|
||||
|
||||
action.update(event)
|
||||
|
||||
assertTrue(event.presentation.isEnabledAndVisible)
|
||||
}
|
||||
|
||||
fun `test open action disabled without manager`() {
|
||||
val action = OpenSessionAction()
|
||||
val local = localItem("ses_1")
|
||||
val event = event(action, null, selection(HistorySource.LOCAL, listOf(local)), controller)
|
||||
|
||||
action.update(event)
|
||||
|
||||
assertFalse(event.presentation.isEnabledAndVisible)
|
||||
}
|
||||
|
||||
fun `test open action disabled with no selection`() {
|
||||
val action = OpenSessionAction()
|
||||
val event = event(action, manager, selection(HistorySource.LOCAL, emptyList()), controller)
|
||||
|
||||
action.update(event)
|
||||
|
||||
assertFalse(event.presentation.isEnabledAndVisible)
|
||||
}
|
||||
|
||||
fun `test open action disabled with multiple local items`() {
|
||||
val action = OpenSessionAction()
|
||||
val items = listOf(localItem("ses_1"), localItem("ses_2"))
|
||||
val event = event(action, manager, selection(HistorySource.LOCAL, items), controller)
|
||||
|
||||
action.update(event)
|
||||
|
||||
assertFalse(event.presentation.isEnabledAndVisible)
|
||||
}
|
||||
|
||||
// ------ OpenSessionAction.actionPerformed ------
|
||||
|
||||
fun `test open action performs opens local item`() {
|
||||
val opened = mutableListOf<String>()
|
||||
val ctrl = HistoryController(sessions, workspace, scope, open = { ref ->
|
||||
when (ref) {
|
||||
is SessionRef.Local -> opened.add(ref.id)
|
||||
is SessionRef.Cloud -> opened.add("cloud:${ref.id}")
|
||||
}
|
||||
})
|
||||
val local = localItem("ses_1")
|
||||
val action = OpenSessionAction()
|
||||
val event = event(action, manager, selection(HistorySource.LOCAL, listOf(local)), ctrl)
|
||||
|
||||
action.actionPerformed(event)
|
||||
flush()
|
||||
|
||||
assertEquals(listOf("ses_1"), opened)
|
||||
}
|
||||
|
||||
fun `test open action performs opens cloud item`() {
|
||||
val opened = mutableListOf<String>()
|
||||
val ctrl = HistoryController(sessions, workspace, scope, open = { ref ->
|
||||
when (ref) {
|
||||
is SessionRef.Local -> opened.add(ref.id)
|
||||
is SessionRef.Cloud -> opened.add("cloud:${ref.id}")
|
||||
}
|
||||
})
|
||||
val item = cloudItem("cloud_1")
|
||||
val action = OpenSessionAction()
|
||||
val event = event(action, manager, selection(HistorySource.CLOUD, emptyList(), listOf(item)), ctrl)
|
||||
|
||||
action.actionPerformed(event)
|
||||
flush()
|
||||
|
||||
assertEquals(listOf("cloud:cloud_1"), opened)
|
||||
}
|
||||
|
||||
// ------ DeleteSessionAction.update ------
|
||||
|
||||
fun `test delete action enabled for non-empty local selection`() {
|
||||
val action = DeleteSessionAction()
|
||||
val local = localItem("ses_1")
|
||||
val event = event(action, manager, selection(HistorySource.LOCAL, listOf(local)), controller)
|
||||
|
||||
action.update(event)
|
||||
|
||||
assertTrue(event.presentation.isEnabledAndVisible)
|
||||
}
|
||||
|
||||
fun `test delete action disabled for cloud-only selection`() {
|
||||
val action = DeleteSessionAction()
|
||||
val item = cloudItem("cloud_1")
|
||||
val event = event(action, manager, selection(HistorySource.CLOUD, emptyList(), listOf(item)), controller)
|
||||
|
||||
action.update(event)
|
||||
|
||||
assertFalse(event.presentation.isEnabledAndVisible)
|
||||
}
|
||||
|
||||
fun `test delete action disabled without manager`() {
|
||||
val action = DeleteSessionAction()
|
||||
val local = localItem("ses_1")
|
||||
val event = event(action, null, selection(HistorySource.LOCAL, listOf(local)), controller)
|
||||
|
||||
action.update(event)
|
||||
|
||||
assertFalse(event.presentation.isEnabledAndVisible)
|
||||
}
|
||||
|
||||
fun `test delete action disabled with empty selection`() {
|
||||
val action = DeleteSessionAction()
|
||||
val event = event(action, manager, selection(HistorySource.LOCAL, emptyList()), controller)
|
||||
|
||||
action.update(event)
|
||||
|
||||
assertFalse(event.presentation.isEnabledAndVisible)
|
||||
}
|
||||
|
||||
// ------ DeleteSessionAction.actionPerformed ------
|
||||
|
||||
fun `test delete action deletes selected local items after confirmation`() {
|
||||
rpc.listed += sessionDto("ses_1", "One")
|
||||
rpc.listed += sessionDto("ses_2", "Two")
|
||||
controller.reloadLocal()
|
||||
flush()
|
||||
|
||||
assertEquals(2, controller.local.items.size)
|
||||
|
||||
val items = controller.local.items.toList()
|
||||
val action = DeleteSessionAction().apply { confirm = { _, _ -> true } }
|
||||
val event = event(action, manager, selection(HistorySource.LOCAL, items), controller)
|
||||
|
||||
action.actionPerformed(event)
|
||||
flush()
|
||||
|
||||
assertEquals(listOf("ses_1", "ses_2"), rpc.deletes.map { it.first })
|
||||
assertTrue(controller.local.items.isEmpty())
|
||||
}
|
||||
|
||||
fun `test delete action skips items already being deleted`() {
|
||||
rpc.listed += sessionDto("ses_1", "One")
|
||||
controller.reloadLocal()
|
||||
flush()
|
||||
|
||||
rpc.deleteGate = kotlinx.coroutines.CompletableDeferred()
|
||||
val item = controller.local.items[0]
|
||||
controller.delete(item)
|
||||
waitFor { controller.deleting(item) }
|
||||
|
||||
val action = DeleteSessionAction().apply { confirm = { _, _ -> true } }
|
||||
val event = event(action, manager, selection(HistorySource.LOCAL, listOf(item)), controller)
|
||||
|
||||
action.actionPerformed(event)
|
||||
flush()
|
||||
|
||||
assertTrue(rpc.deletes.isEmpty())
|
||||
|
||||
rpc.deleteGate?.complete(Unit)
|
||||
waitFor { rpc.deletes.size == 1 }
|
||||
assertEquals(listOf("ses_1"), rpc.deletes.map { it.first })
|
||||
}
|
||||
|
||||
fun `test delete action cancelled when user says no`() {
|
||||
rpc.listed += sessionDto("ses_1", "One")
|
||||
controller.reloadLocal()
|
||||
flush()
|
||||
|
||||
val item = controller.local.items[0]
|
||||
val action = DeleteSessionAction().apply { confirm = { _, _ -> false } }
|
||||
val event = event(action, manager, selection(HistorySource.LOCAL, listOf(item)), controller)
|
||||
|
||||
action.actionPerformed(event)
|
||||
flush()
|
||||
|
||||
assertTrue(rpc.deletes.isEmpty())
|
||||
}
|
||||
|
||||
// ------ RenameSessionAction.update ------
|
||||
|
||||
fun `test rename action enabled for exactly one local item`() {
|
||||
val action = RenameSessionAction()
|
||||
val local = localItem("ses_1")
|
||||
val event = event(action, manager, selection(HistorySource.LOCAL, listOf(local)), controller)
|
||||
|
||||
action.update(event)
|
||||
|
||||
assertTrue(event.presentation.isEnabledAndVisible)
|
||||
}
|
||||
|
||||
fun `test rename action disabled with no selection`() {
|
||||
val action = RenameSessionAction()
|
||||
val event = event(action, manager, selection(HistorySource.LOCAL, emptyList()), controller)
|
||||
|
||||
action.update(event)
|
||||
|
||||
assertFalse(event.presentation.isEnabledAndVisible)
|
||||
}
|
||||
|
||||
fun `test rename action disabled with multiple local items`() {
|
||||
val action = RenameSessionAction()
|
||||
val items = listOf(localItem("ses_1"), localItem("ses_2"))
|
||||
val event = event(action, manager, selection(HistorySource.LOCAL, items), controller)
|
||||
|
||||
action.update(event)
|
||||
|
||||
assertFalse(event.presentation.isEnabledAndVisible)
|
||||
}
|
||||
|
||||
fun `test rename action disabled for cloud selection`() {
|
||||
val action = RenameSessionAction()
|
||||
val item = cloudItem("cloud_1")
|
||||
val event = event(action, manager, selection(HistorySource.CLOUD, emptyList(), listOf(item)), controller)
|
||||
|
||||
action.update(event)
|
||||
|
||||
assertFalse(event.presentation.isEnabledAndVisible)
|
||||
}
|
||||
|
||||
// ------ RenameSessionAction.actionPerformed ------
|
||||
|
||||
fun `test rename action calls controller with trimmed changed title`() {
|
||||
rpc.listed += sessionDto("ses_1", "Original")
|
||||
controller.reloadLocal()
|
||||
flush()
|
||||
|
||||
val item = controller.local.items[0]
|
||||
val action = RenameSessionAction().apply { input = { _, _ -> " Renamed " } }
|
||||
val event = event(action, manager, selection(HistorySource.LOCAL, listOf(item)), controller)
|
||||
|
||||
action.actionPerformed(event)
|
||||
flush()
|
||||
|
||||
assertEquals(listOf(Triple("ses_1", "/test", "Renamed")), rpc.renames)
|
||||
}
|
||||
|
||||
fun `test rename action passes displayed current title to input`() {
|
||||
rpc.listed += sessionDto("ses_1", "Original")
|
||||
controller.reloadLocal()
|
||||
flush()
|
||||
|
||||
val prompts = mutableListOf<String>()
|
||||
val item = controller.local.items[0]
|
||||
val action = RenameSessionAction().apply {
|
||||
input = { _, current ->
|
||||
prompts.add(current)
|
||||
null
|
||||
}
|
||||
}
|
||||
val event = event(action, manager, selection(HistorySource.LOCAL, listOf(item)), controller)
|
||||
|
||||
action.actionPerformed(event)
|
||||
flush()
|
||||
|
||||
assertEquals(listOf("Original"), prompts)
|
||||
assertTrue(rpc.renames.isEmpty())
|
||||
}
|
||||
|
||||
fun `test rename action passes untitled fallback to input`() {
|
||||
rpc.listed += sessionDto("ses_1", "")
|
||||
controller.reloadLocal()
|
||||
flush()
|
||||
|
||||
val prompts = mutableListOf<String>()
|
||||
val item = controller.local.items[0]
|
||||
val action = RenameSessionAction().apply {
|
||||
input = { _, current ->
|
||||
prompts.add(current)
|
||||
null
|
||||
}
|
||||
}
|
||||
val event = event(action, manager, selection(HistorySource.LOCAL, listOf(item)), controller)
|
||||
|
||||
action.actionPerformed(event)
|
||||
flush()
|
||||
|
||||
assertEquals(listOf(KiloBundle.message("history.untitled")), prompts)
|
||||
assertTrue(rpc.renames.isEmpty())
|
||||
}
|
||||
|
||||
fun `test rename action ignores blank input`() {
|
||||
rpc.listed += sessionDto("ses_1", "Original")
|
||||
controller.reloadLocal()
|
||||
flush()
|
||||
|
||||
val item = controller.local.items[0]
|
||||
val action = RenameSessionAction().apply { input = { _, _ -> " " } }
|
||||
val event = event(action, manager, selection(HistorySource.LOCAL, listOf(item)), controller)
|
||||
|
||||
action.actionPerformed(event)
|
||||
flush()
|
||||
|
||||
assertTrue(rpc.renames.isEmpty())
|
||||
}
|
||||
|
||||
fun `test rename action ignores unchanged input`() {
|
||||
rpc.listed += sessionDto("ses_1", "Original")
|
||||
controller.reloadLocal()
|
||||
flush()
|
||||
|
||||
val item = controller.local.items[0]
|
||||
val action = RenameSessionAction().apply { input = { _, _ -> "Original" } }
|
||||
val event = event(action, manager, selection(HistorySource.LOCAL, listOf(item)), controller)
|
||||
|
||||
action.actionPerformed(event)
|
||||
flush()
|
||||
|
||||
assertTrue(rpc.renames.isEmpty())
|
||||
}
|
||||
|
||||
fun `test rename action ignores null input`() {
|
||||
rpc.listed += sessionDto("ses_1", "Original")
|
||||
controller.reloadLocal()
|
||||
flush()
|
||||
|
||||
val item = controller.local.items[0]
|
||||
val action = RenameSessionAction().apply { input = { _, _ -> null } }
|
||||
val event = event(action, manager, selection(HistorySource.LOCAL, listOf(item)), controller)
|
||||
|
||||
action.actionPerformed(event)
|
||||
flush()
|
||||
|
||||
assertTrue(rpc.renames.isEmpty())
|
||||
}
|
||||
|
||||
fun `test frontend descriptor registers history actions`() {
|
||||
val xml = javaClass.classLoader.getResourceAsStream("kilo.jetbrains.frontend.xml")
|
||||
?.bufferedReader()
|
||||
?.use { it.readText() }
|
||||
?: error("missing frontend descriptor")
|
||||
|
||||
assertTrue(xml.contains("id=\"Kilo.Session.Open\""))
|
||||
assertTrue(xml.contains("id=\"Kilo.Session.Rename\""))
|
||||
assertTrue(xml.contains("id=\"Kilo.Session.Delete\""))
|
||||
assertTrue(xml.contains("id=\"Kilo.History.ContextMenu\""))
|
||||
assertTrue(xml.contains("ref=\"Kilo.Session.Open\""))
|
||||
assertTrue(xml.contains("ref=\"Kilo.Session.Rename\""))
|
||||
assertTrue(xml.contains("ref=\"Kilo.Session.Delete\""))
|
||||
}
|
||||
|
||||
// ------ Helpers ------
|
||||
|
||||
private fun event(
|
||||
action: com.intellij.openapi.actionSystem.AnAction,
|
||||
manager: SessionManager?,
|
||||
selection: HistorySelection,
|
||||
ctrl: HistoryController,
|
||||
): AnActionEvent {
|
||||
val presentation = Presentation().apply { copyFrom(action.templatePresentation) }
|
||||
val context = DataContext { id ->
|
||||
when {
|
||||
CommonDataKeys.PROJECT.`is`(id) -> project
|
||||
SessionManager.KEY.`is`(id) -> manager
|
||||
HistoryDataKeys.SELECTION.`is`(id) -> selection
|
||||
HistoryDataKeys.CONTROLLER.`is`(id) -> ctrl
|
||||
else -> null
|
||||
}
|
||||
}
|
||||
return AnActionEvent.createFromDataContext("", presentation, context)
|
||||
}
|
||||
|
||||
private fun selection(
|
||||
source: HistorySource,
|
||||
local: List<LocalHistoryItem>,
|
||||
cloud: List<CloudHistoryItem> = emptyList(),
|
||||
) = HistorySelection(source, local, cloud)
|
||||
|
||||
private fun localItem(id: String, title: String = id) = LocalHistoryItem(sessionDto(id, title))
|
||||
|
||||
private fun sessionDto(id: String, title: String) = SessionDto(
|
||||
id = id,
|
||||
projectID = "prj",
|
||||
directory = "/test",
|
||||
title = title,
|
||||
version = "1",
|
||||
time = SessionTimeDto(created = 1.0, updated = 2.0),
|
||||
)
|
||||
|
||||
private fun cloudItem(id: String, title: String = id) = CloudHistoryItem(
|
||||
CloudSessionDto(
|
||||
id = id,
|
||||
title = title,
|
||||
createdAt = "2026-01-01T00:00:00Z",
|
||||
updatedAt = "2026-01-02T00:00:00Z",
|
||||
version = 1.0,
|
||||
)
|
||||
)
|
||||
|
||||
private fun flush() = runBlocking {
|
||||
repeat(10) {
|
||||
delay(100)
|
||||
ApplicationManager.getApplication().invokeAndWait { UIUtil.dispatchAllInvocationEvents() }
|
||||
}
|
||||
}
|
||||
|
||||
private fun waitFor(done: () -> Boolean) = runBlocking {
|
||||
withTimeout(5_000) {
|
||||
while (!done()) {
|
||||
delay(25)
|
||||
ApplicationManager.getApplication().invokeAndWait { UIUtil.dispatchAllInvocationEvents() }
|
||||
}
|
||||
}
|
||||
ApplicationManager.getApplication().invokeAndWait { UIUtil.dispatchAllInvocationEvents() }
|
||||
}
|
||||
|
||||
private class FakeManager : SessionManager {
|
||||
override fun newSession() {}
|
||||
override fun showHistory() {}
|
||||
override fun openSession(ref: SessionRef) {}
|
||||
}
|
||||
}
|
||||
+100
@@ -0,0 +1,100 @@
|
||||
package ai.kilocode.client.app
|
||||
|
||||
import ai.kilocode.client.testing.FakeSessionRpcApi
|
||||
import ai.kilocode.rpc.dto.SessionDto
|
||||
import ai.kilocode.rpc.dto.SessionTimeDto
|
||||
import com.intellij.testFramework.fixtures.BasePlatformTestCase
|
||||
import kotlinx.coroutines.CoroutineScope
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.SupervisorJob
|
||||
import kotlinx.coroutines.cancel
|
||||
import kotlinx.coroutines.runBlocking
|
||||
import kotlinx.coroutines.withContext
|
||||
|
||||
@Suppress("UnstableApiUsage")
|
||||
class KiloSessionServiceTest : BasePlatformTestCase() {
|
||||
private lateinit var scope: CoroutineScope
|
||||
private lateinit var rpc: FakeSessionRpcApi
|
||||
private lateinit var service: KiloSessionService
|
||||
|
||||
override fun setUp() {
|
||||
super.setUp()
|
||||
scope = CoroutineScope(SupervisorJob())
|
||||
rpc = FakeSessionRpcApi()
|
||||
service = KiloSessionService(project, scope, rpc)
|
||||
}
|
||||
|
||||
override fun tearDown() {
|
||||
try {
|
||||
scope.cancel()
|
||||
} finally {
|
||||
super.tearDown()
|
||||
}
|
||||
}
|
||||
|
||||
fun `test rename replaces cached session in sessions value`() = runBlocking(Dispatchers.Default) {
|
||||
rpc.listed += session("ses_1", "Original")
|
||||
rpc.listed += session("ses_2", "Other")
|
||||
service.list("/test")
|
||||
|
||||
assertEquals(2, service.sessions.value.size)
|
||||
assertEquals("Original", service.sessions.value.find { it.id == "ses_1" }!!.title)
|
||||
|
||||
service.renameSession("ses_1", "/test", "Renamed")
|
||||
|
||||
assertEquals(2, service.sessions.value.size)
|
||||
assertEquals("Renamed", service.sessions.value.find { it.id == "ses_1" }!!.title)
|
||||
assertEquals("Other", service.sessions.value.find { it.id == "ses_2" }!!.title)
|
||||
}
|
||||
|
||||
fun `test rename of unknown id does not insert new item`() = runBlocking(Dispatchers.Default) {
|
||||
rpc.listed += session("ses_1", "Original")
|
||||
service.list("/test")
|
||||
|
||||
assertEquals(1, service.sessions.value.size)
|
||||
|
||||
service.renameSession("ses_unknown", "/test", "Should Not Insert")
|
||||
|
||||
// Size remains 1 — no unexpected insert
|
||||
assertEquals(1, service.sessions.value.size)
|
||||
assertEquals("ses_1", service.sessions.value[0].id)
|
||||
}
|
||||
|
||||
fun `test rename failure propagates exception without mutating cache`() = runBlocking(Dispatchers.Default) {
|
||||
rpc.listed += session("ses_1", "Original")
|
||||
service.list("/test")
|
||||
|
||||
val before = service.sessions.value.toList()
|
||||
rpc.renameThrows = RuntimeException("server error")
|
||||
|
||||
var threw = false
|
||||
try {
|
||||
service.renameSession("ses_1", "/test", "Renamed")
|
||||
} catch (_: RuntimeException) {
|
||||
threw = true
|
||||
}
|
||||
|
||||
assertTrue(threw)
|
||||
assertEquals(before.map { it.id to it.title }, service.sessions.value.map { it.id to it.title })
|
||||
}
|
||||
|
||||
fun `test list populates sessions value`() = runBlocking(Dispatchers.Default) {
|
||||
rpc.listed += session("ses_1", "One")
|
||||
rpc.listed += session("ses_2", "Two")
|
||||
|
||||
service.list("/test")
|
||||
|
||||
assertEquals(2, service.sessions.value.size)
|
||||
assertTrue(service.sessions.value.any { it.id == "ses_1" })
|
||||
assertTrue(service.sessions.value.any { it.id == "ses_2" })
|
||||
}
|
||||
|
||||
private fun session(id: String, title: String) = SessionDto(
|
||||
id = id,
|
||||
projectID = "prj",
|
||||
directory = "/test",
|
||||
title = title,
|
||||
version = "1",
|
||||
time = SessionTimeDto(created = 1.0, updated = 2.0),
|
||||
)
|
||||
}
|
||||
+379
-10
@@ -4,6 +4,7 @@ import ai.kilocode.client.app.KiloSessionService
|
||||
import ai.kilocode.client.app.KiloWorkspaceService
|
||||
import ai.kilocode.client.app.Workspace
|
||||
import ai.kilocode.client.plugin.KiloBundle
|
||||
import ai.kilocode.client.session.SessionManager
|
||||
import ai.kilocode.client.session.SessionRef
|
||||
import ai.kilocode.client.testing.FakeSessionRpcApi
|
||||
import ai.kilocode.client.testing.FakeWorkspaceRpcApi
|
||||
@@ -26,6 +27,7 @@ import java.awt.Cursor
|
||||
import java.awt.event.KeyEvent
|
||||
import java.time.Instant
|
||||
import java.time.temporal.ChronoUnit
|
||||
import java.util.concurrent.atomic.AtomicInteger
|
||||
import javax.swing.JComponent
|
||||
import javax.swing.KeyStroke
|
||||
import javax.swing.event.ListDataEvent
|
||||
@@ -80,13 +82,13 @@ class HistoryControllerTest : BasePlatformTestCase() {
|
||||
rpc.cloudCursor = "next_1"
|
||||
val controller = controller()
|
||||
|
||||
controller.reloadCloud(gitUrl = "git@example.com:repo.git")
|
||||
controller.reloadCloud()
|
||||
flush()
|
||||
|
||||
assertEquals(1, controller.cloud.items.size)
|
||||
assertEquals("cloud_1", controller.cloud.items[0].id)
|
||||
assertEquals("next_1", controller.cloud.cursor)
|
||||
assertEquals(FakeSessionRpcApi.CloudCall("/test", null, 150, "git@example.com:repo.git"), rpc.cloudCalls[0])
|
||||
assertEquals(FakeSessionRpcApi.CloudCall("/test", null, 50, null), rpc.cloudCalls[0])
|
||||
|
||||
rpc.cloud.clear()
|
||||
rpc.cloud += cloud("cloud_2", "Cloud Two")
|
||||
@@ -95,7 +97,7 @@ class HistoryControllerTest : BasePlatformTestCase() {
|
||||
flush()
|
||||
|
||||
assertEquals(listOf("cloud_1", "cloud_2"), controller.cloud.items.map { it.id })
|
||||
assertEquals(FakeSessionRpcApi.CloudCall("/test", "next_1", 150, "git@example.com:repo.git"), rpc.cloudCalls[1])
|
||||
assertEquals(FakeSessionRpcApi.CloudCall("/test", "next_1", 50, null), rpc.cloudCalls[1])
|
||||
}
|
||||
|
||||
fun `test local delete calls rpc and removes item`() {
|
||||
@@ -197,7 +199,7 @@ class HistoryControllerTest : BasePlatformTestCase() {
|
||||
flush()
|
||||
|
||||
val local = panel.defaultFocusedComponent
|
||||
assertFalse(panel.listFocusable())
|
||||
assertTrue(panel.listFocusable())
|
||||
assertEquals(-1, panel.selectedIndex())
|
||||
key(local, KeyEvent.VK_DOWN)
|
||||
assertEquals(0, panel.selectedIndex())
|
||||
@@ -211,7 +213,7 @@ class HistoryControllerTest : BasePlatformTestCase() {
|
||||
|
||||
val cloud = panel.defaultFocusedComponent
|
||||
assertNotSame(local, cloud)
|
||||
assertFalse(panel.listFocusable())
|
||||
assertTrue(panel.listFocusable())
|
||||
assertEquals(-1, panel.selectedIndex())
|
||||
key(cloud, KeyEvent.VK_DOWN)
|
||||
assertEquals(0, panel.selectedIndex())
|
||||
@@ -297,21 +299,382 @@ class HistoryControllerTest : BasePlatformTestCase() {
|
||||
assertEquals(KiloBundle.message("history.time.hours", 10), HistoryTime.relative(offset, now.toEpochMilli()))
|
||||
}
|
||||
|
||||
fun `test local renderer exposes delete and cloud renderer hides it`() {
|
||||
rpc.listed += session("ses_1", "Local")
|
||||
rpc.cloud += cloud("cloud_1", "Cloud")
|
||||
fun `test history section boundaries are inclusive at 7 and 30 days`() {
|
||||
val now = Instant.now()
|
||||
val exactly7 = LocalHistoryItem(session("s7", "7d", now.minus(7, ChronoUnit.DAYS).toEpochMilli().toDouble()))
|
||||
val exactly30 = LocalHistoryItem(session("s30", "30d", now.minus(30, ChronoUnit.DAYS).toEpochMilli().toDouble()))
|
||||
val within7 = LocalHistoryItem(session("s6", "6d", now.minus(6, ChronoUnit.DAYS).toEpochMilli().toDouble()))
|
||||
val within30 = LocalHistoryItem(session("s29", "29d", now.minus(29, ChronoUnit.DAYS).toEpochMilli().toDouble()))
|
||||
val beyond30 = LocalHistoryItem(session("s31", "31d", now.minus(31, ChronoUnit.DAYS).toEpochMilli().toDouble()))
|
||||
|
||||
assertEquals(HistorySection.WEEK, HistoryTime.section(exactly7, now.toEpochMilli()))
|
||||
assertEquals(HistorySection.WEEK, HistoryTime.section(within7, now.toEpochMilli()))
|
||||
assertEquals(HistorySection.MONTH, HistoryTime.section(exactly30, now.toEpochMilli()))
|
||||
assertEquals(HistorySection.MONTH, HistoryTime.section(within30, now.toEpochMilli()))
|
||||
assertEquals(HistorySection.OLDER, HistoryTime.section(beyond30, now.toEpochMilli()))
|
||||
}
|
||||
|
||||
fun `test list is focusable and uses multiple interval selection`() {
|
||||
val panel = HistoryPanel(parent, controller())
|
||||
flush()
|
||||
|
||||
assertTrue(panel.deleteVisible(0))
|
||||
assertTrue(panel.listFocusable())
|
||||
assertEquals(javax.swing.ListSelectionModel.MULTIPLE_INTERVAL_SELECTION, panel.listSelectionMode())
|
||||
}
|
||||
|
||||
fun `test load more button is focusable`() {
|
||||
rpc.cloud += cloud("cloud_1", "Cloud")
|
||||
rpc.cloudCursor = "next"
|
||||
val controller = controller()
|
||||
val panel = HistoryPanel(parent, controller)
|
||||
flush()
|
||||
|
||||
assertTrue(panel.loadMoreFocusable())
|
||||
}
|
||||
|
||||
fun `test rename updates local item title`() {
|
||||
rpc.listed += session("ses_1", "Original")
|
||||
val controller = controller()
|
||||
flush()
|
||||
|
||||
controller.reloadLocal()
|
||||
flush()
|
||||
|
||||
val item = controller.local.items[0]
|
||||
controller.rename(item, "Renamed")
|
||||
flush()
|
||||
|
||||
assertEquals("Renamed", controller.local.items[0].title)
|
||||
assertEquals(listOf(Triple("ses_1", "/test", "Renamed")), rpc.renames)
|
||||
}
|
||||
|
||||
fun `test data context exposes selection and controller`() {
|
||||
rpc.listed += session("ses_1", "Alpha")
|
||||
val controller = controller()
|
||||
val panel = HistoryPanel(parent, controller)
|
||||
flush()
|
||||
|
||||
panel.select(0)
|
||||
|
||||
val sel = panel.getData(HistoryDataKeys.SELECTION.name) as? HistorySelection
|
||||
assertNotNull(sel)
|
||||
assertEquals(1, sel!!.selectedLocal.size)
|
||||
assertEquals("ses_1", sel.selectedLocal[0].id)
|
||||
|
||||
val ctrl = panel.getData(HistoryDataKeys.CONTROLLER.name)
|
||||
assertSame(controller, ctrl)
|
||||
}
|
||||
|
||||
// ------ Multi-selection and cloud selection data context ------
|
||||
|
||||
fun `test data context exposes local multi-selection`() {
|
||||
rpc.listed += session("ses_1", "Alpha")
|
||||
rpc.listed += session("ses_2", "Beta")
|
||||
val controller = controller()
|
||||
val panel = HistoryPanel(parent, controller)
|
||||
flush()
|
||||
|
||||
panel.selectIndices(0, 1)
|
||||
|
||||
val sel = panel.getData(HistoryDataKeys.SELECTION.name) as? HistorySelection
|
||||
assertNotNull(sel)
|
||||
assertEquals(HistorySource.LOCAL, sel!!.source)
|
||||
assertEquals(2, sel.selectedLocal.size)
|
||||
assertTrue(sel.selectedLocal.map { it.id }.containsAll(listOf("ses_1", "ses_2")))
|
||||
}
|
||||
|
||||
fun `test data context exposes cloud selection`() {
|
||||
rpc.cloud += cloud("cloud_1", "Cloud One")
|
||||
val controller = controller()
|
||||
val panel = HistoryPanel(parent, controller)
|
||||
flush()
|
||||
|
||||
panel.clickCloud()
|
||||
flush()
|
||||
assertFalse(panel.cloudDeleteVisible(0))
|
||||
panel.select(0)
|
||||
|
||||
val sel = panel.getData(HistoryDataKeys.SELECTION.name) as? HistorySelection
|
||||
assertNotNull(sel)
|
||||
assertEquals(HistorySource.CLOUD, sel!!.source)
|
||||
assertTrue(sel.selectedLocal.isEmpty())
|
||||
assertEquals(1, sel.cloudItems.size)
|
||||
assertEquals("cloud_1", sel.cloudItems[0].id)
|
||||
}
|
||||
|
||||
fun `test data context exposes session manager`() {
|
||||
val manager = FakeManager()
|
||||
val controller = controller()
|
||||
val panel = HistoryPanel(parent, controller, manager = manager)
|
||||
flush()
|
||||
|
||||
assertSame(manager, panel.getData(SessionManager.KEY.name))
|
||||
}
|
||||
|
||||
fun `test data context returns null for absent session manager`() {
|
||||
val controller = controller()
|
||||
val panel = HistoryPanel(parent, controller)
|
||||
flush()
|
||||
|
||||
assertNull(panel.getData(SessionManager.KEY.name))
|
||||
}
|
||||
|
||||
fun `test local list uses multiple interval selection mode`() {
|
||||
val panel = HistoryPanel(parent, controller())
|
||||
flush()
|
||||
|
||||
assertEquals(javax.swing.ListSelectionModel.MULTIPLE_INTERVAL_SELECTION, panel.listSelectionMode())
|
||||
}
|
||||
|
||||
fun `test cloud list uses single selection mode`() {
|
||||
val panel = HistoryPanel(parent, controller())
|
||||
flush()
|
||||
|
||||
panel.clickCloud()
|
||||
flush()
|
||||
|
||||
assertEquals(javax.swing.ListSelectionModel.SINGLE_SELECTION, panel.listSelectionMode())
|
||||
}
|
||||
|
||||
// ------ Rename failure and directory selection ------
|
||||
|
||||
fun `test rename failure keeps original title and sets error`() {
|
||||
rpc.listed += session("ses_1", "Original")
|
||||
val controller = controller()
|
||||
controller.reloadLocal()
|
||||
flush()
|
||||
|
||||
rpc.renameThrows = IllegalStateException("rename failed")
|
||||
val item = controller.local.items[0]
|
||||
controller.rename(item, "Renamed")
|
||||
flush()
|
||||
|
||||
assertEquals("Original", controller.local.items[0].title)
|
||||
assertNotNull(controller.local.error)
|
||||
}
|
||||
|
||||
fun `test rename uses item directory when present`() {
|
||||
val dto = session("ses_1", "Original").copy(directory = "/worktree/path")
|
||||
rpc.listed += dto
|
||||
val controller = controller()
|
||||
controller.reloadLocal()
|
||||
flush()
|
||||
|
||||
val item = controller.local.items[0]
|
||||
assertEquals("/worktree/path", item.directory)
|
||||
|
||||
controller.rename(item, "Renamed")
|
||||
flush()
|
||||
|
||||
assertEquals(listOf(Triple("ses_1", "/worktree/path", "Renamed")), rpc.renames)
|
||||
}
|
||||
|
||||
fun `test rename falls back to workspace directory when item directory is workspace`() {
|
||||
// When session.directory matches workspace directory (not a worktree override)
|
||||
rpc.listed += session("ses_1", "Original")
|
||||
val controller = controller()
|
||||
controller.reloadLocal()
|
||||
flush()
|
||||
|
||||
val item = controller.local.items[0]
|
||||
controller.rename(item, "Renamed")
|
||||
flush()
|
||||
|
||||
assertEquals(listOf(Triple("ses_1", "/test", "Renamed")), rpc.renames)
|
||||
}
|
||||
|
||||
// ------ HistoryModel update/sorting ------
|
||||
|
||||
fun `test model update re-sorts items by updated time`() {
|
||||
val now = java.time.Instant.now()
|
||||
rpc.listed += session("ses_1", "Alpha", now.toEpochMilli().toDouble())
|
||||
rpc.listed += session("ses_2", "Beta", now.minusSeconds(100).toEpochMilli().toDouble())
|
||||
val controller = controller()
|
||||
controller.reloadLocal()
|
||||
flush()
|
||||
|
||||
// ses_1 is newer so comes first
|
||||
assertEquals("ses_1", controller.local.items[0].id)
|
||||
|
||||
// Update ses_2 to be newer
|
||||
val updated = session("ses_2", "Beta Updated", now.plusSeconds(100).toEpochMilli().toDouble())
|
||||
controller.local.update(LocalHistoryItem(updated))
|
||||
|
||||
// Now ses_2 should come first
|
||||
assertEquals("ses_2", controller.local.items[0].id)
|
||||
assertEquals("Beta Updated", controller.local.items[0].title)
|
||||
}
|
||||
|
||||
fun `test model update with unknown id leaves model unchanged`() {
|
||||
rpc.listed += session("ses_1", "Alpha")
|
||||
val controller = controller()
|
||||
controller.reloadLocal()
|
||||
flush()
|
||||
|
||||
val before = controller.local.items.toList()
|
||||
val unknown = session("unknown_id", "Unknown")
|
||||
controller.local.update(LocalHistoryItem(unknown))
|
||||
|
||||
assertEquals(before.map { it.id }, controller.local.items.map { it.id })
|
||||
}
|
||||
|
||||
fun `test model update removes renamed item from active filter`() {
|
||||
rpc.listed += session("ses_1", "Alpha")
|
||||
rpc.listed += session("ses_2", "Beta")
|
||||
val controller = controller()
|
||||
controller.reloadLocal()
|
||||
flush()
|
||||
|
||||
controller.local.setFilter("alpha")
|
||||
assertEquals(listOf("ses_1"), controller.local.visibleItems.map { it.id })
|
||||
|
||||
controller.local.update(LocalHistoryItem(session("ses_1", "Gamma")))
|
||||
|
||||
assertTrue(controller.local.visibleItems.isEmpty())
|
||||
}
|
||||
|
||||
fun `test model update adds renamed item to active filter`() {
|
||||
rpc.listed += session("ses_1", "Alpha")
|
||||
rpc.listed += session("ses_2", "Beta")
|
||||
val controller = controller()
|
||||
controller.reloadLocal()
|
||||
flush()
|
||||
|
||||
controller.local.setFilter("gamma")
|
||||
assertTrue(controller.local.visibleItems.isEmpty())
|
||||
|
||||
controller.local.update(LocalHistoryItem(session("ses_2", "Gamma")))
|
||||
|
||||
assertEquals(listOf("ses_2"), controller.local.visibleItems.map { it.id })
|
||||
}
|
||||
|
||||
fun `test cloud load passes git url when repo only enabled`() {
|
||||
rpc.cloud += cloud("cloud_1", "Cloud One")
|
||||
val url = "git@github.com:test/repo.git"
|
||||
val controller = controllerWithGit(url)
|
||||
|
||||
controller.reloadCloud()
|
||||
flush()
|
||||
|
||||
assertEquals(1, rpc.cloudCalls.size)
|
||||
assertEquals(url, rpc.cloudCalls[0].gitUrl)
|
||||
assertEquals(true, controller.repoOnly)
|
||||
assertEquals(url, controller.gitUrl)
|
||||
}
|
||||
|
||||
fun `test cloud load passes null when no git url`() {
|
||||
rpc.cloud += cloud("cloud_1", "Cloud One")
|
||||
val controller = controllerWithGit(null)
|
||||
|
||||
controller.reloadCloud()
|
||||
flush()
|
||||
|
||||
assertEquals(1, rpc.cloudCalls.size)
|
||||
assertNull(rpc.cloudCalls[0].gitUrl)
|
||||
assertEquals(false, controller.repoOnly)
|
||||
assertNull(controller.gitUrl)
|
||||
}
|
||||
|
||||
fun `test cloud git url resolves once across overlapping reloads`() {
|
||||
rpc.cloud += cloud("cloud_1", "Cloud One")
|
||||
val calls = AtomicInteger()
|
||||
val controller = HistoryController(sessions, workspace, scope, gitUrlProvider = {
|
||||
calls.incrementAndGet()
|
||||
Thread.sleep(100)
|
||||
"git@github.com:test/repo.git"
|
||||
})
|
||||
|
||||
controller.reloadCloud()
|
||||
controller.reloadCloud()
|
||||
flush()
|
||||
|
||||
assertEquals(1, calls.get())
|
||||
assertEquals(2, rpc.cloudCalls.size)
|
||||
}
|
||||
|
||||
fun `test cloud load passes null when repo only disabled`() {
|
||||
rpc.cloud += cloud("cloud_1", "Cloud One")
|
||||
val url = "git@github.com:test/repo.git"
|
||||
val controller = controllerWithGit(url)
|
||||
|
||||
controller.reloadCloud()
|
||||
flush()
|
||||
assertEquals(url, rpc.cloudCalls[0].gitUrl)
|
||||
|
||||
controller.applyRepoOnly(false)
|
||||
flush()
|
||||
|
||||
assertEquals(2, rpc.cloudCalls.size)
|
||||
assertNull(rpc.cloudCalls[1].gitUrl)
|
||||
}
|
||||
|
||||
fun `test load more passes git url when repo only enabled`() {
|
||||
rpc.cloud += cloud("cloud_1", "Cloud One")
|
||||
rpc.cloudCursor = "next_1"
|
||||
val url = "git@github.com:test/repo.git"
|
||||
val controller = controllerWithGit(url)
|
||||
|
||||
controller.reloadCloud()
|
||||
flush()
|
||||
|
||||
rpc.cloud.clear()
|
||||
rpc.cloud += cloud("cloud_2", "Cloud Two")
|
||||
rpc.cloudCursor = null
|
||||
controller.loadMoreCloud()
|
||||
flush()
|
||||
|
||||
assertEquals(2, rpc.cloudCalls.size)
|
||||
assertEquals(url, rpc.cloudCalls[1].gitUrl)
|
||||
}
|
||||
|
||||
fun `test repo only checkbox visible only when git url exists`() {
|
||||
val url = "git@github.com:test/repo.git"
|
||||
val panel = HistoryPanel(parent, controllerWithGit(url))
|
||||
flush()
|
||||
|
||||
panel.clickCloud()
|
||||
flush()
|
||||
|
||||
assertTrue(panel.repoOnlyVisible())
|
||||
assertTrue(panel.repoOnlySelected())
|
||||
}
|
||||
|
||||
fun `test repo only checkbox hidden when no git url`() {
|
||||
val panel = HistoryPanel(parent, controllerWithGit(null))
|
||||
flush()
|
||||
|
||||
panel.clickCloud()
|
||||
flush()
|
||||
|
||||
assertFalse(panel.repoOnlyVisible())
|
||||
}
|
||||
|
||||
fun `test repo only checkbox toggle reloads cloud history`() {
|
||||
rpc.cloud += cloud("cloud_1", "Cloud One")
|
||||
val url = "git@github.com:test/repo.git"
|
||||
val panel = HistoryPanel(parent, controllerWithGit(url))
|
||||
flush()
|
||||
|
||||
panel.clickCloud()
|
||||
flush()
|
||||
|
||||
val before = rpc.cloudCalls.size
|
||||
|
||||
panel.clickRepoOnly()
|
||||
flush()
|
||||
|
||||
assertTrue(rpc.cloudCalls.size > before)
|
||||
assertFalse(panel.repoOnlySelected())
|
||||
}
|
||||
|
||||
private fun controller() = HistoryController(sessions, workspace, scope)
|
||||
|
||||
private fun controllerWithGit(url: String?) = HistoryController(
|
||||
sessions,
|
||||
workspace,
|
||||
scope,
|
||||
gitUrlProvider = { url },
|
||||
)
|
||||
|
||||
private fun controller(opened: MutableList<String>) = HistoryController(sessions, workspace, scope, open = { open ->
|
||||
val id = when (open) {
|
||||
is SessionRef.Local -> open.id
|
||||
@@ -320,6 +683,12 @@ class HistoryControllerTest : BasePlatformTestCase() {
|
||||
opened.add(id)
|
||||
})
|
||||
|
||||
private class FakeManager : SessionManager {
|
||||
override fun newSession() {}
|
||||
override fun showHistory() {}
|
||||
override fun openSession(ref: SessionRef) {}
|
||||
}
|
||||
|
||||
private fun collect(controller: HistoryController): MutableList<String> {
|
||||
val events = mutableListOf<String>()
|
||||
val listener = object : ListDataListener {
|
||||
|
||||
+16
@@ -86,6 +86,9 @@ class FakeSessionRpcApi : KiloSessionRpcApi {
|
||||
val questionReplies = mutableListOf<Triple<String, String, QuestionReplyDto>>()
|
||||
val questionRejects = mutableListOf<Pair<String, String>>()
|
||||
val deletes = mutableListOf<Pair<String, String>>()
|
||||
var deleteGate: CompletableDeferred<Unit>? = null
|
||||
val renames = mutableListOf<Triple<String, String, String>>()
|
||||
var renameThrows: Exception? = null
|
||||
val lists = mutableListOf<String>()
|
||||
val recentCalls = mutableListOf<Pair<String, Int>>()
|
||||
val cloudCalls = mutableListOf<CloudCall>()
|
||||
@@ -127,10 +130,23 @@ class FakeSessionRpcApi : KiloSessionRpcApi {
|
||||
|
||||
override suspend fun delete(id: String, directory: String) {
|
||||
assertNotEdt("delete")
|
||||
deleteGate?.await()
|
||||
deletes.add(id to directory)
|
||||
listed.removeAll { it.id == id }
|
||||
}
|
||||
|
||||
override suspend fun rename(id: String, directory: String, title: String): SessionDto {
|
||||
assertNotEdt("rename")
|
||||
renameThrows?.let { throw it }
|
||||
renames.add(Triple(id, directory, title))
|
||||
val updated = listed.indexOfFirst { it.id == id }
|
||||
if (updated >= 0) {
|
||||
listed[updated] = listed[updated].copy(title = title)
|
||||
return listed[updated]
|
||||
}
|
||||
return session.copy(id = id, title = title)
|
||||
}
|
||||
|
||||
override suspend fun cloudSessions(directory: String, cursor: String?, limit: Int, gitUrl: String?): CloudSessionListDto {
|
||||
assertNotEdt("cloudSessions")
|
||||
cloudCalls.add(CloudCall(directory, cursor, limit, gitUrl))
|
||||
|
||||
@@ -51,6 +51,9 @@ interface KiloSessionRpcApi : RemoteApi<Unit> {
|
||||
/** Delete a session. */
|
||||
suspend fun delete(id: String, directory: String)
|
||||
|
||||
/** Rename a session. */
|
||||
suspend fun rename(id: String, directory: String, title: String): SessionDto
|
||||
|
||||
/** List cloud-backed sessions. */
|
||||
suspend fun cloudSessions(directory: String, cursor: String?, limit: Int, gitUrl: String?): CloudSessionListDto
|
||||
|
||||
|
||||
Reference in New Issue
Block a user