File size: 10,245 Bytes
d4c5ec7 1cd0943 d4c5ec7 1cd0943 d4c5ec7 1cd0943 d4c5ec7 1cd0943 d4c5ec7 1cd0943 d4c5ec7 1cd0943 d4c5ec7 1cd0943 d4c5ec7 1cd0943 d4c5ec7 1cd0943 d4c5ec7 1cd0943 d4c5ec7 1cd0943 d4c5ec7 1cd0943 d4c5ec7 | 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 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 | // 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.
#include "dosnet.h"
#define DEFAULT_HOST "tencent-hy3.hf.space"
#define PROMPT_EN "Answer in plain ASCII English in at most 40 words."
#define PROMPT_ZH "Answer in Traditional Chinese (Taiwan usage) in at most 80 characters. Plain text only: no markdown, no emoji, no Simplified Chinese characters."
#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;
}
// ---- 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;
}
|