mirror of
https://github.com/QwenLM/qwen-code.git
synced 2025-12-19 09:33:53 +00:00
fix: seperate static QR code and dynamic spin components (#327)
* fix: seperate static QR code and dynamic spin components * fix: format issues
This commit is contained in:
@@ -4,8 +4,8 @@
|
|||||||
* SPDX-License-Identifier: Apache-2.0
|
* SPDX-License-Identifier: Apache-2.0
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import React, { useState, useEffect } from 'react';
|
import React, { useState, useEffect, useMemo } from 'react';
|
||||||
import { Box, Text, useInput, Static } from 'ink';
|
import { Box, Text, useInput } from 'ink';
|
||||||
import Spinner from 'ink-spinner';
|
import Spinner from 'ink-spinner';
|
||||||
import Link from 'ink-link';
|
import Link from 'ink-link';
|
||||||
import qrcode from 'qrcode-terminal';
|
import qrcode from 'qrcode-terminal';
|
||||||
@@ -26,17 +26,93 @@ interface QwenOAuthProgressProps {
|
|||||||
authMessage?: string | null;
|
authMessage?: string | null;
|
||||||
}
|
}
|
||||||
|
|
||||||
interface StaticItem {
|
/**
|
||||||
key: string;
|
* Static QR Code Display Component
|
||||||
type:
|
* Renders the QR code and URL once and doesn't re-render unless the URL changes
|
||||||
| 'title'
|
*/
|
||||||
| 'instructions'
|
function QrCodeDisplay({
|
||||||
| 'url'
|
verificationUrl,
|
||||||
| 'qr-instructions'
|
qrCodeData,
|
||||||
| 'qr-code'
|
}: {
|
||||||
| 'auth-content';
|
verificationUrl: string;
|
||||||
url?: string;
|
qrCodeData: string | null;
|
||||||
qrCode?: string;
|
}): React.JSX.Element | null {
|
||||||
|
if (!qrCodeData) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Box
|
||||||
|
borderStyle="round"
|
||||||
|
borderColor={Colors.AccentBlue}
|
||||||
|
flexDirection="column"
|
||||||
|
padding={1}
|
||||||
|
width="100%"
|
||||||
|
>
|
||||||
|
<Text bold color={Colors.AccentBlue}>
|
||||||
|
Qwen OAuth Authentication
|
||||||
|
</Text>
|
||||||
|
|
||||||
|
<Box marginTop={1}>
|
||||||
|
<Text>Please visit this URL to authorize:</Text>
|
||||||
|
</Box>
|
||||||
|
|
||||||
|
<Link url={verificationUrl} fallback={false}>
|
||||||
|
<Text color={Colors.AccentGreen} bold>
|
||||||
|
{verificationUrl}
|
||||||
|
</Text>
|
||||||
|
</Link>
|
||||||
|
|
||||||
|
<Box marginTop={1}>
|
||||||
|
<Text>Or scan the QR code below:</Text>
|
||||||
|
</Box>
|
||||||
|
|
||||||
|
<Box marginTop={1}>
|
||||||
|
<Text>{qrCodeData}</Text>
|
||||||
|
</Box>
|
||||||
|
</Box>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Dynamic Status Display Component
|
||||||
|
* Shows the loading spinner, timer, and status messages
|
||||||
|
*/
|
||||||
|
function StatusDisplay({
|
||||||
|
timeRemaining,
|
||||||
|
dots,
|
||||||
|
}: {
|
||||||
|
timeRemaining: number;
|
||||||
|
dots: string;
|
||||||
|
}): React.JSX.Element {
|
||||||
|
const formatTime = (seconds: number): string => {
|
||||||
|
const minutes = Math.floor(seconds / 60);
|
||||||
|
const remainingSeconds = seconds % 60;
|
||||||
|
return `${minutes}:${remainingSeconds.toString().padStart(2, '0')}`;
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Box
|
||||||
|
borderStyle="round"
|
||||||
|
borderColor={Colors.AccentBlue}
|
||||||
|
flexDirection="column"
|
||||||
|
padding={1}
|
||||||
|
width="100%"
|
||||||
|
>
|
||||||
|
<Box marginTop={1}>
|
||||||
|
<Text>
|
||||||
|
<Spinner type="dots" /> Waiting for authorization{dots}
|
||||||
|
</Text>
|
||||||
|
</Box>
|
||||||
|
|
||||||
|
<Box marginTop={1} justifyContent="space-between">
|
||||||
|
<Text color={Colors.Gray}>
|
||||||
|
Time remaining: {formatTime(timeRemaining)}
|
||||||
|
</Text>
|
||||||
|
<Text color={Colors.AccentPurple}>(Press ESC to cancel)</Text>
|
||||||
|
</Box>
|
||||||
|
</Box>
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
export function QwenOAuthProgress({
|
export function QwenOAuthProgress({
|
||||||
@@ -60,15 +136,12 @@ export function QwenOAuthProgress({
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
// Generate QR code when device auth is available
|
// Generate QR code once when device auth is available
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (!deviceAuth) {
|
if (!deviceAuth?.verification_uri_complete) {
|
||||||
setQrCodeData(null);
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Only generate QR code if we don't have one yet for this URL
|
|
||||||
if (qrCodeData === null) {
|
|
||||||
const generateQR = () => {
|
const generateQR = () => {
|
||||||
try {
|
try {
|
||||||
qrcode.generate(
|
qrcode.generate(
|
||||||
@@ -85,8 +158,7 @@ export function QwenOAuthProgress({
|
|||||||
};
|
};
|
||||||
|
|
||||||
generateQR();
|
generateQR();
|
||||||
}
|
}, [deviceAuth?.verification_uri_complete]);
|
||||||
}, [deviceAuth, qrCodeData]);
|
|
||||||
|
|
||||||
// Countdown timer
|
// Countdown timer
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
@@ -115,11 +187,17 @@ export function QwenOAuthProgress({
|
|||||||
return () => clearInterval(dotsTimer);
|
return () => clearInterval(dotsTimer);
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
const formatTime = (seconds: number): string => {
|
// Memoize the QR code display to prevent unnecessary re-renders
|
||||||
const minutes = Math.floor(seconds / 60);
|
const qrCodeDisplay = useMemo(() => {
|
||||||
const remainingSeconds = seconds % 60;
|
if (!deviceAuth?.verification_uri_complete) return null;
|
||||||
return `${minutes}:${remainingSeconds.toString().padStart(2, '0')}`;
|
|
||||||
};
|
return (
|
||||||
|
<QrCodeDisplay
|
||||||
|
verificationUrl={deviceAuth.verification_uri_complete}
|
||||||
|
qrCodeData={qrCodeData}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
}, [deviceAuth?.verification_uri_complete, qrCodeData]);
|
||||||
|
|
||||||
// Handle timeout state
|
// Handle timeout state
|
||||||
if (authStatus === 'timeout') {
|
if (authStatus === 'timeout') {
|
||||||
@@ -151,6 +229,7 @@ export function QwenOAuthProgress({
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Show loading state when no device auth is available yet
|
||||||
if (!deviceAuth) {
|
if (!deviceAuth) {
|
||||||
return (
|
return (
|
||||||
<Box
|
<Box
|
||||||
@@ -167,7 +246,8 @@ export function QwenOAuthProgress({
|
|||||||
</Box>
|
</Box>
|
||||||
<Box marginTop={1} justifyContent="space-between">
|
<Box marginTop={1} justifyContent="space-between">
|
||||||
<Text color={Colors.Gray}>
|
<Text color={Colors.Gray}>
|
||||||
Time remaining: {formatTime(timeRemaining)}
|
Time remaining: {Math.floor(timeRemaining / 60)}:
|
||||||
|
{(timeRemaining % 60).toString().padStart(2, '0')}
|
||||||
</Text>
|
</Text>
|
||||||
<Text color={Colors.AccentPurple}>(Press ESC to cancel)</Text>
|
<Text color={Colors.AccentPurple}>(Press ESC to cancel)</Text>
|
||||||
</Box>
|
</Box>
|
||||||
@@ -176,77 +256,12 @@ export function QwenOAuthProgress({
|
|||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<Box flexDirection="column" width="100%">
|
||||||
{qrCodeData && (
|
{/* Static QR Code Display */}
|
||||||
<Static
|
{qrCodeDisplay}
|
||||||
items={
|
|
||||||
[
|
|
||||||
{
|
|
||||||
key: 'auth-content',
|
|
||||||
type: 'auth-content' as const,
|
|
||||||
url: deviceAuth.verification_uri_complete,
|
|
||||||
qrCode: qrCodeData,
|
|
||||||
},
|
|
||||||
] as StaticItem[]
|
|
||||||
}
|
|
||||||
style={{
|
|
||||||
width: '100%',
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
{(item: StaticItem) => (
|
|
||||||
<Box
|
|
||||||
borderStyle="round"
|
|
||||||
borderColor={Colors.AccentBlue}
|
|
||||||
flexDirection="column"
|
|
||||||
padding={1}
|
|
||||||
width="100%"
|
|
||||||
key={item.key}
|
|
||||||
>
|
|
||||||
<Text bold color={Colors.AccentBlue}>
|
|
||||||
Qwen OAuth Authentication
|
|
||||||
</Text>
|
|
||||||
|
|
||||||
<Box marginTop={1}>
|
{/* Dynamic Status Display */}
|
||||||
<Text>Please visit this URL to authorize:</Text>
|
<StatusDisplay timeRemaining={timeRemaining} dots={dots} />
|
||||||
</Box>
|
</Box>
|
||||||
|
|
||||||
<Link url={item.url || ''} fallback={false}>
|
|
||||||
<Text color={Colors.AccentGreen} bold>
|
|
||||||
{item.url || ''}
|
|
||||||
</Text>
|
|
||||||
</Link>
|
|
||||||
|
|
||||||
<Box marginTop={1}>
|
|
||||||
<Text>Or scan the QR code below:</Text>
|
|
||||||
</Box>
|
|
||||||
|
|
||||||
<Box marginTop={1}>
|
|
||||||
<Text>{item.qrCode || ''}</Text>
|
|
||||||
</Box>
|
|
||||||
</Box>
|
|
||||||
)}
|
|
||||||
</Static>
|
|
||||||
)}
|
|
||||||
<Box
|
|
||||||
borderStyle="round"
|
|
||||||
borderColor={Colors.AccentBlue}
|
|
||||||
flexDirection="column"
|
|
||||||
padding={1}
|
|
||||||
width="100%"
|
|
||||||
>
|
|
||||||
<Box marginTop={1}>
|
|
||||||
<Text>
|
|
||||||
<Spinner type="dots" /> Waiting for authorization{dots}
|
|
||||||
</Text>
|
|
||||||
</Box>
|
|
||||||
|
|
||||||
<Box marginTop={1} justifyContent="space-between">
|
|
||||||
<Text color={Colors.Gray}>
|
|
||||||
Time remaining: {formatTime(timeRemaining)}
|
|
||||||
</Text>
|
|
||||||
<Text color={Colors.AccentPurple}>(Press ESC to cancel)</Text>
|
|
||||||
</Box>
|
|
||||||
</Box>
|
|
||||||
</>
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user