@opengeni/react 0.1.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.
package/src/index.ts ADDED
@@ -0,0 +1,115 @@
1
+ // @opengeni/react — hooks + styled components on @opengeni/sdk.
2
+ //
3
+ // Import the styles once in your Tailwind entry CSS:
4
+ // @import "@opengeni/react/styles.css";
5
+ // @source "../node_modules/@opengeni/react/src";
6
+
7
+ export type { SessionClientLike } from "./client";
8
+ export { OpenGeniProvider, useOpenGeni, useOpenGeniClient } from "./provider";
9
+ export type { ClientOverride, OpenGeniContextValue, OpenGeniProviderProps } from "./provider";
10
+
11
+ // Hooks
12
+ export { useSession } from "./hooks/use-session";
13
+ export type { UseSessionOptions, UseSessionResult } from "./hooks/use-session";
14
+ export { useSessionEvents } from "./hooks/use-session-events";
15
+ export type { SessionEventsConnectionState, UseSessionEventsOptions, UseSessionEventsResult } from "./hooks/use-session-events";
16
+ export { useComposer, composeSendInput, shouldSubmitOnKey } from "./hooks/use-composer";
17
+ export type { ComposerMode, ComposerSendExtras, ComposerState, UseComposerOptions } from "./hooks/use-composer";
18
+ export { useFileAttachments } from "./hooks/use-file-attachments";
19
+ export type { FileAttachment, UseFileAttachmentsOptions, UseFileAttachmentsResult } from "./hooks/use-file-attachments";
20
+ export {
21
+ useTurnQueue,
22
+ isTurnQueueEvent,
23
+ queueFromTurns,
24
+ activeTurnFromTurns,
25
+ applyTurnEdit,
26
+ applyTurnReorder,
27
+ applyTurnRemoval,
28
+ } from "./hooks/use-turn-queue";
29
+ export type { UseTurnQueueOptions, UseTurnQueueResult } from "./hooks/use-turn-queue";
30
+ export { useGoal, isGoalEvent } from "./hooks/use-goal";
31
+ export type { UseGoalOptions, UseGoalResult } from "./hooks/use-goal";
32
+ export { useSessionControl } from "./hooks/use-session-control";
33
+ export type { UseSessionControlOptions, UseSessionControlResult } from "./hooks/use-session-control";
34
+ export { useScheduledTasks } from "./hooks/use-scheduled-tasks";
35
+ export type { UseScheduledTasksOptions, UseScheduledTasksResult } from "./hooks/use-scheduled-tasks";
36
+ export { useWorkspaceSessions } from "./hooks/use-workspace-sessions";
37
+ export type { UseWorkspaceSessionsOptions, UseWorkspaceSessionsResult } from "./hooks/use-workspace-sessions";
38
+ export { useEnvironments } from "./hooks/use-environments";
39
+ export type { UseEnvironmentsOptions, UseEnvironmentsResult } from "./hooks/use-environments";
40
+ export { usePacks } from "./hooks/use-packs";
41
+ export type { UsePacksOptions, UsePacksResult } from "./hooks/use-packs";
42
+ export { useWorkspaces } from "./hooks/use-workspaces";
43
+ export type { UseWorkspacesOptions, UseWorkspacesResult } from "./hooks/use-workspaces";
44
+ export { useBillingUsage } from "./hooks/use-billing-usage";
45
+ export type { UseBillingUsageOptions, UseBillingUsageResult } from "./hooks/use-billing-usage";
46
+
47
+ // Pending-approvals projection
48
+ export { approvalsFromRequiresAction, projectPendingApprovals } from "./approvals";
49
+ export type { PendingApproval } from "./approvals";
50
+
51
+ // Timeline projection
52
+ export {
53
+ buildTimeline,
54
+ compactPayloadPreview,
55
+ extractSessionRef,
56
+ groupTimeline,
57
+ sessionStatusFromEvents,
58
+ toolDisplayName,
59
+ } from "./timeline";
60
+ export type {
61
+ AgentMessageItem,
62
+ GoalItem,
63
+ NoticeItem,
64
+ ReasoningItem,
65
+ SandboxItem,
66
+ SessionStatusItem,
67
+ TimelineGroup,
68
+ TimelineItem,
69
+ ToolCallItem,
70
+ UserMessageItem,
71
+ WorkerItem,
72
+ } from "./timeline";
73
+
74
+ // Slash-command palette (registry + UI + hook)
75
+ export {
76
+ argHint,
77
+ defaultCommands,
78
+ filterCommands,
79
+ firstMissingRequiredArg,
80
+ hasPermission,
81
+ matchCommand,
82
+ parseCommandLine,
83
+ } from "./commands/registry";
84
+ export type { ParsedCommandLine } from "./commands/registry";
85
+ export type {
86
+ CommandContext,
87
+ CommandResult,
88
+ Notice,
89
+ SlashArg,
90
+ SlashCommand,
91
+ } from "./commands/types";
92
+ export { useSlashCommands } from "./hooks/use-slash-commands";
93
+ export type {
94
+ ConfirmState,
95
+ SlashCommandContext,
96
+ SlashCommandHandlers,
97
+ UseSlashCommandsOptions,
98
+ UseSlashCommandsResult,
99
+ } from "./hooks/use-slash-commands";
100
+ export { CommandPalette } from "./components/command-palette";
101
+ export type { CommandPaletteProps } from "./components/command-palette";
102
+
103
+ // Components
104
+ export { ChatComposer } from "./components/chat-composer";
105
+ export type { ChatComposerProps } from "./components/chat-composer";
106
+ export { MessageTimeline } from "./components/message-timeline";
107
+ export type { MessageTimelineProps } from "./components/message-timeline";
108
+ export { SessionStatus, StatusDot, SESSION_STATUS_META } from "./components/session-status";
109
+ export type { SessionStatusProps, StatusDotProps, SessionStatusMeta } from "./components/session-status";
110
+ export { FleetTile, sessionDisplayTitle } from "./components/fleet-tile";
111
+ export type { FleetTileProps } from "./components/fleet-tile";
112
+
113
+ // Utilities
114
+ export { cn } from "./lib/cn";
115
+ export { formatBytes, formatRelativeTime, stringifyPayload, truncate, tryParseJson } from "./lib/format";
package/src/lib/cn.ts ADDED
@@ -0,0 +1,7 @@
1
+ import { clsx, type ClassValue } from "clsx";
2
+ import { twMerge } from "tailwind-merge";
3
+
4
+ /** Merge class names with Tailwind-aware conflict resolution. */
5
+ export function cn(...inputs: ClassValue[]): string {
6
+ return twMerge(clsx(inputs));
7
+ }
@@ -0,0 +1,84 @@
1
+ /** Compact relative time: "now", "42s", "7m", "3h", "2d", then a date. */
2
+ export function formatRelativeTime(iso: string, now: Date = new Date()): string {
3
+ const then = new Date(iso).getTime();
4
+ if (Number.isNaN(then)) {
5
+ return "";
6
+ }
7
+ const seconds = Math.max(0, Math.floor((now.getTime() - then) / 1000));
8
+ if (seconds < 10) {
9
+ return "now";
10
+ }
11
+ if (seconds < 60) {
12
+ return `${seconds}s`;
13
+ }
14
+ const minutes = Math.floor(seconds / 60);
15
+ if (minutes < 60) {
16
+ return `${minutes}m`;
17
+ }
18
+ const hours = Math.floor(minutes / 60);
19
+ if (hours < 24) {
20
+ return `${hours}h`;
21
+ }
22
+ const days = Math.floor(hours / 24);
23
+ if (days < 14) {
24
+ return `${days}d`;
25
+ }
26
+ return new Date(iso).toLocaleDateString();
27
+ }
28
+
29
+ /** Human-readable byte size: "512 B", "8.0 KB", "1.4 MB", "3 GB". */
30
+ export function formatBytes(bytes: number): string {
31
+ if (bytes < 1024) {
32
+ return `${bytes} B`;
33
+ }
34
+ const units = ["KB", "MB", "GB"] as const;
35
+ let value = bytes / 1024;
36
+ for (const unit of units) {
37
+ if (value < 1024 || unit === "GB") {
38
+ return `${value.toFixed(value < 10 ? 1 : 0)} ${unit}`;
39
+ }
40
+ value /= 1024;
41
+ }
42
+ return `${bytes} B`;
43
+ }
44
+
45
+ /** Single-line preview of arbitrary text, for tiles and collapsed rows. */
46
+ export function truncate(text: string, maxLength: number): string {
47
+ const collapsed = text.replace(/\s+/g, " ").trim();
48
+ if (collapsed.length <= maxLength) {
49
+ return collapsed;
50
+ }
51
+ return `${collapsed.slice(0, Math.max(0, maxLength - 1)).trimEnd()}…`;
52
+ }
53
+
54
+ /** Render an unknown payload as readable text (pretty JSON when possible). */
55
+ export function stringifyPayload(value: unknown): string {
56
+ if (value === null || value === undefined) {
57
+ return "";
58
+ }
59
+ if (typeof value === "string") {
60
+ const parsed = tryParseJson(value);
61
+ if (parsed !== undefined && typeof parsed === "object") {
62
+ return stringifyPayload(parsed);
63
+ }
64
+ return value;
65
+ }
66
+ try {
67
+ return JSON.stringify(value, null, 2) ?? String(value);
68
+ } catch {
69
+ return String(value);
70
+ }
71
+ }
72
+
73
+ /** JSON.parse that returns `undefined` instead of throwing. */
74
+ export function tryParseJson(text: string): unknown {
75
+ const trimmed = text.trim();
76
+ if (!trimmed.startsWith("{") && !trimmed.startsWith("[") && !trimmed.startsWith('"')) {
77
+ return undefined;
78
+ }
79
+ try {
80
+ return JSON.parse(trimmed) as unknown;
81
+ } catch {
82
+ return undefined;
83
+ }
84
+ }
@@ -0,0 +1,57 @@
1
+ import { createContext, useContext, useMemo, type ReactNode } from "react";
2
+ import type { SessionClientLike } from "./client";
3
+
4
+ export type OpenGeniContextValue = {
5
+ client: SessionClientLike;
6
+ workspaceId: string;
7
+ };
8
+
9
+ const OpenGeniContext = createContext<OpenGeniContextValue | null>(null);
10
+
11
+ export type OpenGeniProviderProps = {
12
+ client: SessionClientLike;
13
+ workspaceId: string;
14
+ children?: ReactNode;
15
+ };
16
+
17
+ /**
18
+ * Supplies the OpenGeni client + workspace to all hooks below it. Hooks also
19
+ * accept `{ client, workspaceId }` overrides per call for multi-workspace UIs.
20
+ */
21
+ export function OpenGeniProvider({ client, workspaceId, children }: OpenGeniProviderProps) {
22
+ const value = useMemo(() => ({ client, workspaceId }), [client, workspaceId]);
23
+ return <OpenGeniContext.Provider value={value}>{children}</OpenGeniContext.Provider>;
24
+ }
25
+
26
+ export type ClientOverride = {
27
+ client?: SessionClientLike | undefined;
28
+ workspaceId?: string | undefined;
29
+ };
30
+
31
+ /** Resolve client + workspace from explicit overrides or the provider. */
32
+ export function useOpenGeni(override: ClientOverride = {}): OpenGeniContextValue {
33
+ const context = useContext(OpenGeniContext);
34
+ const client = override.client ?? context?.client;
35
+ const workspaceId = override.workspaceId ?? context?.workspaceId;
36
+ if (!client || !workspaceId) {
37
+ throw new Error(
38
+ "@opengeni/react: no OpenGeni client/workspace available. Wrap the tree in <OpenGeniProvider> or pass { client, workspaceId } to the hook.",
39
+ );
40
+ }
41
+ return { client, workspaceId };
42
+ }
43
+
44
+ /**
45
+ * Resolve the client only — for hooks that are not workspace-scoped
46
+ * (`useWorkspaces`, `useBillingUsage`).
47
+ */
48
+ export function useOpenGeniClient(override: Pick<ClientOverride, "client"> = {}): SessionClientLike {
49
+ const context = useContext(OpenGeniContext);
50
+ const client = override.client ?? context?.client;
51
+ if (!client) {
52
+ throw new Error(
53
+ "@opengeni/react: no OpenGeni client available. Wrap the tree in <OpenGeniProvider> or pass { client } to the hook.",
54
+ );
55
+ }
56
+ return client;
57
+ }