@linxiraos/pi-stats 1.1.27 → 1.2.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.
Files changed (124) hide show
  1. package/CHANGELOG.md +10 -7
  2. package/build.ts +4 -0
  3. package/dist/client/favicon.ico +0 -0
  4. package/dist/client/index.css +1 -1
  5. package/dist/client/index.html +1 -0
  6. package/dist/client/index.js +96 -96
  7. package/dist/types/aggregator.d.ts +4 -1
  8. package/dist/types/client/api.d.ts +5 -1
  9. package/dist/types/client/app/nav.d.ts +5 -2
  10. package/dist/types/client/data/formatters.d.ts +5 -2
  11. package/dist/types/client/data/live.d.ts +16 -11
  12. package/dist/types/client/data/query-store.d.ts +39 -0
  13. package/dist/types/client/data/query.d.ts +10 -5
  14. package/dist/types/client/data/range.d.ts +4 -3
  15. package/dist/types/client/i18n/format.d.ts +9 -0
  16. package/dist/types/client/i18n/index.d.ts +18 -0
  17. package/dist/types/client/i18n/messages/charts.d.ts +8 -0
  18. package/dist/types/client/i18n/messages/common.d.ts +153 -0
  19. package/dist/types/client/i18n/messages/costs.d.ts +92 -0
  20. package/dist/types/client/i18n/messages/en.d.ts +1255 -0
  21. package/dist/types/client/i18n/messages/errors.d.ts +78 -0
  22. package/dist/types/client/i18n/messages/frustration.d.ts +191 -0
  23. package/dist/types/client/i18n/messages/gain.d.ts +54 -0
  24. package/dist/types/client/i18n/messages/models.d.ts +94 -0
  25. package/dist/types/client/i18n/messages/nav.d.ts +46 -0
  26. package/dist/types/client/i18n/messages/overview.d.ts +80 -0
  27. package/dist/types/client/i18n/messages/projects.d.ts +84 -0
  28. package/dist/types/client/i18n/messages/providers.d.ts +142 -0
  29. package/dist/types/client/i18n/messages/requests.d.ts +72 -0
  30. package/dist/types/client/i18n/messages/tools.d.ts +119 -0
  31. package/dist/types/client/i18n/messages/traces.d.ts +144 -0
  32. package/dist/types/client/i18n/messages/ui.d.ts +44 -0
  33. package/dist/types/client/i18n/messages/zh-CN.d.ts +13 -0
  34. package/dist/types/client/i18n/types.d.ts +15 -0
  35. package/dist/types/client/traces/Minimap.d.ts +2 -0
  36. package/dist/types/client/traces/TimelineCanvas.d.ts +29 -8
  37. package/dist/types/client/traces/TranscriptList.d.ts +3 -1
  38. package/dist/types/db.d.ts +14 -21
  39. package/dist/types/embedded-client.d.ts +5 -0
  40. package/dist/types/frustration.d.ts +18 -9
  41. package/dist/types/live.d.ts +5 -3
  42. package/dist/types/locale.d.ts +16 -0
  43. package/dist/types/parser.d.ts +0 -17
  44. package/dist/types/rollup.d.ts +9 -1
  45. package/dist/types/server.d.ts +6 -0
  46. package/dist/types/shared-types.d.ts +7 -0
  47. package/dist/types/trace.d.ts +18 -6
  48. package/dist/types/types.d.ts +12 -0
  49. package/dist/types/user-metrics.d.ts +23 -1
  50. package/package.json +4 -4
  51. package/src/aggregator.ts +28 -11
  52. package/src/client/api.ts +40 -5
  53. package/src/client/app/LiveChip.tsx +14 -13
  54. package/src/client/app/Shell.tsx +19 -13
  55. package/src/client/app/ThemeToggle.tsx +4 -3
  56. package/src/client/app/nav.ts +19 -16
  57. package/src/client/data/formatters.ts +73 -13
  58. package/src/client/data/live.tsx +85 -45
  59. package/src/client/data/query-store.ts +108 -0
  60. package/src/client/data/query.ts +47 -56
  61. package/src/client/data/range.ts +34 -14
  62. package/src/client/data/series.ts +7 -1
  63. package/src/client/favicon.ico +0 -0
  64. package/src/client/i18n/format.ts +28 -0
  65. package/src/client/i18n/index.ts +53 -0
  66. package/src/client/i18n/messages/charts.ts +8 -0
  67. package/src/client/i18n/messages/common.ts +157 -0
  68. package/src/client/i18n/messages/costs.ts +104 -0
  69. package/src/client/i18n/messages/en.ts +40 -0
  70. package/src/client/i18n/messages/errors.ts +80 -0
  71. package/src/client/i18n/messages/frustration.ts +198 -0
  72. package/src/client/i18n/messages/gain.ts +54 -0
  73. package/src/client/i18n/messages/models.ts +94 -0
  74. package/src/client/i18n/messages/nav.ts +46 -0
  75. package/src/client/i18n/messages/overview.ts +90 -0
  76. package/src/client/i18n/messages/projects.ts +84 -0
  77. package/src/client/i18n/messages/providers.ts +165 -0
  78. package/src/client/i18n/messages/requests.ts +73 -0
  79. package/src/client/i18n/messages/tools.ts +120 -0
  80. package/src/client/i18n/messages/traces.ts +160 -0
  81. package/src/client/i18n/messages/ui.ts +46 -0
  82. package/src/client/i18n/messages/zh-CN.ts +43 -0
  83. package/src/client/i18n/types.ts +19 -0
  84. package/src/client/index.tsx +4 -0
  85. package/src/client/routes/CostsRoute.tsx +61 -48
  86. package/src/client/routes/ErrorsRoute.tsx +71 -47
  87. package/src/client/routes/FrustrationRoute.tsx +131 -116
  88. package/src/client/routes/GainRoute.tsx +42 -30
  89. package/src/client/routes/ModelsRoute.tsx +87 -71
  90. package/src/client/routes/OverviewRoute.tsx +107 -62
  91. package/src/client/routes/ProjectsRoute.tsx +71 -45
  92. package/src/client/routes/ProvidersRoute.tsx +149 -113
  93. package/src/client/routes/RequestsRoute.tsx +68 -45
  94. package/src/client/routes/ToolsRoute.tsx +105 -92
  95. package/src/client/routes/TracesRoute.tsx +25 -23
  96. package/src/client/styles.css +4 -2
  97. package/src/client/traces/AggregatesPanel.tsx +19 -8
  98. package/src/client/traces/Minimap.tsx +65 -27
  99. package/src/client/traces/SpanDrawer.tsx +46 -21
  100. package/src/client/traces/SummaryStrip.tsx +27 -11
  101. package/src/client/traces/TimelineCanvas.tsx +245 -116
  102. package/src/client/traces/TraceView.tsx +51 -36
  103. package/src/client/traces/TranscriptList.tsx +20 -11
  104. package/src/client/traces/traces.css +26 -0
  105. package/src/client/ui/JsonBlock.tsx +3 -2
  106. package/src/client/ui/Modal.tsx +3 -2
  107. package/src/client/ui/RequestDrawer.tsx +33 -30
  108. package/src/client/ui/SearchInput.tsx +2 -1
  109. package/src/client/ui/States.tsx +3 -2
  110. package/src/client/ui/Table.tsx +7 -3
  111. package/src/db.ts +150 -135
  112. package/src/embedded-client.ts +9 -0
  113. package/src/frustration.ts +9 -30
  114. package/src/live.ts +36 -11
  115. package/src/locale.ts +29 -0
  116. package/src/parser.ts +37 -72
  117. package/src/port-conflict.ts +1 -1
  118. package/src/prompts/judge/frustration.json +19 -0
  119. package/src/rollup.ts +41 -20
  120. package/src/server.ts +67 -27
  121. package/src/shared-types.ts +7 -0
  122. package/src/trace.ts +54 -40
  123. package/src/types.ts +12 -0
  124. package/src/user-metrics.ts +220 -6
