feat: mcp server?
This commit is contained in:
@@ -0,0 +1,289 @@
|
||||
import { writeFileSync, mkdirSync, existsSync } from 'fs';
|
||||
import { join } from 'path';
|
||||
|
||||
/**
|
||||
* 卡牌字段定义
|
||||
*/
|
||||
export interface CardField {
|
||||
name: string;
|
||||
description?: string;
|
||||
examples?: string[];
|
||||
}
|
||||
|
||||
/**
|
||||
* 卡牌模板配置
|
||||
*/
|
||||
export interface CardTemplate {
|
||||
fields: CardField[];
|
||||
examples?: Record<string, string>[];
|
||||
}
|
||||
|
||||
/**
|
||||
* Deck 配置
|
||||
*/
|
||||
export interface DeckConfig {
|
||||
size?: string;
|
||||
grid?: string;
|
||||
bleed?: number;
|
||||
padding?: number;
|
||||
shape?: 'rectangle' | 'circle' | 'hex' | 'diamond';
|
||||
layers?: string;
|
||||
backLayers?: string;
|
||||
}
|
||||
|
||||
/**
|
||||
* 生成卡牌组的参数
|
||||
*/
|
||||
export interface GenerateCardDeckParams {
|
||||
deck_name: string;
|
||||
output_dir: string;
|
||||
card_count?: number;
|
||||
card_template?: CardTemplate;
|
||||
deck_config?: DeckConfig;
|
||||
description?: string;
|
||||
}
|
||||
|
||||
/**
|
||||
* 生成卡牌数据 CSV
|
||||
*/
|
||||
function generateCardCSV(
|
||||
template: CardTemplate,
|
||||
cardCount: number
|
||||
): string {
|
||||
const fields = template.fields;
|
||||
|
||||
// 构建 CSV 表头
|
||||
const headers = ['label', ...fields.map(f => f.name), 'body'];
|
||||
|
||||
// 生成示例数据
|
||||
const rows: string[][] = [];
|
||||
const examples = template.examples || [];
|
||||
|
||||
for (let i = 0; i < cardCount; i++) {
|
||||
const row: string[] = [(i + 1).toString()];
|
||||
|
||||
// 为每个字段生成值
|
||||
for (const field of fields) {
|
||||
let value = '';
|
||||
|
||||
if (examples.length > 0) {
|
||||
// 从示例中循环取值
|
||||
const exampleIndex = i % examples.length;
|
||||
const example = examples[exampleIndex];
|
||||
value = example[field.name] || field.examples?.[i % (field.examples?.length || 1)] || '';
|
||||
} else if (field.examples && field.examples.length > 0) {
|
||||
// 从字段的示例中取值
|
||||
value = field.examples[i % field.examples.length];
|
||||
} else {
|
||||
// 默认占位符
|
||||
value = `{{${field.name}_${i + 1}}}`;
|
||||
}
|
||||
|
||||
row.push(value);
|
||||
}
|
||||
|
||||
// body 列使用模板语法
|
||||
const bodyParts: string[] = [];
|
||||
for (const field of fields) {
|
||||
bodyParts.push(`**${field.name}:** {{${field.name}}}`);
|
||||
}
|
||||
row.push(bodyParts.join('\n\n'));
|
||||
|
||||
rows.push(row);
|
||||
}
|
||||
|
||||
// 组合 CSV 内容
|
||||
const csvLines = [headers.join(',')];
|
||||
for (const row of rows) {
|
||||
csvLines.push(row.map(cell => {
|
||||
// 处理包含逗号或换行的单元格
|
||||
if (cell.includes(',') || cell.includes('\n') || cell.includes('"')) {
|
||||
return `"${cell.replace(/"/g, '""')}"`;
|
||||
}
|
||||
return cell;
|
||||
}).join(','));
|
||||
}
|
||||
|
||||
return csvLines.join('\n');
|
||||
}
|
||||
|
||||
/**
|
||||
* 生成卡牌介绍的 Markdown 文件
|
||||
*/
|
||||
function generateDeckMarkdown(
|
||||
deckName: string,
|
||||
csvFileName: string,
|
||||
deckConfig: DeckConfig,
|
||||
description?: string
|
||||
): string {
|
||||
const mdLines: string[] = [];
|
||||
|
||||
// 标题
|
||||
mdLines.push(`# ${deckName}`);
|
||||
mdLines.push('');
|
||||
|
||||
// 描述
|
||||
if (description) {
|
||||
mdLines.push(description);
|
||||
mdLines.push('');
|
||||
}
|
||||
|
||||
// 卡牌预览组件
|
||||
mdLines.push('## 卡牌预览');
|
||||
mdLines.push('');
|
||||
|
||||
// 构建 :md-deck 组件代码
|
||||
const deckComponent = buildDeckComponent(csvFileName, deckConfig);
|
||||
mdLines.push(deckComponent);
|
||||
mdLines.push('');
|
||||
|
||||
// 使用说明
|
||||
mdLines.push('## 使用说明');
|
||||
mdLines.push('');
|
||||
mdLines.push('- 点击卡牌可以查看详情');
|
||||
mdLines.push('- 使用右上角的按钮可以随机抽取卡牌');
|
||||
mdLines.push('- 可以通过编辑面板调整卡牌样式和布局');
|
||||
mdLines.push('');
|
||||
|
||||
return mdLines.join('\n');
|
||||
}
|
||||
|
||||
/**
|
||||
* 构建 :md-deck 组件代码
|
||||
*/
|
||||
function buildDeckComponent(
|
||||
csvFileName: string,
|
||||
config: DeckConfig
|
||||
): string {
|
||||
const parts = [`:md-deck[${csvFileName}]`];
|
||||
const attrs: string[] = [];
|
||||
|
||||
if (config.size) {
|
||||
attrs.push(`size="${config.size}"`);
|
||||
}
|
||||
|
||||
if (config.grid) {
|
||||
attrs.push(`grid="${config.grid}"`);
|
||||
}
|
||||
|
||||
if (config.bleed !== undefined && config.bleed !== 1) {
|
||||
attrs.push(`bleed="${config.bleed}"`);
|
||||
}
|
||||
|
||||
if (config.padding !== undefined && config.padding !== 2) {
|
||||
attrs.push(`padding="${config.padding}"`);
|
||||
}
|
||||
|
||||
if (config.shape && config.shape !== 'rectangle') {
|
||||
attrs.push(`shape="${config.shape}"`);
|
||||
}
|
||||
|
||||
if (config.layers) {
|
||||
attrs.push(`layers="${config.layers}"`);
|
||||
}
|
||||
|
||||
if (config.backLayers) {
|
||||
attrs.push(`back-layers="${config.backLayers}"`);
|
||||
}
|
||||
|
||||
if (attrs.length > 0) {
|
||||
parts.push(`{${attrs.join(' ')}}`);
|
||||
}
|
||||
|
||||
return parts.join('');
|
||||
}
|
||||
|
||||
/**
|
||||
* 自动生成图层配置
|
||||
*/
|
||||
function autoGenerateLayers(fields: CardField[]): string {
|
||||
if (fields.length === 0) return '';
|
||||
|
||||
const layers: string[] = [];
|
||||
const totalHeight = 8;
|
||||
const heightPerField = Math.floor((totalHeight - 2) / fields.length);
|
||||
|
||||
for (let i = 0; i < fields.length; i++) {
|
||||
const field = fields[i];
|
||||
const y1 = 2 + i * heightPerField;
|
||||
const y2 = y1 + heightPerField - 1;
|
||||
const fontSize = Math.min(12, Math.floor(80 / fields.length));
|
||||
layers.push(`${field.name}:1,${y1}-${y2},${fontSize}`);
|
||||
}
|
||||
|
||||
return layers.join(' ');
|
||||
}
|
||||
|
||||
/**
|
||||
* 生成卡牌组的主函数
|
||||
*/
|
||||
export function generateCardDeck(params: GenerateCardDeckParams): {
|
||||
mdFile: string;
|
||||
csvFile: string;
|
||||
deckComponent: string;
|
||||
message: string;
|
||||
} {
|
||||
const {
|
||||
deck_name,
|
||||
output_dir,
|
||||
card_count = 10,
|
||||
card_template,
|
||||
deck_config = {},
|
||||
description
|
||||
} = params;
|
||||
|
||||
// 确保输出目录存在
|
||||
if (!existsSync(output_dir)) {
|
||||
mkdirSync(output_dir, { recursive: true });
|
||||
}
|
||||
|
||||
// 生成文件名
|
||||
const safeName = deck_name.toLowerCase().replace(/\s+/g, '-');
|
||||
const csvFileName = `${safeName}.csv`;
|
||||
const mdFileName = `${safeName}.md`;
|
||||
|
||||
// 创建默认模板(如果没有提供)
|
||||
const template: CardTemplate = card_template || {
|
||||
fields: [
|
||||
{ name: 'name', description: '卡牌名称', examples: ['示例卡牌 1', '示例卡牌 2'] },
|
||||
{ name: 'type', description: '卡牌类型', examples: ['物品', '法术'] },
|
||||
{ name: 'cost', description: '费用', examples: ['1', '2'] },
|
||||
{ name: 'description', description: '效果描述', examples: ['这是一个效果描述', '这是另一个效果'] }
|
||||
]
|
||||
};
|
||||
|
||||
// 创建默认配置(如果没有提供)
|
||||
const config: DeckConfig = {
|
||||
size: deck_config.size || '54x86',
|
||||
grid: deck_config.grid || '5x8',
|
||||
bleed: deck_config.bleed ?? 1,
|
||||
padding: deck_config.padding ?? 2,
|
||||
shape: deck_config.shape || 'rectangle',
|
||||
layers: deck_config.layers || autoGenerateLayers(template.fields)
|
||||
};
|
||||
|
||||
// 生成 CSV 内容
|
||||
const csvContent = generateCardCSV(template, card_count);
|
||||
const csvPath = join(output_dir, csvFileName);
|
||||
writeFileSync(csvPath, csvContent, 'utf-8');
|
||||
|
||||
// 生成 Markdown 内容
|
||||
const mdContent = generateDeckMarkdown(
|
||||
deck_name,
|
||||
`./${csvFileName}`,
|
||||
config,
|
||||
description
|
||||
);
|
||||
const mdPath = join(output_dir, mdFileName);
|
||||
writeFileSync(mdPath, mdContent, 'utf-8');
|
||||
|
||||
// 构建完整的 deck 组件代码
|
||||
const deckComponent = buildDeckComponent(`./${csvFileName}`, config);
|
||||
|
||||
return {
|
||||
mdFile: mdPath,
|
||||
csvFile: csvPath,
|
||||
deckComponent,
|
||||
message: `已生成卡牌组 "${deck_name}":\n- Markdown 文件:${mdPath}\n- CSV 数据:${csvPath}\n- 组件代码:${deckComponent}`
|
||||
};
|
||||
}
|
||||
Reference in New Issue
Block a user