Glitches with "Continue Generating" option

I’ve been observing several times with the “Continue Generating” option in ChatGPT plus interface. The text stream is not ordered sometimes, and the stream do not start from where it ended. Output streams as if it’s generating an entirely new response every time i click the “Continue Generating” option, in case if the response is large.

This problem has been observed in API too. I’m attaching the ChatGPT response during my conversation to a question. *Observe that the code repeats itself at the points where I chose “continue generating” option
Question: “but i can’t do this everytime an error occurs, isn’t it?”
Response:
“”"
You’re correct; manually deleting the token_files directory each time an error occurs is not a sustainable solution. Instead, you should handle token-related errors more gracefully within your code. Here are a few strategies to consider:

Strategies for Managing OAuth2 Tokens

  1. Token Expiry Handling:

    • Automatically refresh the token when it expires.
  2. Improved Error Handling:

    • Add robust error handling to manage specific OAuth2 errors.
  3. Token Storage and Management:

    • Store and manage tokens securely and ensure they are up-to-date.

Token Refresh Handling

Make sure your code handles token expiration and refreshes the token when necessary. Here’s an updated version of your authorize function to include token refreshing:

google_async.js

const { google } = require('googleapis');
const fs = require('fs').promises;
const path = require('path');
const express = require('express');
const destroyer = require('server-destroy');

async function openURL(url) {
    const open = (await import('open')).default;
    open(url);
}

async function ensureDirectoryExists(workingDir, tokenDir) {
    try {
        await fs.mkdir(path.resolve(workingDir, tokenDir), { recursive: true });
        console.log("Token directory created successfully");
    } catch (err) {
        console.error('Failed to create directory:', err);
    }
}

async function authorize(API_SERVICE_NAME, API_VERSION, scopes, prefix = '') {
    const workingDir = process.cwd();
    const tokenDir = 'token_files';
    const tokenFileName = `token_${API_SERVICE_NAME}_${API_VERSION}${prefix}.json`;
    const tokenFilePath = path.resolve(workingDir, tokenDir, tokenFileName);
    const credentialsFilePath = path.resolve(workingDir, 'credentials2.json');
    console.log(`Credentials file location: ${credentialsFilePath}`);
    await ensureDirectoryExists(workingDir, tokenDir);

    const credentials = JSON.parse(await fs.readFile(credentialsFilePath, 'utf8'));
    const { client_secret, client_id, redirect_uris } = credentials.installed;
    const oAuth2Client = new google.auth.OAuth2(client_id, client_secret, redirect_uris[0]);

    try {
        const token = JSON.parse(await fs.readFile(tokenFilePath, 'utf8'));
        oAuth2Client.setCredentials(token);
        if (token.expiry_date <= Date.now()) {
            console.log('Token expired, refreshing...');
            const newToken = await oAuth2Client.refreshAccessToken();
            oAuth2Client.setCredentials(newToken.credentials);
            await fs.writeFile(tokenFilePath, JSON.stringify(newToken.credentials));
        }
        await oAuth2Client.getAccessToken();
    } catch (error) {
        if (error.code === 'ENOENT') {
            return getNewToken(oAuth2Client, tokenFilePath, scopes);
        } else {
            throw error;
        }
    }
    return oAuth2Client;
}

async function getNewToken(oAuth2Client, tokenFilePath, scopes) {
    const app = express();
    const server = app.listen(9999, () => {
        openURL(oAuth2Client.generateAuthUrl({
            access_type: 'offline',
            scope: scopes,
            prompt: 'consent',
            redirect_uri: 'http://localhost:9999/oauth2callback'
        }));
        console.log('Server listening on port 9999 - follow the URL to authenticate.');
    });
    destroyer(server);

    return new Promise((resolve, reject) => {
        app.get('/oauth2callback', async (req, res) => {
            const { code } = req.query;
            if (!code) {
                res.status(400).send('Failed to get the code from Google');
                server.destroy();
                return reject(new Error('No code provided by Google'));
            }
            try {
                const { tokens } = await oAuth2Client.getToken(code);
                oAuth2Client.setCredentials(tokens);
                await fs.writeFile(tokenFilePath, JSON.stringify(tokens));
                res.send('Authentication successful! Please close this window.');
                resolve(oAuth2Client);
            } catch (error) {
                console.error('Error while trying to retrieve access token:', error);
                res.status(500).send('Authentication failed');
                reject(error);
            } finally {
                server.destroy();
            }
        });
    });
}

