plotlink-ows 0.1.15 → 1.0.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 (40) hide show
  1. package/README.md +185 -93
  2. package/app/db.ts +1 -1
  3. package/app/lib/paths.ts +0 -2
  4. package/app/lib/publish.ts +257 -44
  5. package/app/prisma/schema.prisma +0 -36
  6. package/app/routes/dashboard.ts +105 -57
  7. package/app/routes/publish.ts +107 -25
  8. package/app/routes/settings.ts +194 -0
  9. package/app/routes/stories.ts +223 -0
  10. package/app/routes/terminal.ts +258 -0
  11. package/app/routes/wallet.ts +40 -10
  12. package/app/server.ts +35 -81
  13. package/app/web/App.tsx +4 -6
  14. package/app/web/components/Dashboard.tsx +98 -79
  15. package/app/web/components/Layout.tsx +70 -103
  16. package/app/web/components/PreviewPanel.tsx +388 -0
  17. package/app/web/components/Settings.tsx +210 -67
  18. package/app/web/components/StoriesPage.tsx +270 -0
  19. package/app/web/components/StoryBrowser.tsx +161 -0
  20. package/app/web/components/TerminalPanel.tsx +428 -0
  21. package/app/web/components/WalletCard.tsx +14 -8
  22. package/app/web/dist/assets/index-BuOxhUWG.css +32 -0
  23. package/app/web/dist/assets/index-De8CpT47.js +129 -0
  24. package/app/web/dist/index.html +3 -3
  25. package/app/web/dist/plotlink-logo.svg +5 -0
  26. package/app/web/public/plotlink-logo.svg +5 -0
  27. package/app/web/styles.css +18 -0
  28. package/bin/plotlink-ows.js +18 -62
  29. package/package.json +15 -6
  30. package/scripts/fix-index-status.ts +93 -0
  31. package/app/lib/llm-client.ts +0 -265
  32. package/app/lib/writer-prompt.ts +0 -44
  33. package/app/routes/chat.ts +0 -135
  34. package/app/routes/config.ts +0 -210
  35. package/app/routes/oauth.ts +0 -150
  36. package/app/web/components/Chat.tsx +0 -272
  37. package/app/web/components/LLMSetup.tsx +0 -291
  38. package/app/web/components/Publish.tsx +0 -245
  39. package/app/web/dist/assets/index-C9kXlYO_.css +0 -2
  40. package/app/web/dist/assets/index-CJiiaLHs.js +0 -9
@@ -9,17 +9,27 @@ interface WalletInfo {
9
9
  usdcBalance: string;
10
10
  }
11
11
 
