hf-oauth-demo / public /test_interface.html
其天
Refactor to HF-only OAuth demo aligned with ms-oauth-studio structure
03be224
Raw
History Blame Contribute Delete
18.5 kB
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Hugging Face API 测试工具</title>
<style>
body {
font-family: sans-serif;
margin: 0;
padding: 20px;
background-color: #f0f2f5;
line-height: 1.6;
}
.container {
max-width: 1200px;
margin: 0 auto;
background: white;
padding: 2rem;
border-radius: 8px;
box-shadow: 0 4px 8px rgba(0,0,0,0.1);
}
h1 { color: #333; text-align: center; }
.section {
margin-bottom: 2rem;
padding: 1.5rem;
border: 1px solid #ddd;
border-radius: 8px;
background-color: #fafafa;
}
.input-group {
margin-bottom: 1rem;
}
label {
display: block;
margin-bottom: 0.5rem;
font-weight: bold;
}
input[type="text"], input[type="password"], input[type="number"] {
width: 100%;
padding: 0.5rem;
border: 1px solid #ccc;
border-radius: 4px;
box-sizing: border-box;
}
button {
background-color: #007bff;
color: white;
border: none;
padding: 0.5rem 1rem;
border-radius: 4px;
cursor: pointer;
margin-right: 0.5rem;
margin-bottom: 0.5rem;
}
button:hover {
background-color: #0056b3;
}
.result {
margin-top: 1rem;
padding: 1rem;
background-color: #f8f9fa;
border: 1px solid #dee2e6;
border-radius: 4px;
white-space: pre-wrap;
word-wrap: break-word;
}
.hidden {
display: none;
}
.nav-link {
display: block;
text-align: center;
margin-top: 20px;
padding: 10px;
background-color: #e9ecef;
border-radius: 4px;
text-decoration: none;
color: #007bff;
}
.tab {
display: none;
}
.tab.active {
display: block;
}
.tab-buttons {
margin-bottom: 1rem;
border-bottom: 1px solid #ddd;
}
.tab-buttons button {
background-color: #e9ecef;
color: #333;
border: 1px solid #ddd;
border-bottom: none;
border-radius: 4px 4px 0 0;
}
.tab-buttons button.active {
background-color: #007bff;
color: white;
}
</style>
</head>
<body>
<div class="container">
<h1>Hugging Face API 测试工具</h1>
<div class="section">
<h2>认证信息</h2>
<div class="input-group">
<label for="accessToken">Access Token:</label>
<input type="password" id="accessToken" placeholder="请输入你的 Hugging Face Access Token">
</div>
<div class="input-group">
<input type="checkbox" id="showToken" onchange="toggleTokenVisibility()">
<label for="showToken" style="display: inline; font-weight: normal;">显示 Access Token (明文)</label>
</div>
<button onclick="saveToken()">保存 Token</button>
<button onclick="fetchWhoami()">获取当前用户信息</button>
<div id="authStatus" class="result hidden"></div>
</div>
<div class="tab-buttons">
<button class="active" onclick="showTab('oauth')">OAuth 流程测试</button>
<button onclick="showTab('api')">API 测试</button>
</div>
<div id="oauth-tab" class="tab active">
<div class="section">
<h2>1. 启动 OAuth 流程</h2>
<div class="input-group">
<label for="oauthScope">Scope:</label>
<input type="text" id="oauthScope" value="openid profile inference-api" placeholder="openid profile">
</div>
<button onclick="startOAuth()">生成授权链接</button>
<div id="oauthStartResult" class="result hidden"></div>
</div>
<div class="section">
<h2>2. 查询授权码</h2>
<div class="input-group">
<label for="oauthState">State:</label>
<input type="text" id="oauthState" placeholder="从步骤1获取">
</div>
<button onclick="pollCode()">轮询 Code</button>
<div id="oauthCodeResult" class="result hidden"></div>
</div>
<div class="section">
<h2>3. 换取 Token</h2>
<div class="input-group">
<label for="exchangeCode">Code:</label>
<input type="text" id="exchangeCode" placeholder="从步骤2获取">
</div>
<button onclick="exchangeToken()">换取 Token</button>
<div id="tokenResult" class="result hidden"></div>
</div>
<div class="section">
<h2>4. 获取 UserInfo</h2>
<button onclick="fetchUserInfo()">获取 UserInfo</button>
<div id="userInfoResult" class="result hidden"></div>
</div>
</div>
<div id="api-tab" class="tab">
<div class="section">
<h2>查询用户资源</h2>
<div class="input-group">
<label for="ownerName">用户名 (如: iSolver-AI):</label>
<input type="text" id="ownerName" placeholder="输入用户名" value="iSolver-AI">
</div>
<div class="input-group">
<label for="pageSize">每页数量 (默认50):</label>
<input type="number" id="pageSize" placeholder="输入每页数量" value="50" min="1" max="100">
</div>
<button onclick="fetchUserModels()">获取模型列表</button>
<button onclick="fetchUserDatasets()">获取数据集列表</button>
<button onclick="fetchUserSpaces()">获取 Spaces 列表</button>
<div id="userResourcesResult" class="result hidden"></div>
</div>
<div class="section">
<h2>查询特定资源详情</h2>
<div class="input-group">
<label for="resourcePath">资源路径 (格式: owner/repo_name):</label>
<input type="text" id="resourcePath" placeholder="输入 owner/repo_name">
</div>
<button onclick="fetchModelDetails()">获取模型详情</button>
<button onclick="fetchDatasetDetails()">获取数据集详情</button>
<button onclick="fetchSpaceDetails()">获取 Space 详情</button>
<div id="resourceDetailsResult" class="result hidden"></div>
</div>
</div>
<a href="/" class="nav-link">返回首页</a>
</div>
<script>
let accessToken = localStorage.getItem('hf_accessToken') || '';
let currentState = '';
const urlParams = new URLSearchParams(window.location.search);
const urlAccessToken = urlParams.get('access_token');
const tabParam = urlParams.get('tab');
const stateParam = urlParams.get('state');
if (urlAccessToken) {
accessToken = decodeURIComponent(urlAccessToken);
document.getElementById('accessToken').value = accessToken;
localStorage.setItem('hf_accessToken', accessToken);
document.getElementById('authStatus').textContent = 'Token 已从授权流程自动加载';
document.getElementById('authStatus').classList.remove('hidden');
} else if (accessToken) {
document.getElementById('accessToken').value = accessToken;
document.getElementById('authStatus').textContent = 'Token 已保存';
document.getElementById('authStatus').classList.remove('hidden');
}
if (stateParam) {
document.getElementById('oauthState').value = stateParam;
currentState = stateParam;
showTab('oauth');
}
function showTab(tabName) {
document.querySelectorAll('.tab').forEach(t => t.classList.remove('active'));
document.querySelectorAll('.tab-buttons button').forEach(b => b.classList.remove('active'));
document.getElementById(tabName + '-tab').classList.add('active');
event.target.classList.add('active');
}
function toggleTokenVisibility() {
const tokenInput = document.getElementById('accessToken');
const showTokenCheckbox = document.getElementById('showToken');
tokenInput.type = showTokenCheckbox.checked ? 'text' : 'password';
}
function saveToken() {
accessToken = document.getElementById('accessToken').value.trim();
if (accessToken) {
localStorage.setItem('hf_accessToken', accessToken);
document.getElementById('authStatus').textContent = 'Token 已保存';
document.getElementById('authStatus').classList.remove('hidden');
} else {
document.getElementById('authStatus').textContent = '请输入有效的 Token';
document.getElementById('authStatus').classList.remove('hidden');
}
}
async function apiRequest(endpoint, method = 'GET', body = null) {
if (!accessToken) {
alert('请先输入并保存 Access Token');
return null;
}
try {
const response = await fetch(`/api${endpoint}`, {
method: method,
headers: {
'Authorization': `Bearer ${accessToken}`,
'Content-Type': 'application/json'
},
...(method !== 'GET' && body !== null ? { body: JSON.stringify(body) } : {})
});
const data = await response.json();
return data;
} catch (error) {
console.error('API 请求失败:', error);
return { error: `请求失败: ${error.message}` };
}
}
async function fetchWhoami() {
const result = await apiRequest('/whoami');
const resultDiv = document.getElementById('authStatus');
resultDiv.textContent = JSON.stringify(result, null, 2);
resultDiv.classList.remove('hidden');
}
async function startOAuth() {
const scope = document.getElementById('oauthScope').value.trim();
try {
const response = await fetch(`/api/oauth/start?scope=${encodeURIComponent(scope)}`);
const data = await response.json();
currentState = data.state;
document.getElementById('oauthStartResult').textContent = JSON.stringify(data, null, 2);
document.getElementById('oauthStartResult').classList.remove('hidden');
window.open(data.authorize_url, '_blank');
} catch (error) {
document.getElementById('oauthStartResult').textContent = `请求失败: ${error.message}`;
document.getElementById('oauthStartResult').classList.remove('hidden');
}
}
async function pollCode() {
const state = document.getElementById('oauthState').value.trim() || currentState;
if (!state) {
alert('请输入 State');
return;
}
try {
const response = await fetch(`/api/oauth/code/${encodeURIComponent(state)}`);
const data = await response.json();
document.getElementById('oauthCodeResult').textContent = JSON.stringify(data, null, 2);
document.getElementById('oauthCodeResult').classList.remove('hidden');
if (data.code) {
document.getElementById('exchangeCode').value = data.code;
}
} catch (error) {
document.getElementById('oauthCodeResult').textContent = `请求失败: ${error.message}`;
document.getElementById('oauthCodeResult').classList.remove('hidden');
}
}
async function exchangeToken() {
const code = document.getElementById('exchangeCode').value.trim();
if (!code) {
alert('请输入 Code');
return;
}
try {
const response = await fetch('/api/oauth/token', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ grant_type: 'authorization_code', code: code })
});
const data = await response.json();
document.getElementById('tokenResult').textContent = JSON.stringify(data, null, 2);
document.getElementById('tokenResult').classList.remove('hidden');
if (data.data && data.data.access_token) {
accessToken = data.data.access_token;
document.getElementById('accessToken').value = accessToken;
localStorage.setItem('hf_accessToken', accessToken);
document.getElementById('authStatus').textContent = 'Token 已自动保存';
document.getElementById('authStatus').classList.remove('hidden');
}
} catch (error) {
document.getElementById('tokenResult').textContent = `请求失败: ${error.message}`;
document.getElementById('tokenResult').classList.remove('hidden');
}
}
async function fetchUserInfo() {
try {
const response = await fetch('/api/oauth/userinfo', {
headers: { 'Authorization': `Bearer ${accessToken}` }
});
const data = await response.json();
document.getElementById('userInfoResult').textContent = JSON.stringify(data, null, 2);
document.getElementById('userInfoResult').classList.remove('hidden');
} catch (error) {
document.getElementById('userInfoResult').textContent = `请求失败: ${error.message}`;
document.getElementById('userInfoResult').classList.remove('hidden');
}
}
async function fetchUserModels() {
const owner = document.getElementById('ownerName').value.trim();
const pageSize = document.getElementById('pageSize').value.trim() || '50';
if (!owner) { alert('请输入用户名'); return; }
const result = await apiRequest(`/models/${owner}?page_size=${pageSize}`);
showResourceResult('userResourcesResult', result, '模型列表');
}
async function fetchUserDatasets() {
const owner = document.getElementById('ownerName').value.trim();
const pageSize = document.getElementById('pageSize').value.trim() || '50';
if (!owner) { alert('请输入用户名'); return; }
const result = await apiRequest(`/datasets/${owner}?page_size=${pageSize}`);
showResourceResult('userResourcesResult', result, '数据集列表');
}
async function fetchUserSpaces() {
const owner = document.getElementById('ownerName').value.trim();
const pageSize = document.getElementById('pageSize').value.trim() || '50';
if (!owner) { alert('请输入用户名'); return; }
const result = await apiRequest(`/spaces/${owner}?page_size=${pageSize}`);
showResourceResult('userResourcesResult', result, 'Spaces 列表');
}
function showResourceResult(elementId, result, title) {
const resultDiv = document.getElementById(elementId);
resultDiv.textContent = `${title}:\n${JSON.stringify(result, null, 2)}`;
resultDiv.classList.remove('hidden');
}
async function fetchModelDetails() {
const resourcePath = document.getElementById('resourcePath').value.trim();
if (!resourcePath) { alert('请输入资源路径'); return; }
const [owner, repoName] = resourcePath.split('/');
if (!owner || !repoName) { alert('资源路径格式错误'); return; }
const result = await apiRequest(`/models/${owner}/${repoName}`);
document.getElementById('resourceDetailsResult').textContent = JSON.stringify(result, null, 2);
document.getElementById('resourceDetailsResult').classList.remove('hidden');
}
async function fetchDatasetDetails() {
const resourcePath = document.getElementById('resourcePath').value.trim();
if (!resourcePath) { alert('请输入资源路径'); return; }
const [owner, repoName] = resourcePath.split('/');
if (!owner || !repoName) { alert('资源路径格式错误'); return; }
const result = await apiRequest(`/datasets/${owner}/${repoName}`);
document.getElementById('resourceDetailsResult').textContent = JSON.stringify(result, null, 2);
document.getElementById('resourceDetailsResult').classList.remove('hidden');
}
async function fetchSpaceDetails() {
const resourcePath = document.getElementById('resourcePath').value.trim();
if (!resourcePath) { alert('请输入资源路径'); return; }
const [owner, repoName] = resourcePath.split('/');
if (!owner || !repoName) { alert('资源路径格式错误'); return; }
const result = await apiRequest(`/spaces/${owner}/${repoName}`);
document.getElementById('resourceDetailsResult').textContent = JSON.stringify(result, null, 2);
document.getElementById('resourceDetailsResult').classList.remove('hidden');
}
</script>
</body>
</html>