wsb-daemon 0.275.7622

wsb's daemon: the workbench, the GitHub mirror's schedule and peer continuity, for every workspace on the machine
<script lang="ts">
  import type { Message } from "../../api/types.js";
  import {
    parseContent,
    enrichSegments,
  } from "../../utils/content-parser.js";
  import { formatTimestamp } from "../../utils/format.js";
  import { copyToClipboard } from "../../utils/clipboard.js";
  import AppIcon from "../layout/AppIcon.svelte";
  import ToolBlock from "./ToolBlock.svelte";

  interface Props {
    messages: Message[];
    timestamp: string;
    highlightQuery?: string;
    isCurrentHighlight?: boolean;
  }

  let {
    messages,
    timestamp,
    highlightQuery = "",
    isCurrentHighlight = false,
  }: Props = $props();

  let copied = $state(false);

  let toolSegments = $derived(
    messages.flatMap((m) =>
      enrichSegments(
        parseContent(m.content, m.has_tool_use),
        m.tool_calls,
      ).filter((s) => s.type === "tool"),
    ),
  );

  let label = $derived(
    toolSegments.length === 1
      ? "1 tool call"
      : `${toolSegments.length} tool calls`,
  );

  let copyTimer: ReturnType<typeof setTimeout>;

  async function handleCopy() {
    const combined = messages.map((m) => m.content).join("\n\n");
    try {
      await copyToClipboard(combined);
      clearTimeout(copyTimer);
      copied = true;
      copyTimer = setTimeout(() => { copied = false; }, 1500);
    } catch { /* clipboard unavailable */ }
  }
</script>

<div class="tool-group">
  <div class="tool-group-header">
    <span class="gear-icon">
      <AppIcon name="filter" size={12} />
    </span>
    <span class="group-label">{label}</span>
    <button
      type="button"
      class="copy-btn"
      title={copied ? "Copied!" : "Copy tool calls"}
      onclick={handleCopy}
    >
      {#if copied}
        <AppIcon name="check" size={14} />
      {:else}
        <AppIcon name="copy" size={14} />
      {/if}
    </button>
    <span class="group-timestamp">
      {formatTimestamp(timestamp)}
    </span>
  </div>

  <div class="tool-group-body">
    {#each toolSegments as segment}
      <ToolBlock
        content={segment.content}
        label={segment.label}
        toolCall={segment.toolCall}
        {highlightQuery}
        {isCurrentHighlight}
      />
    {/each}
  </div>
</div>

<style>
  .tool-group {
    border-left: 3px solid var(--vscode-editorWarning-foreground);
    background: var(--wb-tool-bg);
    border-radius: 0 var(--wsb-radius-md) var(--wsb-radius-md) 0;
    padding: var(--wb-spacing-8) var(--wb-spacing-12);
  }

  .tool-group-header {
    display: flex;
    align-items: center;
    gap: var(--wb-spacing-8);
    margin-bottom: var(--wb-spacing-6);
  }

  .gear-icon {
    display: flex;
    align-items: center;
    flex-shrink: 0;
  }

  .group-label {
    font-size: var(--wb-font-size-12);
    font-weight: 600;
    color: var(--vscode-editorWarning-foreground);
  }

  .group-timestamp {
    font-size: var(--wb-font-size-12);
    color: var(--vscode-disabledForeground);
    margin-left: auto;
  }

  .copy-btn {
    display: flex;
    align-items: center;
    justify-content: center;
    width: var(--wb-control-size-26);
    height: var(--wb-control-size-26);
    border: none;
    border-radius: var(--wsb-radius-sm, var(--wb-corner-radius-4));
    background: transparent;
    color: var(--vscode-disabledForeground);
    cursor: pointer;
    opacity: 0;
    transition: opacity 0.15s, background 0.15s, color 0.15s;
    flex-shrink: 0;
  }

  .tool-group:hover .copy-btn,
  .copy-btn:focus-visible {
    opacity: 1;
  }

  @media (hover: none) {
    .copy-btn {
      opacity: 1;
    }
  }

  .copy-btn:hover {
    background: var(--vscode-list-hoverBackground);
    color: var(--vscode-foreground);
  }

  .copy-btn:active {
    transform: scale(0.92);
  }

  .tool-group-body {
    display: flex;
    flex-direction: column;
    gap: var(--wb-spacing-2);
  }

  .tool-group-body :global(.tool-block) {
    margin: 0;
    border-left: none;
    border-radius: 0;
  }
</style>