Skip to content

Commit

Permalink
feat: support arkose in chat
Browse files Browse the repository at this point in the history
  • Loading branch information
moeakwak committed Feb 8, 2024
1 parent a5b678d commit 8bb837d
Show file tree
Hide file tree
Showing 13 changed files with 205 additions and 139 deletions.
5 changes: 3 additions & 2 deletions backend/api/routers/arkose.py
Original file line number Diff line number Diff line change
Expand Up @@ -31,8 +31,9 @@ async def forward_arkose_request(path: str, _user: User = Depends(current_active
return ArkoseForwardException(code=500, message=str(e))


@router.get("/arkose/api_js_path", tags=["arkose"])
async def get_arkose_api_js_path(_user: User = Depends(current_active_user)):
@router.get("/arkose/info", tags=["arkose"])
async def get_arkose_info(_user: User = Depends(current_active_user)):
return {
"enabled": config.openai_web.enable_arkose_endpoint,
"url": f"{config.openai_web.arkose_endpoint_base}35536E1E-65B4-4D96-9D97-6ADB7EFF8147/api.js"
}
1 change: 1 addition & 0 deletions backend/api/routers/chat.py
Original file line number Diff line number Diff line change
Expand Up @@ -361,6 +361,7 @@ async def reply(response: AskResponse):
plugin_ids=ask_request.openai_web_plugin_ids if ask_request.new_conversation else None,
attachments=ask_request.openai_web_attachments,
multimodal_image_parts=ask_request.openai_web_multimodal_image_parts,
arkose_token=ask_request.arkose_token,
):
has_got_reply = True

Expand Down
1 change: 1 addition & 0 deletions backend/api/schemas/conversation_schemas.py
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,7 @@ class AskRequest(BaseModel):
openai_web_plugin_ids: Optional[list[str]] = None
openai_web_attachments: Optional[list[OpenaiWebChatMessageMetadataAttachment]] = None
openai_web_multimodal_image_parts: Optional[list[OpenaiWebChatMessageMultimodalTextContentImagePart]] = None
arkose_token: Optional[str] = None

@model_validator(mode='before')
@classmethod
Expand Down
19 changes: 12 additions & 7 deletions backend/api/sources/openai_web.py
Original file line number Diff line number Diff line change
Expand Up @@ -320,6 +320,7 @@ async def complete(self, model: OpenaiWebChatModels, text_content: str, use_team
plugin_ids: list[str] = None,
attachments: list[OpenaiWebChatMessageMetadataAttachment] = None,
multimodal_image_parts: list[OpenaiWebChatMessageMultimodalTextContentImagePart] = None,
arkose_token: str = None,
**_kwargs):

assert config.openai_web.enabled, "OpenAI Web is not enabled"
Expand Down Expand Up @@ -367,23 +368,27 @@ async def complete(self, model: OpenaiWebChatModels, text_content: str, use_team

completion_request = OpenaiWebCompleteRequest(
action=action,
arkose_token=None,
arkose_token=arkose_token,
conversation_mode=OpenaiWebCompleteRequestConversationMode(kind="primary_assistant"),
conversation_id=str(conversation_id) if conversation_id else None,
messages=messages,
parent_message_id=str(parent_message_id) if parent_message_id else None,
model=model.code(),
plugin_ids=plugin_ids
).dict(exclude_none=True)
completion_request["arkose_token"] = None
)
completion_request_dict = completion_request.dict(exclude_none=True)
if "arkose_token" not in completion_request_dict:
completion_request_dict["arkose_token"] = None
data_json = json.dumps(jsonable_encoder(completion_request))

headers = req_headers(use_team) | {
"referer": "https://chat.openai.com/" + (f"c/{conversation_id}" if conversation_id else "")}
if arkose_token is not None:
headers["Openai-Sentinel-Arkose-Token"] = arkose_token

async with self.session.stream(method="POST", url=f"{config.openai_web.chatgpt_base_url}conversation",
data=data_json, timeout=timeout,
headers=req_headers(use_team) | {
"referer": "https://chat.openai.com/" + (
f"c/{conversation_id}" if conversation_id else "")
}) as response:
headers=headers) as response:
await _check_response(response)

async for line in response.aiter_lines():
Expand Down
4 changes: 2 additions & 2 deletions frontend/src/api/arkose.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,6 @@ import axios from 'axios';

import ApiUrl from './url';

