@agentforge-io/chat-sdk 2.4.0-dev.7 → 2.4.0-dev.9

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.
@@ -192,13 +192,38 @@ function ChatDrawer(props) {
192
192
  // Motion / sizing portion of the surface style, kept separate from
193
193
  // the host-supplied theming so a re-render driven by drag offset
194
194
  // doesn't smash the host's CSS vars.
195
- const surfaceMotionStyle = {
196
- height: `${drawerHeight}px`,
197
- transform: `translate3d(0, ${translateY}, 0)`,
198
- bottom: `${vv.offsetTop}px`,
199
- transition: isDragging ? 'none' : 'transform 240ms cubic-bezier(.32,.72,0,1)',
200
- willChange: 'transform',
201
- };
195
+ //
196
+ // Two distinct positioning modes:
197
+ //
198
+ // **fullscreen (snap=1)**: pin the surface to the VISUAL viewport
199
+ // box. `top = visualViewport.offsetTop` + `height = visualViewport.h`
200
+ // means the drawer sits exactly on the area the user can see, with
201
+ // the bottom edge flush against the on-screen keyboard (when it's up)
202
+ // or the bottom of the screen (when it's down). No layout viewport
203
+ // gymnastics needed.
204
+ //
205
+ // **bottom-sheet (snap<1)**: anchor the surface to the bottom of the
206
+ // visual viewport. `bottom = visualViewport.offsetTop` is the legacy
207
+ // path that handles the rubber-band scroll case on iOS Safari.
208
+ const surfaceMotionStyle = fullscreen
209
+ ? {
210
+ top: `${vv.offsetTop}px`,
211
+ height: `${vv.h}px`,
212
+ transform: `translate3d(0, ${translateY}, 0)`,
213
+ transition: isDragging
214
+ ? 'none'
215
+ : 'transform 240ms cubic-bezier(.32,.72,0,1)',
216
+ willChange: 'transform',
217
+ }
218
+ : {
219
+ height: `${drawerHeight}px`,
220
+ transform: `translate3d(0, ${translateY}, 0)`,
221
+ bottom: `${vv.offsetTop}px`,
222
+ transition: isDragging
223
+ ? 'none'
224
+ : 'transform 240ms cubic-bezier(.32,.72,0,1)',
225
+ willChange: 'transform',
226
+ };
202
227
  return (0, react_dom_1.createPortal)((0, jsx_runtime_1.jsxs)("div", { className: "af-drawer-root", "data-state": open ? 'open' : 'closed', style: {
203
228
  position: 'fixed',
204
229
  inset: 0,
package/dist/session.d.ts CHANGED
@@ -69,6 +69,7 @@ export declare class ChatSession {
69
69
  private removeMessage;
70
70
  private setStatus;
71
71
  private emitStateChange;
72
+ private debug;
72
73
  private handleError;
73
74
  private emit;
74
75
  }
package/dist/session.js CHANGED
@@ -172,6 +172,7 @@ class ChatSession {
172
172
  const trimmed = text.trim();
173
173
  if (!trimmed)
174
174
  return '';
175
+ this.debug('send() called', { text: trimmed, status: this.state.status });
175
176
  if (this.state.status === 'ended') {
176
177
  throw new Error('Conversation has ended. Start a fresh chat to continue.');
177
178
  }
@@ -180,6 +181,7 @@ class ChatSession {
180
181
  // where the user taps Send while the initial agent/theme
181
182
  // fetch is still in flight and conversationId is unset.
182
183
  await this.start();
184
+ this.debug('send() start awaited, status=' + this.state.status);
183
185
  if (!opts?.silent) {
184
186
  this.appendMessage({
185
187
  id: makeMessageId('u'),
@@ -203,6 +205,7 @@ class ChatSession {
203
205
  }
204
206
  // ─── Internals ──────────────────────────────────────────────────────────
205
207
  async runStream(text, assistant) {
208
+ this.debug('runStream start', { hasConvId: !!this.state.conversationId });
206
209
  this.setStatus('sending');
207
210
  // The "active" message is the one we're currently appending
208
211
  // text_deltas into. For non-team sessions it's always the initial
@@ -221,8 +224,11 @@ class ChatSession {
221
224
  const generator = this.state.conversationId
222
225
  ? this.transport.streamSendMessage(this.state.conversationId, text, this.browserSessionId)
223
226
  : this.transport.streamCreateConversation(text, this.browserSessionId);
227
+ this.debug('runStream got generator, awaiting events');
224
228
  let sawAnyChunk = false;
229
+ let chunkCount = 0;
225
230
  for await (const evt of generator) {
231
+ this.debug('SSE evt', evt.kind, chunkCount++);
226
232
  if (evt.kind === 'conversation') {
227
233
  this.state.conversationId = evt.id;
228
234
  this.emit({ type: 'conversation_started', conversationId: evt.id });
@@ -398,6 +404,7 @@ class ChatSession {
398
404
  cleanup(assistant);
399
405
  if (active !== assistant)
400
406
  cleanup(active);
407
+ this.debug('runStream THREW', err instanceof Error ? err.message : String(err));
401
408
  this.handleError(err);
402
409
  return assistant.content;
403
410
  }
@@ -458,6 +465,50 @@ class ChatSession {
458
465
  emitStateChange() {
459
466
  this.emit({ type: 'state', state: this.getState() });
460
467
  }
468
+ // Lightweight debug logger that BOTH logs to console AND appends
469
+ // to a visible on-screen overlay so mobile QA without remote
470
+ // devtools can still see what's happening. Enable by running
471
+ // `localStorage.setItem('af-chat-debug', '1')` in the browser
472
+ // console (or via the URL `?af-debug=1` once mounted) and
473
+ // reloading. Disabled by default so production ships clean.
474
+ debug(...args) {
475
+ try {
476
+ if (typeof window === 'undefined')
477
+ return;
478
+ const ls = window.localStorage;
479
+ let enabled = ls?.getItem('af-chat-debug') === '1';
480
+ if (!enabled && window.location?.search?.includes('af-debug=1')) {
481
+ ls?.setItem('af-chat-debug', '1');
482
+ enabled = true;
483
+ }
484
+ if (!enabled)
485
+ return;
486
+ const msg = args
487
+ .map((a) => (typeof a === 'string' ? a : JSON.stringify(a)))
488
+ .join(' ');
489
+ // eslint-disable-next-line no-console
490
+ console.log('[chat-sdk]', msg);
491
+ // Visible overlay (single shared element across all sessions).
492
+ let panel = document.getElementById('af-debug-panel');
493
+ if (!panel) {
494
+ panel = document.createElement('div');
495
+ panel.id = 'af-debug-panel';
496
+ panel.style.cssText =
497
+ 'position:fixed;top:0;left:0;right:0;max-height:38vh;overflow:auto;' +
498
+ 'background:rgba(0,0,0,0.85);color:#7CFC00;font:11px/1.3 monospace;' +
499
+ 'padding:6px 8px;z-index:2147483647;pointer-events:auto;' +
500
+ 'white-space:pre-wrap;word-break:break-all;';
501
+ document.body.appendChild(panel);
502
+ }
503
+ const line = document.createElement('div');
504
+ line.textContent = `${new Date().toISOString().slice(11, 23)} ${msg}`;
505
+ panel.appendChild(line);
506
+ panel.scrollTop = panel.scrollHeight;
507
+ }
508
+ catch {
509
+ /* localStorage can throw in private mode — ignore */
510
+ }
511
+ }
461
512
  handleError(err) {
462
513
  const message = err instanceof Error ? err.message : String(err);
463
514
  const code = err && typeof err === 'object' && 'code' in err
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@agentforge-io/chat-sdk",
3
- "version": "2.4.0-dev.7",
3
+ "version": "2.4.0-dev.9",
4
4
  "description": "Framework-free chat session SDK for AgentForge public chat tokens. Headless — no DOM. Drop into any frontend (React, Vue, Svelte, vanilla) and listen for events.",
5
5
  "license": "MIT",
6
6
  "main": "dist/index.js",