akhaliq HF Staff commited on
Commit
2f53e3f
·
1 Parent(s): afb81bf

show reasoning

Browse files
backend_api.py CHANGED
@@ -733,12 +733,49 @@ def cleanup_generated_code(code: str, language: str) -> str:
733
  return code
734
 
735
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
736
  @app.post("/api/generate")
737
  async def generate_code(
738
  request: CodeGenerationRequest,
739
  authorization: Optional[str] = Header(None)
740
  ):
741
  """Generate code based on user query - returns streaming response"""
 
 
742
  # Dev mode: No authentication required - just use server's HF_TOKEN
743
  # In production, you would check real OAuth tokens here
744
 
@@ -872,14 +909,21 @@ async def generate_code(
872
  })
873
  yield f"data: {event_data}\n\n"
874
 
 
 
 
875
  # Clean up generated code (remove LLM explanatory text and markdown)
876
  generated_code = cleanup_generated_code(generated_code, language)
877
 
878
- # Send completion event (optimized - no timestamp in hot path)
879
- completion_data = json.dumps({
880
  "type": "complete",
881
  "code": generated_code
882
- })
 
 
 
 
883
  yield f"data: {completion_data}\n\n"
884
 
885
  # Auto-deploy after code generation (if authenticated and not skipped)
 
733
  return code
734
 
735
 
736
+ def extract_reasoning(code: str, language: str) -> str:
737
+ """Extract LLM reasoning/explanatory text that's outside the main code block"""
738
+ try:
739
+ if not code:
740
+ return ""
741
+
742
+ # 1. Check for <think> tags (e.g. from DeepSeek-R1 or newer GLM-4)
743
+ think_match = re.search(r'<think>([\s\S]*?)</think>', code, re.IGNORECASE)
744
+ if think_match:
745
+ return think_match.group(1).strip()
746
+
747
+ # 2. Extract everything outside of markdown code blocks
748
+ blocks = list(re.finditer(r'```(?:[\w]*)\s*\n([\s\S]*?)(?:\n```|$)', code, re.IGNORECASE))
749
+
750
+ if not blocks:
751
+ return ""
752
+
753
+ text_parts = []
754
+ last_end = 0
755
+ for match in blocks:
756
+ pre_text = code[last_end:match.start()].strip()
757
+ if pre_text and len(pre_text) > 10:
758
+ text_parts.append(pre_text)
759
+ last_end = match.end()
760
+
761
+ post_text = code[last_end:].strip()
762
+ if post_text and len(post_text) > 10:
763
+ text_parts.append(post_text)
764
+
765
+ return "\n\n".join(text_parts).strip()
766
+ except Exception as e:
767
+ print(f"[Reasoning Extraction] Error: {e}")
768
+ return ""
769
+
770
+
771
  @app.post("/api/generate")
772
  async def generate_code(
773
  request: CodeGenerationRequest,
774
  authorization: Optional[str] = Header(None)
775
  ):
776
  """Generate code based on user query - returns streaming response"""
777
+
778
+
779
  # Dev mode: No authentication required - just use server's HF_TOKEN
780
  # In production, you would check real OAuth tokens here
781
 
 
909
  })
910
  yield f"data: {event_data}\n\n"
911
 
912
+ # Extract reasoning before cleaning up
913
+ reasoning = extract_reasoning(generated_code, language)
914
+
915
  # Clean up generated code (remove LLM explanatory text and markdown)
916
  generated_code = cleanup_generated_code(generated_code, language)
917
 
918
+ # Send completion event (include reasoning for GLM-4.7)
919
+ completion_dict = {
920
  "type": "complete",
921
  "code": generated_code
922
+ }
923
+ if selected_model_id == "zai-org/GLM-4.7" and reasoning:
924
+ completion_dict["reasoning"] = reasoning
925
+
926
+ completion_data = json.dumps(completion_dict)
927
  yield f"data: {completion_data}\n\n"
928
 
929
  # Auto-deploy after code generation (if authenticated and not skipped)
frontend/src/app/page.tsx CHANGED
@@ -367,16 +367,20 @@ export default function Home() {
367
  });
368
  },
369
  // onComplete
