create-report.mjs 20 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446
  1. import {
  2. Document, Packer, Paragraph, TextRun, Table, TableRow, TableCell,
  3. AlignmentType, HeadingLevel, BorderStyle, WidthType, ShadingType,
  4. VerticalAlign, LevelFormat, ExternalHyperlink, Header, Footer, PageNumber
  5. } from "docx";
  6. import { writeFileSync } from "fs";
  7. // ── Colors ───────────────────────────────────────────────────────────────────
  8. const CLAUDE_ORANGE = "E8834B";
  9. const HEADER_BG = "2C3E50";
  10. const ROW_ALT = "F8F9FA";
  11. const ROW_HIGHLIGHT = "FFF3CD";
  12. const BORDER_COLOR = "CCCCCC";
  13. const WHITE = "FFFFFF";
  14. const border = (color = BORDER_COLOR) => ({ style: BorderStyle.SINGLE, size: 1, color });
  15. const allBorders = (color = BORDER_COLOR) => ({
  16. top: border(color), bottom: border(color),
  17. left: border(color), right: border(color),
  18. });
  19. // ── Helper: table cell ────────────────────────────────────────────────────────
  20. function cell(text, { bold = false, bg = WHITE, width, align = AlignmentType.LEFT, color = "000000", fontSize = 20 } = {}) {
  21. return new TableCell({
  22. borders: allBorders(),
  23. width: { size: width, type: WidthType.DXA },
  24. shading: { fill: bg, type: ShadingType.CLEAR },
  25. verticalAlign: VerticalAlign.CENTER,
  26. margins: { top: 80, bottom: 80, left: 120, right: 120 },
  27. children: [new Paragraph({
  28. alignment: align,
  29. children: [new TextRun({ text, bold, color, size: fontSize, font: "Arial" })],
  30. })],
  31. });
  32. }
  33. // ── Helper: header cell (dark bg, white text) ─────────────────────────────────
  34. function headerCell(text, width) {
  35. return cell(text, { bold: true, bg: HEADER_BG, color: WHITE, width, fontSize: 20 });
  36. }
  37. // ── Helper: section heading ───────────────────────────────────────────────────
  38. function sectionHeading(text) {
  39. return new Paragraph({
  40. spacing: { before: 320, after: 160 },
  41. children: [new TextRun({ text, bold: true, size: 28, color: "2C3E50", font: "Arial" })],
  42. border: { bottom: { style: BorderStyle.SINGLE, size: 4, color: CLAUDE_ORANGE, space: 4 } },
  43. });
  44. }
  45. // ── Helper: normal paragraph ─────────────────────────────────────────────────
  46. function para(text, { bold = false, size = 20, spacing = { after: 120 } } = {}) {
  47. return new Paragraph({
  48. spacing,
  49. children: [new TextRun({ text, bold, size, font: "Arial", color: "333333" })],
  50. });
  51. }
  52. // ── Helper: bullet ────────────────────────────────────────────────────────────
  53. function bullet(text) {
  54. return new Paragraph({
  55. numbering: { reference: "bullets", level: 0 },
  56. spacing: { after: 80 },
  57. children: [new TextRun({ text, size: 20, font: "Arial", color: "333333" })],
  58. });
  59. }
  60. // ── Helper: note paragraph ───────────────────────────────────────────────────
  61. function note(text) {
  62. return new Paragraph({
  63. spacing: { before: 100, after: 160 },
  64. children: [new TextRun({ text: `📌 ${text}`, size: 18, italics: true, color: "555555", font: "Arial" })],
  65. });
  66. }
  67. // ── Section 1: Core pricing table (6 columns) ─────────────────────────────────
  68. // Content width: A4 9026, use 9000 for slight breathing room
  69. const COL1 = 1500; // 层级
  70. const COL2 = 2000; // Claude model
  71. const COL3 = 1500; // Claude price
  72. const COL4 = 2000; // OpenAI model
  73. const COL5 = 1500; // OpenAI price
  74. const TABLE1_WIDTH = COL1 + COL2 + COL3 + COL4 + COL5; // 8500
  75. function pricingTable() {
  76. const rows = [
  77. ["旗舰", "Claude Opus 4.6", "$5 / $25", "GPT-4o", "$2.50 / $10", false],
  78. ["标准(推荐)", "Claude Sonnet 4.6", "$3 / $15", "GPT-4o", "$2.50 / $10", true ],
  79. ["轻量", "Claude Haiku 4.5", "$1 / $5", "GPT-4o mini", "$0.15 / $0.60", false],
  80. ];
  81. return new Table({
  82. width: { size: TABLE1_WIDTH, type: WidthType.DXA },
  83. columnWidths: [COL1, COL2, COL3, COL4, COL5],
  84. rows: [
  85. new TableRow({
  86. tableHeader: true,
  87. children: [
  88. headerCell("层级", COL1),
  89. headerCell("Claude(Anthropic)", COL2),
  90. headerCell("价格(输入/输出)", COL3),
  91. headerCell("ChatGPT(OpenAI)", COL4),
  92. headerCell("价格(输入/输出)", COL5),
  93. ],
  94. }),
  95. ...rows.map(([tier, claude, cp, oai, op, highlight]) =>
  96. new TableRow({
  97. children: [
  98. cell(tier, { width: COL1, bg: highlight ? ROW_HIGHLIGHT : WHITE, bold: highlight }),
  99. cell(claude, { width: COL2, bg: highlight ? ROW_HIGHLIGHT : WHITE, bold: highlight, color: highlight ? "C0392B" : "000000" }),
  100. cell(cp, { width: COL3, bg: highlight ? ROW_HIGHLIGHT : WHITE, bold: highlight, align: AlignmentType.CENTER }),
  101. cell(oai, { width: COL4, bg: highlight ? ROW_HIGHLIGHT : WHITE }),
  102. cell(op, { width: COL5, bg: highlight ? ROW_HIGHLIGHT : WHITE, align: AlignmentType.CENTER }),
  103. ],
  104. })
  105. ),
  106. ],
  107. });
  108. }
  109. // ── Section 2: Cost estimate table ───────────────────────────────────────────
  110. const E_COL1 = 2200;
  111. const E_COL2 = 3200;
  112. const E_COL3 = 3200;
  113. const TABLE2_WIDTH = E_COL1 + E_COL2 + E_COL3; // 8600
  114. function costTable() {
  115. const rows = [
  116. ["每次对话成本", "~$0.0037", "~$0.0027"],
  117. ["1,000 次对话/月", "$3.70", "$2.70"],
  118. ["10,000 次对话/月", "$37", "$27"],
  119. ["100,000 次对话/月", "$370", "$270"],
  120. ];
  121. return new Table({
  122. width: { size: TABLE2_WIDTH, type: WidthType.DXA },
  123. columnWidths: [E_COL1, E_COL2, E_COL3],
  124. rows: [
  125. new TableRow({
  126. tableHeader: true,
  127. children: [
  128. headerCell("", E_COL1),
  129. headerCell("Claude Sonnet 4.6", E_COL2),
  130. headerCell("GPT-4o", E_COL3),
  131. ],
  132. }),
  133. ...rows.map(([label, claude, oai], i) =>
  134. new TableRow({
  135. children: [
  136. cell(label, { width: E_COL1, bg: i % 2 === 0 ? WHITE : ROW_ALT, bold: true }),
  137. cell(claude, { width: E_COL2, bg: i % 2 === 0 ? WHITE : ROW_ALT, align: AlignmentType.CENTER, color: "C0392B", bold: true }),
  138. cell(oai, { width: E_COL3, bg: i % 2 === 0 ? WHITE : ROW_ALT, align: AlignmentType.CENTER }),
  139. ],
  140. })
  141. ),
  142. ],
  143. });
  144. }
  145. // ── Section 3: Feature comparison ────────────────────────────────────────────
  146. const F_COL1 = 2400;
  147. const F_COL2 = 3000;
  148. const F_COL3 = 3000;
  149. const TABLE3_WIDTH = F_COL1 + F_COL2 + F_COL3; // 8400
  150. function featureTable(rows) {
  151. return new Table({
  152. width: { size: TABLE3_WIDTH, type: WidthType.DXA },
  153. columnWidths: [F_COL1, F_COL2, F_COL3],
  154. rows: [
  155. new TableRow({
  156. tableHeader: true,
  157. children: [
  158. headerCell("功能", F_COL1),
  159. headerCell("Claude API", F_COL2),
  160. headerCell("ChatGPT API", F_COL3),
  161. ],
  162. }),
  163. ...rows.map(([feat, claude, oai], i) =>
  164. new TableRow({
  165. children: [
  166. cell(feat, { width: F_COL1, bg: i % 2 === 0 ? WHITE : ROW_ALT, bold: true }),
  167. cell(claude, { width: F_COL2, bg: i % 2 === 0 ? WHITE : ROW_ALT, color: "1A6B2A" }),
  168. cell(oai, { width: F_COL3, bg: i % 2 === 0 ? WHITE : ROW_ALT }),
  169. ],
  170. })
  171. ),
  172. ],
  173. });
  174. }
  175. // ── Section 4: Capability comparison ─────────────────────────────────────────
  176. const C_COL1 = 2200;
  177. const C_COL2 = 3200;
  178. const C_COL3 = 3200;
  179. const TABLE4_WIDTH = C_COL1 + C_COL2 + C_COL3;
  180. function capabilityTable(rows) {
  181. return new Table({
  182. width: { size: TABLE4_WIDTH, type: WidthType.DXA },
  183. columnWidths: [C_COL1, C_COL2, C_COL3],
  184. rows: [
  185. new TableRow({
  186. tableHeader: true,
  187. children: [
  188. headerCell("维度", C_COL1),
  189. headerCell("Claude Sonnet 4.6", C_COL2),
  190. headerCell("GPT-4o", C_COL3),
  191. ],
  192. }),
  193. ...rows.map(([dim, claude, oai], i) =>
  194. new TableRow({
  195. children: [
  196. cell(dim, { width: C_COL1, bg: i % 2 === 0 ? WHITE : ROW_ALT, bold: true }),
  197. cell(claude, { width: C_COL2, bg: i % 2 === 0 ? WHITE : ROW_ALT }),
  198. cell(oai, { width: C_COL3, bg: i % 2 === 0 ? WHITE : ROW_ALT }),
  199. ],
  200. })
  201. ),
  202. ],
  203. });
  204. }
  205. // ── Section 6: Annual forecast ────────────────────────────────────────────────
  206. const Y_COL1 = 2200;
  207. const Y_COL2 = 3200;
  208. const Y_COL3 = 3200;
  209. const TABLE5_WIDTH = Y_COL1 + Y_COL2 + Y_COL3;
  210. function yearlyTable() {
  211. const rows = [
  212. ["月费估算", "~$30 – $50", "~$25 – $40"],
  213. ["年费估算", "~$360 – $600", "~$300 – $480"],
  214. ];
  215. return new Table({
  216. width: { size: TABLE5_WIDTH, type: WidthType.DXA },
  217. columnWidths: [Y_COL1, Y_COL2, Y_COL3],
  218. rows: [
  219. new TableRow({
  220. tableHeader: true,
  221. children: [
  222. headerCell("", Y_COL1),
  223. headerCell("Claude Sonnet 4.6(含缓存)", Y_COL2),
  224. headerCell("GPT-4o(含缓存)", Y_COL3),
  225. ],
  226. }),
  227. ...rows.map(([label, claude, oai], i) =>
  228. new TableRow({
  229. children: [
  230. cell(label, { width: Y_COL1, bg: i % 2 === 0 ? WHITE : ROW_ALT, bold: true }),
  231. cell(claude, { width: Y_COL2, bg: i % 2 === 0 ? WHITE : ROW_ALT, align: AlignmentType.CENTER, bold: true, color: "C0392B" }),
  232. cell(oai, { width: Y_COL3, bg: i % 2 === 0 ? WHITE : ROW_ALT, align: AlignmentType.CENTER }),
  233. ],
  234. })
  235. ),
  236. ],
  237. });
  238. }
  239. // ── Build document ────────────────────────────────────────────────────────────
  240. const doc = new Document({
  241. numbering: {
  242. config: [
  243. {
  244. reference: "bullets",
  245. levels: [{
  246. level: 0, format: LevelFormat.BULLET, text: "\u2022",
  247. alignment: AlignmentType.LEFT,
  248. style: { paragraph: { indent: { left: 720, hanging: 360 } } },
  249. }],
  250. },
  251. ],
  252. },
  253. styles: {
  254. default: {
  255. document: { run: { font: "Arial", size: 20, color: "333333" } },
  256. },
  257. },
  258. sections: [{
  259. properties: {
  260. page: {
  261. size: { width: 11906, height: 16838 }, // A4
  262. margin: { top: 1080, right: 1080, bottom: 1080, left: 1080 },
  263. },
  264. },
  265. headers: {
  266. default: new Header({
  267. children: [new Paragraph({
  268. alignment: AlignmentType.RIGHT,
  269. border: { bottom: { style: BorderStyle.SINGLE, size: 4, color: CLAUDE_ORANGE, space: 4 } },
  270. spacing: { after: 200 },
  271. children: [new TextRun({
  272. text: "Claude API vs ChatGPT API — 价格对比报告",
  273. size: 16, color: "888888", font: "Arial",
  274. })],
  275. })],
  276. }),
  277. },
  278. footers: {
  279. default: new Footer({
  280. children: [new Paragraph({
  281. alignment: AlignmentType.CENTER,
  282. border: { top: { style: BorderStyle.SINGLE, size: 2, color: BORDER_COLOR, space: 4 } },
  283. spacing: { before: 100 },
  284. children: [
  285. new TextRun({ text: "数据来源:platform.claude.com / openai.com/api/pricing | 第 ", size: 16, color: "888888", font: "Arial" }),
  286. new TextRun({ children: [PageNumber.CURRENT], size: 16, color: "888888", font: "Arial" }),
  287. new TextRun({ text: " 页", size: 16, color: "888888", font: "Arial" }),
  288. ],
  289. })],
  290. }),
  291. },
  292. children: [
  293. // ── Title block ──────────────────────────────────────────────────────
  294. new Paragraph({
  295. spacing: { before: 0, after: 80 },
  296. children: [new TextRun({
  297. text: "Claude API vs ChatGPT API",
  298. bold: true, size: 52, color: "2C3E50", font: "Arial",
  299. })],
  300. }),
  301. new Paragraph({
  302. spacing: { before: 0, after: 60 },
  303. children: [new TextRun({
  304. text: "价格对比报告",
  305. bold: true, size: 36, color: CLAUDE_ORANGE, font: "Arial",
  306. })],
  307. }),
  308. new Paragraph({
  309. spacing: { after: 320 },
  310. border: { bottom: { style: BorderStyle.SINGLE, size: 6, color: CLAUDE_ORANGE, space: 6 } },
  311. children: [new TextRun({ text: "制作日期:2026年4月", size: 18, color: "888888", font: "Arial" })],
  312. }),
  313. // ── Section 1 ────────────────────────────────────────────────────────
  314. sectionHeading("一、核心模型价格对比(每百万 Token)"),
  315. pricingTable(),
  316. note("本项目 chatbot 使用 Claude Sonnet 4.6,对标 OpenAI 的 GPT-4o 层级。"),
  317. // ── Section 2 ────────────────────────────────────────────────────────
  318. sectionHeading("二、实际对话成本估算"),
  319. para("以 chatbot 典型对话为例:"),
  320. bullet("输入:~500 tokens(系统提示 + 对话历史)"),
  321. bullet("输出:~150 tokens(Bot 回复)"),
  322. new Paragraph({ spacing: { after: 120 } }),
  323. costTable(),
  324. note("月差额约 $10–$100,对企业级系统而言可忽略不计。"),
  325. // ── Section 3 ────────────────────────────────────────────────────────
  326. sectionHeading("三、成本优化功能对比"),
  327. featureTable([
  328. ["Batch 批处理折扣", "标准价 5折", "标准价 5折"],
  329. ["Prompt Caching", "命中缓存仅需原价 10%", "命中缓存 50%"],
  330. ["Context 窗口", "200K tokens", "128K tokens"],
  331. ["最长单次输入", "更适合长文档/历史记录", "受限于 128K"],
  332. ]),
  333. note("Prompt Caching:本项目系统提示词(Ellie 角色设定)每次对话都会重复发送,Claude 缓存命中价格仅 $0.30/百万 tokens(原价 $3 的 10%),长期运行下成本优势显著。"),
  334. // ── Section 4 ────────────────────────────────────────────────────────
  335. sectionHeading("四、功能与定位对比"),
  336. capabilityTable([
  337. ["中文理解质量", "优秀", "优秀"],
  338. ["长上下文处理", "200K tokens(领先)", "128K tokens"],
  339. ["代码生成", "优秀", "优秀"],
  340. ["内容安全合规", "Constitutional AI,策略更严谨", "中等"],
  341. ["响应稳定性", "更一致", "中等"],
  342. ["企业 SLA", "有", "有"],
  343. ["数据隐私", "不用于模型训练", "不用于模型训练"],
  344. ["API 成熟度", "成熟", "成熟"],
  345. ]),
  346. // ── Section 5 ────────────────────────────────────────────────────────
  347. sectionHeading("五、推荐选择 Claude API 的理由"),
  348. para("1. 成本差异极小,价值更高", { bold: true }),
  349. para("对于本项目规模(B2B dealer chatbot),两者月费差额通常在 $10–$50 以内,而 Claude 在长文档处理和一致性上表现更好。"),
  350. para("2. Prompt Caching 优势明显", { bold: true }),
  351. para("本项目系统提示词固定(Ellie 角色设定 + ERP 数据上下文),每次对话都能命中缓存,实际输入成本可降至标准价的 10%,有效抵消价格差。"),
  352. para("3. 200K 上下文窗口", { bold: true }),
  353. para("随着 ERP 数据集成(订单详情、产品目录),注入 LLM 的 context 数据量会增加。200K 窗口提供更大余量,无需担心超出限制。"),
  354. para("4. 技术栈已完成集成", { bold: true }),
  355. para("项目代码已使用 claude-sonnet-4-6 完成集成,切换到 ChatGPT API 需要额外改造工作和测试成本。"),
  356. para("5. 内容安全合规", { bold: true }),
  357. para("Anthropic 的 Constitutional AI 在拒绝不适当请求、保持回复边界方面更可靠,适合面向 dealer 的 B2B 场景。"),
  358. // ── Section 6 ────────────────────────────────────────────────────────
  359. sectionHeading("六、费用预测(年度)"),
  360. para("假设每月 20,000 次对话,启用 Prompt Caching:"),
  361. yearlyTable(),
  362. note("两者年费差额约 $60–$120,可忽略不计。"),
  363. // ── Conclusion ───────────────────────────────────────────────────────
  364. new Paragraph({
  365. spacing: { before: 320, after: 160 },
  366. shading: { fill: "EBF5FB", type: ShadingType.CLEAR },
  367. border: {
  368. top: { style: BorderStyle.SINGLE, size: 4, color: CLAUDE_ORANGE },
  369. bottom: { style: BorderStyle.SINGLE, size: 4, color: CLAUDE_ORANGE },
  370. left: { style: BorderStyle.THICK, size: 12, color: CLAUDE_ORANGE },
  371. right: { style: BorderStyle.SINGLE, size: 4, color: CLAUDE_ORANGE },
  372. },
  373. children: [new TextRun({
  374. text: "结论:推荐使用 Claude API(Anthropic)",
  375. bold: true, size: 26, color: "2C3E50", font: "Arial",
  376. })],
  377. }),
  378. para("在价格接近的前提下,Claude 提供更大的 Context 窗口、更优的 Prompt Caching 折扣,且代码已完成集成,综合性价比更高,是本项目的最优选择。"),
  379. // ── Sources ──────────────────────────────────────────────────────────
  380. new Paragraph({
  381. spacing: { before: 400, after: 80 },
  382. border: { top: { style: BorderStyle.SINGLE, size: 2, color: BORDER_COLOR, space: 6 } },
  383. children: [new TextRun({ text: "数据来源", bold: true, size: 18, color: "888888", font: "Arial" })],
  384. }),
  385. new Paragraph({
  386. spacing: { after: 60 },
  387. children: [
  388. new TextRun({ text: "Anthropic Claude API Pricing: ", size: 16, color: "888888", font: "Arial" }),
  389. new ExternalHyperlink({
  390. link: "https://platform.claude.com/docs/en/about-claude/pricing",
  391. children: [new TextRun({ text: "platform.claude.com/docs/en/about-claude/pricing", size: 16, style: "Hyperlink", font: "Arial" })],
  392. }),
  393. ],
  394. }),
  395. new Paragraph({
  396. spacing: { after: 60 },
  397. children: [
  398. new TextRun({ text: "OpenAI API Pricing: ", size: 16, color: "888888", font: "Arial" }),
  399. new ExternalHyperlink({
  400. link: "https://openai.com/api/pricing/",
  401. children: [new TextRun({ text: "openai.com/api/pricing", size: 16, style: "Hyperlink", font: "Arial" })],
  402. }),
  403. ],
  404. }),
  405. ],
  406. }],
  407. });
  408. const outPath = "C:\\Codes\\homelegance-chatbot\\deploy\\Claude_vs_ChatGPT_API价格对比.docx";
  409. Packer.toBuffer(doc).then(buf => {
  410. writeFileSync(outPath, buf);
  411. console.log("Created:", outPath);
  412. });