/**
* Create TikTok Analytics Dashboard (Approach A: Bitable Dashboard)
* Creates a comprehensive dashboard with all recommended charts and metrics
* Uses the lark-mcp MCP server proxy for enhanced reliability
*
* Run with: npm run tiktok:create
*/
import {
LarkDashboardClient,
ChartBlockBuilder,
MetricsBlockBuilder,
ViewBlockBuilder,
TextBlockBuilder,
AggregationType,
ChartType,
} from '../src';
// 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',
};
/**
* Create KPI metrics cards
*/
function createKPICards(appToken: string, tableId: string) {
console.log('Creating KPI cards...');
// Card 1: Total Views
const totalViewsCard = new MetricsBlockBuilder()
.dataSource(appToken, tableId)
.field(FIELDS.VIEWS)
.aggregation(AggregationType.SUM)
.title('Total Views')
.decimals(0)
.position(0, 0)
.size(3, 2)
.build();
// Card 2: Total Likes
const totalLikesCard = new MetricsBlockBuilder()
.dataSource(appToken, tableId)
.field(FIELDS.LIKES)
.aggregation(AggregationType.SUM)
.title('Total Engagement')
.decimals(0)
.position(3, 0)
.size(3, 2)
.build();
// Card 3: Total Videos
const totalVideosCard = new MetricsBlockBuilder()
.dataSource(appToken, tableId)
.field(FIELDS.VIDEO_ID)
.aggregation(AggregationType.COUNT)
.title('Total Videos')
.decimals(0)
.suffix(' videos')
.position(6, 0)
.size(3, 2)
.build();
// Card 4: Average Watch Rate
const avgWatchRateCard = new MetricsBlockBuilder()
.dataSource(appToken, tableId)
.field(FIELDS.WATCH_RATE)
.aggregation(AggregationType.AVG)
.title('Avg Watch Rate')
.decimals(1)
.suffix('%')
.position(9, 0)
.size(3, 2)
.build();
return [totalViewsCard, totalLikesCard, totalVideosCard, avgWatchRateCard];
}
/**
* Create performance trend line chart
*/
function createPerformanceTrend(appToken: string, tableId: string) {
console.log('Creating performance trend chart...');
const lineChart = ChartBlockBuilder.line()
.dataSource(appToken, tableId)
.xAxis(FIELDS.DATE_PUBLISHED)
.yAxes([
{
fieldName: FIELDS.VIEWS,
aggregation: AggregationType.SUM,
label: 'Total Views',
},
{
fieldName: FIELDS.LIKES,
aggregation: AggregationType.SUM,
label: 'Total Likes',
},
])
.title('Views & Engagement Over Time')
.legend(true)
.colors(['#1890ff', '#ff4d4f'])
.position(0, 2)
.size(12, 4)
.build();
return lineChart;
}
/**
* Create top 10 videos bar chart
*/
function createTopVideosChart(appToken: string, tableId: string) {
console.log('Creating top videos chart...');
const barChart = ChartBlockBuilder.bar()
.dataSource(appToken, tableId)
.xAxis(FIELDS.VIEWS, AggregationType.SUM)
.yAxes([{ fieldName: FIELDS.DESCRIPTION }])
.title('Top 10 Videos by Views')
.legend(false)
.colors(['#1890ff'])
.position(0, 6)
.size(6, 4)
.build();
return barChart;
}
/**
* Create engagement breakdown pie chart
*/
function createEngagementPieChart(appToken: string, tableId: string) {
console.log('Creating engagement pie chart...');
// Note: Pie charts in Lark require specific configuration
// This is a simplified version
const pieChart = ChartBlockBuilder.pie()
.dataSource(appToken, tableId)
.series(FIELDS.LIKES)
.yAxes([
{
fieldName: FIELDS.LIKES,
aggregation: AggregationType.SUM,
label: 'Likes',
},
])
.title('Engagement Distribution')
.legend(true)
.colors(['#ff4d4f', '#1890ff', '#52c41a'])
.position(6, 6)
.size(6, 4)
.build();
return pieChart;
}
/**
* Create data table view
*/
function createDataTable(appToken: string, tableId: string, viewId?: string) {
console.log('Creating data table...');
const tableView = ViewBlockBuilder.grid()
.dataSource(appToken, tableId, viewId)
.title('All Videos - Detailed View')
.toolbar(true)
.height(400)
.position(0, 10)
.size(12, 5)
.build();
return tableView;
}
/**
* Main execution
*/
async function main() {
try {
console.log('╔═══════════════════════════════════════════════════════════════╗');
console.log('║ TikTok Analytics Dashboard Builder (Approach A) ║');
console.log('║ Using lark-mcp MCP Proxy ║');
console.log('╚═══════════════════════════════════════════════════════════════╝\n');
// Initialize client with MCP proxy URL
const client = new LarkDashboardClient({
apiKey: CONFIG.API_KEY || 'mcp-proxy-no-key-needed',
region: CONFIG.REGION,
apiUrl: CONFIG.MCP_PROXY_URL,
logging: true,
});
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: Create dashboard by copying existing one
console.log('Step 1: Creating dashboard...');
const dashboardName = `TikTok Analytics Dashboard - ${new Date().toISOString().split('T')[0]}`;
const dashboardId = await client.createDashboard(
{
name: dashboardName,
appToken: CONFIG.APP_TOKEN,
},
CONFIG.EXISTING_DASHBOARD_ID
);
console.log(`Dashboard created: ${dashboardId}\n`);
// Step 2: Add KPI cards
console.log('Step 2: Adding KPI cards...');
const kpiCards = createKPICards(CONFIG.APP_TOKEN, CONFIG.TABLE_ID);
for (const card of kpiCards) {
try {
const blockId = await client.addBlock(CONFIG.APP_TOKEN, dashboardId, card);
console.log(` Added KPI card: ${blockId}`);
} catch (error: any) {
console.error(` Failed to add KPI card: ${error.message}`);
}
}
console.log();
// Step 3: Add performance trend chart
console.log('Step 3: Adding performance trend chart...');
try {
const trendChart = createPerformanceTrend(CONFIG.APP_TOKEN, CONFIG.TABLE_ID);
const trendBlockId = await client.addBlock(CONFIG.APP_TOKEN, dashboardId, trendChart);
console.log(` Added trend chart: ${trendBlockId}\n`);
} catch (error: any) {
console.error(` Failed to add trend chart: ${error.message}\n`);
}
// Step 4: Add top videos chart
console.log('Step 4: Adding top videos chart...');
try {
const topChart = createTopVideosChart(CONFIG.APP_TOKEN, CONFIG.TABLE_ID);
const topBlockId = await client.addBlock(CONFIG.APP_TOKEN, dashboardId, topChart);
console.log(` Added top videos chart: ${topBlockId}\n`);
} catch (error: any) {
console.error(` Failed to add top videos chart: ${error.message}\n`);
}
// Step 5: Add engagement pie chart
console.log('Step 5: Adding engagement pie chart...');
try {
const pieChart = createEngagementPieChart(CONFIG.APP_TOKEN, CONFIG.TABLE_ID);
const pieBlockId = await client.addBlock(CONFIG.APP_TOKEN, dashboardId, pieChart);
console.log(` Added engagement chart: ${pieBlockId}\n`);
} catch (error: any) {
console.error(` Failed to add engagement chart: ${error.message}\n`);
}
// Step 6: Add data table
console.log('Step 6: Adding data table...');
try {
const table = createDataTable(CONFIG.APP_TOKEN, CONFIG.TABLE_ID);
const tableBlockId = await client.addBlock(CONFIG.APP_TOKEN, dashboardId, table);
console.log(` Added data table: ${tableBlockId}\n`);
} catch (error: any) {
console.error(` Failed to add data table: ${error.message}\n`);
}
// Success message
console.log();
console.log('╔═══════════════════════════════════════════════════════════════╗');
console.log('║ ✓ Dashboard Created Successfully! ║');
console.log('╚═══════════════════════════════════════════════════════════════╝\n');
console.log('Dashboard Details:');
console.log(` Name: ${dashboardName}`);
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();
console.log('Next Steps:');
console.log(' 1. Open the dashboard in Lark Bitable');
console.log(' 2. Adjust chart positions and sizes as needed');
console.log(' 3. Add filters for date range and engagement levels');
console.log(' 4. Configure auto-refresh if desired');
console.log(' 5. Share with your team');
console.log();
console.log('Related Commands:');
console.log(' npm run tiktok:analyze - Analyze TikTok data');
console.log(' npm run tiktok:copy - Copy existing dashboard');
console.log(' npm run approach-a:quickstart - Run all Approach A tasks');
console.log();
} catch (error: any) {
console.error('\nError:', error.message);
if (error.message.includes('No existing dashboards found')) {
console.error('\nYou need to create a dashboard manually first:');
console.error('1. Go to your Lark Bitable app');
console.error('2. Create a new dashboard (even an empty one)');
console.error('3. Then run this script again');
}
if (error.message.includes('Authentication')) {
console.error('\nAuthentication failed. Please check:');
console.error('1. Your LARK_API_KEY is correct');
console.error('2. The API key has proper permissions');
console.error('3. The region setting matches your workspace');
}
process.exit(1);
}
}
// Run if executed directly
if (require.main === module) {
main();
}
export { main };