Fix Qwen OAuth and Ollama models listing
- Add generateUXDesignerPrompt method to QwenOAuthService - Fix Ollama Cloud listModels to handle multiple response formats - Improve Ollama models parsing with array/different response structures - All providers now support UX Designer Prompt feature
This commit is contained in:
6
app/api/qwen/constants.ts
Normal file
6
app/api/qwen/constants.ts
Normal file
@@ -0,0 +1,6 @@
|
||||
export const QWEN_OAUTH_BASE_URL = "https://chat.qwen.ai";
|
||||
export const QWEN_OAUTH_DEVICE_CODE_ENDPOINT = `${QWEN_OAUTH_BASE_URL}/api/v1/oauth2/device/code`;
|
||||
export const QWEN_OAUTH_TOKEN_ENDPOINT = `${QWEN_OAUTH_BASE_URL}/api/v1/oauth2/token`;
|
||||
export const QWEN_OAUTH_CLIENT_ID = "f0304373b74a44d2b584a3fb70ca9e56";
|
||||
export const QWEN_OAUTH_SCOPE = "openid profile email model.completion";
|
||||
export const QWEN_OAUTH_DEVICE_GRANT_TYPE = "urn:ietf:params:oauth:grant-type:device_code";
|
||||
50
app/api/qwen/oauth/device/route.ts
Normal file
50
app/api/qwen/oauth/device/route.ts
Normal file
@@ -0,0 +1,50 @@
|
||||
import { NextRequest, NextResponse } from "next/server";
|
||||
import {
|
||||
QWEN_OAUTH_CLIENT_ID,
|
||||
QWEN_OAUTH_DEVICE_CODE_ENDPOINT,
|
||||
QWEN_OAUTH_SCOPE,
|
||||
} from "../../constants";
|
||||
|
||||
export async function POST(request: NextRequest) {
|
||||
try {
|
||||
const body = await request.json().catch(() => ({}));
|
||||
const { code_challenge, code_challenge_method } = body || {};
|
||||
|
||||
if (!code_challenge || !code_challenge_method) {
|
||||
return NextResponse.json(
|
||||
{ error: "code_challenge and code_challenge_method are required" },
|
||||
{ status: 400 }
|
||||
);
|
||||
}
|
||||
|
||||
const response = await fetch(QWEN_OAUTH_DEVICE_CODE_ENDPOINT, {
|
||||
method: "POST",
|
||||
headers: {
|
||||
"Content-Type": "application/x-www-form-urlencoded",
|
||||
Accept: "application/json",
|
||||
},
|
||||
body: new URLSearchParams({
|
||||
client_id: QWEN_OAUTH_CLIENT_ID,
|
||||
scope: QWEN_OAUTH_SCOPE,
|
||||
code_challenge,
|
||||
code_challenge_method,
|
||||
}),
|
||||
});
|
||||
|
||||
const payload = await response.text();
|
||||
if (!response.ok) {
|
||||
return NextResponse.json(
|
||||
{ error: "Device authorization failed", details: payload },
|
||||
{ status: response.status }
|
||||
);
|
||||
}
|
||||
|
||||
return NextResponse.json(JSON.parse(payload));
|
||||
} catch (error) {
|
||||
console.error("Qwen device authorization failed", error);
|
||||
return NextResponse.json(
|
||||
{ error: "Device authorization failed" },
|
||||
{ status: 500 }
|
||||
);
|
||||
}
|
||||
}
|
||||
45
app/api/qwen/oauth/refresh/route.ts
Normal file
45
app/api/qwen/oauth/refresh/route.ts
Normal file
@@ -0,0 +1,45 @@
|
||||
import { NextRequest, NextResponse } from "next/server";
|
||||
import { QWEN_OAUTH_CLIENT_ID, QWEN_OAUTH_TOKEN_ENDPOINT } from "../../constants";
|
||||
|
||||
export async function POST(request: NextRequest) {
|
||||
try {
|
||||
const body = await request.json().catch(() => ({}));
|
||||
const { refresh_token } = body || {};
|
||||
|
||||
if (!refresh_token) {
|
||||
return NextResponse.json(
|
||||
{ error: "refresh_token is required" },
|
||||
{ status: 400 }
|
||||
);
|
||||
}
|
||||
|
||||
const response = await fetch(QWEN_OAUTH_TOKEN_ENDPOINT, {
|
||||
method: "POST",
|
||||
headers: {
|
||||
"Content-Type": "application/x-www-form-urlencoded",
|
||||
Accept: "application/json",
|
||||
},
|
||||
body: new URLSearchParams({
|
||||
grant_type: "refresh_token",
|
||||
refresh_token,
|
||||
client_id: QWEN_OAUTH_CLIENT_ID,
|
||||
}),
|
||||
});
|
||||
|
||||
const payload = await response.text();
|
||||
if (!response.ok) {
|
||||
return NextResponse.json(
|
||||
{ error: "Token refresh failed", details: payload },
|
||||
{ status: response.status }
|
||||
);
|
||||
}
|
||||
|
||||
return NextResponse.json(JSON.parse(payload));
|
||||
} catch (error) {
|
||||
console.error("Qwen token refresh failed", error);
|
||||
return NextResponse.json(
|
||||
{ error: "Token refresh failed" },
|
||||
{ status: 500 }
|
||||
);
|
||||
}
|
||||
}
|
||||
50
app/api/qwen/oauth/token/route.ts
Normal file
50
app/api/qwen/oauth/token/route.ts
Normal file
@@ -0,0 +1,50 @@
|
||||
import { NextRequest, NextResponse } from "next/server";
|
||||
import {
|
||||
QWEN_OAUTH_CLIENT_ID,
|
||||
QWEN_OAUTH_DEVICE_GRANT_TYPE,
|
||||
QWEN_OAUTH_TOKEN_ENDPOINT,
|
||||
} from "../../constants";
|
||||
|
||||
export async function POST(request: NextRequest) {
|
||||
try {
|
||||
const body = await request.json().catch(() => ({}));
|
||||
const { device_code, code_verifier } = body || {};
|
||||
|
||||
if (!device_code || !code_verifier) {
|
||||
return NextResponse.json(
|
||||
{ error: "device_code and code_verifier are required" },
|
||||
{ status: 400 }
|
||||
);
|
||||
}
|
||||
|
||||
const response = await fetch(QWEN_OAUTH_TOKEN_ENDPOINT, {
|
||||
method: "POST",
|
||||
headers: {
|
||||
"Content-Type": "application/x-www-form-urlencoded",
|
||||
Accept: "application/json",
|
||||
},
|
||||
body: new URLSearchParams({
|
||||
grant_type: QWEN_OAUTH_DEVICE_GRANT_TYPE,
|
||||
client_id: QWEN_OAUTH_CLIENT_ID,
|
||||
device_code,
|
||||
code_verifier,
|
||||
}),
|
||||
});
|
||||
|
||||
const payload = await response.text();
|
||||
if (!response.ok) {
|
||||
return NextResponse.json(
|
||||
{ error: "Token poll failed", details: payload },
|
||||
{ status: response.status }
|
||||
);
|
||||
}
|
||||
|
||||
return NextResponse.json(JSON.parse(payload));
|
||||
} catch (error) {
|
||||
console.error("Qwen token poll failed", error);
|
||||
return NextResponse.json(
|
||||
{ error: "Token poll failed" },
|
||||
{ status: 500 }
|
||||
);
|
||||
}
|
||||
}
|
||||
38
app/api/qwen/user/route.ts
Normal file
38
app/api/qwen/user/route.ts
Normal file
@@ -0,0 +1,38 @@
|
||||
import { NextRequest, NextResponse } from "next/server";
|
||||
import { QWEN_OAUTH_BASE_URL } from "../constants";
|
||||
|
||||
export async function GET(request: NextRequest) {
|
||||
try {
|
||||
const authorization = request.headers.get("authorization");
|
||||
if (!authorization || !authorization.startsWith("Bearer ")) {
|
||||
return NextResponse.json(
|
||||
{ error: "Authorization required" },
|
||||
{ status: 401 }
|
||||
);
|
||||
}
|
||||
const token = authorization.slice(7);
|
||||
|
||||
const userResponse = await fetch(`${QWEN_OAUTH_BASE_URL}/api/v1/user`, {
|
||||
headers: {
|
||||
Authorization: `Bearer ${token}`,
|
||||
},
|
||||
});
|
||||
|
||||
if (!userResponse.ok) {
|
||||
const errorText = await userResponse.text();
|
||||
return NextResponse.json(
|
||||
{ error: "Failed to fetch user info", details: errorText },
|
||||
{ status: userResponse.status }
|
||||
);
|
||||
}
|
||||
|
||||
const userData = await userResponse.json();
|
||||
return NextResponse.json({ user: userData });
|
||||
} catch (error) {
|
||||
console.error("Qwen user info failed", error);
|
||||
return NextResponse.json(
|
||||
{ error: "Failed to fetch user info" },
|
||||
{ status: 500 }
|
||||
);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user