pythx-cli 0.0.4 → 0.0.6
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/cli.js +146 -13
- package/package.json +1 -1
package/dist/cli.js
CHANGED
|
@@ -1,4 +1,9 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
+
var __defProp = Object.defineProperty;
|
|
3
|
+
var __export = (target, all) => {
|
|
4
|
+
for (var name in all)
|
|
5
|
+
__defProp(target, name, { get: all[name], enumerable: true });
|
|
6
|
+
};
|
|
2
7
|
|
|
3
8
|
// src/cli.tsx
|
|
4
9
|
import { render } from "ink";
|
|
@@ -444,6 +449,11 @@ import { drizzle } from "drizzle-orm/postgres-js";
|
|
|
444
449
|
import postgres from "postgres";
|
|
445
450
|
|
|
446
451
|
// ../core/src/db/schema.ts
|
|
452
|
+
var schema_exports = {};
|
|
453
|
+
__export(schema_exports, {
|
|
454
|
+
posts: () => posts,
|
|
455
|
+
snapshots: () => snapshots
|
|
456
|
+
});
|
|
447
457
|
import {
|
|
448
458
|
pgTable,
|
|
449
459
|
serial,
|
|
@@ -507,20 +517,106 @@ var posts = pgTable(
|
|
|
507
517
|
]
|
|
508
518
|
);
|
|
509
519
|
|
|
520
|
+
// ../core/src/db/client.ts
|
|
521
|
+
var db = null;
|
|
522
|
+
function getDb() {
|
|
523
|
+
if (db) return db;
|
|
524
|
+
const connectionString = process.env.POSTGRES_URL;
|
|
525
|
+
if (!connectionString) {
|
|
526
|
+
throw new Error("POSTGRES_URL environment variable is required");
|
|
527
|
+
}
|
|
528
|
+
const client = postgres(connectionString, { prepare: false });
|
|
529
|
+
db = drizzle(client, { schema: schema_exports });
|
|
530
|
+
return db;
|
|
531
|
+
}
|
|
532
|
+
|
|
510
533
|
// ../core/src/db/store.ts
|
|
511
534
|
import { desc, eq } from "drizzle-orm";
|
|
535
|
+
async function persistAnalysis(entityId, snapshot, posts2, modelId) {
|
|
536
|
+
const db2 = getDb();
|
|
537
|
+
await db2.insert(schema_exports.snapshots).values({
|
|
538
|
+
entityId,
|
|
539
|
+
source: null,
|
|
540
|
+
avgScore: snapshot.averageScore,
|
|
541
|
+
positiveCount: snapshot.distribution.positive,
|
|
542
|
+
negativeCount: snapshot.distribution.negative,
|
|
543
|
+
neutralCount: snapshot.distribution.neutral,
|
|
544
|
+
totalCount: snapshot.distribution.total
|
|
545
|
+
});
|
|
546
|
+
for (const breakdown of snapshot.bySource) {
|
|
547
|
+
await db2.insert(schema_exports.snapshots).values({
|
|
548
|
+
entityId,
|
|
549
|
+
source: breakdown.source,
|
|
550
|
+
avgScore: breakdown.averageScore,
|
|
551
|
+
positiveCount: breakdown.distribution.positive,
|
|
552
|
+
negativeCount: breakdown.distribution.negative,
|
|
553
|
+
neutralCount: breakdown.distribution.neutral,
|
|
554
|
+
totalCount: breakdown.distribution.total
|
|
555
|
+
});
|
|
556
|
+
}
|
|
557
|
+
if (posts2.length > 0) {
|
|
558
|
+
await db2.insert(schema_exports.posts).values(
|
|
559
|
+
posts2.map((post) => ({
|
|
560
|
+
externalId: post.id,
|
|
561
|
+
source: post.source,
|
|
562
|
+
entityId,
|
|
563
|
+
text: post.text,
|
|
564
|
+
authorId: post.authorId,
|
|
565
|
+
authorUsername: post.authorUsername ?? null,
|
|
566
|
+
url: post.url,
|
|
567
|
+
createdAt: new Date(post.createdAt),
|
|
568
|
+
upvotes: post.metrics.upvotes,
|
|
569
|
+
comments: post.metrics.comments,
|
|
570
|
+
shares: post.metrics.shares,
|
|
571
|
+
sentimentLabel: post.sentiment.label,
|
|
572
|
+
sentimentConfidence: post.sentiment.score,
|
|
573
|
+
modelId
|
|
574
|
+
}))
|
|
575
|
+
).onConflictDoNothing({ target: [schema_exports.posts.externalId, schema_exports.posts.source] });
|
|
576
|
+
}
|
|
577
|
+
}
|
|
578
|
+
async function persistEntityAnalysis(analysis, modelId) {
|
|
579
|
+
await persistAnalysis(
|
|
580
|
+
analysis.entity.id,
|
|
581
|
+
analysis.snapshot,
|
|
582
|
+
analysis.posts,
|
|
583
|
+
modelId
|
|
584
|
+
);
|
|
585
|
+
}
|
|
586
|
+
async function loadCachedPosts(entityId, limit = 50) {
|
|
587
|
+
const db2 = getDb();
|
|
588
|
+
const rows = await db2.select().from(schema_exports.posts).where(eq(schema_exports.posts.entityId, entityId)).orderBy(desc(schema_exports.posts.createdAt)).limit(limit);
|
|
589
|
+
return rows.map((row) => ({
|
|
590
|
+
id: row.externalId,
|
|
591
|
+
source: row.source,
|
|
592
|
+
text: row.text,
|
|
593
|
+
authorId: row.authorId ?? "",
|
|
594
|
+
authorUsername: row.authorUsername ?? void 0,
|
|
595
|
+
createdAt: row.createdAt.toISOString(),
|
|
596
|
+
url: row.url ?? "",
|
|
597
|
+
metrics: {
|
|
598
|
+
upvotes: row.upvotes ?? 0,
|
|
599
|
+
comments: row.comments ?? 0,
|
|
600
|
+
shares: row.shares ?? 0
|
|
601
|
+
},
|
|
602
|
+
sentiment: {
|
|
603
|
+
label: row.sentimentLabel,
|
|
604
|
+
score: row.sentimentConfidence
|
|
605
|
+
}
|
|
606
|
+
}));
|
|
607
|
+
}
|
|
512
608
|
|
|
513
609
|
// src/hooks/use-live-data.ts
|
|
514
610
|
var POLL_INTERVAL = 30 * 6e4;
|
|
515
|
-
function useLiveData(
|
|
611
|
+
function useLiveData(apiUrl2) {
|
|
516
612
|
const [data, setData] = useState2([]);
|
|
517
613
|
const [loading, setLoading] = useState2(true);
|
|
518
614
|
const [error, setError] = useState2(null);
|
|
519
615
|
const [lastUpdated, setLastUpdated] = useState2(null);
|
|
520
616
|
const fetchViaApi = useCallback(async () => {
|
|
521
|
-
if (!
|
|
617
|
+
if (!apiUrl2) return;
|
|
522
618
|
try {
|
|
523
|
-
const res = await fetch(`${
|
|
619
|
+
const res = await fetch(`${apiUrl2}/api/compare`, {
|
|
524
620
|
method: "POST",
|
|
525
621
|
headers: { "Content-Type": "application/json" },
|
|
526
622
|
body: JSON.stringify({
|
|
@@ -541,7 +637,22 @@ function useLiveData(apiUrl) {
|
|
|
541
637
|
} finally {
|
|
542
638
|
setLoading(false);
|
|
543
639
|
}
|
|
544
|
-
}, [
|
|
640
|
+
}, [apiUrl2]);
|
|
641
|
+
const loadFromCache = useCallback(async () => {
|
|
642
|
+
const results = [];
|
|
643
|
+
for (const entity of DEFAULT_ENTITIES) {
|
|
644
|
+
const cached = await loadCachedPosts(entity.id, 50);
|
|
645
|
+
if (cached.length === 0) continue;
|
|
646
|
+
const snapshot = aggregate(cached);
|
|
647
|
+
results.push({
|
|
648
|
+
entity,
|
|
649
|
+
snapshot,
|
|
650
|
+
posts: cached,
|
|
651
|
+
fetchedAt: cached[0]?.sentiment ? (/* @__PURE__ */ new Date()).toISOString() : (/* @__PURE__ */ new Date()).toISOString()
|
|
652
|
+
});
|
|
653
|
+
}
|
|
654
|
+
return results;
|
|
655
|
+
}, []);
|
|
545
656
|
const fetchDirect = useCallback(async () => {
|
|
546
657
|
try {
|
|
547
658
|
const results = [];
|
|
@@ -554,23 +665,38 @@ function useLiveData(apiUrl) {
|
|
|
554
665
|
allClassified.push(...classified);
|
|
555
666
|
}
|
|
556
667
|
const snapshot = aggregate(allClassified);
|
|
557
|
-
|
|
668
|
+
const analysis = {
|
|
558
669
|
entity,
|
|
559
670
|
snapshot,
|
|
560
671
|
posts: allClassified,
|
|
561
672
|
fetchedAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
562
|
-
}
|
|
673
|
+
};
|
|
674
|
+
results.push(analysis);
|
|
675
|
+
try {
|
|
676
|
+
await persistEntityAnalysis(analysis, "hf-default");
|
|
677
|
+
} catch {
|
|
678
|
+
}
|
|
563
679
|
}
|
|
564
680
|
setData(results);
|
|
565
681
|
setLastUpdated(/* @__PURE__ */ new Date());
|
|
566
682
|
setError(null);
|
|
567
683
|
} catch (err) {
|
|
684
|
+
try {
|
|
685
|
+
const cached = await loadFromCache();
|
|
686
|
+
if (cached.length > 0) {
|
|
687
|
+
setData(cached);
|
|
688
|
+
setLastUpdated(/* @__PURE__ */ new Date());
|
|
689
|
+
setError("Live fetch failed \u2014 showing cached data");
|
|
690
|
+
return;
|
|
691
|
+
}
|
|
692
|
+
} catch {
|
|
693
|
+
}
|
|
568
694
|
setError(err instanceof Error ? err.message : "Analysis failed");
|
|
569
695
|
} finally {
|
|
570
696
|
setLoading(false);
|
|
571
697
|
}
|
|
572
|
-
}, []);
|
|
573
|
-
const fetchData =
|
|
698
|
+
}, [loadFromCache]);
|
|
699
|
+
const fetchData = apiUrl2 ? fetchViaApi : fetchDirect;
|
|
574
700
|
useEffect2(() => {
|
|
575
701
|
fetchData();
|
|
576
702
|
const interval = setInterval(fetchData, POLL_INTERVAL);
|
|
@@ -581,8 +707,8 @@ function useLiveData(apiUrl) {
|
|
|
581
707
|
|
|
582
708
|
// src/app.tsx
|
|
583
709
|
import { jsx as jsx5, jsxs as jsxs5 } from "react/jsx-runtime";
|
|
584
|
-
function App({ apiUrl }) {
|
|
585
|
-
const { data, loading, error, lastUpdated, refresh } = useLiveData(
|
|
710
|
+
function App({ apiUrl: apiUrl2 }) {
|
|
711
|
+
const { data, loading, error, lastUpdated, refresh } = useLiveData(apiUrl2);
|
|
586
712
|
const [activeIndex, setActiveIndex] = useState3(0);
|
|
587
713
|
const [showDetail, setShowDetail] = useState3(true);
|
|
588
714
|
const { exit } = useApp();
|
|
@@ -628,31 +754,38 @@ function App({ apiUrl }) {
|
|
|
628
754
|
|
|
629
755
|
// src/cli.tsx
|
|
630
756
|
import { jsx as jsx6 } from "react/jsx-runtime";
|
|
757
|
+
var DEFAULT_API_URL = "https://pythx.vercel.app";
|
|
631
758
|
var cli = meow(
|
|
632
759
|
`
|
|
633
760
|
Usage
|
|
634
761
|
$ pythx
|
|
635
762
|
|
|
636
763
|
Options
|
|
637
|
-
--api-url URL of the Pythx web API (default:
|
|
764
|
+
--api-url URL of the Pythx web API (default: ${DEFAULT_API_URL})
|
|
765
|
+
--direct Use direct mode (requires HF_API_TOKEN and POSTGRES_URL env vars)
|
|
638
766
|
|
|
639
767
|
Examples
|
|
640
768
|
$ pythx
|
|
641
769
|
$ pythx --api-url http://localhost:3000
|
|
642
|
-
$ pythx --
|
|
770
|
+
$ pythx --direct
|
|
643
771
|
`,
|
|
644
772
|
{
|
|
645
773
|
importMeta: import.meta,
|
|
646
774
|
flags: {
|
|
647
775
|
apiUrl: {
|
|
648
776
|
type: "string"
|
|
777
|
+
},
|
|
778
|
+
direct: {
|
|
779
|
+
type: "boolean",
|
|
780
|
+
default: false
|
|
649
781
|
}
|
|
650
782
|
}
|
|
651
783
|
}
|
|
652
784
|
);
|
|
785
|
+
var apiUrl = cli.flags.direct ? void 0 : cli.flags.apiUrl ?? DEFAULT_API_URL;
|
|
653
786
|
process.stdout.write("\x1B[?1049h");
|
|
654
787
|
process.stdout.write("\x1B[H");
|
|
655
|
-
var instance = render(/* @__PURE__ */ jsx6(App, { apiUrl
|
|
788
|
+
var instance = render(/* @__PURE__ */ jsx6(App, { apiUrl }), { patchConsole: false });
|
|
656
789
|
instance.waitUntilExit().then(() => {
|
|
657
790
|
process.stdout.write("\x1B[?1049l");
|
|
658
791
|
});
|