12
- interface Story {
12
+ interface StoryFile {
13
+ file: string;
14
+ status: string;
15
+ txHash?: string | null;
16
+ gasCostEth?: string | null;
17
+ publishedAt?: string | null;
18
+ }
19
+
20
+ interface StoryGroup {
13
21
  id: string;
14
22
  title: string;
15
23
  genre: string | null;
16
- status: string;
17
- txHash?: string | null;
24
+ storyName: string;
18
25
  storylineId?: number | null;
19
- gasCostEth?: string | null;
20
- gasCostUsd?: string | null;
21
- createdAt: string;
22
- updatedAt?: string;
26
+ plotCount: number;
27
+ publishedFiles: number;
28
+ hasNotIndexed: boolean;
29
+ totalGasCostEth?: string | null;
30
+ totalGasCostUsd?: string | null;
31
+ latestPublishedAt?: string | null;
32
+ files: StoryFile[];
23
33
  }
24
34
 
25
35
  interface DashboardData {
@@ -28,18 +38,16 @@ interface DashboardData {
28
38
  royalties: { earned: string; claimed: string; unclaimed: string; token: string };
29
39
  pnl: { totalCostsEth: string; totalCostsUsd: string; totalRoyaltiesPlot: string; totalRoyaltiesUsd: string; netPnlUsd: string; plotUsdPrice: string };
30
40
  stories: {
31
- published: Story[];
32
- drafts: Story[];
41
+ published: StoryGroup[];
33
42
  totalPublished: number;
34
- totalDrafts: number;
43
+ totalStories: number;
44
+ totalFiles: number;
45
+ pendingFiles: number;
35
46
  };
36
- sessions: { total: number; totalMessages: number };
37
47
  }
38
48
 
39
49
  export function Dashboard({ token }: { token: string }) {
40
50
  const [data, setData] = useState<DashboardData | null>(null);
41
- const [deleting, setDeleting] = useState<string | null>(null);
42
-
43
51
  const authFetch = (url: string, opts?: RequestInit) =>
44
52
  fetch(url, { ...opts, headers: { ...opts?.headers, Authorization: `Bearer ${token}`, "Content-Type": "application/json" } });
45
53
 
@@ -51,15 +59,13 @@ export function Dashboard({ token }: { token: string }) {
51
59
 
52
60
  useEffect(() => { loadDashboard(); }, []);
53
61
 
54
- const handleDelete = async (id: string) => {
55
- setDeleting(id);
56
- await authFetch(`${API_BASE}/api/dashboard/drafts/${id}`, { method: "DELETE" });
57
- loadDashboard();
58
- setDeleting(null);
59
- };
60
-
61
62
  const truncate = (addr: string) => `${addr.slice(0, 6)}...${addr.slice(-4)}`;
62
- const formatDate = (d: string) => new Date(d).toLocaleDateString("en-US", { month: "short", day: "numeric", year: "numeric" });
63
+ const formatDate = (d: string | undefined | null) => {
64
+ if (!d) return "Unknown date";
65
+ const date = new Date(d);
66
+ if (isNaN(date.getTime())) return "Unknown date";
67
+ return date.toLocaleDateString("en-US", { month: "short", day: "numeric", year: "numeric" });
68
+ };
63
69
 
64
70
  if (!data) {
65
71
  return (
@@ -80,16 +86,16 @@ export function Dashboard({ token }: { token: string }) {
80
86
  <div className="text-muted text-[10px] uppercase tracking-wider">published</div>
81
87
  </div>
82
88
  <div className="border-border rounded border p-3 text-center">
83
- <div className="text-foreground text-lg font-bold">{data.stories.totalDrafts}</div>
84
- <div className="text-muted text-[10px] uppercase tracking-wider">drafts</div>
89
+ <div className="text-foreground text-lg font-bold">{data.stories.pendingFiles}</div>
90
+ <div className="text-muted text-[10px] uppercase tracking-wider">pending</div>
85
91
  </div>
86
92
  <div className="border-border rounded border p-3 text-center">
87
- <div className="text-foreground text-lg font-bold">{data.sessions.total}</div>
88
- <div className="text-muted text-[10px] uppercase tracking-wider">sessions</div>
93
+ <div className="text-foreground text-lg font-bold">{data.stories.totalStories}</div>
94
+ <div className="text-muted text-[10px] uppercase tracking-wider">stories</div>
89
95
  </div>
90
96
  <div className="border-border rounded border p-3 text-center">
91
- <div className="text-foreground text-lg font-bold">{data.sessions.totalMessages}</div>
92
- <div className="text-muted text-[10px] uppercase tracking-wider">messages</div>
97
+ <div className="text-foreground text-lg font-bold">{data.stories.totalFiles}</div>
98
+ <div className="text-muted text-[10px] uppercase tracking-wider">files</div>
93
99
  </div>
94
100
  </div>
95
101
 
@@ -149,38 +155,74 @@ export function Dashboard({ token }: { token: string }) {
149
155
  {data.stories.published.length === 0 ? (
150
156
  <p className="text-muted text-xs">no published stories yet</p>
151
157
  ) : (
152
- <div className="space-y-2">
158
+ <div className="space-y-3">
153
159
  {data.stories.published.map((story) => (
154
- <div key={story.id} className="bg-surface rounded p-3">
155
- <div className="flex items-center justify-between">
160
+ <div key={story.id} className="bg-surface rounded border border-border p-4">
161
+ <div className="flex items-start justify-between">
156
162
  <div>
157
- <span className="text-foreground text-sm font-medium">{story.title}</span>
158
- {story.genre && <span className="text-accent ml-2 text-[10px]">{story.genre}</span>}
163
+ {story.genre && (
164
+ <span className="bg-accent/10 text-accent rounded px-2 py-0.5 text-[10px] font-medium">{story.genre}</span>
165
+ )}
166
+ <h4 className="text-foreground mt-1 text-sm font-serif font-medium">{story.title}</h4>
167
+ <p className="text-muted mt-0.5 text-[10px] font-mono">{story.storyName}</p>
159
168
  </div>
160
169
  <div className="flex items-center gap-2">
161
- <span className="rounded border border-green-700/30 px-1.5 py-0.5 text-[9px] text-accent">published</span>
162
- {story.storylineId ? (
163
- <a
164
- href={`https://plotlink.xyz/story/${story.storylineId}`}
165
- target="_blank"
166
- rel="noopener noreferrer"
167
- className="text-accent text-[10px] underline"
168
- >
169
- view
170
- </a>
171
- ) : (
172
- <a href="https://plotlink.xyz" target="_blank" rel="noopener noreferrer" className="text-accent text-[10px] underline">plotlink.xyz</a>
170
+ {story.hasNotIndexed && (
171
+ <span className="rounded border border-amber-600/30 px-1.5 py-0.5 text-[9px] text-amber-700">not indexed</span>
173
172
  )}
173
+ <span className="rounded border border-green-700/30 px-1.5 py-0.5 text-[9px] text-green-700">
174
+ {story.publishedFiles} published
175
+ </span>
176
+ </div>
177
+ </div>
178
+ <div className="mt-2 grid grid-cols-3 gap-2 text-center">
179
+ <div className="rounded bg-background p-1.5">
180
+ <div className="text-foreground text-sm font-medium">{story.plotCount}</div>
181
+ <div className="text-muted text-[9px]">Plots</div>
182
+ </div>
183
+ <div className="rounded bg-background p-1.5">
184
+ <div className="text-foreground text-sm font-medium font-mono">
185
+ {story.storylineId ? `#${story.storylineId}` : "—"}
186
+ </div>
187
+ <div className="text-muted text-[9px]">Storyline</div>
188
+ </div>
189
+ <div className="rounded bg-background p-1.5">
190
+ <div className="text-foreground text-sm font-medium">
191
+ {story.totalGasCostEth ?? "—"}
192
+ </div>
193
+ <div className="text-muted text-[9px]">Gas (ETH)</div>
174
194
  </div>
175
195
  </div>
176
- <div className="mt-1 flex items-center gap-3 text-[10px]">
177
- <span className="text-muted">{formatDate(story.createdAt)}</span>
178
- {story.txHash && (
179
- <a href={`https://basescan.org/tx/${story.txHash}`} target="_blank" rel="noopener noreferrer" className="text-muted hover:text-accent font-mono">
180
- tx:{story.txHash.slice(0, 10)}...
196
+ {/* Individual files */}
197
+ <div className="mt-2 space-y-1">
198
+ {story.files.map((f) => (
199
+ <div key={f.file} className="flex items-center justify-between text-[10px]">
200
+ <div className="flex items-center gap-1.5">
201
+ <span className={f.status === "published-not-indexed" ? "text-amber-700" : "text-green-700"}>
202
+ {f.status === "published-not-indexed" ? "\u26A0" : "\u2713"}
203
+ </span>
204
+ <span className="text-muted font-mono">{f.file}</span>
205
+ </div>
206
+ {f.txHash && (
207
+ <a href={`https://basescan.org/tx/${f.txHash}`} target="_blank" rel="noopener noreferrer" className="text-muted hover:text-accent font-mono">
208
+ tx:{f.txHash.slice(0, 8)}...
209
+ </a>
210
+ )}
211
+ </div>
212
+ ))}
213
+ </div>
214
+ <div className="mt-2 flex items-center justify-between text-[10px]">
215
+ <span className="text-muted">{formatDate(story.latestPublishedAt)}</span>
216
+ {story.storylineId && (
217
+ <a
218
+ href={`https://plotlink.xyz/story/${story.storylineId}`}
219
+ target="_blank"
220
+ rel="noopener noreferrer"
221
+ className="text-accent underline"
222
+ >
223
+ View on PlotLink
181
224
  </a>
182
225
  )}
183
- {story.gasCostEth && <span className="text-muted">{story.gasCostEth} ETH{story.gasCostUsd ? ` (~$${story.gasCostUsd})` : ""}</span>}
184
226
  </div>
185
227
  </div>
186
228
  ))}
@@ -188,35 +230,12 @@ export function Dashboard({ token }: { token: string }) {
188
230
  )}
189
231
  </div>
190
232
 
191
- {/* Draft stories */}
192
- <div className="border-border rounded border p-4">
193
- <h3 className="text-accent mb-3 text-xs font-bold uppercase tracking-wider">Drafts</h3>
194
- {data.stories.drafts.length === 0 ? (
195
- <p className="text-muted text-xs">no drafts — start writing from the chat</p>
196
- ) : (
197
- <div className="space-y-2">
198
- {data.stories.drafts.map((draft) => (
199
- <div key={draft.id} className="bg-surface flex items-center justify-between rounded p-3">
200
- <div>
201
- <span className="text-foreground text-sm font-medium">{draft.title}</span>
202
- {draft.genre && <span className="text-accent ml-2 text-[10px]">{draft.genre}</span>}
203
- <div className="text-muted text-[10px]">{formatDate(draft.createdAt)}</div>
204
- </div>
205
- <div className="flex items-center gap-2">
206
- <span className="border-border rounded border px-1.5 py-0.5 text-[9px] text-muted">{draft.status}</span>
207
- <button
208
- onClick={() => handleDelete(draft.id)}
209
- disabled={deleting === draft.id}
210
- className="text-muted hover:text-error text-[10px] transition-colors"
211
- >
212
- {deleting === draft.id ? "..." : "delete"}
213
- </button>
214
- </div>
215
- </div>
216
- ))}
217
- </div>
218
- )}
219
- </div>
233
+ {/* Pending files info */}
234
+ {data.stories.pendingFiles > 0 && (
235
+ <div className="border-border rounded border p-4">
236
+ <p className="text-muted text-xs">{data.stories.pendingFiles} file(s) pending publish — go to Stories to publish them.</p>
237
+ </div>
238
+ )}
220
239
  </div>
221
240
  );
222
241
  }
@@ -1,14 +1,10 @@
1
- import React, { useState, useEffect } from "react";
2
- import { LLMSetup } from "./LLMSetup";
3
- import { WalletCard } from "./WalletCard";
1
+ import React, { useState, useEffect, useCallback } from "react";
4
2
  import { Settings } from "./Settings";
5
- import { Chat } from "./Chat";
6
- import { Publish } from "./Publish";
7
3
  import { Dashboard } from "./Dashboard";
4
+ import { StoriesPage } from "./StoriesPage";
5
+ import { WalletCard } from "./WalletCard";
8
6
 
9
- const API_BASE = "http://localhost:7777";
10
-
11
- type Page = "home" | "chat" | "publish" | "dashboard" | "llm-setup" | "wallet-setup" | "settings";
7
+ type Page = "home" | "stories" | "dashboard" | "wallet-setup" | "settings";
12
8
 
13
9
  function WalletSetupPage({ token, onComplete }: { token: string; onComplete: () => void }) {
14
10
  const [creating, setCreating] = useState(false);
@@ -19,7 +15,7 @@ function WalletSetupPage({ token, onComplete }: { token: string; onComplete: ()
19
15
  setCreating(true);
20
16
  setError(null);
21
17
  try {
22
- const res = await fetch(`${API_BASE}/api/wallet/create`, {
18
+ const res = await fetch("/api/wallet/create", {
23
19
  method: "POST",
24
20
  headers: { Authorization: `Bearer ${token}`, "Content-Type": "application/json" },
25
21
  });
@@ -32,12 +28,13 @@ function WalletSetupPage({ token, onComplete }: { token: string; onComplete: ()
32
28
  setCreating(false);
33
29
  };
34
30
 
31
+ // eslint-disable-next-line react-hooks/exhaustive-deps
35
32
  useEffect(() => { createWallet(); }, []);
36
33
 
37
34
  return (
38
35
  <div className="mx-auto max-w-sm p-6 text-center">
39
36
  <h2 className="text-accent mb-1 text-lg font-bold">Wallet Setup</h2>
40
- <p className="text-muted mb-6 text-xs">creating your OWS wallet for autonomous transactions</p>
37
+ <p className="text-muted mb-6 text-xs">creating your OWS wallet for on-chain publishing</p>
41
38
 
42
39
  {creating && <p className="text-accent text-sm">creating wallet...</p>}
43
40
 
@@ -71,64 +68,58 @@ function WalletSetupPage({ token, onComplete }: { token: string; onComplete: ()
71
68
 
72
69
  export function Layout({ token, onLogout }: { token: string; onLogout: () => void }) {
73
70
  const [page, setPage] = useState<Page>("home");
74
- const [llmConfigured, setLlmConfigured] = useState<boolean | null>(null);
71
+ const [storyCount, setStoryCount] = useState(0);
72
+
73
+ const authFetch = useCallback(async (url: string, opts?: RequestInit) => {
74
+ return fetch(url, {
75
+ ...opts,
76
+ headers: {
77
+ ...(opts?.headers || {}),
78
+ Authorization: `Bearer ${token}`,
79
+ },
80
+ });
81
+ }, [token]);
75
82
 
76
83
  useEffect(() => {
77
84
  async function checkSetup() {
78
85
  try {
79
- // Check LLM config
80
- const llmRes = await fetch(`${API_BASE}/api/config/llm`, {
81
- headers: { Authorization: `Bearer ${token}` },
82
- });
83
- const llmData = await llmRes.json();
84
- const hasLlm = llmData.configured?.length > 0;
85
- setLlmConfigured(hasLlm);
86
-
87
- if (!hasLlm) {
88
- setPage("llm-setup");
89
- return;
90
- }
91
-
92
86
  // Check wallet existence
93
- const walletRes = await fetch(`${API_BASE}/api/wallet`, {
94
- headers: { Authorization: `Bearer ${token}` },
95
- });
87
+ const walletRes = await authFetch("/api/wallet");
96
88
  const walletData = await walletRes.json();
97
89
  if (!walletData.exists) {
98
90
  setPage("wallet-setup");
99
91
  return;
100
92
  }
101
- } catch {
102
- setLlmConfigured(false);
103
- }
93
+
94
+ // Load story count
95
+ const storiesRes = await authFetch("/api/stories");
96
+ if (storiesRes.ok) {
97
+ const data = await storiesRes.json();
98
+ setStoryCount(data.stories.filter((s: { name: string }) => s.name !== "_example").length);
99
+ }
100
+ } catch { /* ignore */ }
104
101
  }
105
102
  checkSetup();
106
- }, []);
103
+ // eslint-disable-next-line react-hooks/exhaustive-deps
104
+ }, [token]);
107
105
 
108
106
  return (
109
107
  <div className="flex h-screen flex-col">
110
108
  {/* Header */}
111
- <header className="border-border flex items-center justify-between border-b px-4 py-3">
109
+ <header className="border-border flex h-14 items-center justify-between border-b px-4 flex-shrink-0">
112
110
  <div className="flex items-center gap-3">
113
111
  <button onClick={() => { if (page !== "wallet-setup") setPage("home"); }} className="flex items-center gap-2 hover:opacity-80">
114
- <img src="/plotlink-logo.svg" alt="PlotLink" className="h-5 w-5" />
115
112
  <span className="text-accent text-sm font-bold tracking-tight">PlotLink OWS</span>
116
113
  </button>
117
- <span className="text-muted text-[10px] uppercase tracking-wider">local writer</span>
114
+ <span className="text-muted text-[10px] uppercase tracking-wider">writer</span>
118
115
  </div>
119
116
  {page !== "wallet-setup" && (
120
117
  <nav className="flex items-center gap-4">
121
118
  <button
122
- onClick={() => setPage("chat")}
123
- className={`text-xs transition-colors ${page === "chat" ? "text-accent" : "text-muted hover:text-foreground"}`}
124
- >
125
- write
126
- </button>
127
- <button
128
- onClick={() => setPage("publish")}
129
- className={`text-xs transition-colors ${page === "publish" ? "text-accent" : "text-muted hover:text-foreground"}`}
119
+ onClick={() => setPage("stories")}
120
+ className={`text-xs transition-colors ${page === "stories" ? "text-accent" : "text-muted hover:text-foreground"}`}
130
121
  >
131
- publish
122
+ stories
132
123
  </button>
133
124
  <button
134
125
  onClick={() => setPage("dashboard")}
@@ -136,12 +127,6 @@ export function Layout({ token, onLogout }: { token: string; onLogout: () => voi
136
127
  >
137
128
  dashboard
138
129
  </button>
139
- <button
140
- onClick={() => setPage("llm-setup")}
141
- className={`text-xs transition-colors ${page === "llm-setup" ? "text-accent" : "text-muted hover:text-foreground"}`}
142
- >
143
- llm
144
- </button>
145
130
  <button
146
131
  onClick={() => setPage("settings")}
147
132
  className={`text-xs transition-colors ${page === "settings" ? "text-accent" : "text-muted hover:text-foreground"}`}
@@ -156,62 +141,51 @@ export function Layout({ token, onLogout }: { token: string; onLogout: () => voi
156
141
  </header>
157
142
 
158
143
  {/* Main content */}
159
- <main className="flex-1 overflow-y-auto">
144
+ <main className="flex-1 min-h-0">
160
145
  {page === "home" && (
161
- <div className="mx-auto max-w-lg space-y-6 p-6">
162
- {llmConfigured === false && (
163
- <div className="border-accent/30 rounded border p-4 text-center">
164
- <p className="text-accent text-sm font-medium">setup required</p>
165
- <p className="text-muted mt-1 text-xs">connect an LLM provider to get started</p>
166
- <button
167
- onClick={() => setPage("llm-setup")}
168
- className="border-accent text-accent hover:bg-accent/10 mt-3 rounded border px-4 py-2 text-xs font-medium transition-colors"
169
- >
170
- setup LLM
171
- </button>
172
- </div>
173
- )}
174
-
175
- {llmConfigured && (
176
- <>
177
- <div className="text-center">
178
- <p className="text-foreground text-sm font-medium">ready to write</p>
179
- <p className="text-muted mt-1 text-xs">start a collaborative story session with the AI writer</p>
180
- <button
181
- onClick={() => setPage("chat")}
182
- className="border-accent text-accent hover:bg-accent/10 mt-3 rounded border px-4 py-2 text-xs font-medium transition-colors"
183
- >
184
- start writing
185
- </button>
186
- </div>
187
- <WalletCard token={token} />
188
- </>
189
- )}
146
+ <div className="mx-auto max-w-lg space-y-6 p-8">
147
+ <div className="text-center space-y-2">
148
+ <h1 className="text-2xl font-serif text-foreground">Write. Publish. Earn.</h1>
149
+ <p className="text-muted text-sm">
150
+ Claude CLI writes stories. You publish them on-chain.
151
+ </p>
152
+ </div>
153
+
154
+ <div className="text-center space-y-3">
155
+ <button
156
+ onClick={() => setPage("stories")}
157
+ className="bg-accent text-white hover:bg-accent-dim px-6 py-2.5 rounded text-sm font-medium transition-colors"
158
+ >
159
+ Start Writing
160
+ </button>
161
+ {storyCount > 0 && (
162
+ <p className="text-muted text-xs">{storyCount} {storyCount === 1 ? "story" : "stories"} in progress</p>
163
+ )}
164
+ </div>
165
+
166
+ <div className="rounded border border-border p-4 space-y-2 text-xs text-muted">
167
+ <p className="font-medium text-foreground text-sm">How it works</p>
168
+ <ol className="space-y-1.5 list-decimal list-inside">
169
+ <li>Open the <strong>Stories</strong> tab — Claude CLI launches in the terminal</li>
170
+ <li>Tell Claude your story idea — it brainstorms, outlines, and writes</li>
171
+ <li>Review the live preview as Claude creates files</li>
172
+ <li>Click <strong>Publish</strong> to put your story on-chain</li>
173
+ <li>Earn 5% royalties on every trade at <a href="https://plotlink.xyz" target="_blank" rel="noopener noreferrer" className="text-accent underline">plotlink.xyz</a></li>
174
+ </ol>
175
+ </div>
176
+
177
+ <WalletCard token={token} />
190
178
  </div>
191
179
  )}
192
180
 
193
- {page === "chat" && (
194
- <Chat token={token} />
195
- )}
196
-
197
- {page === "publish" && (
198
- <Publish token={token} />
181
+ {page === "stories" && (
182
+ <StoriesPage token={token} authFetch={authFetch} />
199
183
  )}
200
184
 
201
185
  {page === "dashboard" && (
202
186
  <Dashboard token={token} />
203
187
  )}
204
188
 
205
- {page === "llm-setup" && (
206
- <LLMSetup
207
- token={token}
208
- onComplete={() => {
209
- setLlmConfigured(true);
210
- setPage("wallet-setup");
211
- }}
212
- />
213
- )}
214
-
215
189
  {page === "wallet-setup" && (
216
190
  <WalletSetupPage
217
191
  token={token}
@@ -220,16 +194,9 @@ export function Layout({ token, onLogout }: { token: string; onLogout: () => voi
220
194
  )}
221
195
 
222
196
  {page === "settings" && (
223
- <Settings token={token} onLogout={onLogout} onChangeLLM={() => setPage("llm-setup")} />
197
+ <Settings token={token} onLogout={onLogout} />
224
198
  )}
225
199
  </main>
226
-
227
- {/* Footer */}
228
- <footer className="border-border border-t px-4 py-2">
229
- <p className="text-muted text-[10px]">
230
- session active &middot; localhost:7777
231
- </p>
232
- </footer>
233
200
  </div>
234
201
  );
235
202
  }