export function getArkoseApiJsPath() {
return axios.get<{ url: string }>(ApiUrl.ArkoseApiJsPath);
export function getArkoseInfo() {
return axios.get<{ enabled: boolean, url: string }>(ApiUrl.ArkoseInfo);
}
2 changes: 1 addition & 1 deletion frontend/src/api/url.ts
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,7 @@ enum ApiUrl {
FilesOpenaiWebUploadComplete = '/files/openai-web/upload-complete',
FilesLocalUploadToOpenaiWeb = '/files/local/upload-to-openai-web',

ArkoseApiJsPath = '/arkose/api_js_path',
ArkoseInfo = '/arkose/info',
}

export default ApiUrl;
11 changes: 8 additions & 3 deletions frontend/src/locales/zh-CN.json
Original file line number Diff line number Diff line change
Expand Up @@ -239,7 +239,9 @@
"unknown": "未知 OpenAI 错误"
},
"unknown": "未知错误",
"invalidRequest": "非法请求"
"invalidRequest": "非法请求",
"arkoseError": "Arkose 验证失败",
"arkoseTokenError": "Arkose 验证失败,请联系管理员处理。"
},
"tips": {
"loginExpired": "登录已过期。是否跳转到登录页面?",
Expand Down Expand Up @@ -359,7 +361,8 @@
"enabled_models": "启用的模型",
"model_code_mapping": "模型代码映射",
"file_upload_strategy": "文件上传策略",
"max_completion_concurrency": "最大并行对话数量"
"max_completion_concurrency": "最大并行对话数量",
"enable_arkose_endpoint": "启用前端 Arkose 验证"
},
"conversation_id": "对话ID",
"queueing_time": "排队时长",
Expand Down Expand Up @@ -406,7 +409,9 @@
"config": {
"enabled_models": "设置全局启用哪些模型,在此处不启用的模型所有账号都无法使用",
"file_upload_strategy": "配置使用 code interpreter 模型时文件如何上传,详见文档",
"max_completion_concurrency": "决定同时能有多少用户进行对话。如果设置为一个很大的值,相当于禁用排队对话功能。"
"max_completion_concurrency": "决定同时能有多少用户进行对话。如果设置为一个很大的值,相当于禁用排队对话功能。",
"enable_arkose_endpoint": "实验性功能,启用前请仔细阅读文档说明。启用后务必填写下方的 arkose_endpoint_base。如果启用,将会在前端取得 Arkose token,用户可能需要进行手动验证。",
"arkose_endpoint_base": "Arkose 代理的地址前缀,必须以 /v2/ 结尾,如:http://ninja/v2/"
}
},
"dialog": {
Expand Down
2 changes: 1 addition & 1 deletion frontend/src/types/json/openapi.json

Large diffs are not rendered by default.

11 changes: 11 additions & 0 deletions frontend/src/types/json/schemas.json
Original file line number Diff line number Diff line change
Expand Up @@ -637,6 +637,17 @@
}
],
"title": "Openai Web Multimodal Image Parts"
},
"arkose_token": {
"anyOf": [
{
"type": "string"
},
{
"type": "null"
}
],
"title": "Arkose Token"
}
},
"type": "object",
Expand Down
18 changes: 10 additions & 8 deletions frontend/src/types/openapi.ts
Original file line number Diff line number Diff line change
Expand Up @@ -205,16 +205,16 @@ export interface paths {
*/
post: operations["upload_local_file_to_openai_web_files_local_upload_to_openai_web__file_id__post"];
};
"/arkose/{path}": {
"/arkose/v2/{path}": {
/**
* Forward Arkose Request
* @description TODO 经过转发,arkose 会报错 "API_REQUEST_ERROR"
*/
get: operations["forward_arkose_request_arkose__path__get"];
get: operations["forward_arkose_request_arkose_v2__path__get"];
};
"/arkose/api_js_path": {
/** Get Arkose Api Js Path */
get: operations["get_arkose_api_js_path_arkose_api_js_path_get"];
"/arkose/info": {
/** Get Arkose Info */
get: operations["get_arkose_info_arkose_info_get"];
};
}

Expand Down Expand Up @@ -290,6 +290,8 @@ export interface components {
openai_web_attachments?: components["schemas"]["OpenaiWebChatMessageMetadataAttachment"][] | null;
/** Openai Web Multimodal Image Parts */
openai_web_multimodal_image_parts?: components["schemas"]["OpenaiWebChatMessageMultimodalTextContentImagePart-Input"][] | null;
/** Arkose Token */
arkose_token?: string | null;
};
/** AskResponse */
AskResponse: {
Expand Down Expand Up @@ -2983,7 +2985,7 @@ export interface operations {
* Forward Arkose Request
* @description TODO 经过转发,arkose 会报错 "API_REQUEST_ERROR"
*/
forward_arkose_request_arkose__path__get: {
forward_arkose_request_arkose_v2__path__get: {
parameters: {
path: {
path: string;
Expand All @@ -3004,8 +3006,8 @@ export interface operations {
};
};
};
/** Get Arkose Api Js Path */
get_arkose_api_js_path_arkose_api_js_path_get: {
/** Get Arkose Info */
get_arkose_info_arkose_info_get: {
responses: {
/** @description Successful Response */
200: {
Expand Down
135 changes: 84 additions & 51 deletions frontend/src/utils/arkose.ts
Original file line number Diff line number Diff line change
@@ -1,12 +1,6 @@
import { nextTick } from 'vue';

import { getArkoseApiJsPath } from '@/api/arkose';

import { Message } from './tips';

declare global {
interface Window {
myEnforcement: any;
arkoseEnforcement: any;
setupEnforcement: any;
}
}
Expand Down Expand Up @@ -47,60 +41,54 @@ function removeScript(scriptId: string) {
}

export function setupEnforcement(
arkoseUrl: string,
config: ArkoseEnforcementConfig,
scriptId: string,
onLoaded: () => void,
onError: (error: any) => void
): void {
removeScript(scriptId);

getArkoseApiJsPath()
.then((response) => {
const { url } = response.data;
const script = document.createElement('script');
script.setAttribute('data-callback', 'setupEnforcement');
script.id = scriptId;
script.type = 'text/javascript';
script.src = url;
script.async = true;
script.defer = true;
if (config.nonce) {
script.setAttribute('data-nonce', config.nonce);
}
const script = document.createElement('script');
script.setAttribute('data-callback', 'setupEnforcement');
script.id = scriptId;
script.type = 'text/javascript';
script.src = arkoseUrl;
script.async = true;
script.defer = true;
if (config.nonce) {
script.setAttribute('data-nonce', config.nonce);
}

const setupEnforcement = (myEnforcement: any) => {
window.myEnforcement = myEnforcement;
window.myEnforcement.setConfig({
selector: config.selector,
mode: config.mode,
onReady: config.onReady,
onShown: config.onShown,
onShow: config.onShow,
onSuppress: config.onSuppress,
onCompleted: config.onCompleted,
onReset: config.onReset,
onHide: config.onHide,
onError: config.onError,
onFailed: config.onFailed,
});
};
window.setupEnforcement = setupEnforcement;
const setupEnforcement = (arkoseEnforcement: any) => {
window.arkoseEnforcement = arkoseEnforcement;
window.arkoseEnforcement.setConfig({
selector: config.selector,
mode: config.mode,
onReady: config.onReady,
onShown: config.onShown,
onShow: config.onShow,
onSuppress: config.onSuppress,
onCompleted: config.onCompleted,
onReset: config.onReset,
onHide: config.onHide,
onError: config.onError,
onFailed: config.onFailed,
});
};
window.setupEnforcement = setupEnforcement;

script.onload = () => {
onLoaded();
};
script.onerror = onError;
script.onload = () => {
onLoaded();
};
script.onerror = onError;

document.body.appendChild(script);
})
.catch((error) => {
Message.error('Failed to get Arkose api.js URL. Did you set arkose_endpoint_base in the config?');
});
document.body.appendChild(script);
}

export function removeEnforcement(scriptId: string) {
if (window.myEnforcement) {
delete window.myEnforcement;
if (window.arkoseEnforcement) {
delete window.arkoseEnforcement;
}
if (window.setupEnforcement) {
delete window.setupEnforcement;
Expand All @@ -109,10 +97,55 @@ export function removeEnforcement(scriptId: string) {
}

export function runEnforcement() {
console.log('Running enforcement', window.myEnforcement);
if (window.myEnforcement) {
window.myEnforcement.run();
console.log('Running enforcement', window.arkoseEnforcement);
if (window.arkoseEnforcement) {
window.arkoseEnforcement.run();
} else {
console.error('Arkose Enforcment not set up');
}
}

export function getArkoseToken(arkoseUrl: string): Promise<string | null> {
return new Promise((resolve, reject) => {
const config = {
onReady() {
console.log('Arkose is ready');
runEnforcement();
},
onShown() {
console.log('Arkose is shown');
},
onReset() {
console.log('Arkose is reset');
},
onSuppress() {
console.log('Arkose is suppressed');
},
onCompleted(response) {
console.log('Arkose is completed', response);
resolve(response.token || null);
},
onError(response) {
const error = response.error?.error || 'null';
reject(new Error(`Arkose error: ${error}`));
},
onFailed() {
reject(new Error('Arkose Failed'));
},
} as ArkoseEnforcementConfig;

// 调用 setupEnforcement 并传入配置
setupEnforcement(
arkoseUrl,
config,
'arkose-token=fetcher',
() => {
console.log('Arkose API is loaded');
},
(ev) => {
console.error('Arkose API loaded error:', ev);
reject(new Error('Arkose API loaded error'));
}
);
});
}
Loading

0 comments on commit 8bb837d

Please sign in to comment.