@k2b/cloud 0.16.1 → 0.17.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@k2b/cloud",
3
- "version": "0.16.1",
3
+ "version": "0.17.0",
4
4
  "description": "Application platform library for independently deployed Hono and SolidJS services behind a dynamic gateway.",
5
5
  "license": "AGPL-3.0-or-later",
6
6
  "repository": {
@@ -99,7 +99,7 @@
99
99
  "@tailwindcss/typography": "0.5.20",
100
100
  "@k2b/nessi": "0.12.1",
101
101
  "@k2b/ssr": "0.14.0",
102
- "@k2b/ui": "0.6.3",
102
+ "@k2b/ui": "0.7.0",
103
103
  "@k2b/stdlib": "0.26.0",
104
104
  "@k2b/sync": "6.5.0",
105
105
  "@nats-io/transport-node": "3.4.0",
@@ -24,6 +24,7 @@ import {
24
24
  jsonPreview,
25
25
  memoryToolPresentation,
26
26
  } from "./message-utils";
27
+ import { aiChatMessages } from "./messages";
27
28
  import { AssistantMarkdownBlock } from "./primitives";
28
29
  import { AiToolActivity, AiToolDisclosureProvider, type AiToolDisclosureState, createAiToolDisclosureState } from "./tool-disclosure";
29
30
  import { groupToolBlocks, isFailedTool, summarizeToolGroup } from "./tool-groups";
@@ -57,16 +58,18 @@ function ReviewDetailValue(props: { detail: ReviewDetail }) {
57
58
  // turn streams, so every branch must re-evaluate when the store updates.
58
59
 
59
60
  function ThinkingBlockView(props: { text: string; streaming?: boolean }) {
61
+ const locale = useLocale();
62
+ const t = () => aiChatMessages(locale());
60
63
  return (
61
64
  <Show
62
65
  when={props.text.trim()}
63
66
  fallback={
64
67
  <Show when={props.streaming}>
65
- <Chat.Activity label="Thinking" icon="ti ti-sparkles" tone="ai" busy />
68
+ <Chat.Activity label={t().thinking} icon="ti ti-sparkles" tone="ai" busy />
66
69
  </Show>
67
70
  }
68
71
  >
69
- <Chat.Activity label="Show reasoning" icon="ti ti-sparkles" tone="ai" bodyInset={false}>
72
+ <Chat.Activity label={t().showReasoning} icon="ti ti-sparkles" tone="ai" bodyInset={false}>
70
73
  <pre class="max-h-52 w-full min-w-0 overflow-auto whitespace-pre-wrap rounded-md bg-zinc-100/70 p-2 text-[11px] leading-5 text-secondary [box-shadow:var(--ui-control-recess)] dark:bg-zinc-950/70">
71
74
  {props.text}
72
75
  </pre>
@@ -18,6 +18,8 @@ const messages = i18n.define({
18
18
  toolIssues: "Tool issues",
19
19
  none: "None",
20
20
  read: "Read",
21
+ thinking: "Thinking",
22
+ showReasoning: "Show reasoning",
21
23
  byteRange: ({ start, end }: { start: string; end: string }) => `Bytes ${start}–${end}`,
22
24
  },
23
25
  de: {
@@ -36,6 +38,8 @@ const messages = i18n.define({
36
38
  toolIssues: "Werkzeugprobleme",
37
39
  none: "Keine",
38
40
  read: "Gelesen",
41
+ thinking: "Denkt nach",
42
+ showReasoning: "Denkprozess anzeigen",
39
43
  byteRange: ({ start, end }) => `Bytes ${start}–${end}`,
40
44
  },
41
45
  },
@@ -27,10 +27,15 @@ export type AiChatTimelineSession = {
27
27
 
28
28
  export { type AiChatActions, AiChatActionsProvider };
29
29
 
30
- const isWideBlock = (block: AiAssistantTimelineItem["blocks"][number]) => block.kind === "tool";
31
-
32
30
  type AssistantBlock = AiAssistantTimelineItem["blocks"][number];
33
31
 
32
+ // Activity blocks span the full message column so disclosure chevrons share one
33
+ // right edge. Tool and compaction blocks count even while busy; reasoning counts
34
+ // only once it has text, because empty reasoning renders at most a busy row and
35
+ // must not widen a prose-only reply.
36
+ const isWideBlock = (block: AssistantBlock) =>
37
+ block.kind === "tool" || block.kind === "compaction" || (block.kind === "thinking" && block.text.trim().length > 0);
38
+
34
39
  type SurveyResultBlock = Extract<AssistantBlock, { kind: "tool" }>;
35
40
  type SurveySegment = { type: "assistant"; blocks: AssistantBlock[] } | { type: "survey"; block: SurveyResultBlock };
36
41
 
package/src/ai/routes.ts CHANGED
@@ -200,6 +200,7 @@ const MemoryUpdateSchema = MemoryCreateSchema.partial().refine((value) => Object
200
200
  const MemoryIdSchema = z.string().regex(AI_SHORT_ID_PATTERN);
201
201
 
202
202
  const notFound = (c: Context<AuthContext>) => respond(c, fail(err.notFound("Conversation")));
203
+ const fileNotFound = (c: Context<AuthContext>) => respond(c, fail(err.notFound("File")));
203
204
 
204
205
  const publicConversation = (conversation: AiConversation, projectId: string | null = null) => ({
205
206
  ...conversation,
@@ -1294,9 +1295,9 @@ export const aiRoutes = (() => {
1294
1295
  const conversation = await loadConversation(c, ctx);
1295
1296
  if (!conversation) return notFound(c);
1296
1297
  const path = normalizeAiFilePath(c.req.valid("query").path);
1297
- if (!path) return notFound(c);
1298
+ if (!path) return fileNotFound(c);
1298
1299
  const stored = await aiFileStore.read({ conversationId: conversation.id, path });
1299
- if (!stored) return notFound(c);
1300
+ if (!stored) return fileNotFound(c);
1300
1301
  const filename = path.slice(path.lastIndexOf("/") + 1).replaceAll('"', "");
1301
1302
  return c.body(stored.bytes as unknown as ArrayBuffer, 200, {
1302
1303
  "Content-Type": stored.mediaType || "application/octet-stream",
@@ -1311,9 +1312,9 @@ export const aiRoutes = (() => {
1311
1312
  const conversation = await loadConversation(c, ctx);
1312
1313
  if (!conversation) return notFound(c);
1313
1314
  const path = normalizeAiFilePath(c.req.valid("query").path);
1314
- if (!path) return notFound(c);
1315
+ if (!path) return fileNotFound(c);
1315
1316
  const removed = await aiFileStore.remove({ conversationId: conversation.id, path, recursive: false });
1316
- if (removed === 0) return notFound(c);
1317
+ if (removed === 0) return fileNotFound(c);
1317
1318
  return respond(c, ok({ deleted: true }));
1318
1319
  })
1319
1320
  .put("/conversations/:conversationId/files/content", v("json", FileWriteSchema), async (c) => {