370
- (code: string) => {
371
  setGeneratedCode(code);
372
  setIsGenerating(false);
373
 
374
- // Update final message - just show success, not the code
375
  setMessages((prev) => {
376
  const newMessages = [...prev];
 
 
 
 
377
  newMessages[newMessages.length - 1] = {
378
  ...assistantMessage,
379
- content: '✅ Code generated successfully! Check the editor →',
380
  };
381
  return newMessages;
382
  });
 
367
  });
368
  },
369
  // onComplete
370
+ (code: string, reasoning?: string) => {
371
  setGeneratedCode(code);
372
  setIsGenerating(false);
373
 
374
+ // Update final message - include reasoning if available
375
  setMessages((prev) => {
376
  const newMessages = [...prev];
377
+ const content = reasoning
378
+ ? `✅ Code generated successfully!\n\n**Reasoning:**\n${reasoning}\n\nCheck the editor →`
379
+ : '✅ Code generated successfully! Check the editor →';
380
+
381
  newMessages[newMessages.length - 1] = {
382
  ...assistantMessage,
383
+ content: content,
384
  };
385
  return newMessages;
386
  });
frontend/src/lib/api.ts CHANGED
@@ -18,20 +18,20 @@ const getApiUrl = () => {
18
  console.log('[API Client] Using explicit API URL:', process.env.NEXT_PUBLIC_API_URL);
19
  return process.env.NEXT_PUBLIC_API_URL;
20
  }
21
-
22
  // For server-side rendering, always use relative URLs
23
  if (typeof window === 'undefined') {
24
  console.log('[API Client] SSR mode: using relative URLs');
25
  return '';
26
  }
27
-
28
  // On localhost (dev mode), use direct backend URL
29
  const hostname = window.location.hostname;
30
  if (hostname === 'localhost' || hostname === '127.0.0.1') {
31
  console.log('[API Client] Localhost dev mode: using http://localhost:8000');
32
  return 'http://localhost:8000';
33
  }
34
-
35
  // In production (HF Space), use relative URLs (Next.js proxies to backend)
36
  console.log('[API Client] Production mode: using relative URLs (proxied by Next.js)');
37
  return '';
@@ -70,23 +70,23 @@ class ApiClient {
70
  if (error.response && error.response.status === 401) {
71
  const errorData = error.response.data;
72
  const errorMessage = errorData?.detail || errorData?.message || '';
73
-
74
  // Only log out if it's an authentication/session issue
75
  // Don't log out for permission errors on specific resources
76
- const shouldLogout =
77
  errorMessage.includes('Authentication required') ||
78
  errorMessage.includes('Invalid token') ||
79
  errorMessage.includes('Token expired') ||
80
  errorMessage.includes('Session expired') ||
81
  error.config?.url?.includes('/auth/');
82
-
83
  if (shouldLogout && typeof window !== 'undefined') {
84
  // Clear ALL authentication data including session token
85
  localStorage.removeItem('hf_oauth_token');
86
  localStorage.removeItem('hf_session_token');
87
  localStorage.removeItem('hf_user_info');
88
  this.token = null;
89
-
90
  // Dispatch custom event to notify UI components
91
  window.dispatchEvent(new CustomEvent('auth-expired', {
92
  detail: { message: 'Your session has expired. Please sign in again.' }
@@ -116,19 +116,19 @@ class ApiClient {
116
  // Cache helpers
117
  private getCachedData<T>(key: string, maxAgeMs: number): T | null {
118
  if (typeof window === 'undefined') return null;
119
-
120
  try {
121
  const cached = localStorage.getItem(key);
122
  if (!cached) return null;
123
-
124
  const { data, timestamp } = JSON.parse(cached);
125
  const age = Date.now() - timestamp;
126
-
127
  if (age > maxAgeMs) {
128
  localStorage.removeItem(key);
129
  return null;
130
  }
131
-
132
  return data;
133
  } catch (error) {
134
  console.error(`Failed to get cached data for ${key}:`, error);
@@ -138,7 +138,7 @@ class ApiClient {
138
 
139
  private setCachedData<T>(key: string, data: T): void {
140
  if (typeof window === 'undefined') return;
141
-
142
  try {
143
  localStorage.setItem(key, JSON.stringify({
144
  data,
@@ -161,26 +161,26 @@ class ApiClient {
161
  console.log('Fetching models from API...');
162
  const response = await this.client.get<Model[]>('/api/models');
163
  const models = response.data;
164
-
165
  // Cache the successful response
166
  if (models && models.length > 0) {
167
  this.setCachedData('anycoder_models', models);
168
  console.log('Cached', models.length, 'models (valid for 24 hours)');
169
  }
170
-
171
  return models;
172
  } catch (error: any) {
173
  // Handle connection errors gracefully
174
- const isConnectionError =
175
- error.code === 'ECONNABORTED' ||
176
- error.code === 'ECONNRESET' ||
177
  error.code === 'ECONNREFUSED' ||
178
  error.message?.includes('socket hang up') ||
179
  error.message?.includes('timeout') ||
180
  error.message?.includes('Network Error') ||
181
  error.response?.status === 503 ||
182
  error.response?.status === 502;
183
-
184
  if (isConnectionError) {
185
  // Try to return stale cache if available
186
  const staleCache = this.getCachedData<Model[]>('anycoder_models', Infinity);
@@ -188,7 +188,7 @@ class ApiClient {
188
  console.warn('Backend not available, using stale cached models');
189
  return staleCache;
190
  }
191
-
192
  console.warn('Backend not available, cannot load models');
193
  return [];
194
  }
@@ -209,26 +209,26 @@ class ApiClient {
209
  console.log('Fetching languages from API...');
210
  const response = await this.client.get<{ languages: Language[] }>('/api/languages');
211
  const languages = response.data.languages;
212
-
213
  // Cache the successful response
214
  if (languages && languages.length > 0) {
215
  this.setCachedData('anycoder_languages', languages);
216
  console.log('Cached', languages.length, 'languages (valid for 24 hours)');
217
  }
218
-
219
  return response.data;
220
  } catch (error: any) {
221
  // Handle connection errors gracefully
222
- const isConnectionError =
223
- error.code === 'ECONNABORTED' ||
224
- error.code === 'ECONNRESET' ||
225
  error.code === 'ECONNREFUSED' ||
226
  error.message?.includes('socket hang up') ||
227
  error.message?.includes('timeout') ||
228
  error.message?.includes('Network Error') ||
229
  error.response?.status === 503 ||
230
  error.response?.status === 502;
231
-
232
  if (isConnectionError) {
233
  // Try to return stale cache if available
234
  const staleCache = this.getCachedData<Language[]>('anycoder_languages', Infinity);
@@ -236,7 +236,7 @@ class ApiClient {
236
  console.warn('Backend not available, using stale cached languages');
237
  return { languages: staleCache };
238
  }
239
-
240
  // Fall back to default languages
241
  console.warn('Backend not available, using default languages');
242
  return { languages: ['html', 'gradio', 'transformers.js', 'streamlit', 'comfyui', 'react'] };
@@ -273,7 +273,7 @@ class ApiClient {
273
  generateCodeStream(
274
  request: CodeGenerationRequest,
275
  onChunk: (content: string) => void,
276
- onComplete: (code: string) => void,
277
  onError: (error: string) => void,
278
  onDeploying?: (message: string) => void,
279
  onDeployed?: (message: string, spaceUrl: string) => void,
@@ -282,11 +282,11 @@ class ApiClient {
282
  // Build the URL correctly whether we have a base URL or not
283
  const baseUrl = API_URL || window.location.origin;
284
  const url = new URL('/api/generate', baseUrl);
285
-
286
  let abortController = new AbortController();
287
  let accumulatedCode = '';
288
  let buffer = ''; // Buffer for incomplete SSE lines
289
-
290
  // Use fetch with POST to support large payloads
291
  fetch(url.toString(), {
292
  method: 'POST',
@@ -303,21 +303,21 @@ class ApiClient {
303
  onError('⏱️ Rate limit exceeded. Free tier allows up to 20 requests per minute. Please wait a moment and try again.');
304
  return;
305
  }
306
-
307
  if (!response.ok) {
308
  throw new Error(`HTTP error! status: ${response.status}`);
309
  }
310
-
311
  if (!response.body) {
312
  throw new Error('Response body is null');
313
  }
314
-
315
  const reader = response.body.getReader();
316
  const decoder = new TextDecoder();
317
-
318
  while (true) {
319
  const { done, value } = await reader.read();
320
-
321
  if (done) {
322
  console.log('[Stream] Stream ended, total code length:', accumulatedCode.length);
323
  if (accumulatedCode) {
@@ -325,20 +325,20 @@ class ApiClient {
325
  }
326
  break;
327
  }
328
-
329
  // Decode chunk and add to buffer
330
  buffer += decoder.decode(value, { stream: true });
331
-
332
  // Process complete SSE messages (ending with \n\n)
333
  const messages = buffer.split('\n\n');
334
-
335
  // Keep the last incomplete message in the buffer
336
  buffer = messages.pop() || '';
337
-
338
  // Process each complete message
339
  for (const message of messages) {
340
  if (!message.trim()) continue;
341
-
342
  // Parse SSE format: "data: {...}"
343
  const lines = message.split('\n');
344
  for (const line of lines) {
@@ -347,7 +347,7 @@ class ApiClient {
347
  const jsonStr = line.substring(6);
348
  const data = JSON.parse(jsonStr);
349
  console.log('[Stream] Received event:', data.type, data.content?.substring(0, 50));
350
-
351
  if (data.type === 'chunk' && data.content) {
352
  accumulatedCode += data.content;
353
  onChunk(data.content);
@@ -355,7 +355,7 @@ class ApiClient {
355
  console.log('[Stream] Generation complete, total code length:', data.code?.length || accumulatedCode.length);
356
  // Use the complete code from the message if available, otherwise use accumulated
357
  const finalCode = data.code || accumulatedCode;
358
- onComplete(finalCode);
359
  // Don't return yet - might have deployment events coming
360
  } else if (data.type === 'deploying') {
361
  console.log('[Stream] Deployment started:', data.message);
@@ -419,7 +419,7 @@ class ApiClient {
419
  ws.onmessage = (event) => {
420
  try {
421
  const data = JSON.parse(event.data);
422
-
423
  if (data.type === 'chunk' && data.content) {
424
  onChunk(data.content);
425
  } else if (data.type === 'complete' && data.code) {
@@ -453,7 +453,7 @@ class ApiClient {
453
  space_name: request.space_name,
454
  existing_repo_id: request.existing_repo_id,
455
  });
456
-
457
  try {
458
  const response = await this.client.post<DeploymentResponse>('/api/deploy', request);
459
  console.log('[API Client] Deploy response:', response.status, response.data);
@@ -521,13 +521,13 @@ class ApiClient {
521
  const response = await axios.get('https://huggingface.co/api/spaces', {
522
  timeout: 5000,
523
  });
524
-
525
  // Filter for apps with 'anycoder' tag and sort by trendingScore
526
  const anycoderApps = response.data
527
  .filter((space: any) => space.tags && space.tags.includes('anycoder'))
528
  .sort((a: any, b: any) => (b.trendingScore || 0) - (a.trendingScore || 0))
529
  .slice(0, 6);
530
-
531
  return anycoderApps;
532
  } catch (error) {
533
  console.error('Failed to fetch trending anycoder apps:', error);
 
18
  console.log('[API Client] Using explicit API URL:', process.env.NEXT_PUBLIC_API_URL);
19
  return process.env.NEXT_PUBLIC_API_URL;
20
  }
21
+
22
  // For server-side rendering, always use relative URLs
23
  if (typeof window === 'undefined') {
24
  console.log('[API Client] SSR mode: using relative URLs');
25
  return '';
26
  }
27
+
28
  // On localhost (dev mode), use direct backend URL
29
  const hostname = window.location.hostname;
30
  if (hostname === 'localhost' || hostname === '127.0.0.1') {
31
  console.log('[API Client] Localhost dev mode: using http://localhost:8000');
32
  return 'http://localhost:8000';
33
  }
34
+
35
  // In production (HF Space), use relative URLs (Next.js proxies to backend)
36
  console.log('[API Client] Production mode: using relative URLs (proxied by Next.js)');
37
  return '';
 
70
  if (error.response && error.response.status === 401) {
71
  const errorData = error.response.data;
72
  const errorMessage = errorData?.detail || errorData?.message || '';
73
+
74
  // Only log out if it's an authentication/session issue
75
  // Don't log out for permission errors on specific resources
76
+ const shouldLogout =
77
  errorMessage.includes('Authentication required') ||
78
  errorMessage.includes('Invalid token') ||
79
  errorMessage.includes('Token expired') ||
80
  errorMessage.includes('Session expired') ||
81
  error.config?.url?.includes('/auth/');
82
+
83
  if (shouldLogout && typeof window !== 'undefined') {
84
  // Clear ALL authentication data including session token
85
  localStorage.removeItem('hf_oauth_token');
86
  localStorage.removeItem('hf_session_token');
87
  localStorage.removeItem('hf_user_info');
88
  this.token = null;
89
+
90
  // Dispatch custom event to notify UI components
91
  window.dispatchEvent(new CustomEvent('auth-expired', {
92
  detail: { message: 'Your session has expired. Please sign in again.' }
 
116
  // Cache helpers
117
  private getCachedData<T>(key: string, maxAgeMs: number): T | null {
118
  if (typeof window === 'undefined') return null;
119
+
120
  try {
121
  const cached = localStorage.getItem(key);
122
  if (!cached) return null;
123
+
124
  const { data, timestamp } = JSON.parse(cached);
125
  const age = Date.now() - timestamp;
126
+
127
  if (age > maxAgeMs) {
128
  localStorage.removeItem(key);
129
  return null;
130
  }
131
+
132
  return data;
133
  } catch (error) {
134
  console.error(`Failed to get cached data for ${key}:`, error);
 
138
 
139
  private setCachedData<T>(key: string, data: T): void {
140
  if (typeof window === 'undefined') return;
141
+
142
  try {
143
  localStorage.setItem(key, JSON.stringify({
144
  data,
 
161
  console.log('Fetching models from API...');
162
  const response = await this.client.get<Model[]>('/api/models');
163
  const models = response.data;
164
+
165
  // Cache the successful response
166
  if (models && models.length > 0) {
167
  this.setCachedData('anycoder_models', models);
168
  console.log('Cached', models.length, 'models (valid for 24 hours)');
169
  }
170
+
171
  return models;
172
  } catch (error: any) {
173
  // Handle connection errors gracefully
174
+ const isConnectionError =
175
+ error.code === 'ECONNABORTED' ||
176
+ error.code === 'ECONNRESET' ||
177
  error.code === 'ECONNREFUSED' ||
178
  error.message?.includes('socket hang up') ||
179
  error.message?.includes('timeout') ||
180
  error.message?.includes('Network Error') ||
181
  error.response?.status === 503 ||
182
  error.response?.status === 502;
183
+
184
  if (isConnectionError) {
185
  // Try to return stale cache if available
186
  const staleCache = this.getCachedData<Model[]>('anycoder_models', Infinity);
 
188
  console.warn('Backend not available, using stale cached models');
189
  return staleCache;
190
  }
191
+
192
  console.warn('Backend not available, cannot load models');
193
  return [];
194
  }
 
209
  console.log('Fetching languages from API...');
210
  const response = await this.client.get<{ languages: Language[] }>('/api/languages');
211
  const languages = response.data.languages;
212
+
213
  // Cache the successful response
214
  if (languages && languages.length > 0) {
215
  this.setCachedData('anycoder_languages', languages);
216
  console.log('Cached', languages.length, 'languages (valid for 24 hours)');
217
  }
218
+
219
  return response.data;
220
  } catch (error: any) {
221
  // Handle connection errors gracefully
222
+ const isConnectionError =
223
+ error.code === 'ECONNABORTED' ||
224
+ error.code === 'ECONNRESET' ||
225
  error.code === 'ECONNREFUSED' ||
226
  error.message?.includes('socket hang up') ||
227
  error.message?.includes('timeout') ||
228
  error.message?.includes('Network Error') ||
229
  error.response?.status === 503 ||
230
  error.response?.status === 502;
231
+
232
  if (isConnectionError) {
233
  // Try to return stale cache if available
234
  const staleCache = this.getCachedData<Language[]>('anycoder_languages', Infinity);
 
236
  console.warn('Backend not available, using stale cached languages');
237
  return { languages: staleCache };
238
  }
239
+
240
  // Fall back to default languages
241
  console.warn('Backend not available, using default languages');
242
  return { languages: ['html', 'gradio', 'transformers.js', 'streamlit', 'comfyui', 'react'] };
 
273
  generateCodeStream(
274
  request: CodeGenerationRequest,
275
  onChunk: (content: string) => void,
276
+ onComplete: (code: string, reasoning?: string) => void,
277
  onError: (error: string) => void,
278
  onDeploying?: (message: string) => void,
279
  onDeployed?: (message: string, spaceUrl: string) => void,
 
282
  // Build the URL correctly whether we have a base URL or not
283
  const baseUrl = API_URL || window.location.origin;
284
  const url = new URL('/api/generate', baseUrl);
285
+
286
  let abortController = new AbortController();
287
  let accumulatedCode = '';
288
  let buffer = ''; // Buffer for incomplete SSE lines
289
+
290
  // Use fetch with POST to support large payloads
291
  fetch(url.toString(), {
292
  method: 'POST',
 
303
  onError('⏱️ Rate limit exceeded. Free tier allows up to 20 requests per minute. Please wait a moment and try again.');
304
  return;
305
  }
306
+
307
  if (!response.ok) {
308
  throw new Error(`HTTP error! status: ${response.status}`);
309
  }
310
+
311
  if (!response.body) {
312
  throw new Error('Response body is null');
313
  }
314
+
315
  const reader = response.body.getReader();
316
  const decoder = new TextDecoder();
317
+
318
  while (true) {
319
  const { done, value } = await reader.read();
320
+
321
  if (done) {
322
  console.log('[Stream] Stream ended, total code length:', accumulatedCode.length);
323
  if (accumulatedCode) {
 
325
  }
326
  break;
327
  }
328
+
329
  // Decode chunk and add to buffer
330
  buffer += decoder.decode(value, { stream: true });
331
+
332
  // Process complete SSE messages (ending with \n\n)
333
  const messages = buffer.split('\n\n');
334
+
335
  // Keep the last incomplete message in the buffer
336
  buffer = messages.pop() || '';
337
+
338
  // Process each complete message
339
  for (const message of messages) {
340
  if (!message.trim()) continue;
341
+
342
  // Parse SSE format: "data: {...}"
343
  const lines = message.split('\n');
344
  for (const line of lines) {
 
347
  const jsonStr = line.substring(6);
348
  const data = JSON.parse(jsonStr);
349
  console.log('[Stream] Received event:', data.type, data.content?.substring(0, 50));
350
+
351
  if (data.type === 'chunk' && data.content) {
352
  accumulatedCode += data.content;
353
  onChunk(data.content);
 
355
  console.log('[Stream] Generation complete, total code length:', data.code?.length || accumulatedCode.length);
356
  // Use the complete code from the message if available, otherwise use accumulated
357
  const finalCode = data.code || accumulatedCode;
358
+ onComplete(finalCode, data.reasoning);
359
  // Don't return yet - might have deployment events coming
360
  } else if (data.type === 'deploying') {
361
  console.log('[Stream] Deployment started:', data.message);
 
419
  ws.onmessage = (event) => {
420
  try {
421
  const data = JSON.parse(event.data);
422
+
423
  if (data.type === 'chunk' && data.content) {
424
  onChunk(data.content);
425
  } else if (data.type === 'complete' && data.code) {
 
453
  space_name: request.space_name,
454
  existing_repo_id: request.existing_repo_id,
455
  });
456
+
457
  try {
458
  const response = await this.client.post<DeploymentResponse>('/api/deploy', request);
459
  console.log('[API Client] Deploy response:', response.status, response.data);
 
521
  const response = await axios.get('https://huggingface.co/api/spaces', {
522
  timeout: 5000,
523
  });
524
+
525
  // Filter for apps with 'anycoder' tag and sort by trendingScore
526
  const anycoderApps = response.data
527
  .filter((space: any) => space.tags && space.tags.includes('anycoder'))
528
  .sort((a: any, b: any) => (b.trendingScore || 0) - (a.trendingScore || 0))
529
  .slice(0, 6);
530
+
531
  return anycoderApps;
532
  } catch (error) {
533
  console.error('Failed to fetch trending anycoder apps:', error);