Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
22 changes: 22 additions & 0 deletions backend/src/agents/builtin_tools/excel_spreadsheet_tool.py
Original file line number Diff line number Diff line change
Expand Up @@ -277,6 +277,28 @@ async def create_excel_spreadsheet(
ws.append(['Q1', 100])
ws.append(['Q2', 120])
ws['B4'] = '=SUM(B2:B3)'
# Freeze the header so it stays visible while
# scrolling -- do this on EVERY sheet that has a
# header row ('A2' freezes row 1, 'B2' also freezes
# column A):
ws.freeze_panes = 'A2'
# Give every number a format. Without one, Excel
# shows raw values -- 1234567.891 instead of
# $1,234,568, and 0.1834 instead of 18.3%:
for cell in ws['B'][1:]:
cell.number_format = '#,##0'
# Common formats: '#,##0.00' (2dp), '$#,##0'
# (currency), '0.0%' (percent -- store 0.183, not
# 18.3), 'yyyy-mm-dd' (date).

Formulas are written WITHOUT a cached result, because
openpyxl does not evaluate them. Excel fills them in on
open, but until then the cell reads as empty to
everything else -- including read_excel_spreadsheet and
the in-app preview. So when a total is meant to be read
back or shown, compute it in Python and write the value
(optionally alongside the formula on another cell):
ws['B4'] = sum(r[1] for r in rows)

Example (add a second sheet + a bar chart):
ws2 = wb.create_sheet('Chart')
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -368,6 +368,14 @@ async def create_powerpoint_presentation(
Inches(8), Inches(2)).table
tbl.cell(0, 0).text = 'Quarter'; tbl.cell(0, 1).text = 'Revenue'

Speaker notes -- add them to every content slide unless
the user says otherwise. They are what the presenter
actually says, so keep the detail there and the slide
itself sparse:
slide.notes_slide.notes_text_frame.text = (
'Revenue grew 15% on enterprise renewals; '
'call out the churn improvement before moving on.')

A matplotlib chart image:
import matplotlib.pyplot as plt
plt.figure(figsize=(8, 4.5))
Expand Down
14 changes: 13 additions & 1 deletion backend/src/agents/main_agent/core/system_prompt_builder.py
Original file line number Diff line number Diff line change
Expand Up @@ -81,14 +81,26 @@
- Respond using markdown.
- You can ONLY use tools that are explicitly provided to you in each conversation
- When approriate, you may use KaTeX to render mathematical equations.
- Since the $ character is used to denote a variable in KaTeX, other uses of $ should be use the HTML entity $
- KaTeX treats $ as a math delimiter, so in your own chat replies write other
uses of $ as the HTML entity $. This applies ONLY to the markdown you
send to the user. Never use the entity inside a file you generate, inside
code, or inside a tool argument -- a spreadsheet cell or slide holding
"$100K" is simply wrong, and it stays wrong when the user opens the file.
There, write a plain $.
- When the user asks for a diagram or chart, you may use Mermaid to render it.
- Available tools may change throughout the conversation based on user preferences
- When multiple tools are available, select and use the most appropriate combination in the optimal order to fulfill the user's request
- Break down complex tasks into steps and use multiple tools sequentially or in parallel as needed
- Always explain your reasoning when using tools
- If you don't have the right tool for a task, clearly inform the user about the limitation

PREVIEWING FILES:
Every .docx and .pptx in the conversation has a "Preview" button the user
clicks to see it laid out; you cannot open it for them. When they ask to LOOK
at one ("show me this deck"), say to use that button -- never read the file
or re-create it just to show it. Reading is still right when the request is
about its CONTENT: summarize, check, answer from it, edit it.

HANDLING MISSING TOOLS:
Users can toggle individual tools on and off from Customize → Tools in the
sidebar. When a user asks for something you would normally handle with a tool
Expand Down
30 changes: 30 additions & 0 deletions frontend/ai.client/package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

5 changes: 5 additions & 0 deletions frontend/ai.client/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -43,11 +43,13 @@
"chart.js": "4.5.1",
"clipboard": "2.0.11",
"docx-preview": "0.4.0",
"echarts": "file:./shims/echarts-stub",
"katex": "0.16.45",
"marked": "17.0.6",
"mermaid": "11.16.1",
"ng2-charts": "10.0.0",
"ngx-markdown": "21.2.0",
"pptx-preview": "1.0.7",
"prismjs": "1.30.0",
"rxjs": "7.8.2",
"tslib": "2.8.1",
Expand Down Expand Up @@ -88,6 +90,9 @@
"@babel/core": ">=7.29.6 <8.0.0",
"mermaid": {
"uuid": "14.0.0"
},
"pptx-preview": {
"uuid": "14.0.0"
}
}
}
72 changes: 72 additions & 0 deletions frontend/ai.client/shims/echarts-stub/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
# echarts-stub

