File size: 7,412 Bytes
a1e3a8f
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
// G.EXE - ask a Gradio Space one question, over plain HTTP, from DOS.
//
//   G <question words...>
//   environment: GRADIO_HOST (default tencent-hy3.hf.space)
//
// It speaks the documented Gradio HTTP API itself:
//   1) POST /gradio_api/call/chat   {"data":[...9 parameters...]}   -> {"event_id":"..."}
//   2) GET  /gradio_api/call/chat/<event_id>                        -> server-sent events
//      ("generating" frames while the model streams, then one "complete" frame)
// DOS has no TLS, so the page's bridge (gradio-bridge.js) forwards these requests over HTTPS.
#include "mqtt.h"

#define DEFAULT_HOST "tencent-hy3.hf.space"
#define SYSTEM_PROMPT "Answer in plain ASCII English in at most 40 words."
#define LINE_MAX_LEN 2000
// The emulator's TCP sends 1460-byte segments back to back, ignores the receive window and never
// retransmits, so a segment that does not fit in our buffer is lost for good and the connection hangs.
// Keep the receive buffer several segments large and drain it quickly.
#define RECV_BUF 8192

static char line[LINE_MAX_LEN];
static int  lineLen = 0;
static char finalData[LINE_MAX_LEN];
static char evt[16];
static int  haveFinal = 0, sawError = 0, frames = 0;
static char statusLine[48];
static int  statusDone = 0;

static char resp[1024];
static int  respLen = 0;

static void handleLine( void ) {
  if ( !statusDone ) {                               // first line of an HTTP response
    strncpy( statusLine, line, sizeof( statusLine ) - 1 );
    statusDone = 1;
    return;
  }
  if ( strncmp( line, "event: ", 7 ) == 0 ) {
    strncpy( evt, line + 7, sizeof( evt ) - 1 );
    evt[sizeof( evt ) - 1] = 0;
    if ( strcmp( evt, "generating" ) == 0 ) { frames++; printf( "." ); fflush( stdout ); }
    if ( strcmp( evt, "error" ) == 0 ) sawError = 1;
  }
  else if ( strncmp( line, "data: ", 6 ) == 0 && strcmp( evt, "complete" ) == 0 ) {
    strcpy( finalData, line + 6 );
    haveFinal = 1;
  }
}

static void feed( const uint8_t *p, int n, int sse ) {
  for ( int i = 0; i < n; i++ ) {
    if ( !sse ) { if ( respLen < (int)sizeof( resp ) - 1 ) resp[respLen++] = (char)p[i]; continue; }
    char c = (char)p[i];
    if ( c == '\n' ) { line[lineLen] = 0; handleLine( ); lineLen = 0; }
    else if ( c != '\r' && lineLen < LINE_MAX_LEN - 1 ) line[lineLen++] = c;
  }
}

static uint16_t portCounter = 0;

// One HTTP request/response on a fresh TCP connection. The response is read until the server closes.
static int httpExchange( IpAddr_t addr, const char *req, int sse, uint32_t timeoutSec ) {
  TcpSocket *s = TcpSocketMgr::getSocket( );
  if ( s->setRecvBuffer( RECV_BUF ) ) { printf( "TCP: no memory for the receive buffer\n" ); TcpSocketMgr::freeSocket( s ); return -1; }
  portCounter += 17;
  if ( s->connect( (uint16_t)( pseudoPort( ) + portCounter ), addr, 80, 10000 ) != 0 ) {
    TcpSocketMgr::freeSocket( s );
    printf( "TCP: connect to port 80 failed\n" );
    return -1;
  }
  lineLen = 0; respLen = 0; statusDone = 0; statusLine[0] = 0; evt[0] = 0;
  s->send( (uint8_t *)req, (uint16_t)strlen( req ) );

  static uint8_t buf[1024];
  clockTicks_t start = TIMER_GET_CURRENT( );
  int closed = 0, rc = 0;
  while ( !CtrlBreakDetected ) {
    Utils::processPackets( );
    int16_t r = s->recv( buf, sizeof( buf ) );
    if ( r > 0 ) { feed( buf, r, sse ); continue; }
    if ( closed ) break;
    if ( s->isRemoteClosed( ) ) { closed = 1; continue; }   // one more pass to drain the buffer
    if ( Timer_diff( start, TIMER_GET_CURRENT( ) ) > TIMER_SECS_TO_TICKS( timeoutSec ) ) {
      printf( "\nHTTP: timed out after %lu s\n", (unsigned long)timeoutSec ); rc = -2; break;
    }
  }
  s->close( );
  TcpSocketMgr::freeSocket( s );
  if ( sse && lineLen > 0 ) { line[lineLen] = 0; handleLine( ); }
  return rc;
}

