/**
* Quick Start Script for Approach A: Bitable Dashboard
*
* This unified script runs all Approach A functionality:
* 1. Analyzes TikTok data from the Bitable table
* 2. Displays key insights and metrics
* 3. Optionally creates a new dashboard
*
* Uses the lark-mcp MCP server proxy for enhanced reliability
*
* Run with: npm run approach-a:quickstart
*/
import axios from 'axios';
import { LarkDashboardClient, ChartBlockBuilder, MetricsBlockBuilder, AggregationType } from '../src';
import * as readline from 'readline';
// Configuration
const CONFIG = {
APP_TOKEN: 'C8kmbTsqoa6rBesTKRpl8nV8gHd',
TABLE_ID: 'tblG4uuUvbwfvI9Z',
EXISTING_DASHBOARD_ID: 'blkxYx6MmEeujy0v',
MCP_PROXY_URL: process.env.LARK_MCP_PROXY_URL || 'https://lark-mcp.hypelive.app',
API_KEY: process.env.LARK_API_KEY || '',
REGION: (process.env.LARK_REGION as 'sg' | 'cn' | 'us') || 'sg',
};
// Field names from the TikTok table
const FIELDS = {
VIDEO_ID: 'Unique identifier of the video',
DATE_PUBLISHED: 'Date and time the video was published',
VIEWS: 'Total video views',
LIKES: 'Total number of likes the video received',
COMMENTS: 'Total number of comments the video received',
SHARES: 'Total number of times the video was shared',
WATCH_RATE: 'Percentage of video watched completely',
DESCRIPTION: 'Video description',
DURATION: 'Duration of the video in seconds',
};
interface TikTokRecord {
record_id: string;
fields: {
[key: string]: any;
};
}
interface AnalysisSummary {
totalRecords: number;
totalViews: number;
totalLikes: number;
avgWatchRate: number;
topVideo: {
description: string;
views: number;
};
}
/**
* Fetch records via MCP proxy
*/
async function fetchRecords(): Promise<TikTokRecord[]> {
const url = `${CONFIG.MCP_PROXY_URL}/api/bitable/records`;
console.log('Fetching TikTok video data via MCP proxy...');
try {
const response = await axios.get(url, {
params: {
app_token: CONFIG.APP_TOKEN,
table_id: CONFIG.TABLE_ID,
page_size: 500,
},
timeout: 30000,
});
if (response.data.error) {
throw new Error(`MCP Proxy Error: ${response.data.error}`);
}
const data = response.data.data || response.data;
const items = data.items || [];
console.log(`✓ Fetched ${items.length} records\n`);
return items;
} catch (error: any) {
console.error('✗ Error fetching records:', error.message);
throw error;
}
}
/**
* Analyze the data and return summary
*/
function analyzeRecords(records: TikTokRecord[]): AnalysisSummary {
console.log('Analyzing data...\n');
let totalViews = 0;
let totalLikes = 0;
let totalWatchRate = 0;
let watchRateCount = 0;
let topVideo = { description: '', views: 0 };
for (const record of records) {
const views = Number(record.fields[FIELDS.VIEWS]) || 0;
const likes = Number(record.fields[FIELDS.LIKES]) || 0;
const watchRate = Number(record.fields[FIELDS.WATCH_RATE]) || 0;
const description = String(record.fields[FIELDS.DESCRIPTION] || '');
totalViews += views;
totalLikes += likes;
if (watchRate > 0) {
totalWatchRate += watchRate;
watchRateCount++;
}
if (views > topVideo.views) {
topVideo = { description: description.substring(0, 60), views };
}
}
return {
totalRecords: records.length,
totalViews,
totalLikes,
avgWatchRate: watchRateCount > 0 ? totalWatchRate / watchRateCount : 0,
topVideo,
};
}
/**
* Display analysis results
*/
function displayResults(summary: AnalysisSummary): void {
console.log('╔═══════════════════════════════════════════════════════════════╗');
console.log('║ ANALYSIS SUMMARY ║');
console.log('╚═══════════════════════════════════════════════════════════════╝\n');
console.log('📊 Overview:');
console.log(` Total Videos: ${summary.totalRecords.toLocaleString()}`);
console.log(` Total Views: ${summary.totalViews.toLocaleString()}`);
console.log(` Total Likes: ${summary.totalLikes.toLocaleString()}`);
console.log(` Avg Watch Rate: ${summary.avgWatchRate.toFixed(2)}%`);
console.log();
console.log('🏆 Top Performer:');
console.log(` ${summary.topVideo.description}...`);
console.log(` Views: ${summary.topVideo.views.toLocaleString()}`);
console.log();
}
/**
* Create a dashboard
*/
async function createDashboard(): Promise<string> {
console.log('Creating TikTok Analytics Dashboard...\n');
const client = new LarkDashboardClient({
apiKey: CONFIG.API_KEY || 'mcp-proxy-no-key-needed',
region: CONFIG.REGION,
apiUrl: CONFIG.MCP_PROXY_URL,
logging: false,
});
const dashboardName = `TikTok Analytics - ${new Date().toISOString().split('T')[0]}`;
// Create dashboard by copying existing one
console.log('Step 1: Creating dashboard...');
const dashboardId = await client.createDashboard(
{
name: dashboardName,
appToken: CONFIG.APP_TOKEN,
},
CONFIG.EXISTING_DASHBOARD_ID
);
console.log(`✓ Dashboard created: ${dashboardId}\n`);
// Add KPI cards
console.log('Step 2: Adding KPI metrics...');
const totalViewsCard = new MetricsBlockBuilder()
.dataSource(CONFIG.APP_TOKEN, CONFIG.TABLE_ID)
.field(FIELDS.VIEWS)
.aggregation(AggregationType.SUM)
.title('Total Views')
.decimals(0)
.build();
try {
await client.addBlock(CONFIG.APP_TOKEN, dashboardId, totalViewsCard);
console.log('✓ Added Total Views metric\n');
} catch (error: any) {
console.log(`Note: ${error.message}\n`);
}
// Add performance chart
console.log('Step 3: Adding performance chart...');
const lineChart = ChartBlockBuilder.line()
.dataSource(CONFIG.APP_TOKEN, CONFIG.TABLE_ID)
.xAxis(FIELDS.DATE_PUBLISHED)
.yAxes([
{
fieldName: FIELDS.VIEWS,
aggregation: AggregationType.SUM,
label: 'Total Views',
},
])
.title('Views Over Time')
.build();
try {
await client.addBlock(CONFIG.APP_TOKEN, dashboardId, lineChart);
console.log('✓ Added performance chart\n');
} catch (error: any) {
console.log(`Note: ${error.message}\n`);
}
return dashboardId;
}
/**
* Prompt user for input
*/
function prompt(question: string): Promise<string> {
const rl = readline.createInterface({
input: process.stdin,
output: process.stdout,
});
return new Promise((resolve) => {
rl.question(question, (answer) => {
rl.close();
resolve(answer);
});
});
}
/**
* Main execution
*/
async function main() {
try {
console.clear();
console.log('╔═══════════════════════════════════════════════════════════════╗');
console.log('║ APPROACH A: Bitable Dashboard Quick Start ║');
console.log('║ Using lark-mcp MCP Proxy ║');
console.log('╚═══════════════════════════════════════════════════════════════╝\n');
console.log('Configuration:');
console.log(` App Token: ${CONFIG.APP_TOKEN}`);
console.log(` Table ID: ${CONFIG.TABLE_ID}`);
console.log(` MCP Proxy: ${CONFIG.MCP_PROXY_URL}`);
console.log(` Region: ${CONFIG.REGION}\n`);
// Step 1: Fetch and analyze data
console.log('═══════════════════════════════════════════════════════════════');
console.log('STEP 1: Fetching and Analyzing TikTok Data');
console.log('═══════════════════════════════════════════════════════════════\n');
const records = await fetchRecords();
if (records.length === 0) {
console.log('No records found. Exiting...');
return;
}
const summary = analyzeRecords(records);
displayResults(summary);
// Step 2: Ask if user wants to create dashboard
console.log('═══════════════════════════════════════════════════════════════');
console.log('STEP 2: Dashboard Creation (Optional)');
console.log('═══════════════════════════════════════════════════════════════\n');
const createDash = await prompt('Would you like to create a new dashboard? (y/n): ');
if (createDash.toLowerCase() === 'y' || createDash.toLowerCase() === 'yes') {
const dashboardId = await createDashboard();
console.log('╔═══════════════════════════════════════════════════════════════╗');
console.log('║ ✓ ALL COMPLETE! ║');
console.log('╚═══════════════════════════════════════════════════════════════╝\n');
console.log('Dashboard Details:');
console.log(` Dashboard ID: ${dashboardId}`);
console.log();
console.log('View your dashboard at:');
console.log(` https://hypelive.sg.larksuite.com/base/${CONFIG.APP_TOKEN}?dashboard=${dashboardId}`);
console.log();
} else {
console.log('\nSkipping dashboard creation.\n');
}
// Show available commands
console.log('═══════════════════════════════════════════════════════════════');
console.log('Available Commands:');
console.log('═══════════════════════════════════════════════════════════════\n');
console.log(' npm run tiktok:analyze - Analyze TikTok data in detail');
console.log(' npm run tiktok:analyze:export - Export analysis as JSON');
console.log(' npm run tiktok:create - Create new dashboard with all blocks');
console.log(' npm run tiktok:copy - Copy existing dashboard');
console.log(' npm run approach-a:quickstart - Run this quick start again');
console.log();
console.log('Next Steps:');
console.log(' 1. Review the TIKTOK_DASHBOARD_CONFIG.md for detailed setup');
console.log(' 2. Customize your dashboard in Lark Bitable');
console.log(' 3. Set up automated reports and alerts');
console.log(' 4. Share insights with your team');
console.log();
} catch (error: any) {
console.error('\n╔═══════════════════════════════════════════════════════════════╗');
console.error('║ ERROR ║');
console.error('╚═══════════════════════════════════════════════════════════════╝\n');
console.error(`Error: ${error.message}`);
if (error.message.includes('ECONNREFUSED') || error.message.includes('timeout')) {
console.error('\nConnection Error: Cannot reach the MCP proxy.');
console.error('Please check:');
console.error(' 1. Your internet connection');
console.error(' 2. The MCP proxy URL is correct');
console.error(' 3. The proxy service is running');
}
console.error('\nFor help, see: APPROACH_A_COMPLETE.md');
process.exit(1);
}
}
// Run if executed directly
if (require.main === module) {
main();
}
export { main, fetchRecords, analyzeRecords };