Download src/chat.js from pigracing/wq: direct link, hf CLI and curl.
- Browser
- Download file 6.23 kB
-
https://huggingface.co/spaces/pigracing/wq/resolve/main/src/chat.js
- Command line
-
hf download hf://spaces/pigracing/wq/src/chat.js
-
curl -L -o chat.js https://huggingface.co/spaces/pigracing/wq/resolve/main/src/chat.js
6.23 kB
| import { chatHeaders } from './headers.js'; | |
| import { reportTokenError, reportTokenSuccess } from './auth.js'; | |
| const BASE_URL = 'https://chat.qwen.ai'; | |
| export async function createChat(token, model, chatMode = 't2t') { | |
| const res = await fetch(`${BASE_URL}/api/v2/chats/new`, { | |
| method: 'POST', | |
| headers: { | |
| 'authorization': `Bearer ${token}`, | |
| 'content-type': 'application/json', | |
| 'source': 'web', | |
| 'version': '0.2.57', | |
| 'bx-v': '2.5.36', | |
| 'origin': 'https://chat.qwen.ai', | |
| 'referer': 'https://chat.qwen.ai/c/new-chat', | |
| 'user-agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/148.0.0.0 Safari/537.36', | |
| }, | |
| body: JSON.stringify({ | |
| title: '新建对话', | |
| models: [model], | |
| chat_mode: chatMode, | |
| chat_type: chatMode, | |
| timestamp: Date.now(), | |
| project_id: '', | |
| }), | |
| }); | |
| const json = await res.json(); | |
| const chatId = json.data?.id; | |
| if (!chatId) throw new Error(`Failed to create chat: ${JSON.stringify(json)}`); | |
| return chatId; | |
| } | |
| export async function completion({ token, model, messages, chatMode = 't2t', thinkingEnabled = true, searchEnabled = true }) { | |
| const chatId = await createChat(token, model, chatMode); | |
| const timestamp = Math.floor(Date.now() / 1000); | |
| const isSpecialMode = chatMode !== 't2t'; | |
| const isImageMode = chatMode === 't2i'; | |
| const isVideoMode = chatMode === 't2v'; | |
| const isDeepResearch = chatMode === 'deep_research'; | |
| const featureConfig = { | |
| thinking_enabled: isImageMode || isVideoMode ? false : thinkingEnabled, | |
| output_schema: 'phase', | |
| research_mode: isDeepResearch ? 'deep' : 'normal', | |
| auto_thinking: isImageMode || isVideoMode ? false : thinkingEnabled, | |
| thinking_mode: (isImageMode || isVideoMode || !thinkingEnabled) ? 'Disabled' : 'Auto', | |
| thinking_format: 'summary', | |
| auto_search: isImageMode || isVideoMode ? false : searchEnabled, | |
| }; | |
| const body = { | |
| stream: true, | |
| version: '2.1', | |
| incremental_output: true, | |
| chat_id: chatId, | |
| chat_mode: chatMode, | |
| model, | |
| parent_id: null, | |
| messages: messages.map(msg => ({ | |
| fid: crypto.randomUUID(), | |
| parentId: null, | |
| childrenIds: [crypto.randomUUID()], | |
| role: msg.role, | |
| content: msg.content, | |
| user_action: 'chat', | |
| files: [], | |
| timestamp, | |
| models: [model], | |
| chat_type: chatMode, | |
| feature_config: featureConfig, | |
| extra: { meta: { subChatType: chatMode } }, | |
| sub_chat_type: chatMode, | |
| parent_id: null, | |
| })), | |
| timestamp, | |
| }; | |
| const res = await fetch(`${BASE_URL}/api/v2/chat/completions?chat_id=${chatId}`, { | |
| method: 'POST', | |
| headers: chatHeaders(token, chatId), | |
| body: JSON.stringify(body), | |
| }); | |
| if (!res.ok) { | |
| reportTokenError(token); | |
| const text = await res.text(); | |
| throw new Error(`Completion failed: ${res.status} ${text}`); | |
| } | |
| reportTokenSuccess(token); | |
| return { body: res.body }; | |
| } | |
| export async function* parseSSEStream(body) { | |
| const reader = body.getReader(); | |
| const decoder = new TextDecoder(); | |
| let buffer = ''; | |
| let lastPhaseStatus = ''; | |
| try { | |
| while (true) { | |
| const { done, value } = await reader.read(); | |
| if (done) break; | |
| buffer += decoder.decode(value, { stream: true }); | |
| const lines = buffer.split('\n'); | |
| buffer = lines.pop() || ''; | |
| for (const line of lines) { | |
| if (!line.startsWith('data: ')) continue; | |
| const data = line.slice(6).trim(); | |
| if (!data) continue; | |
| try { | |
| const parsed = JSON.parse(data); | |
| if (parsed['response.created'] || parsed['response.info']) continue; | |
| if (parsed.choices) { | |
| for (const choice of parsed.choices) { | |
| const delta = choice.delta; | |
| if (!delta) continue; | |
| const phase = delta.phase; | |
| const status = delta.status; | |
| const content = delta.content || ''; | |
| const usage = parsed.usage; | |
| // Deduplicate: skip same phase+status combo | |
| const key = `${phase}:${status}`; | |
| if (key === lastPhaseStatus && status === 'typing' && !content) continue; | |
| lastPhaseStatus = key; | |
| if (status === 'finished' && phase === 'answer') { | |
| yield { type: 'done', usage }; | |
| return; | |
| } | |
| // Image generation: content is the CDN URL | |
| if (phase === 'image_gen') { | |
| if (status === 'finished') continue; | |
| if (content) { | |
| yield { type: 'image', content, usage }; | |
| } | |
| continue; | |
| } | |
| // Deep research phases | |
| const researchPhases = ['ResearchNotice', 'ResearchPlanning', 'ResearchSearching', 'ResearchReading', 'Writing']; | |
| if (researchPhases.includes(phase)) { | |
| if (status === 'finished' && !content) continue; | |
| const extra = delta.extra || {}; | |
| const drInfo = extra.deep_research || {}; | |
| const stage = drInfo.stage || phase; | |
| if (content) { | |
| yield { type: 'research', content, stage, usage }; | |
| } | |
| continue; | |
| } | |
| // Thinking summary | |
| if (phase === 'thinking_summary') { | |
| if (status === 'finished') continue; | |
| const extra = delta.extra || {}; | |
| const summaryTitle = extra.summary_title?.content?.join('') || ''; | |
| const summaryThought = extra.summary_thought?.content?.join('') || ''; | |
| const thinkingContent = summaryThought || summaryTitle || content; | |
| if (thinkingContent) { | |
| yield { type: 'thinking', content: thinkingContent, usage }; | |
| } | |
| continue; | |
| } | |
| // Regular answer | |
| if (phase === 'answer' && content) { | |
| yield { type: 'content', content, usage }; | |
| } | |
| } | |
| } | |
| } catch { | |
| // skip unparseable lines | |
| } | |
| } | |
| } | |
| } finally { | |
| reader.releaseLock(); | |
| } | |
| } | |