A deliberately empty stand-in for [`echarts`](https://echarts.apache.org/),
substituted into `pptx-preview` by declaring it as the project's own
`echarts` dependency:

```json
"dependencies": { "echarts": "file:./shims/echarts-stub" }
```

Its version (`5.6.0`) satisfies the `^5.5.1` that `pptx-preview` asks
for, so npm dedupes both onto this one directory instead of fetching the
real library.

## Why

`pptx-preview` reaches for ECharts in exactly one place: rendering a
*native* OOXML chart part (`ppt/charts/*.xml`) — a chart PowerPoint draws
itself from embedded data, rather than a picture. It does so via
`import * as echarts from 'echarts'`, a static namespace import, so no
amount of tree-shaking will drop it. Depending on `pptx-preview` means
shipping the whole charting library.

Measured against this repo's build, that is the difference between a
**357 kB** and a **~60 kB** gzipped lazy chunk — ECharts is roughly five
sixths of the viewer's weight.

We do not need it. `create_powerpoint_presentation` builds charts by
rendering a matplotlib PNG and calling `add_picture`, which lands in the
deck as an ordinary image. A survey of every `.pptx` in the dev files
store found **zero** native chart parts across four real decks, including
a 4.3 MB branded template deck carrying 15 images.

## What it costs

An *uploaded* deck that does contain a native chart throws instead of
drawing it, and `PptxViewerComponent` reports the file as unreadable
rather than silently dropping a slide's centrepiece. That is the whole
trade.

## Why a dependency, and not `tsconfig` `paths`

The import lives inside `node_modules/pptx-preview/dist/pptx-preview.es.js`,
a pre-built JavaScript file. `paths` governs TypeScript's resolution of
*our own sources*; the bundler resolves a dependency's own imports with
the node resolver and walks straight past it. This was tried first and
the built chunk still contained the whole of ECharts.

Substituting the package is the only mechanism that applies at the point
the import is resolved. It has to be a **top-level dependency** rather
than a `file:` spec nested under `overrides`: npm resolves the latter
relative to wherever it happens to place the package, so the symlink
lands in a different spot depending on whether the dependency is hoisted,
and it pointed at a non-existent path. A top-level `file:` spec is
defined to resolve against the package root.

Verified by grepping the built chunks for `zrender`, ECharts' renderer,
which is absent.

## Removing it

Replace the `echarts` dependency with a real version and delete this
directory. Pin **6.1.0 or later**: every release below it carries
GHSA-fgmj-fm8m-jvvx, and `pptx-preview`'s own `^5.5.1` range resolves to
a vulnerable one.

## Why the version says 5.6.0

It has to satisfy the `^5.5.1` range `pptx-preview` declares, or `npm ls`
reports the tree as invalid and exits non-zero. The number tracks that
range and says nothing about the contents — there is no ECharts code
here at any version.
4 changes: 4 additions & 0 deletions frontend/ai.client/shims/echarts-stub/index.d.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
export declare function init(): never;
export declare function use(): void;
declare const _default: { init: typeof init; use: typeof use };
export default _default;
24 changes: 24 additions & 0 deletions frontend/ai.client/shims/echarts-stub/index.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
/**
* Build-time stand-in for `echarts`. See README.md for why this exists.
*
* Only the two entry points `pptx-preview` actually calls are provided.
*/

/** Thrown when a deck really does contain a native OOXML chart. */
function unsupported() {
throw new Error(
'pptx-preview: native OOXML charts are not supported in this build',
);
}

export function init() {
return unsupported();
}

export function use() {
// No-op. Registration is meaningless without a charting runtime, and
// it may be called before the deck is known to contain a chart —
// throwing here would fail decks that have none.
}

export default { init, use };
17 changes: 17 additions & 0 deletions frontend/ai.client/shims/echarts-stub/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
{
"name": "echarts-stub",
"version": "5.6.0",
"description": "Build-time stand-in for echarts, substituted into pptx-preview. Version tracks the range pptx-preview asks for (^5.5.1) so npm ls does not report the tree as invalid; it carries no echarts code. See README.md.",
"license": "MIT",
"private": true,
"type": "module",
"main": "index.js",
"module": "index.js",
"types": "index.d.ts",
"exports": {
".": {
"types": "./index.d.ts",
"default": "./index.js"
}
}
}
12 changes: 6 additions & 6 deletions frontend/ai.client/src/app/artifacts/artifact-library.page.ts
Original file line number Diff line number Diff line change
Expand Up @@ -72,37 +72,37 @@ const TYPE_STYLES: Record<string, TypeStyle> = {
label: 'Markdown',
icon: 'heroDocumentText',
bg: 'bg-filetype-markdown-100 dark:bg-filetype-markdown-900/60',
text: 'text-filetype-markdown-600 dark:text-filetype-markdown-300',
text: 'text-filetype-markdown-700 dark:text-filetype-markdown-300',
},
'text/x-markdown': {
label: 'Markdown',
icon: 'heroDocumentText',
bg: 'bg-filetype-markdown-100 dark:bg-filetype-markdown-900/60',
text: 'text-filetype-markdown-600 dark:text-filetype-markdown-300',
text: 'text-filetype-markdown-700 dark:text-filetype-markdown-300',
},
'text/html': {
label: 'Web page',
icon: 'heroCodeBracket',
bg: 'bg-filetype-code-100 dark:bg-filetype-code-900/60',
text: 'text-filetype-code-600 dark:text-filetype-code-300',
text: 'text-filetype-code-700 dark:text-filetype-code-300',
},
'application/xhtml+xml': {
label: 'Web page',
icon: 'heroCodeBracket',
bg: 'bg-filetype-code-100 dark:bg-filetype-code-900/60',
text: 'text-filetype-code-600 dark:text-filetype-code-300',
text: 'text-filetype-code-700 dark:text-filetype-code-300',
},
'text/csv': {
label: 'CSV',
icon: 'heroTableCells',
bg: 'bg-filetype-sheet-100 dark:bg-filetype-sheet-900/60',
text: 'text-filetype-sheet-600 dark:text-filetype-sheet-300',
text: 'text-filetype-sheet-700 dark:text-filetype-sheet-300',
},
'image/svg+xml': {
label: 'SVG',
icon: 'heroPhoto',
bg: 'bg-filetype-image-100 dark:bg-filetype-image-900/60',
text: 'text-filetype-image-600 dark:text-filetype-image-300',
text: 'text-filetype-image-700 dark:text-filetype-image-300',
},
};

Expand Down
Loading