quassel 0.4.2 → 0.4.3

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/ChatMessages.d.ts CHANGED
@@ -1,5 +1,5 @@
1
1
  import { type CSSProperties, ReactNode } from "react";
2
- import { ChatAnnouncement, DetailMode, Message, PendingAction, ToolInfo } from "./types";
2
+ import { ChatAnnouncement, DetailMode, Message, PendingAction, ToolInfo, TranscriptMode } from "./types";
3
3
  import { ChatTexts } from "./texts";
4
4
  import { type LinkClickHandler } from "./Markdown";
5
5
  import { type ChatAppearance, type TimestampOptions, type CodeBlockOptions, type BubbleOptions, type MessageActionsOptions } from "./options";
@@ -7,11 +7,12 @@ import { type ChatAppearance, type TimestampOptions, type CodeBlockOptions, type
7
7
  * The transcript: its own scroll container with autoscroll while you are at the bottom, and a
8
8
  * jump-to-end button as soon as you are reading. Usable read-only without a composer.
9
9
  */
10
- export declare function ChatMessages({ messages, owner, detailMode, running, working, stepsExpandable, groupsExpandable, texts, toolArgumentsText, renderTool, renderAction, onDismissAction, onLinkClick, emptyState, className, scrollerClassName, showTimestamps, announce, scrollerRef, maxWidth, horizontalPadding, jumpToEndThreshold, appearance, timestampOptions, codeBlockOptions, bubbleOptions, messageActions, toolElapsedThreshold, }: {
10
+ export declare function ChatMessages({ messages, owner, detailMode, transcriptMode, running, working, stepsExpandable, groupsExpandable, texts, toolArgumentsText, renderTool, renderAction, onDismissAction, onLinkClick, emptyState, className, scrollerClassName, showTimestamps, announce, scrollerRef, maxWidth, horizontalPadding, jumpToEndThreshold, appearance, timestampOptions, codeBlockOptions, bubbleOptions, messageActions, toolElapsedThreshold, }: {
11
11
  messages: Message[];
12
12
  /** Contributions with this Message.sender appear without a speech bubble; null keeps the message defaults. */
13
13
  owner?: string | null;
14
14
  detailMode?: DetailMode;
15
+ transcriptMode?: TranscriptMode;
15
16
  running?: boolean;
16
17
  working?: ReactNode;
17
18
  /** false = thinking and tool steps cannot be expanded (no popover). */
package/README.md CHANGED
@@ -43,6 +43,7 @@ variables too.
43
43
  - Streaming-safe Markdown (tables, code with copy button, lists) via Streamdown.
44
44
  - Steering: typing while a turn runs sends into it; stop, retry and failed-send recovery built in.
45
45
  - Attachments by button, drag and drop or paste, with previews and size limits.
46
+ - Condensed conversation: all user inputs and the latest answer between them, independently of tool/thinking detail.
46
47
  - Timestamps, day separators, message actions, configurable bubbles and appearance.
47
48
  - Screen reader friendly: the transcript is not a live region; finished replies and new
48
49
  actions are announced once, with the host in control of the wording.
@@ -0,0 +1,8 @@
1
+ import { type TranscriptMode } from "./types";
2
+ import { type ChatTexts } from "./texts";
3
+ export declare function TranscriptModeSwitch({ mode, onChange, texts, className }: {
4
+ mode: TranscriptMode;
5
+ onChange: (mode: TranscriptMode) => void;
6
+ texts?: Partial<ChatTexts>;
7
+ className?: string;
8
+ }): import("react").JSX.Element;
package/index.d.ts CHANGED
@@ -1,4 +1,4 @@
1
- export type { Role, ToolInfo, PendingAction, Message, ChatEvent, ChatTextCursor, ChatJournalCursor, ChatAttachment, ChatAttachmentInput, ChatAttachmentCapabilities, ChatStartupStatus, DetailMode, StepState, ChatAnnouncement, } from "./types";
1
+ export type { Role, ToolInfo, PendingAction, Message, ChatEvent, ChatTextCursor, ChatJournalCursor, ChatAttachment, ChatAttachmentInput, ChatAttachmentCapabilities, ChatStartupStatus, DetailMode, TranscriptMode, StepState, ChatAnnouncement, } from "./types";
2
2
  export { applyEvent, prettyJson, compactToolLine, stepState } from "./types";
3
3
  export type { ChatTexts } from "./texts";
4
4
  export { defaultTexts, fillText } from "./texts";
@@ -6,6 +6,7 @@ export { ChatMessages } from "./ChatMessages";
6
6
  export { ChatPanel } from "./ChatPanel";
7
7
  export { ChatInputToolbar, type ChatInputHandle, type ToolbarAction } from "./ChatInputToolbar";
8
8
  export { DetailModeSwitch, DETAIL_MODES, detailModeLabel } from "./DetailModeSwitch";
9
+ export { TranscriptModeSwitch } from "./TranscriptModeSwitch";
9
10
  export { TimestampSwitch } from "./TimestampSwitch";
10
11
  export { Markdown, MarkdownCodeBlocks, MarkdownLinks, markdownPlainText, type LinkClickHandler } from "./Markdown";
11
12
  export { MessageActions } from "./MessageActions";
package/index.js CHANGED
@@ -51,6 +51,8 @@ var defaultTexts = {
51
51
  detailModeGrouped: "grouped",
52
52
  detailModeCompact: "single line",
53
53
  detailModeFull: "everything",
54
+ showAllMessages: "Show intermediate replies",
55
+ showLatestExchange: "Show latest reply between inputs",
54
56
  showTimestamps: "Show timestamps",
55
57
  hideTimestamps: "Hide timestamps",
56
58
  copyMessage: "Copy message",
@@ -1097,6 +1099,7 @@ function ChatMessages({
1097
1099
  messages,
1098
1100
  owner,
1099
1101
  detailMode = "current",
1102
+ transcriptMode = "all",
1100
1103
  running: running2 = false,
1101
1104
  working,
1102
1105
  stepsExpandable = true,
@@ -1123,6 +1126,21 @@ function ChatMessages({
1123
1126
  messageActions,
1124
1127
  toolElapsedThreshold = 3e3
1125
1128
  }) {
1129
+ const selected = useMemo3(() => {
1130
+ if (transcriptMode === "all") return messages;
1131
+ const answers = /* @__PURE__ */ new Set();
1132
+ let hasAnswer = false;
1133
+ for (let index = messages.length - 1; index >= 0; index--) {
1134
+ const message = messages[index];
1135
+ if (message.role === "user") {
1136
+ hasAnswer = false;
1137
+ } else if (message.role === "assistant" && !hasAnswer && (message.text.trim() !== "" || message.attachments?.length)) {
1138
+ answers.add(message);
1139
+ hasAnswer = true;
1140
+ }
1141
+ }
1142
+ return messages.filter((message) => message.role !== "assistant" || answers.has(message));
1143
+ }, [messages, transcriptMode]);
1126
1144
  const allTexts = { ...defaultTexts, ...texts };
1127
1145
  const { Button: Button2 } = useQuasselComponents();
1128
1146
  const scrollArea = useRef3(null);
@@ -1150,8 +1168,8 @@ function ChatMessages({
1150
1168
  if (announce2 === false || due.length > maxAnnouncementsAtOnce) {
1151
1169
  return;
1152
1170
  }
1153
- due.map((message) => announce2 ? announce2({ kind: message.role === "action" ? "action" : "reply", message }) : markdownPlainText(message.text)).forEach((text) => text && announce(text));
1154
- }, [messages, running2, announce2]);
1171
+ due.filter((message) => selected.includes(message)).map((message) => announce2 ? announce2({ kind: message.role === "action" ? "action" : "reply", message }) : markdownPlainText(message.text)).forEach((text) => text && announce(text));
1172
+ }, [messages, selected, running2, announce2]);
1155
1173
  const updateJumpVisibility = useCallback2((viewport) => {
1156
1174
  if (viewport.clientHeight > 0) setShowJumpToEnd(viewport.scrollHeight - viewport.clientHeight - viewport.scrollTop > jumpToEndThreshold);
1157
1175
  }, [jumpToEndThreshold]);
@@ -1203,8 +1221,8 @@ function ChatMessages({
1203
1221
  });
1204
1222
  const visible = useMemo3(
1205
1223
  () => {
1206
- const lastNonUser = messages.reduce((last, message, index) => message.role === "user" ? last : index, -1);
1207
- return messages.filter((message, index) => {
1224
+ const lastNonUser = selected.reduce((last, message, index) => message.role === "user" ? last : index, -1);
1225
+ return selected.filter((message, index) => {
1208
1226
  if (dock !== void 0 && isOpenAction(message)) {
1209
1227
  return false;
1210
1228
  }
@@ -1220,7 +1238,7 @@ function ChatMessages({
1220
1238
  return true;
1221
1239
  });
1222
1240
  },
1223
- [messages, detailMode, running2, dock]
1241
+ [selected, detailMode, running2, dock]
1224
1242
  );
1225
1243
  const openActions = dock ? messages.filter(isOpenAction) : [];
1226
1244
  const withTime = (content, key, at, dense = false) => showTimestamps ? /* @__PURE__ */ jsxs6("div", { className: cn("qsl:flex qsl:items-baseline qsl:gap-2", dense ? denseStepClasses : stepClasses), children: [
@@ -2352,14 +2370,37 @@ function DetailModeSwitch({
2352
2370
  return /* @__PURE__ */ jsx16(Button2, { "aria-label": title, className, onClick: () => onChange(next2), size: "icon-sm", title, variant: "ghost", children: /* @__PURE__ */ jsx16(Icon, {}) });
2353
2371
  }
2354
2372
 
2373
+ // packages/quassel/src/TranscriptModeSwitch.tsx
2374
+ import { MessagesSquareIcon, MessageSquareIcon } from "lucide-react";
2375
+ import { jsx as jsx17 } from "react/jsx-runtime";
2376
+ function TranscriptModeSwitch({ mode, onChange, texts, className }) {
2377
+ const labels = { ...defaultTexts, ...texts };
2378
+ const { Button: Button2 } = useQuasselComponents();
2379
+ const latest = mode === "latest";
2380
+ const title = latest ? labels.showAllMessages : labels.showLatestExchange;
2381
+ return /* @__PURE__ */ jsx17(
2382
+ Button2,
2383
+ {
2384
+ "aria-label": title,
2385
+ "aria-pressed": latest,
2386
+ className,
2387
+ onClick: () => onChange(latest ? "all" : "latest"),
2388
+ size: "icon-sm",
2389
+ title,
2390
+ variant: "ghost",
2391
+ children: latest ? /* @__PURE__ */ jsx17(MessageSquareIcon, {}) : /* @__PURE__ */ jsx17(MessagesSquareIcon, {})
2392
+ }
2393
+ );
2394
+ }
2395
+
2355
2396
  // packages/quassel/src/TimestampSwitch.tsx
2356
2397
  import { ClockFadingIcon, ClockIcon } from "lucide-react";
2357
- import { jsx as jsx17 } from "react/jsx-runtime";
2398
+ import { jsx as jsx18 } from "react/jsx-runtime";
2358
2399
  function TimestampSwitch({ showTimestamps, onChange, texts, className }) {
2359
2400
  const labels = { ...defaultTexts, ...texts };
2360
2401
  const { Button: Button2 } = useQuasselComponents();
2361
2402
  const title = showTimestamps ? labels.hideTimestamps : labels.showTimestamps;
2362
- return /* @__PURE__ */ jsx17(
2403
+ return /* @__PURE__ */ jsx18(
2363
2404
  Button2,
2364
2405
  {
2365
2406
  "aria-label": title,
@@ -2369,7 +2410,7 @@ function TimestampSwitch({ showTimestamps, onChange, texts, className }) {
2369
2410
  size: "icon-sm",
2370
2411
  title,
2371
2412
  variant: "ghost",
2372
- children: showTimestamps ? /* @__PURE__ */ jsx17(ClockIcon, {}) : /* @__PURE__ */ jsx17(ClockFadingIcon, {})
2413
+ children: showTimestamps ? /* @__PURE__ */ jsx18(ClockIcon, {}) : /* @__PURE__ */ jsx18(ClockFadingIcon, {})
2373
2414
  }
2374
2415
  );
2375
2416
  }
@@ -2390,6 +2431,7 @@ export {
2390
2431
  QuasselProvider,
2391
2432
  StepPopover,
2392
2433
  TimestampSwitch,
2434
+ TranscriptModeSwitch,
2393
2435
  WorkingScenes,
2394
2436
  announce,
2395
2437
  appearanceStyle,