birdclaw 0.6.0 → 0.8.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 (62) hide show
  1. package/CHANGELOG.md +64 -0
  2. package/README.md +32 -2
  3. package/package.json +29 -30
  4. package/scripts/build-docs-site.mjs +39 -12
  5. package/src/cli.ts +457 -26
  6. package/src/components/AppNav.tsx +10 -0
  7. package/src/components/MarkdownViewer.tsx +438 -72
  8. package/src/components/ProfileAnalysisStream.tsx +428 -0
  9. package/src/components/ProfilePreview.tsx +120 -9
  10. package/src/components/SavedTimelineView.tsx +30 -8
  11. package/src/components/SyncNowButton.tsx +5 -2
  12. package/src/components/TimelineCard.tsx +20 -4
  13. package/src/components/TimelineRouteFrame.tsx +16 -0
  14. package/src/components/TweetRichText.tsx +36 -12
  15. package/src/components/useTimelineRouteData.ts +74 -6
  16. package/src/lib/account-sync-job.ts +15 -3
  17. package/src/lib/archive-finder.ts +1 -1
  18. package/src/lib/archive-import.ts +245 -7
  19. package/src/lib/authored-live.ts +1 -0
  20. package/src/lib/avatar-cache.ts +50 -0
  21. package/src/lib/backup.ts +4 -3
  22. package/src/lib/bird.ts +33 -0
  23. package/src/lib/config.ts +35 -2
  24. package/src/lib/data-sources.ts +219 -0
  25. package/src/lib/db.ts +62 -1
  26. package/src/lib/geocoding.ts +296 -0
  27. package/src/lib/link-insights.ts +2 -0
  28. package/src/lib/location.ts +137 -0
  29. package/src/lib/mention-threads-live.ts +94 -1
  30. package/src/lib/mentions-live.ts +187 -40
  31. package/src/lib/network-map.ts +382 -0
  32. package/src/lib/period-digest.ts +468 -29
  33. package/src/lib/profile-analysis.ts +1272 -0
  34. package/src/lib/profile-bio-entities.ts +1 -1
  35. package/src/lib/queries.ts +14 -4
  36. package/src/lib/search-discussion.ts +1016 -0
  37. package/src/lib/timeline-live.ts +272 -19
  38. package/src/lib/tweet-account-edges.ts +2 -0
  39. package/src/lib/tweet-render.ts +141 -1
  40. package/src/lib/tweet-search-live.ts +565 -0
  41. package/src/lib/types.ts +37 -2
  42. package/src/lib/ui.ts +1 -1
  43. package/src/lib/web-sync.ts +7 -2
  44. package/src/lib/xurl-rate-limits.ts +267 -0
  45. package/src/lib/xurl.ts +551 -41
  46. package/src/routeTree.gen.ts +231 -0
  47. package/src/routes/__root.tsx +5 -6
  48. package/src/routes/api/data-sources.tsx +24 -0
  49. package/src/routes/api/network-map.tsx +55 -0
  50. package/src/routes/api/period-digest.tsx +29 -3
  51. package/src/routes/api/profile-analysis.tsx +152 -0
  52. package/src/routes/api/query.tsx +1 -0
  53. package/src/routes/api/search-discussion.tsx +169 -0
  54. package/src/routes/api/xurl-rate-limits.tsx +24 -0
  55. package/src/routes/data-sources.tsx +257 -0
  56. package/src/routes/discuss.tsx +419 -0
  57. package/src/routes/network-map.tsx +1035 -0
  58. package/src/routes/profile-analyze.tsx +112 -0
  59. package/src/routes/profiles.$handle.tsx +228 -0
  60. package/src/routes/rate-limits.tsx +309 -0
  61. package/src/routes/today.tsx +22 -8
  62. package/src/styles.css +22 -0
