Browse Source

Task/auto-pad holdings table in AI prompt using tablemark (#5772)

* Auto-pad holdings table in AI prompt using tablemark

* Update changelog
main
David Requeno 19 hours ago
committed by GitHub
parent
commit
5bfcceb959
No known key found for this signature in database GPG Key ID: B5690EEEBB952194
  1. 2
      CHANGELOG.md
  2. 59
      apps/api/src/app/endpoints/ai/ai.service.ts
  3. 58
      package-lock.json
  4. 1
      package.json

2
CHANGELOG.md

@ -13,6 +13,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
### Changed
- Formatted the holdings table in the _Copy AI prompt to clipboard for analysis_ action on the analysis page (experimental)
- Formatted the holdings table in the _Copy portfolio data to clipboard for AI prompt_ action of the analysis page (experimental)
- Improved the language localization for German (`de`)
## 2.209.0 - 2025-10-18

59
apps/api/src/app/endpoints/ai/ai.service.ts

@ -10,6 +10,7 @@ import type { AiPromptMode } from '@ghostfolio/common/types';
import { Injectable } from '@nestjs/common';
import { createOpenRouter } from '@openrouter/ai-sdk-provider';
import { generateText } from 'ai';
import tablemark, { ColumnDescriptor } from 'tablemark';
@Injectable()
export class AiService {
@ -58,34 +59,50 @@ export class AiService {
userId
});
const holdingsTable = [
'| Name | Symbol | Currency | Asset Class | Asset Sub Class | Allocation in Percentage |',
'| --- | --- | --- | --- | --- | --- |',
...Object.values(holdings)
.sort((a, b) => {
return b.allocationInPercentage - a.allocationInPercentage;
})
.map(
({
allocationInPercentage,
assetClass,
assetSubClass,
currency,
name,
symbol
}) => {
return `| ${name} | ${symbol} | ${currency} | ${assetClass} | ${assetSubClass} | ${(allocationInPercentage * 100).toFixed(3)}% |`;
}
)
const holdingsTableColumns: ColumnDescriptor[] = [
{ name: 'Name' },
{ name: 'Symbol' },
{ name: 'Currency' },
{ name: 'Asset Class' },
{ name: 'Asset Sub Class' },
{ align: 'right', name: 'Allocation in Percentage' }
];
const holdingsTableRows = Object.values(holdings)
.sort((a, b) => {
return b.allocationInPercentage - a.allocationInPercentage;
})
.map(
({
allocationInPercentage,
assetClass,
assetSubClass,
currency,
name,
symbol
}) => {
return {
Name: name,
Symbol: symbol,
Currency: currency,
'Asset Class': assetClass ?? '',
'Asset Sub Class': assetSubClass ?? '',
'Allocation in Percentage': `${(allocationInPercentage * 100).toFixed(3)}%`
};
}
);
const holdingsTableString = tablemark(holdingsTableRows, {
columns: holdingsTableColumns
});
if (mode === 'portfolio') {
return holdingsTable.join('\n');
return holdingsTableString;
}
return [
`You are a neutral financial assistant. Please analyze the following investment portfolio (base currency being ${userCurrency}) in simple words.`,
...holdingsTable,
holdingsTableString,
'Structure your answer with these sections:',
'Overview: Briefly summarize the portfolio’s composition and allocation rationale.',
'Risk Assessment: Identify potential risks, including market volatility, concentration, and sectoral imbalances.',

58
package-lock.json

@ -90,6 +90,7 @@
"rxjs": "7.8.1",
"stripe": "18.5.0",
"svgmap": "2.12.2",
"tablemark": "3.1.0",
"twitter-api-v2": "1.23.0",
"uuid": "11.1.0",
"yahoo-finance2": "3.10.0",
@ -23653,6 +23654,15 @@
"node": ">= 0.4"
}
},
"node_modules/get-stdin": {
"version": "5.0.1",
"resolved": "https://registry.npmjs.org/get-stdin/-/get-stdin-5.0.1.tgz",
"integrity": "sha512-jZV7n6jGE3Gt7fgSTJoz91Ak5MuTLwMwkoYdjxuJ/AmjIsE1UC03y/IWkZCQGEvVNS9qoRNwy5BCqxImv0FVeA==",
"license": "MIT",
"engines": {
"node": ">=0.12.0"
}
},
"node_modules/get-stream": {
"version": "5.2.0",
"resolved": "https://registry.npmjs.org/get-stream/-/get-stream-5.2.0.tgz",
@ -31970,7 +31980,6 @@
"version": "2.0.2",
"resolved": "https://registry.npmjs.org/lower-case/-/lower-case-2.0.2.tgz",
"integrity": "sha512-7fm3l3NAF9WfN6W3JOmf5drwpVqX78JtoGJ3A6W0a6ZnldM41w2fV5D490psKFTpMds8TJse/eHLFFsNHHjHgg==",
"dev": true,
"license": "MIT",
"dependencies": {
"tslib": "^2.0.3"
@ -32874,7 +32883,6 @@
"version": "3.0.4",
"resolved": "https://registry.npmjs.org/no-case/-/no-case-3.0.4.tgz",
"integrity": "sha512-fgAN3jGAh+RoxUGZHTSOLJIqUc2wmoBwGR4tbpNAKmmovFoWq0OdRkb0VkldReO2a2iBT/OEulG9XSUc10r3zg==",
"dev": true,
"license": "MIT",
"dependencies": {
"lower-case": "^2.0.2",
@ -37629,6 +37637,17 @@
"integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==",
"license": "MIT"
},
"node_modules/sentence-case": {
"version": "3.0.4",
"resolved": "https://registry.npmjs.org/sentence-case/-/sentence-case-3.0.4.tgz",
"integrity": "sha512-8LS0JInaQMCRoQ7YUytAo/xUu5W2XnQxV2HI/6uM6U7CITS1RqPElr30V6uIqyMKM9lJGRVFy5/4CuzcixNYSg==",
"license": "MIT",
"dependencies": {
"no-case": "^3.0.4",
"tslib": "^2.0.3",
"upper-case-first": "^2.0.2"
}
},
"node_modules/serialize-javascript": {
"version": "6.0.2",
"resolved": "https://registry.npmjs.org/serialize-javascript/-/serialize-javascript-6.0.2.tgz",
@ -38278,6 +38297,19 @@
"wbuf": "^1.7.3"
}
},
"node_modules/split-text-to-chunks": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/split-text-to-chunks/-/split-text-to-chunks-1.0.0.tgz",
"integrity": "sha512-HLtEwXK/T4l7QZSJ/kOSsZC0o5e2Xg3GzKKFxm0ZexJXw0Bo4CaEl39l7MCSRHk9EOOL5jT8JIDjmhTtcoe6lQ==",
"license": "MIT",
"dependencies": {
"get-stdin": "^5.0.1",
"minimist": "^1.2.0"
},
"bin": {
"wordwrap": "cli.js"
}
},
"node_modules/sprintf-js": {
"version": "1.1.3",
"resolved": "https://registry.npmjs.org/sprintf-js/-/sprintf-js-1.1.3.tgz",
@ -39046,6 +39078,19 @@
"url": "https://opencollective.com/synckit"
}
},
"node_modules/tablemark": {
"version": "3.1.0",
"resolved": "https://registry.npmjs.org/tablemark/-/tablemark-3.1.0.tgz",
"integrity": "sha512-IwO6f0SEzp1Z+zqz/7ANUmeEac4gaNlknWyj/S9aSg11wZmWYnLeyI/xXvEOU88BYUIf8y30y0wxB58xIKrVlQ==",
"license": "MIT",
"dependencies": {
"sentence-case": "^3.0.4",
"split-text-to-chunks": "^1.0.0"
},
"engines": {
"node": ">=14.16"
}
},
"node_modules/tapable": {
"version": "2.2.2",
"resolved": "https://registry.npmjs.org/tapable/-/tapable-2.2.2.tgz",
@ -40525,6 +40570,15 @@
"browserslist": ">= 4.21.0"
}
},
"node_modules/upper-case-first": {
"version": "2.0.2",
"resolved": "https://registry.npmjs.org/upper-case-first/-/upper-case-first-2.0.2.tgz",
"integrity": "sha512-514ppYHBaKwfJRK/pNC6c/OxfGa0obSnAl106u97Ed0I625Nin96KAjttZF6ZL3e1XLtphxnqrOi9iWgm+u+bg==",
"license": "MIT",
"dependencies": {
"tslib": "^2.0.3"
}
},
"node_modules/uri-js": {
"version": "4.4.1",
"resolved": "https://registry.npmjs.org/uri-js/-/uri-js-4.4.1.tgz",

1
package.json

@ -136,6 +136,7 @@
"rxjs": "7.8.1",
"stripe": "18.5.0",
"svgmap": "2.12.2",
"tablemark": "3.1.0",
"twitter-api-v2": "1.23.0",
"uuid": "11.1.0",
"yahoo-finance2": "3.10.0",

Loading…
Cancel
Save