recappi 0.1.80 → 0.1.81

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/dist/index.js CHANGED
@@ -1493,12 +1493,14 @@ function AskScreen({
1493
1493
  onOpenTranscript,
1494
1494
  spinnerFrame
1495
1495
  }) {
1496
+ const size = useTerminalSize();
1496
1497
  const [phase, setPhase] = useState5("input");
1497
1498
  const [question, setQuestion] = useState5("");
1498
1499
  const [asked, setAsked] = useState5("");
1499
1500
  const [content, setContent] = useState5("");
1500
1501
  const [citations, setCitations] = useState5([]);
1501
1502
  const [error51, setError] = useState5(void 0);
1503
+ const [scroll, setScroll] = useState5(0);
1502
1504
  const runIdRef = useRef2(0);
1503
1505
  const submit = useCallback(
1504
1506
  (raw) => {
@@ -1510,6 +1512,7 @@ function AskScreen({
1510
1512
  setContent("");
1511
1513
  setCitations([]);
1512
1514
  setError(void 0);
1515
+ setScroll(0);
1513
1516
  void (async () => {
1514
1517
  try {
1515
1518
  let text = "";
@@ -1552,7 +1555,16 @@ function AskScreen({
1552
1555
  setContent("");
1553
1556
  setCitations([]);
1554
1557
  setError(void 0);
1558
+ setScroll(0);
1555
1559
  };
1560
+ const answerText = phase === "done" ? formatAskAnswerPlain(content, citations) : content ? strippedAskAnswer(content) : "";
1561
+ const innerWidth = Math.max(10, size.columns - 2);
1562
+ const lines = answerText ? wrapToLines(answerText, innerWidth) : [];
1563
+ const paneBudget = Math.max(3, size.rows - 9);
1564
+ const maxScroll = Math.max(0, lines.length - paneBudget);
1565
+ const effectiveScroll = phase === "asking" ? maxScroll : Math.min(scroll, maxScroll);
1566
+ const windowLines = lines.slice(effectiveScroll, effectiveScroll + paneBudget);
1567
+ const page = Math.max(1, paneBudget - 1);
1556
1568
  useInput5((input, key) => {
1557
1569
  if (key.escape) {
1558
1570
  runIdRef.current++;
@@ -1565,10 +1577,14 @@ function AskScreen({
1565
1577
  if (input && !key.ctrl && !key.meta) setQuestion((q) => q + input);
1566
1578
  return;
1567
1579
  }
1568
- if (phase === "done" || phase === "error") {
1569
- if (input === "a") return reset();
1570
- if (input === "t" && onOpenTranscript) return onOpenTranscript();
1571
- }
1580
+ if (key.downArrow || input === "j") setScroll((s) => Math.min(maxScroll, s + 1));
1581
+ else if (key.upArrow || input === "k") setScroll((s) => Math.max(0, s - 1));
1582
+ else if (key.pageDown || input === " ") setScroll((s) => Math.min(maxScroll, s + page));
1583
+ else if (key.pageUp || input === "b") setScroll((s) => Math.max(0, s - page));
1584
+ else if (input === "g") setScroll(0);
1585
+ else if (input === "G") setScroll(maxScroll);
1586
+ else if (input === "a") reset();
1587
+ else if (input === "t" && onOpenTranscript) onOpenTranscript();
1572
1588
  });
1573
1589
  return /* @__PURE__ */ jsxs12(Box13, { flexDirection: "column", paddingX: 1, children: [
1574
1590
  /* @__PURE__ */ jsxs12(Text13, { dimColor: true, children: [
@@ -1584,63 +1600,56 @@ function AskScreen({
1584
1600
  /* @__PURE__ */ jsx15(Box13, { marginTop: 1, children: /* @__PURE__ */ jsx15(Text13, { dimColor: true, children: "Type a question \xB7 \u23CE ask \xB7 esc back" }) })
1585
1601
  ] }) : /* @__PURE__ */ jsxs12(Box13, { marginTop: 1, flexDirection: "column", children: [
1586
1602
  /* @__PURE__ */ jsx15(Text13, { dimColor: true, wrap: "truncate-end", children: `? ${asked}` }),
1587
- /* @__PURE__ */ jsx15(Box13, { marginTop: 1, flexDirection: "column", children: /* @__PURE__ */ jsx15(AnswerBody, { phase, content, citations, error: error51, spinnerFrame }) }),
1588
- phase === "done" ? /* @__PURE__ */ jsx15(Sources, { citations }) : null,
1603
+ /* @__PURE__ */ jsx15(Box13, { marginTop: 1, flexDirection: "column", children: phase === "asking" && !content ? /* @__PURE__ */ jsx15(Text13, { color: "cyan", children: `${SPINNER2[spinnerFrame % SPINNER2.length]} Thinking\u2026` }) : windowLines.map((line, i) => /* @__PURE__ */ jsx15(AnswerLine, { line }, effectiveScroll + i)) }),
1604
+ maxScroll > 0 ? /* @__PURE__ */ jsx15(Text13, { dimColor: true, children: ` ${Math.min(effectiveScroll + paneBudget, lines.length)} / ${lines.length}${phase === "asking" ? " \xB7 streaming" : " \xB7 \u2191\u2193 scroll"}` }) : null,
1605
+ phase === "error" ? /* @__PURE__ */ jsx15(Box13, { marginTop: 1, children: /* @__PURE__ */ jsx15(Text13, { color: "red", children: error51 ? `Ask failed: ${error51}` : "Ask failed" }) }) : null,
1589
1606
  /* @__PURE__ */ jsx15(Box13, { marginTop: 1, children: /* @__PURE__ */ jsxs12(Text13, { dimColor: true, children: [
1590
1607
  phase === "asking" ? "esc cancel" : "a ask again",
1591
1608
  (phase === "done" || phase === "error") && onOpenTranscript ? " \xB7 t transcript" : "",
1609
+ maxScroll > 0 ? " \xB7 \u2191\u2193 scroll" : "",
1592
1610
  phase !== "asking" ? " \xB7 esc back" : ""
1593
1611
  ] }) })
1594
1612
  ] })
1595
1613
  ] });
1596
1614
  }
1597
- function AnswerBody({
1598
- phase,
1599
- content,
1600
- citations,
1601
- error: error51,
1602
- spinnerFrame
1603
- }) {
1604
- if (phase === "error") {
1605
- return /* @__PURE__ */ jsx15(Text13, { color: "red", children: error51 ? `Ask failed: ${error51}` : "Ask failed" });
1606
- }
1607
- if (phase === "asking" && !content) {
1608
- return /* @__PURE__ */ jsx15(Text13, { color: "cyan", children: `${SPINNER2[spinnerFrame % SPINNER2.length]} Thinking\u2026` });
1609
- }
1610
- if (phase === "asking") {
1611
- return /* @__PURE__ */ jsx15(Text13, { children: strippedAskAnswer(content) });
1612
- }
1613
- const segments = renderAskInline(content, citations);
1614
- return /* @__PURE__ */ jsx15(Text13, { children: segments.map(
1615
- (segment, i) => segment.kind === "text" ? /* @__PURE__ */ jsx15(Text13, { children: segment.text }, i) : /* @__PURE__ */ jsx15(Text13, { color: "cyan", children: `\u27E8${segment.label}\u27E9` }, i)
1615
+ function AnswerLine({ line }) {
1616
+ if (line === "") return /* @__PURE__ */ jsx15(Text13, { children: " " });
1617
+ const parts = line.split(/(⟨[^⟩]*⟩)/);
1618
+ return /* @__PURE__ */ jsx15(Text13, { children: parts.map(
1619
+ (part, i) => part.startsWith("\u27E8") && part.endsWith("\u27E9") ? /* @__PURE__ */ jsx15(Text13, { color: "cyan", children: part }, i) : /* @__PURE__ */ jsx15(Text13, { children: part }, i)
1616
1620
  ) });
1617
1621
  }
1618
- function Sources({ citations }) {
1619
- const seen = /* @__PURE__ */ new Set();
1620
- const unique = [];
1621
- for (const citation of citations) {
1622
- const key = citation.segmentId ?? askCitationInlineLabel(citation);
1623
- if (seen.has(key)) continue;
1624
- seen.add(key);
1625
- unique.push(citation);
1626
- }
1627
- if (unique.length === 0) return null;
1628
- return /* @__PURE__ */ jsxs12(Box13, { marginTop: 1, flexDirection: "column", children: [
1629
- /* @__PURE__ */ jsx15(Text13, { dimColor: true, children: "Sources" }),
1630
- unique.map((citation, i) => {
1631
- const meta3 = [citation.speaker?.trim(), citation.snippet?.trim()].filter(Boolean).join(" \xB7 ");
1632
- return /* @__PURE__ */ jsxs12(Text13, { wrap: "truncate-end", children: [
1633
- /* @__PURE__ */ jsx15(Text13, { color: "cyan", children: `\u27E8${askCitationInlineLabel(citation)}\u27E9` }),
1634
- meta3 ? /* @__PURE__ */ jsx15(Text13, { dimColor: true, children: ` ${meta3}` }) : null
1635
- ] }, i);
1636
- })
1637
- ] });
1622
+ function wrapToLines(text, width) {
1623
+ const out = [];
1624
+ for (const paragraph of text.split("\n")) {
1625
+ if (paragraph === "") {
1626
+ out.push("");
1627
+ continue;
1628
+ }
1629
+ let current = "";
1630
+ let currentWidth = 0;
1631
+ for (const ch of paragraph) {
1632
+ const w = displayWidth(ch);
1633
+ if (current !== "" && currentWidth + w > width) {
1634
+ out.push(current);
1635
+ current = ch;
1636
+ currentWidth = w;
1637
+ } else {
1638
+ current += ch;
1639
+ currentWidth += w;
1640
+ }
1641
+ }
1642
+ out.push(current);
1643
+ }
1644
+ return out;
1638
1645
  }
1639
1646
  var SPINNER2;
1640
1647
  var init_AskScreen = __esm({
1641
1648
  "src/tui/AskScreen.tsx"() {
1642
1649
  "use strict";
1643
1650
  init_askInline();
1651
+ init_format();
1652
+ init_terminal();
1644
1653
  SPINNER2 = "\u280B\u2819\u2839\u2838\u283C\u2834\u2826\u2827\u2807\u280F";
1645
1654
  }
1646
1655
  });