// Decode the first JSON string that follows `[["` in the SSE data line and print it word wrapped.
static void printAnswer( const char *data ) {
  const char *p = strstr( data, "[[\"" );
  if ( !p ) { printf( "A> (no text in reply: %.60s)\n", data ); return; }
  p += 3;
  char out[1200]; int n = 0;
  while ( *p && n < (int)sizeof( out ) - 2 ) {
    if ( *p == '"' ) break;
    if ( *p == '\\' ) {
      p++;
      if ( *p == 'n' ) out[n++] = ' ';
      else if ( *p == 'u' ) {
        unsigned v = 0; int k;
        for ( k = 1; k <= 4 && p[k]; k++ ) { char h = p[k]; v = v * 16 + ( h <= '9' ? h - '0' : ( h | 32 ) - 'a' + 10 ); }
        out[n++] = ( v >= 32 && v < 127 ) ? (char)v : '?';
        p += 4;
      }
      else out[n++] = *p;
      if ( *p ) p++;
      continue;
    }
    out[n++] = *p++;
  }
  out[n] = 0;
  printf( "A> " );
  int col = 3;
  for ( char *w = strtok( out, " " ); w; w = strtok( NULL, " " ) ) {
    int l = (int)strlen( w );
    if ( col + l + 1 > 78 ) { printf( "\n   " ); col = 3; }
    printf( "%s ", w ); col += l + 1;
  }
  printf( "\n" );
}

int main( int argc, char *argv[] ) {
  char question[160]; question[0] = 0;
  for ( int i = 1; i < argc; i++ ) {
    if ( strlen( question ) + strlen( argv[i] ) + 2 > sizeof( question ) ) break;
    if ( i > 1 ) strcat( question, " " );
    strcat( question, argv[i] );
  }
  for ( char *q = question; *q; q++ ) if ( *q < 32 || *q > 126 || *q == '"' || *q == '\\' ) *q = ' ';
  if ( !question[0] ) { printf( "Usage: G <question>\n   env GRADIO_HOST (default %s)\n", DEFAULT_HOST ); return 1; }

  const char *host = getenv( "GRADIO_HOST" );
  if ( !host || !*host ) host = DEFAULT_HOST;

  printf( "G.EXE - asking Gradio Space %s\nQ> %s\n", host, question );
  if ( netInit( ) ) { printf( "TCP/IP init failed (is the packet driver loaded?)\n" ); return 2; }

  IpAddr_t addr;
  printf( "DNS: resolving %s ...\n", host );
  if ( resolveHost( host, addr ) ) { printf( "DNS: failed\n" ); netShutdown( 3 ); }
  printf( "DNS: %d.%d.%d.%d\n", addr[0], addr[1], addr[2], addr[3] );

  clockTicks_t t0 = TIMER_GET_CURRENT( );
  char body[400], req[700], now[16];
  sprintf( body, "{\"data\":[\"%s\",\"%s\",[],\"no_think\",null,0,0,null,\"\"]}", question, SYSTEM_PROMPT );
  sprintf( req, "POST /gradio_api/call/chat HTTP/1.0\r\nHost: %s\r\nContent-Type: application/json\r\nContent-Length: %d\r\nConnection: close\r\n\r\n%s",
           host, (int)strlen( body ), body );
  stamp( now );
  printf( "[%s] POST /gradio_api/call/chat (%d bytes)\n", now, (int)strlen( body ) );
  if ( httpExchange( addr, req, 0, 30 ) ) netShutdown( 4 );
  resp[respLen] = 0;
  printf( "HTTP: %.40s\n", resp );

  char *e = strstr( resp, "\"event_id\":\"" );
  if ( !e ) { printf( "No event_id in reply:\n%.300s\n", resp ); netShutdown( 5 ); }
  e += 12;
  char id[64]; int k = 0;
  while ( *e && *e != '"' && k < 62 ) id[k++] = *e++;
  id[k] = 0;
  printf( "event_id: %s\n", id );

  sprintf( req, "GET /gradio_api/call/chat/%s HTTP/1.0\r\nHost: %s\r\nAccept: text/event-stream\r\nConnection: close\r\n\r\n", id, host );
  stamp( now );
  printf( "[%s] GET  /gradio_api/call/chat/<event_id>  (waiting for the model) ", now );
  fflush( stdout );
  if ( httpExchange( addr, req, 1, 150 ) ) netShutdown( 6 );
  printf( "\n" );

  stamp( now );
  if ( !haveFinal ) { printf( "[%s] No answer received (%s, events: %s)\n", now, statusLine, sawError ? "error" : "none" ); netShutdown( 7 ); }
  unsigned long secs = ( TIMER_GET_CURRENT( ) - t0 ) / 18;
  printf( "[%s] %d streaming frames, done in %lu s\n", now, frames, secs );
  printAnswer( finalData );
  netShutdown( 0 );
  return 0;
}