@@ -61,6 +61,10 @@ export interface UserMessageMetrics {
61
61
  *
62
62
  * Bare `still` / `again` are too ambiguous to count alone (they show up
63
63
  * in normal speech like "try again" or "still works").
64
+ *
65
+ * Also counts the Chinese recall/still markers (`我说过`, `还是不行`, `又错了`)
66
+ * and whole-message repetition of the previous user message (`prevText`),
67
+ * which is the signal that survives both languages and voice input.
64
68
  */
65
69
  repetition: number;
66
70
  /**
@@ -69,8 +73,24 @@ export interface UserMessageMetrics {
69
73
  * - `you (didn't|did not|broke|missed|forgot|keep|always|never|still|ignored)`
70
74
  * - `why (would|did) (you|u)`
71
75
  * - sentence-leading `stop <verb>ing` imperatives
76
+ * - the Chinese forms (`你怎么又`, `你没改`, `我明明说过`, `不是让你`)
72
77
  */
73
78
  blame: number;
79
+ /**
80
+ * Emphatic markers the caps-based {@link yelling} cannot see, because they
81
+ * are how Chinese - and any voice-transcribed text - shouts: full-width
82
+ * `!?` bursts, a Han character or short word repeated three or more times
83
+ * (`不不不`, `不对不对不对`), and elongated interjections (`啊啊啊`).
84
+ * English text scores 0 here; its shouting is already in {@link yelling}.
85
+ */
86
+ emphasis: number;
87
+ /**
88
+ * 1 when the prose is dominated by a script no shipped lexicon covers
89
+ * (Kana, Hangul, Cyrillic, Arabic, …). The other counts are then *not*
90
+ * evidence of calm - they are evidence of a missing lexicon, and the
91
+ * dashboard reports those messages as unscored rather than as content.
92
+ */
93
+ unsupportedScript: number;
74
94
  }