@@ -51,6 +51,8 @@ function digestUrl(
51
51
  const url = new URL("/api/period-digest", window.location.origin);
52
52
  url.searchParams.set("period", period);
53
53
  url.searchParams.set("includeDms", String(includeDms));
54
+ url.searchParams.set("maxTweets", "5000");
55
+ url.searchParams.set("maxLinks", "20");
54
56
  if (refresh) {
55
57
  url.searchParams.set("refresh", "true");
56
58
  }
@@ -175,6 +177,7 @@ function useDigestStream(period: PeriodOption, includeDms: boolean) {
175
177
  const [result, setResult] = useState<PeriodDigestRunResult | null>(null);
176
178
  const [error, setError] = useState<string | null>(null);
177
179
  const [loading, setLoading] = useState(false);
180
+ const [status, setStatus] = useState("Starting digest");
178
181
  const abortRef = useRef<AbortController | null>(null);
179
182
  const requestIdRef = useRef(0);
180
183
  const hydratedHandlesRef = useRef(new Set<string>());
@@ -195,6 +198,7 @@ function useDigestStream(period: PeriodOption, includeDms: boolean) {
195
198
  setContext(null);
196
199
  setResult(null);
197
200
  setError(null);
201
+ setStatus("Starting digest");
198
202
  setLoading(true);
199
203
 
200
204
  fetch(digestUrl(period, includeDms, refresh), {
@@ -222,16 +226,27 @@ function useDigestStream(period: PeriodOption, includeDms: boolean) {
222
226
  if (line) {
223
227
  const event = JSON.parse(line) as PeriodDigestStreamEvent;
224
228
  if (!isActiveRequest()) return;
225
- if (event.type === "start") {
229
+ if (event.type === "status") {
230
+ setStatus(
231
+ event.detail
232
+ ? `${event.label} · ${event.detail}`
233
+ : event.label,
234
+ );
235
+ } else if (event.type === "start") {
226
236
  setContext(event.context);
227
237
  } else if (event.type === "delta") {
238
+ setStatus("Streaming AI summary");
228
239
  setMarkdown((current) => current + event.delta);
229
240
  } else if (event.type === "done") {
230
241
  setResult(event.result);
231
242
  setContext(event.result.context);
232
243
  setMarkdown(event.result.markdown);
244
+ setStatus(
245
+ event.result.cached ? "Loaded cached report" : "Ready",
246
+ );
233
247
  } else if (event.type === "error") {
234
248
  setError(event.error);
249
+ setStatus("Digest failed");
235
250
  }
236
251
  }
237
252
  newline = buffer.indexOf("\n");
@@ -243,6 +258,7 @@ function useDigestStream(period: PeriodOption, includeDms: boolean) {
243
258
  .catch((cause: unknown) => {
244
259
  if (!isActiveRequest()) return;
245
260
  setError(cause instanceof Error ? cause.message : "Digest failed");
261
+ setStatus("Digest failed");
246
262
  })
247
263
  .finally(() => {
248
264
  if (isActiveRequest()) {
@@ -342,16 +358,14 @@ function useDigestStream(period: PeriodOption, includeDms: boolean) {
342
358
  };
343
359
  }, [result]);
344
360
 
345
- return { context, error, loading, markdown, result, run };
361
+ return { context, error, loading, markdown, result, run, status };
346
362
  }
347
363
 
348
364
  function TodayRoute() {
349
365
  const [period, setPeriod] = useState<PeriodOption>("today");
350
366
  const [includeDms, setIncludeDms] = useState(false);
351
- const { context, error, loading, markdown, result, run } = useDigestStream(
352
- period,
353
- includeDms,
354
- );
367
+ const { context, error, loading, markdown, result, run, status } =
368
+ useDigestStream(period, includeDms);
355
369
  const sourceLabel = useMemo(
356
370
  () => formatCounts(result?.context ?? context),
357
371
  [context, result],
@@ -419,7 +433,7 @@ function TodayRoute() {
419
433
  <Sparkles className="size-4" aria-hidden="true" />
420
434
  )}
421
435
  {loading
422
- ? "Streaming report"
436
+ ? status
423
437
  : result
424
438
  ? `${result.cached ? "Cached" : "Ready"} · ${result.context.window.label}`
425
439
  : "Ready"}
@@ -433,7 +447,7 @@ function TodayRoute() {
433
447
  />
434
448
  ) : (
435
449
  <div className="px-4 py-5 text-[14px] text-[var(--ink-soft)]">
436
- Waiting for the first tokens...
450
+ {loading ? status : "Waiting for the first tokens..."}
437
451
  </div>
438
452
  )}
439
453
  </div>
package/src/styles.css CHANGED
@@ -129,6 +129,28 @@ textarea {
129
129
  background: color-mix(in srgb, var(--ink-soft) 55%, transparent);
130
130
  }
131
131
 
132
+ [data-theme="dark"] .mapboxgl-ctrl-group {
133
+ background: var(--bg-elevated);
134
+ border: 1px solid var(--line-strong);
135
+ box-shadow: 0 10px 28px var(--shadow);
136
+ }
137
+
138
+ [data-theme="dark"] .mapboxgl-ctrl-group button {
139
+ background-color: var(--bg-elevated);
140
+ }
141
+
142
+ [data-theme="dark"] .mapboxgl-ctrl-group button + button {
143
+ border-top-color: var(--line);
144
+ }
145
+
146
+ [data-theme="dark"] .mapboxgl-ctrl-group button:not(:disabled):hover {
147
+ background-color: var(--bg-hover);
148
+ }
149
+
150
+ [data-theme="dark"] .mapboxgl-ctrl-group .mapboxgl-ctrl-icon {
151
+ filter: invert(0.88);
152
+ }
153
+
132
154
  @keyframes theme-circle-transition {
133
155
  0% {
134
156
  clip-path: circle(