Download src/g.cpp from HelloSun/dosllm: direct link, hf CLI and curl.
- Browser
- Download file 10.2 kB
-
https://huggingface.co/spaces/HelloSun/dosllm/resolve/main/src/g.cpp
- Command line
-
hf download hf://spaces/HelloSun/dosllm/src/g.cpp
-
curl -L -o g.cpp https://huggingface.co/spaces/HelloSun/dosllm/resolve/main/src/g.cpp
10.2 kB
| // G.EXE - ask a Gradio Space one question, over plain HTTP, from DOS. | |
| // | |
| // G <question words...> (Big5 Chinese works when XIAOMAO.EXE is loaded: type with Ctrl+Space) | |
| // G (reads the question from the hex variables GQ1..GQ4, see below) | |
| // environment: GRADIO_HOST (default tencent-hy3.hf.space) | |
| // GQ1..GQ4 = the question as hex bytes (the web page uses this because it cannot type Zhuyin) | |
| // | |
| // 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. | |
| // 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. | |
| 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; | |
| } | |
| // ---- Big5 (the encoding XIAOMAO.EXE displays): lead A1..F9, trail 40..7E or A1..FE ---- | |
| static int isLead( unsigned c ) { return c >= 0xA1 && c <= 0xF9; } | |
| static int isTrail( unsigned c ) { return ( c >= 0x40 && c <= 0x7E ) || ( c >= 0xA1 && c <= 0xFE ); } | |
| static int isBig5Pair( const unsigned char *p ) { return isLead( p[0] ) && isTrail( p[1] ); } | |
| // Keep Big5 pairs intact (the trail byte may be 5Ch '\\'); blank out anything that could break the JSON or the DOS shell. | |
| // Returns 1 if the text contains Chinese. | |
| static int sanitize( char *q ) { | |
| unsigned char *p = (unsigned char *)q, *o = p; | |
| int chinese = 0; | |
| while ( *p ) { | |
| if ( isBig5Pair( p ) ) { *o++ = *p++; *o++ = *p++; chinese = 1; continue; } | |
| unsigned char c = *p++; | |
| if ( c < 32 || c > 126 || c == '"' || c == '\\' ) c = ' '; | |
| *o++ = c; | |
| } | |
| *o = 0; | |
| return chinese; | |
| } | |
| static int hexVal( char h ) { return h >= '0' && h <= '9' ? h - '0' : ( h | 32 ) >= 'a' && ( h | 32 ) <= 'f' ? ( h | 32 ) - 'a' + 10 : -1; } | |
| // Append the bytes encoded as hex in environment variable `name` to q (at most room-1 bytes in total). | |
| static void appendHexEnv( const char *name, char *q, int room ) { | |
| const char *h = getenv( name ); | |
| int n = (int)strlen( q ); | |
| if ( !h ) return; | |
| while ( h[0] && h[1] && n < room - 1 ) { | |
| int hi = hexVal( h[0] ), lo = hexVal( h[1] ); | |
| if ( hi < 0 || lo < 0 ) break; | |
| q[n++] = (char)( hi * 16 + lo ); h += 2; | |
| } | |
| q[n] = 0; | |
| } | |
| // Print `A> text` wrapped at 78 columns. A Chinese character is 2 columns wide and may break anywhere; | |
| // ASCII words break at spaces. | |
| static void wrapPrint( const char *s ) { | |
| const unsigned char *p = (const unsigned char *)s; | |
| int col = 3, space = 0; | |
| printf( "A> " ); | |
| while ( *p ) { | |
| if ( *p == ' ' ) { space = 1; p++; continue; } | |
| int w; const unsigned char *u = p; | |
| if ( isBig5Pair( p ) ) { w = 2; p += 2; } | |
| else { while ( *p && *p != ' ' && !isBig5Pair( p ) ) p++; w = (int)( p - u ); } | |
| int need = w + ( space && col > 3 ? 1 : 0 ); | |
| if ( col + need > 78 ) { printf( "\n " ); col = 3; space = 0; } | |
| if ( space && col > 3 ) { printf( " " ); col++; } | |
| fwrite( u, 1, w, stdout ); col += w; space = 0; | |
| } | |
| printf( "\n" ); | |
| } | |
| // Decode the first JSON string that follows `[["` in the SSE data line and print it. | |
| static void printAnswer( const char *data ) { | |
| const unsigned char *p = (const unsigned char *)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 ) - 3 ) { | |
| if ( isBig5Pair( p ) ) { out[n++] = (char)p[0]; out[n++] = (char)p[1]; p += 2; continue; } // before escape/quote handling | |
| 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 = (char)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++] = (char)*p; | |
| if ( *p ) p++; | |
| continue; | |
| } | |
| out[n++] = (char)*p++; | |
| } | |
| out[n] = 0; | |
| wrapPrint( out ); | |
| } | |
| 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] ); | |
| } | |
| if ( !question[0] ) { appendHexEnv( "GQ1", question, sizeof( question ) ); appendHexEnv( "GQ2", question, sizeof( question ) ); | |
| appendHexEnv( "GQ3", question, sizeof( question ) ); appendHexEnv( "GQ4", question, sizeof( question ) ); } | |
| int chinese = sanitize( question ); | |
| if ( !question[0] ) { printf( "Usage: G <question>\n or set GQ1..GQ4 (hex) 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[520], req[820], now[16]; | |
| sprintf( body, "{\"data\":[\"%s\",\"%s\",[],\"no_think\",null,0,0,null,\"\"]}", question, chinese ? PROMPT_ZH : PROMPT_EN ); | |
| 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; | |
| } | |