75
95
 
76
96
  /**
@@ -517,6 +537,171 @@ const BLAME_WHY_RE = /\bwhy\s+(?:would|did)\s+(?:you|u)\b/gi;
517
537
  // to start a sentence (line start or after a sentence-terminating punctuator).
518
538
  const BLAME_STOP_RE = /(?:^|(?<=[.!?\n]))\s*stop\s+\w+ing\b/gim;
519
539
 
540
+ // --- Chinese and voice-input signals ----------------------------------------
541
+ // Chinese has no word boundaries and no letter case, so every pattern below is
542
+ // boundary-free (`\b` never fires between two Han characters) and independent
543
+ // of punctuation. That independence is the point: most Chinese traffic reaches
544
+ // the agent through voice transcription, which emits one long unpunctuated
545
+ // line and renders swearing as whichever homophone the recognizer chose
546
+ // (`卧槽`/`我艹`/`淦`). A pattern that needed `!` or a space would score zero on
547
+ // exactly the messages this layer exists for.
548
+ //
549
+ // Every list is deliberately narrow: a term earns its place only when its
550
+ // dominant use in a coding chat is frustration. So `沙雕` (playful) and `呵呵`
551
+ // (merely dry) are out, bare `草`/`靠` are out (`草莓`, `草稿`, `依靠` are far
552
+ // more common), and repeated agreement forms (`好好好`, `对对对`, `哈哈哈`) are
553
+ // out because they are at least as often genuine.
554
+ const ZH_PROFANITY: readonly string[] = [
555
+ "傻逼",
556
+ "傻b",
557
+ "煞笔",
558
+ "傻x",
559
+ "智障",
560
+ "弱智",
561
+ "废物",
562
+ "垃圾",
563
+ "滚蛋",
564
+ "去死",
565
+ "妈的",
566
+ "他妈的",
567
+ "特么",
568
+ "卧槽",
569
+ "我操",
570
+ "我艹",
571
+ "我草",
572
+ "操你",
573
+ "草你",
574
+ "艹你",
575
+ "操蛋",
576
+ "淦",
577
+ "我靠",
578
+ "哇靠",
579
+ "靠北",
580
+ "狗屎",
581
+ "什么破",
582
+ "破玩意",
583
+ "烂死",
584
+ ];
585
+ const ZH_PROFANITY_RE = new RegExp(ZH_PROFANITY.join("|"), "gi");
586
+
587
+ // Exasperation. `唉`/`哎` are the Chinese `ugh`; the internet registers
588
+ // (`服了`, `醉了`, `无语`, `离谱`, `逆天`, `绝了`) are what a Chinese user
589
+ // actually types when the agent has failed twice in a row. `干什么` is out on
590
+ // purpose: `这个是干什么用的` is an ordinary question, while its exasperated twin
591
+ // `搞什么呢` is in.
592
+ const ZH_ANGUISH: readonly string[] = [
593
+ "哎呀",
594
+ "哎哟",
595
+ "哎呦",
596
+ "天哪",
597
+ "天啊",
598
+ "妈呀",
599
+ "我的天",
600
+ "不是吧",
601
+ "不会吧",
602
+ "救命",
603
+ "崩溃",
604
+ "崩了",
605
+ "麻了",
606
+ "服了",
607
+ "服气",
608
+ "醉了",
609
+ "无语",
610
+ "离谱",
611
+ "逆天",
612
+ "绝了",
613
+ "要命",
614
+ "疯了",
615
+ "烦死",
616
+ "气死",
617
+ "急死",
618
+ "完蛋",
619
+ "搞什么",
620
+ "什么鬼",
621
+ "咋回事",
622
+ ];
623
+ const ZH_ANGUISH_RE = new RegExp(ZH_ANGUISH.join("|"), "g");
624
+
625
+ // Corrective negation. Anchored to the start of the prose body like the
626
+ // English lead (`不对`, `错了`, `搞错`); mid-sentence occurrences are usually
627
+ // ordinary description. Bare `不是` only counts as the interjection form -
628
+ // when nothing Han follows it - so the very common `不是我做的` / `不是这个
629
+ // 问题` stay neutral.
630
+ const ZH_NEGATION_LEAD_RE =
631
+ /^[ \t]*(?:不对|不是(?![\p{Script=Han}])|错了|搞错|弄错|怎么可能|怎么会|咋会|什么鬼|不可能|哪里对|哪儿对)/gu;
632
+ const ZH_NEGATION_PHRASE_RE =
633
+ /(?:不是(?:这个|这|那个|那)?意思|搞错(?:了|了吧)?|弄错(?:了)?|我说的是|我指的是|我要的是|不应该(?:是)?这样|不该这样|(?:又|还是)(?:错|不对)(?:了)?)/g;
634
+
635
+ // "You already made me say this." Pronoun-free forms (`又错了`, `还是不行`)
636
+ // matter more than the ones that need `你`, because a recognizer may drop the
637
+ // subject entirely.
638
+ const ZH_REPETITION_RE =
639
+ /(?:我说过|我说了|我早就说|我已经说|跟你说过?|重复(?:了)?|又(?:错|来|是|搞|弄|要|不)|还是(?:不行|不对|没|这样|那样|一样|老样子)|(?:依旧|依然|仍然)(?:没|不|一样|这样|错|是)|老样子|没变|没改|又来了|第[二三]次|再来一次|重来|重新来过?|白做|白干|白弄|浪费时间)/g;
640
+
641
+ // Direct reproach. `你怎么` needs a follow-up that only a complaint uses -
642
+ // `你怎么看` (what do you think?) is neutral - and `你能不能` needs the
643
+ // exasperated tail (`认真点`, `别…`).
644
+ const ZH_BLAME_RE =
645
+ /(?:你怎么(?:又|还|这么|那么|回事|搞|弄|能|不|没|老是|总是)|你干嘛|你干吗|你搞的|你弄的|你没(?:有)?(?:改|做|弄|看|听|按)|你(?:又|老是|总是)|谁让你|我让你|我明明(?:说|要|让|告诉|讲)|不是让你|我是不是(?:说|讲)过|你到底(?:想|要|在)|你能不能(?:认真|好好|别))/g;
646
+
647
+ // Shouting without capital letters: a denial/vent character run (`不不不`,
648
+ // `啊啊啊`, `唉唉唉`) or a repeated short denial (`不对不对`, `不是不是`).
649
+ // Long vowel-free Han runs are unambiguous; 2x repeats of ordinary words
650
+ // (`谢谢`) are common, so runs need three in a row and words come from a
651
+ // negative list.
652
+ const ZH_REPEAT_CHAR_RE = /([不别没错唉哎啊呀呜])\1{2,}/gu;
653
+ const ZH_REPEAT_WORD_RE = /(不对|不是|不行|不要|没用|没变|又错)(?:\1)+/gu;
654
+ // Full-width bursts (`???`, `?!?`). The first mark must be full-width so a
655
+ // pure-ASCII `!!!` stays where it belongs, in the English `anguish` bucket.
656
+ const ZH_DRAMA_RE = /[!?][!?!?]{2,}/g;
657
+
658
+ // Scripts no shipped lexicon covers. Kana/Hangul/Cyrillic/Arabic/… text scores
659
+ // zero on every signal above, and a dashboard that reported those zeros as
660
+ // "not annoyed" would be lying about its coverage.
661
+ const SCRIPT_LETTER_RE = /\p{L}/gu;
662
+ const UNSUPPORTED_SCRIPT_RE =
663
+ /[\p{Script=Hiragana}\p{Script=Katakana}\p{Script=Hangul}\p{Script=Cyrillic}\p{Script=Arabic}\p{Script=Hebrew}\p{Script=Thai}\p{Script=Devanagari}\p{Script=Greek}]/gu;
664
+ const UNSUPPORTED_SCRIPT_SHARE = 0.3;
665
+
666
+ /** 1 when the prose leans on a script the lexicons do not cover. */
667
+ function detectUnsupportedScript(prose: string): number {
668
+ const letters = countMatches(prose, SCRIPT_LETTER_RE);
669
+ if (letters < 4) return 0;
670
+ return countMatches(prose, UNSUPPORTED_SCRIPT_RE) / letters > UNSUPPORTED_SCRIPT_SHARE ? 1 : 0;
671
+ }
672
+
673
+ /** Whole-message repetition: normalised character-bigram Jaccard similarity. */
674
+ const REPEATED_MESSAGE_SIMILARITY = 0.7;
675
+ const REPEATED_MESSAGE_MIN_CHARS = 8;
676
+
677
+ function normalizeForComparison(text: string): string {
678
+ return text.replace(/[\s\p{P}\p{S}]/gu, "").toLowerCase();
679
+ }
680
+
681
+ function bigrams(normalized: string): Set<string> {
682
+ const grams = new Set<string>();
683
+ for (let i = 0; i + 1 < normalized.length; i++) grams.add(normalized.slice(i, i + 2));
684
+ return grams;
685
+ }
686
+
687
+ /**
688
+ * 1 when the message says essentially the same thing as the one before it -
689
+ * the one frustration signal that needs no lexicon and therefore works for
690
+ * any language and survives every transcription error.
691
+ */
692
+ function detectRepeatedMessage(prose: string, prevProse: string): number {
693
+ const current = normalizeForComparison(prose);
694
+ const previous = normalizeForComparison(prevProse);
695
+ if (current.length < REPEATED_MESSAGE_MIN_CHARS || previous.length < REPEATED_MESSAGE_MIN_CHARS) return 0;
696
+ if (current === previous) return 1;
697
+ const a = bigrams(current);
698
+ const b = bigrams(previous);
699
+ if (a.size === 0 || b.size === 0) return 0;
700
+ let shared = 0;
701
+ for (const gram of a) if (b.has(gram)) shared++;
702
+ return shared / (a.size + b.size - shared) >= REPEATED_MESSAGE_SIMILARITY ? 1 : 0;
703
+ }
704
+
520
705
  // Stripped from the analyzed body before scoring so that structured
