Nanny7 commited on
Commit
e3c88c2
Β·
1 Parent(s): 6666a2d

Deploy guest upgrade flow

Browse files
Files changed (2) hide show
  1. Dockerfile +3 -0
  2. config/minimal_ui_patch.js +163 -29
Dockerfile CHANGED
@@ -10,6 +10,9 @@ ENV ENABLE_FOLLOW_UP_QUESTIONS=false \
10
  ENABLE_EVALUATION_ARENA_MODELS=false \
11
  ENABLE_USER_STATUS=false \
12
  ENABLE_PUBLIC_ACTIVE_USERS_COUNT=false \
 
 
 
13
  ENABLE_EASTER_EGGS=false
14
 
15
  # Montaigne config and entrypoint
 
10
  ENABLE_EVALUATION_ARENA_MODELS=false \
11
  ENABLE_USER_STATUS=false \
12
  ENABLE_PUBLIC_ACTIVE_USERS_COUNT=false \
13
+ ENABLE_FORWARD_USER_INFO_HEADERS=true \
14
+ ENABLE_LOGIN_FORM=true \
15
+ ENABLE_SIGNUP=true \
16
  ENABLE_EASTER_EGGS=false
17
 
18
  # Montaigne config and entrypoint
config/minimal_ui_patch.js CHANGED
@@ -1,4 +1,5 @@
1
  (() => {
 
2
  const MONTAIGNE_SUGGESTIONS = [
3
  {
4
  title: ['What occupies your thoughts', 'most lately?'],
@@ -26,6 +27,7 @@
26
  }
27
  ];
28
 
 
29
  const HIDE_TEXT = new Set([
30
  'Settings',
31
  'Archived Chats',
@@ -41,53 +43,188 @@
41
  const HIDE_HREF = new Set(['/playground', '/admin']);
42
  const HIDE_ARIA = new Set(['Controls', 'Add Model', 'Remove Model', 'Voice Input', 'Voice mode']);
43
 
 
 
 
 
 
44
  function normalize(text) {
45
  return (text || '').replace(/\s+/g, ' ').trim();
46
  }
47
 
48
- function installConfigFetchPatch() {
49
- if (window.__montaigneConfigFetchPatched) {
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
50
  return;
51
  }
52
 
53
- window.__montaigneConfigFetchPatched = true;
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
54
 
55
  const originalFetch = window.fetch.bind(window);
 
56
  window.fetch = async (...args) => {
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
57
  const response = await originalFetch(...args);
58
 
 
59
  try {
60
  const input = args[0];
61
  const rawUrl = input instanceof Request ? input.url : String(input);
62
  const url = new URL(rawUrl, window.location.origin);
63
 
64
- if (url.pathname !== '/api/config') {
65
- return response;
 
 
 
 
 
 
 
 
 
 
66
  }
67
 
68
- const payload = await response.clone().json();
69
- payload.default_prompt_suggestions = MONTAIGNE_SUGGESTIONS;
 
 
 
 
 
70
 
71
- const headers = new Headers(response.headers);
72
- headers.set('content-type', 'application/json');
73
- headers.delete('content-length');
 
 
 
 
74
 
75
- return new Response(JSON.stringify(payload), {
76
- status: response.status,
77
- statusText: response.statusText,
78
- headers
79
- });
80
- } catch (_error) {
81
- return response;
 
 
 
 
82
  }
 
 
83
  };
84
  }
85
 
 
86
  function hide(el) {
87
- if (!el || el.dataset.montaigneHidden === '1') {
88
- return;
89
- }
90
-
91
  el.dataset.montaigneHidden = '1';
92
  el.style.display = 'none';
93
  }
@@ -98,9 +235,7 @@
98
 
99
  function hideModelSelector(trigger) {
100
  hide(trigger);
101
-
102
  let node = trigger;
103
-
104
  for (let i = 0; i < 6 && node; i += 1) {
105
  const className = typeof node.className === 'string' ? node.className : '';
106
  if (className.includes('overflow-hidden')) {
@@ -109,11 +244,12 @@
109
  }
110
  node = node.parentElement;
111
  }
112
-
113
  hide(trigger);
114
  }
115
 
116
  function applyMinimalUi() {
 
 
117
  document
118
  .querySelectorAll('[id^="model-selector-"][id$="-button"]')
119
  .forEach(hideModelSelector);
@@ -125,9 +261,7 @@
125
  const text = normalize(el.textContent);
126
  if (text.includes("What's New") || text.includes('Release Notes')) {
127
  const closeButton = el.querySelector('button[aria-label="Close"]');
128
- if (closeButton) {
129
- closeButton.click();
130
- }
131
  hide(el);
132
  }
133
  });
@@ -136,7 +270,6 @@
136
  const text = normalize(el.textContent);
137
  const aria = normalize(el.getAttribute('aria-label'));
138
  const href = el.getAttribute('href');
139
-
140
  if (HIDE_TEXT.has(text) || HIDE_ARIA.has(aria) || HIDE_HREF.has(href)) {
141
  hideMenuItem(el);
142
  }
@@ -145,7 +278,8 @@
145
  document.querySelectorAll('#temporary-chat-button').forEach(hide);
146
  }
147
 
148
- installConfigFetchPatch();
 
149
 
150
  const run = () => window.requestAnimationFrame(applyMinimalUi);
151
 
 
1
  (() => {
2
+ // ── Prompt suggestions ──────────────────────────────────────────
3
  const MONTAIGNE_SUGGESTIONS = [
4
  {
5
  title: ['What occupies your thoughts', 'most lately?'],
 
27
  }
28
  ];
29
 
30
+ // ── Elements to hide ────────────────────────────────────────────
31
  const HIDE_TEXT = new Set([
32
  'Settings',
33
  'Archived Chats',
 
43
  const HIDE_HREF = new Set(['/playground', '/admin']);
44
  const HIDE_ARIA = new Set(['Controls', 'Add Model', 'Remove Model', 'Voice Input', 'Voice mode']);
45
 
46
+ // ── Guest upgrade constants ─────────────────────────────────────
47
+ const GUEST_TOKEN_KEY = 'montaigne_guest_upgrade_token';
48
+ const CHAT_COMPLETIONS_PATH = '/api/chat/completions';
49
+ const GUEST_SIGNUP_NOTICE_ID = 'montaigne-guest-signup-notice';
50
+
51
  function normalize(text) {
52
  return (text || '').replace(/\s+/g, ' ').trim();
53
  }
54
 
55
+ // ── Guest upgrade: CTA banner ───────────────────────────────────
56
+ function showGuestBanner() {
57
+ if (document.getElementById('montaigne-guest-banner')) return;
58
+
59
+ const banner = document.createElement('div');
60
+ banner.id = 'montaigne-guest-banner';
61
+ banner.style.cssText =
62
+ 'position:fixed;bottom:0;left:0;right:0;z-index:99999;' +
63
+ 'background:linear-gradient(135deg,#3d2b1f,#5c3d2e);color:#f5e6d3;' +
64
+ 'padding:14px 20px;display:flex;align-items:center;justify-content:center;' +
65
+ 'gap:16px;font-family:Inter,system-ui,sans-serif;font-size:14px;' +
66
+ 'box-shadow:0 -2px 12px rgba(0,0,0,.3);';
67
+
68
+ const msg = document.createElement('span');
69
+ msg.textContent =
70
+ 'You\u2019ve used your 5 free messages. Create a free account to continue chatting with Montaigne.';
71
+
72
+ const btn = document.createElement('a');
73
+ btn.textContent = 'Create free account';
74
+ btn.style.cssText =
75
+ 'background:#c9a96e;color:#2b1d0e;padding:8px 18px;border-radius:6px;' +
76
+ 'text-decoration:none;font-weight:600;white-space:nowrap;cursor:pointer;' +
77
+ 'transition:background .15s;';
78
+ btn.onmouseenter = () => (btn.style.background = '#d4b87a');
79
+ btn.onmouseleave = () => (btn.style.background = '#c9a96e');
80
+
81
+ const currentPath = window.location.pathname + window.location.search;
82
+ const authUrl = '/auth?form=true&redirect=' + encodeURIComponent(currentPath) + '&guestUpgrade=1';
83
+ btn.href = authUrl;
84
+
85
+ const close = document.createElement('button');
86
+ close.textContent = '\u00d7';
87
+ close.style.cssText =
88
+ 'background:none;border:none;color:#f5e6d3;font-size:20px;cursor:pointer;' +
89
+ 'padding:0 4px;line-height:1;';
90
+ close.onclick = () => banner.remove();
91
+
92
+ banner.appendChild(msg);
93
+ banner.appendChild(btn);
94
+ banner.appendChild(close);
95
+ document.body.appendChild(banner);
96
+ }
97
+
98
+ function applyGuestUpgradeAuthPrompt() {
99
+ const url = new URL(window.location.href);
100
+ if (url.pathname !== '/auth' || url.searchParams.get('guestUpgrade') !== '1') {
101
  return;
102
  }
103
 
104
+ const authContainer = document.getElementById('auth-container');
105
+ if (!authContainer) {
106
+ return;
107
+ }
108
+
109
+ if (!document.getElementById(GUEST_SIGNUP_NOTICE_ID)) {
110
+ const notice = document.createElement('div');
111
+ notice.id = GUEST_SIGNUP_NOTICE_ID;
112
+ notice.textContent = 'Create an account to keep this conversation and continue where you left off.';
113
+ notice.style.cssText =
114
+ 'margin:18px auto 14px;max-width:28rem;padding:12px 14px;border-radius:10px;' +
115
+ 'background:rgba(201,169,110,.12);border:1px solid rgba(201,169,110,.35);' +
116
+ 'color:inherit;font-size:14px;line-height:1.45;';
117
+ const target = authContainer.querySelector('.w-full.px-10') || authContainer.firstElementChild;
118
+ if (target) {
119
+ target.insertBefore(notice, target.firstChild || null);
120
+ }
121
+ }
122
+
123
+ if (document.documentElement.dataset.montaigneGuestSignupActivated === '1') {
124
+ return;
125
+ }
126
+
127
+ const toggleButtons = Array.from(authContainer.querySelectorAll('button[type="button"]'));
128
+ const signupToggle = toggleButtons.find((button) => normalize(button.textContent) === 'Sign up');
129
+ if (signupToggle) {
130
+ document.documentElement.dataset.montaigneGuestSignupActivated = '1';
131
+ signupToggle.click();
132
+ }
133
+ }
134
+
135
+ // ── Unified fetch patch (config + guest upgrade) ───────────────
136
+ function installFetchPatch() {
137
+ if (window.__montaigneFetchPatched) return;
138
+ window.__montaigneFetchPatched = true;
139
 
140
  const originalFetch = window.fetch.bind(window);
141
+
142
  window.fetch = async (...args) => {
143
+ // ── Outgoing request: inject guest upgrade token ──────────
144
+ try {
145
+ const input = args[0];
146
+ const rawUrl = input instanceof Request ? input.url : String(input);
147
+ const url = new URL(rawUrl, window.location.origin);
148
+
149
+ if (url.pathname === CHAT_COMPLETIONS_PATH) {
150
+ const storedToken = localStorage.getItem(GUEST_TOKEN_KEY);
151
+ if (storedToken) {
152
+ let body = args[1] && args[1].body;
153
+ if (typeof body === 'string') {
154
+ try {
155
+ const parsed = JSON.parse(body);
156
+ if (!parsed.variables) parsed.variables = {};
157
+ parsed.variables.guest_upgrade_token = storedToken;
158
+ args[1] = { ...args[1], body: JSON.stringify(parsed) };
159
+ } catch (_) {
160
+ // Body not JSON β€” skip injection
161
+ }
162
+ }
163
+ }
164
+ }
165
+ } catch (_) {
166
+ // URL parsing failed β€” proceed with original args
167
+ }
168
+
169
+ // ── Perform request ───────────────────────────────────────
170
  const response = await originalFetch(...args);
171
 
172
+ // ── Incoming response handling ────────────────────────────
173
  try {
174
  const input = args[0];
175
  const rawUrl = input instanceof Request ? input.url : String(input);
176
  const url = new URL(rawUrl, window.location.origin);
177
 
178
+ // Patch 1: inject prompt suggestions into /api/config
179
+ if (url.pathname === '/api/config') {
180
+ const payload = await response.clone().json();
181
+ payload.default_prompt_suggestions = MONTAIGNE_SUGGESTIONS;
182
+ const headers = new Headers(response.headers);
183
+ headers.set('content-type', 'application/json');
184
+ headers.delete('content-length');
185
+ return new Response(JSON.stringify(payload), {
186
+ status: response.status,
187
+ statusText: response.statusText,
188
+ headers
189
+ });
190
  }
191
 
192
+ // Patch 2: intercept 403 guest_limit_reached on chat completions
193
+ if (url.pathname === CHAT_COMPLETIONS_PATH && response.status === 403) {
194
+ const clone = response.clone();
195
+ try {
196
+ const body = await clone.json();
197
+ const code = typeof body === 'object' ? body.code : null;
198
+ const token = typeof body === 'object' ? body.guest_upgrade_token : null;
199
 
200
+ if (code === 'guest_limit_reached' && token) {
201
+ localStorage.setItem(GUEST_TOKEN_KEY, token);
202
+ showGuestBanner();
203
+ }
204
+ } catch (_) {
205
+ // Not JSON β€” fall through
206
+ }
207
 
208
+ // Patch 3: on successful chat response, clear consumed token
209
+ } else if (
210
+ url.pathname === CHAT_COMPLETIONS_PATH &&
211
+ response.ok
212
+ ) {
213
+ localStorage.removeItem(GUEST_TOKEN_KEY);
214
+ const banner = document.getElementById('montaigne-guest-banner');
215
+ if (banner) banner.remove();
216
+ }
217
+ } catch (_) {
218
+ // Swallow β€” never break the page
219
  }
220
+
221
+ return response;
222
  };
223
  }
224
 
225
+ // ── Minimal UI: hide power-user elements ───────────────────────
226
  function hide(el) {
227
+ if (!el || el.dataset.montaigneHidden === '1') return;
 
 
 
228
  el.dataset.montaigneHidden = '1';
229
  el.style.display = 'none';
230
  }
 
235
 
236
  function hideModelSelector(trigger) {
237
  hide(trigger);
 
238
  let node = trigger;
 
239
  for (let i = 0; i < 6 && node; i += 1) {
240
  const className = typeof node.className === 'string' ? node.className : '';
241
  if (className.includes('overflow-hidden')) {
 
244
  }
245
  node = node.parentElement;
246
  }
 
247
  hide(trigger);
248
  }
249
 
250
  function applyMinimalUi() {
251
+ applyGuestUpgradeAuthPrompt();
252
+
253
  document
254
  .querySelectorAll('[id^="model-selector-"][id$="-button"]')
255
  .forEach(hideModelSelector);
 
261
  const text = normalize(el.textContent);
262
  if (text.includes("What's New") || text.includes('Release Notes')) {
263
  const closeButton = el.querySelector('button[aria-label="Close"]');
264
+ if (closeButton) closeButton.click();
 
 
265
  hide(el);
266
  }
267
  });
 
270
  const text = normalize(el.textContent);
271
  const aria = normalize(el.getAttribute('aria-label'));
272
  const href = el.getAttribute('href');
 
273
  if (HIDE_TEXT.has(text) || HIDE_ARIA.has(aria) || HIDE_HREF.has(href)) {
274
  hideMenuItem(el);
275
  }
 
278
  document.querySelectorAll('#temporary-chat-button').forEach(hide);
279
  }
280
 
281
+ // ── Boot ───────────────────────────────────────────────────────
282
+ installFetchPatch();
283
 
284
  const run = () => window.requestAnimationFrame(applyMinimalUi);
285