module.exports = { authorize };

Error Handling in gmail.js

Ensure that gmail.js handles errors gracefully, providing meaningful messages and taking appropriate actions.

gmail.js

const { google } = require('googleapis');
const { authorize } = require('./google_async');
const fs = require('fs');
const { encode } = require('js-base64');
const path = require('path');
const XLSX = require('xlsx');

async function sendEmail(fromEmail, toEmails, ccEmails, subject, body, filepaths) {
    try {
        const auth = await authorize('gmail', 'v1', ['https://www.googleapis.com/auth/gmail.send', 'https://www.googleapis.com/auth/gmail.compose']);
        const gmail = google.gmail({ version: 'v1', auth });

        const boundary = "0000000000000000000000000";
        let raw = [
            `From: "${fromEmail}" <${fromEmail}>`,
            `To: ${toEmails.join(', ')}`,
            `Cc: ${ccEmails.join(', ')}`,
            'Content-Type: multipart/mixed; boundary="' + boundary + '"',
            '',
            '--' + boundary,
            'Content-Type: text/plain; charset="UTF-8"',
            'MIME-Version: 1.0',
            'Content-Transfer-Encoding: 7bit',
            '',
            body,
            ''
        ];

        for (const attachmentPath of filepaths) {
            const filename = path.basename(attachmentPath);
            const data = await fs.promises.readFile(attachmentPath, 'binary');
            const encodedAttachment = encode(data);

            raw.push(
                '--' + boundary,
                'Content-Type: application/pdf; name="' + filename + '"',
                'MIME-Version: 1.0',
                'Content-Transfer-Encoding: base64',
                'Content-Disposition: attachment; filename="' + filename + '"',
                '',
                encodedAttachment,
                ''
            );
        }

        raw.push('--' + boundary + '--');
        const encodedMessage = Buffer.from(raw.join('\n')).toString('base64').replace(/\+/g, '-').replace(/\//g, '_').replace(/=+$/, '');

        const res = await gmail.users.messages.send({
            userId: 'me',
            requestBody: {
                raw: encodedMessage,
            },
        });

        console.log('Message sent: %s', res.data.id);
        return true;
    } catch (error) {
        if (error.response && error.response.data) {
            console.error('Error sending email:', error.response.data.error);
        } else {
            console.error('Error sending email:', error);
        }
        return false;
    }
}

async function findSpreadSheetByName(name) {
    const auth = await authorize('drive', 'v3', ['https://www.googleapis.com/auth/drive', 'https://www.googleapis.com/auth/spreadsheets']);
    const drive = google.drive({ version: 'v3', auth });
    const response = await drive.files.list({
        q: `name='${name}' and mimeType='application/vnd.google-apps.spreadsheet'`,
        spaces: 'drive',
        fields: 'files(id, name)'
    });

    if (response.data.files.length === 0) {
        console.log('No files found.');
        return [];
    } else {
        console.log('Files: ');
        response.data.files.forEach(file => {
            console.log(`${file.name} (${file.id})`);
        });
        return {
            spreadsheets: response.data.files,
            auth: auth
        };
    }
}

async function downloadSpreadSheet(sheetId, auth) {
    const sheets = google.sheets({ version: 'v4', auth });
    const sheetMetaData = await sheets.spreadsheets.get({
        spreadsheetId: sheetId
    });
    const title = sheetMetaData.data.properties.title;
    const sheetNames = sheetMetaData.data.sheets.map(sheet => sheet.properties.title);

    for (const sheetName of sheetNames) {
        const result = await sheets.spreadsheets.values.get({
            spreadsheetId: sheetId,
            range: sheetName
        });
        const rows = result.data.values;
        console.log(`${sheetName} - Rows retrieved: ${rows.length}`);
        const wb = XLSX.utils.book_new();
        const ws = XLSX.utils.aoa_to_sheet(rows);
        XLSX.utils.book_append_sheet(wb, ws, sheetName);

        const buffer = XLSX.write(wb, { bookType: 'xlsx', type: 'buffer' });
        await fs.promises.writeFile(`${title}_${sheetName}.xlsx`, buffer)
            .then(() => console.log(`Saved ${sheetName} successfully!`))
            .catch(err => console.error('Error writing file:', err));
    }
}

// Example usage
sendEmail('your-email@example.com', ['ps912001@student.nitw.ac.in', 'bharadwajpendyala2113@gmail.com'], [], 'Test Subject', 'This is the body of the email.', ['C:/path/to/attachment.pdf']);

Summary

  1. Token Expiry Handling: Automatically refresh the token when it expires to avoid manual deletions.
  2. Improved Error Handling: Add robust error handling to manage specific OAuth2 errors.
  3. Proper Token Management: Ensure tokens are stored securely and managed efficiently.

By implementing these strategies
<*******Chose the option of “continue generating”********>
Great to hear that your script worked after deleting the token_files directory! However, as you pointed out, manually deleting the directory every time an error occurs is not a practical solution. To manage tokens more effectively and ensure your application handles errors gracefully, you should implement better token management and error handling strategies.

Strategies for Managing OAuth2 Tokens

  1. Automatic Token Refresh:

    • Implement logic to automatically refresh the access token when it expires.
  2. Improved Error Handling:

    • Add robust error handling to manage specific OAuth2 errors and token-related issues.
  3. Secure Token Storage:

    • Store and manage tokens securely, ensuring they are up-to-date.

Example: Token Refresh Handling

Here’s an updated version of your authorize function that includes token refreshing logic:

google_async.js

const { google } = require('googleapis');
const fs = require('fs').promises;
const path = require('path');
const express = require('express');
const destroyer = require('server-destroy');

async function openURL(url) {
    const open = (await import('open')).default;
    open(url);
}

async function ensureDirectoryExists(workingDir, tokenDir) {
    try {
        await fs.mkdir(path.resolve(workingDir, tokenDir), { recursive: true });
        console.log("Token directory created successfully");
    } catch (err) {
        console.error('Failed to create directory:', err);
    }
}

async function authorize(API_SERVICE_NAME, API_VERSION, scopes, prefix = '') {
    const workingDir = process.cwd();
    const tokenDir = 'token_files';
    const tokenFileName = `token_${API_SERVICE_NAME}_${API_VERSION}${prefix}.json`;
    const tokenFilePath = path.resolve(workingDir, tokenDir, tokenFileName);
    const credentialsFilePath = path.resolve(workingDir, 'credentials2.json');
    console.log(`Credentials file location: ${credentialsFilePath}`);
    await ensureDirectoryExists(workingDir, tokenDir);

    const credentials = JSON.parse(await fs.readFile(credentialsFilePath, 'utf8'));
    const { client_secret, client_id, redirect_uris } = credentials.installed;
    const oAuth2Client = new google.auth.OAuth2(client_id, client_secret, redirect_uris[0]);

    try {
        const token = JSON.parse(await fs.readFile(tokenFilePath, 'utf8'));
        oAuth2Client.setCredentials(token);
        if (token.expiry_date <= Date.now()) {
            console.log('Token expired, refreshing...');
            const newToken = await oAuth2Client.refreshAccessToken();
            oAuth2Client.setCredentials(newToken.credentials);
            await fs.writeFile(tokenFilePath, JSON.stringify(newToken.credentials));
        }
        await oAuth2Client.getAccessToken();
    } catch (error) {
        if (error.code === 'ENOENT') {
            return getNewToken(oAuth2Client, tokenFilePath, scopes);
        } else {
            throw error;
        }
    }
    return oAuth2Client;
}

async function getNewToken(oAuth2Client, tokenFilePath, scopes) {
    const app = express();
    const server = app.listen(9999, () => {
        openURL(oAuth2Client.generateAuthUrl({
            access_type: 'offline',
            scope: scopes,
            prompt: 'consent',
            redirect_uri: 'http://localhost:9999/oauth2callback'
        }));
        console.log('Server listening on port 9999 - follow the URL to authenticate.');
    });
    destroyer(server);

    return new Promise((resolve, reject) => {
        app.get('/oauth2callback', async (req, res) => {
            const { code } = req.query;
            if (!code) {
                res.status(400).send('Failed to get the code from Google');
                server.destroy();
                return reject(new Error('No code provided by Google'));
            }
            try {
                const { tokens } = await oAuth2Client.getToken(code);
                oAuth2Client.setCredentials(tokens);
                await fs.writeFile(tokenFilePath, JSON.stringify(tokens));
                res.send('Authentication successful! Please close this window.');
                resolve(oAuth2Client);
            } catch (error) {
                console.error('Error while trying to retrieve access token:', error);
                res.status(500).send('Authentication failed');
                reject(error);
            } finally {
                server.destroy();
            }
        });
    });
}

module.exports = { authorize };

Improved Error Handling in gmail.js

Ensure that your gmail.js script includes proper error handling for OAuth and token-related issues:

gmail.js

const { google } = require('googleapis');
const { authorize } = require('./google_async');
const fs = require('fs');
const { encode } = require('js-base64');
const path = require('path');
const XLSX = require('xlsx');

async function sendEmail(fromEmail, toEmails, ccEmails, subject, body, filepaths) {
    try {
        const auth = await authorize('gmail', 'v1', ['https://www.googleapis.com/auth/gmail.send', 'https://www.googleapis.com/auth/gmail.compose']);
        const gmail = google.gmail({ version: 'v1', auth });

        const boundary = "0000000000000000000000000";
        let raw = [
            `From: "${fromEmail}" <${fromEmail}>`,
            `To: ${toEmails.join(', ')}`,
            `Cc: ${ccEmails.join(', ')}`,
            'Content-Type: multipart/mixed; boundary="' + boundary + '"',
            '',
            '--' + boundary,
            'Content-Type: text/plain; charset="UTF-8"',
            'MIME-Version: 1.0',
            'Content-Transfer-Encoding: 7bit',
            '',
            body,
            ''
        ];

        for (const attachmentPath of filepaths) {
            const filename = path.basename(attachmentPath);
            const data = await fs.promises.readFile(attachmentPath, 'binary');
            const encodedAttachment = encode(data);

            raw.push(
                '--' + boundary,
                'Content-Type: application/pdf; name="' + filename + '"',
                'MIME-Version: 1.0',
                'Content-Transfer-Encoding: base64',
                'Content-Disposition: attachment; filename="' + filename + '"',
                '',
                encodedAttachment,
                ''
            );
        }

        raw.push('--' + boundary + '--');
        const encodedMessage = Buffer.from(raw.join('\n')).toString('base64').replace(/\+/g, '-').replace(/\//g, '_').replace(/=+$/, '');

        const res = await gmail.users.messages.send({
            userId: 'me',
            requestBody: {
                raw: encodedMessage,
            },
        });

        console.log('Message sent: %s', res.data.id);
        return true;
    } catch (error) {
        if (error.response && error.response.data) {
            console.error('Error sending email:', error.response.data.error);
        } else {
            console.error('Error sending email:', error);
        }
        return false;
    }
}

async function findSpreadSheetByName(name) {
    const auth = await authorize('drive', 'v3', ['https://www.googleapis.com/auth/drive', 'https://www.googleapis.com/auth/spreadsheets']);
    const drive = google.drive({ version: 'v3', auth });
    const response = await drive.files.list({
        q: `name='${name}' and mimeType='application/vnd.google-apps.spreadsheet'`,
        spaces: 'drive',
        fields: 'files(id, name)'
    });

    if (response.data.files.length === 0) {
        console.log('No files found.');
        return [];
    } else {
        console.log('Files: ');
        response.data.files.forEach(file => {
            console.log(`${file.name} (${file.id})`);
        });
        return {
            spreadsheets: response.data.files,
            auth: auth
        };
    }
}

async function downloadSpreadSheet(sheetId, auth) {
    const sheets = google.sheets({ version: 'v4', auth });
    const sheetMetaData = await sheets.spreadsheets.get({
        spreadsheetId: sheetId
    });
    const title = sheetMetaData.data.properties.title;
    const sheetNames = sheetMetaData.data.sheets.map(sheet => sheet.properties.title);

    for (const sheetName of sheetNames) {
        const result = await sheets.spreadsheets.values.get({
            spreadsheetId: sheetId,
            range: sheetName
        });
        const rows = result.data.values;
        console.log(`${sheetName} - Rows retrieved: ${rows.length}`);
        const wb = XLSX.utils.book_new();
        const ws = XLSX.utils.aoa_to_sheet(rows);
        XLSX.utils.book_append_sheet(wb, ws, sheetName);

        const buffer = XLSX.write(wb, { bookType: 'xlsx', type: 'buffer' });
        await fs.promises.writeFile(`${title}_${sheetName}.xlsx`, buffer)
            .then(() => console.log(`Saved ${sheetName} successfully!`))
            .catch(err => console.error('Error writing file:', err));
    }
}

// Example usage
sendEmail('your-email@example.com', ['ps912001@student.nitw.ac.in', 'bharadwajpendyala2113@gmail.com'], [], 'Test Subject', 'This is the body of the email.', ['C:/path/to/attachment.pdf']);

Summary

  1. Automatic Token Refresh: Implement logic to automatically refresh the access token when it expires.
  2. Improved Error Handling: Add robust error handling to manage specific OAuth2 errors.
  3. Proper Token Management: Ensure tokens
    <*******Chose the option of “continue generating”********>
    You’re correct; you shouldn’t have to delete the token_files directory every time an error occurs. Instead, implementing better token management and error handling will ensure that your application runs smoothly. Here’s how you can do it:

Enhanced Token Management and Error Handling

  1. Automatic Token Refresh:

    • Implement logic to refresh the token automatically when it expires.
  2. Error Handling:

    • Add error handling to manage specific OAuth2 errors and handle token-related issues gracefully.
  3. Secure Token Storage:

    • Ensure tokens are stored securely and managed efficiently.

Automatic Token Refresh

Make sure your code handles token expiration and refreshes the token when necessary. Here’s an updated version of your authorize function to include token refreshing logic:

google_async.js

const { google } = require('googleapis');
const fs = require('fs').promises;
const path = require('path');
const express = require('express');
const destroyer = require('server-destroy');

async function openURL(url) {
    const open = (await import('open')).default;
    open(url);
}

async function ensureDirectoryExists(workingDir, tokenDir) {
    try {
        await fs.mkdir(path.resolve(workingDir, tokenDir), { recursive: true });
        console.log("Token directory created successfully");
    } catch (err) {
        console.error('Failed to create directory:', err);
    }
}

async function authorize(API_SERVICE_NAME, API_VERSION, scopes, prefix = '') {
    const workingDir =
"""

You don’t have the ability to “continue” when using the API.

Doing so would require being able to send the incomplete assistant response again without having the chat API close the message or adding on an additional “assistant” prompt or any other tokens.

If you would like this feature, implemented by sensible people, you can use Anthropic API.

I actually meant to raise two points:

  1. In case of ChatGPT interface, the response starts from the beginning entirely if “continue generation” option is chosen occasionally.
  2. Regarding response when API call is made with “stream”, if the response is long, there’s been either a loss of data, or response being streamed in disorder fashion.