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:
Gemini AI
2025-12-25 23:17:55 +04:00
Unverified
parent f510683e18
commit 07dbe552f7
10 changed files with 735 additions and 100 deletions

View 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";

View 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 }
);
}
}

View 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 }
);
}
}

View 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 }
);
}
}

View 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 }
);
}
}