// G.EXE - ask a Gradio Space one question, over plain HTTP, from DOS. // // G // 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/ -> 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 \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/ (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; }