521
706
  // content (code, XML/HTML, URLs, file mentions, quoted blocks) doesn't
522
707
  // pollute behavior signals. We replace with a newline so line counts
@@ -637,7 +822,12 @@ function countNonEmptyLines(text: string): number {
637
822
  *
638
823
  * `text` may be empty or whitespace; in that case every metric is 0.
639
824
  */
640
- export function computeUserMessageMetrics(text: string): UserMessageMetrics {
825
+ export function computeUserMessageMetrics(
826
+ text: string,
827
+ options: {
828
+ /** The user message before this one in the same session, for whole-message repetition. */ prevText?: string;
829
+ } = {},
830
+ ): UserMessageMetrics {
641
831
  const trimmed = text.trim();
642
832
  if (!trimmed) {
643
833
  return {
@@ -649,6 +839,8 @@ export function computeUserMessageMetrics(text: string): UserMessageMetrics {
649
839
  negation: 0,
650
840
  repetition: 0,
651
841
  blame: 0,
842
+ emphasis: 0,
843
+ unsupportedScript: 0,
652
844
  };
653
845
  }
654
846
 
@@ -669,6 +861,8 @@ export function computeUserMessageMetrics(text: string): UserMessageMetrics {
669
861
  negation: 0,
670
862
  repetition: 0,
671
863
  blame: 0,
864
+ emphasis: 0,
865
+ unsupportedScript: detectUnsupportedScript(prose),
672
866
  };
673
867
  }
674
868
 
@@ -676,22 +870,40 @@ export function computeUserMessageMetrics(text: string): UserMessageMetrics {
676
870
  countMatches(prose, DRAMA_RE) +
677
871
  countMatches(prose, ANGUISH_RE) +
678
872
  countMatches(prose, DUDE_RE) +
679
- countMatches(prose, SAD_EMOTICON_RE);
873
+ countMatches(prose, SAD_EMOTICON_RE) +
874
+ countMatches(prose, ZH_ANGUISH_RE);
680
875
 
681
- const negation = countMatches(prose, NEGATION_LEAD_RE) + countMatches(prose, NEGATION_PHRASE_RE);
682
- const repetition = countMatches(prose, REPETITION_RECALL_RE) + countMatches(prose, REPETITION_STILL_RE);
876
+ const negation =
877
+ countMatches(prose, NEGATION_LEAD_RE) +
878
+ countMatches(prose, NEGATION_PHRASE_RE) +
879
+ countMatches(prose, ZH_NEGATION_LEAD_RE) +
880
+ countMatches(prose, ZH_NEGATION_PHRASE_RE);
881
+ const repetition =
882
+ countMatches(prose, REPETITION_RECALL_RE) +
883
+ countMatches(prose, REPETITION_STILL_RE) +
884
+ countMatches(prose, ZH_REPETITION_RE) +
885
+ detectRepeatedMessage(prose, options.prevText ?? "");
683
886
  const blame =
684
- countMatches(prose, BLAME_YOU_RE) + countMatches(prose, BLAME_WHY_RE) + countMatches(prose, BLAME_STOP_RE);
887
+ countMatches(prose, BLAME_YOU_RE) +
888
+ countMatches(prose, BLAME_WHY_RE) +
889
+ countMatches(prose, BLAME_STOP_RE) +
890
+ countMatches(prose, ZH_BLAME_RE);
891
+ const emphasis =
892
+ countMatches(prose, ZH_REPEAT_CHAR_RE) +
893
+ countMatches(prose, ZH_REPEAT_WORD_RE) +
894
+ countMatches(prose, ZH_DRAMA_RE);
685
895
 
686
896
  return {
687
897
  chars,
688
898
  words,
689
899
  yelling: countYellingSentences(prose),
690
- profanity: countMatches(prose, PROFANITY_RE),
900
+ profanity: countMatches(prose, PROFANITY_RE) + countMatches(prose, ZH_PROFANITY_RE),
691
901
  anguish,
692
902
  negation,
693
903
  repetition,
694
904
  blame,
905
+ emphasis,
906
+ unsupportedScript: detectUnsupportedScript(prose),
695
907
  };
696
908
  }
697
909
 
@@ -705,4 +917,6 @@ export const EMPTY_USER_METRICS: UserMessageMetrics = Object.freeze({
705
917
  negation: 0,
706
918
  repetition: 0,
707
919
  blame: 0,
920
+ emphasis: 0,
921
+ unsupportedScript: 0,
708
922
  });