Skip to content
Open
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
83 changes: 64 additions & 19 deletions Cargo.lock

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

14 changes: 11 additions & 3 deletions open-pdf-studio/js/assistant-mcp-relay.js
Original file line number Diff line number Diff line change
Expand Up @@ -22,10 +22,18 @@ export function registerAssistantSubmit(fn) { _submit = fn; }
/** AssistantPanel registers how to read the current conversation. */
export function registerAssistantMessages(fn) { _getMessages = fn; }

/** app_assistant_ask — submit a message as if the user typed it in the window. */
export function submitAssistantMessage(text) {
/**
* app_assistant_ask — submit a message as if the user typed it in the window.
*
* `opts` gaat ongewijzigd door naar de send() van het paneel, zodat een
* aanroeper (AI-lint, MCP-client) ook een serveractie kan kiezen in plaats van
* een vrij chatbericht: `{ action: 'summarize' }`, `{ action: 'translate',
* language: 'English' }`, `{ useServer: false }`. Zonder opts is het gedrag
* exact als voorheen.
*/
export function submitAssistantMessage(text, opts) {
if (typeof _submit !== 'function') return { ok: false, error: 'assistent nog niet gereed' };
_submit(String(text ?? ''));
_submit(String(text ?? ''), opts || {});
return { ok: true };
}

Expand Down
75 changes: 37 additions & 38 deletions open-pdf-studio/js/assistant-skills.js
Original file line number Diff line number Diff line change
@@ -1,45 +1,44 @@
// OpenAEC-assistent skill set.
//
// Each skill is a capability the assistant can perform on the open PDF. Clicking
// a skill chip sends `invoke` as a user message; via the provider chain it reaches
// the brain (Claude Code over the MCP relay, or any AI provider) which executes
// it using the app's MCP tools. SKILLS_SYSTEM_PROMPT teaches the brain how.
// a skill chip sends its prompt as a user message; via the provider chain it
// reaches the brain (the OpenAEC AI server, Claude Code over the MCP relay, or
// any AI provider) which executes it.
//
// This module holds only what is language-INDEPENDENT: the id, the icon, and
// how the skill is routed. Every user-visible string (label, hint) and the
// prompt itself live in i18n under `common:assistant.skills.<id>` and
// `common:assistant.prompts.<id>`, so the panel follows the app's language.
// Hardcoding them here is what made the chips Dutch in every locale.
//
// `serverAction` maps a skill onto an action of the OpenAEC AI-server
// (POST /v1/chat: summarize | qa | translate | rewrite | explain | extract |
// chat). Skills WITH a serverAction get the extracted document text attached
// and can be answered by the server. Skills WITHOUT one (draw, detect doors)
// need the app's MCP tools to act on the drawing — the server cannot call
// those, so they always go to the relay/Claude.

export const ASSISTANT_SKILLS = [
{
id: 'translate',
icon: '🌐',
label: 'Vertaal',
hint: 'Vertaal de tekst van het document',
invoke: 'Vertaal de tekst van het geopende document. Is het Nederlands, vertaal dan naar het Engels; anders naar het Nederlands. Geef de vertaling overzichtelijk terug.',
},
{
id: 'summarize',
icon: '📝',
label: 'Vat samen',
hint: 'Vat het document of de tekening samen',
invoke: 'Vat het geopende document of de tekening bondig samen: waar gaat het over, de belangrijkste onderdelen en eventuele aandachtspunten.',
},
{
id: 'draw',
icon: '✏️',
label: 'Teken',
hint: 'Teken een element of annotatie op de tekening',
invoke: 'Teken op de tekening: ',
needsInput: true,
},
{
id: 'detect-doors',
icon: '🚪',
label: 'Herken deuren',
hint: 'Detecteer de deuren in de plattegrond en markeer ze',
invoke: 'Bekijk de plattegrond, herken de deuren en markeer elke deur op de tekening met een markering en een korte label.',
},
{ id: 'translate', icon: '🌐', serverAction: 'translate' },
{ id: 'summarize', icon: '📝', serverAction: 'summarize' },
{ id: 'draw', icon: '✏️', needsInput: true },
{ id: 'detect-doors', icon: '🚪' },
];

export const SKILLS_SYSTEM_PROMPT =
'Je beschikt over een vaardigheden-set en kunt ACTIES uitvoeren op het geopende PDF-document via de MCP-tools van de app:\n' +
'- Vertalen / samenvatten: gebruik app_screenshot_view (width 2000) om de pagina te bekijken en te lezen; geef het resultaat als tekst terug.\n' +
'- Tekenen: gebruik app_create_annotation. Coordinaten zijn paginapunten op 100% zoom; haal de paginamaat op met app_get_viewport_state (pageW/pageH).\n' +
'- Deuren herkennen: doe eerst app_fit_page, maak dan app_screenshot_view (width 2000), herken de deuren visueel en markeer elke deur met app_create_annotation (bijvoorbeeld een box of cloud rond de deur + een textbox-label). Reken screenshot-pixels om naar paginapunten via pageW/pageH.\n' +
'Antwoord in het Nederlands, bondig en praktisch. Voer gevraagde acties direct uit en meld kort wat je gedaan hebt.';
/**
* System prompt for the MCP-relay/Claude path, which drives the app's own
* tools. English because the instructions are for the model, not the user;
* the final line pins the ANSWER to the app's language.
*
* @param {string} [responseLanguage] English name of the UI language, e.g. 'Dutch'.
*/
export function skillsSystemPrompt(responseLanguage) {
return (
'You have a skill set and can perform ACTIONS on the open PDF document via the app\'s MCP tools:\n' +
'- Translate / summarize: use app_screenshot_view (width 2000) to look at and read the page; return the result as text.\n' +
'- Draw: use app_create_annotation. Coordinates are page points at 100% zoom; get the page size with app_get_viewport_state (pageW/pageH).\n' +
'- Detect doors: first app_fit_page, then app_screenshot_view (width 2000), recognise the doors visually and mark each one with app_create_annotation (for example a box or cloud around the door plus a textbox label). Convert screenshot pixels to page points via pageW/pageH.\n' +
'Be concise and practical. Carry out requested actions directly and briefly report what you did.' +
(responseLanguage ? `\nAlways write your answer in ${responseLanguage}.` : '')
);
}
68 changes: 67 additions & 1 deletion open-pdf-studio/js/i18n/locales/en/common.json
Original file line number Diff line number Diff line change
Expand Up @@ -91,6 +91,10 @@
"minimize": "Minimize",
"maximize": "Maximize",
"sendFeedback": "Send Feedback",
"openaecSignIn": "Sign in with OpenAEC",
"openaecSigningIn": "Signing in via the browser…",
"openaecPortal": "Open Portal",
"openaecSignOut": "Sign out",
"loading": "Loading...",
"leftPanel": {
"noDocumentOpen": "No document open",
Expand Down Expand Up @@ -137,5 +141,67 @@
"bcfExportFailed": "BCF export failed.",
"bcfImportFailed": "Could not read BCF file.",
"bcfInvalidFile": "Invalid or unsupported BCF file.",
"bcfNoTopics": "No topics found in BCF file."
"bcfNoTopics": "No topics found in BCF file.",
"assistant": {
"title": "OpenAEC Assistant",
"greeting": "Hello! I am the **OpenAEC Assistant**. I can: 🌐 translate, 📝 summarize, ✏️ draw on the drawing, and 🚪 detect doors. Pick a skill below or ask your question.",
"workingIn": "working in",
"viaServer": "via OpenAEC AI server",
"notConnected": "not connected",
"thinking": "Thinking…",
"inputPlaceholder": "Ask something about this PDF…",
"send": "Send",
"close": "Close",
"noAnswer": "No answer received.",
"skills": {
"translate": {
"label": "Translate",
"hint": "Translate the document text"
},
"summarize": {
"label": "Summarize",
"hint": "Summarize the document or drawing"
},
"draw": {
"label": "Draw",
"hint": "Draw an element or annotation on the drawing"
},
"detect-doors": {
"label": "Detect doors",
"hint": "Detect the doors in the floor plan and mark them"
}
},
"prompts": {
"translate": "Translate the text of the open document and present the translation clearly.",
"summarize": "Summarize the open document or drawing concisely: what it is about, the main parts and any points of attention.",
"draw": "Draw on the drawing: ",
"detect-doors": "Look at the floor plan, identify the doors and mark each one on the drawing with a marker and a short label.",
"explain": "Explain this document in plain language and unpack the jargon.",
"extract": "Extract the key data from this document: names, dates, amounts, addresses and tables.",
"rewrite": "Rewrite the text of this document for better readability."
},
"errors": {
"insufficientCredits": "⚠️ Out of credits. Top up your balance in the OpenAEC portal.",
"notSignedIn": "⚠️ Not signed in. Sign in with your OpenAEC account, top right.",
"rateLimited": "⚠️ Too many requests in a short time. Try again in a minute.",
"noDocumentText": "⚠️ This document has no text layer, so there is nothing to send.",
"offline": "⚠️ No connection to the AI service.",
"failed": "⚠️ AI call failed.",
"detail": "Detail",
"unknown": "unknown error",
"needsRelay": "⚠️ This skill acts on the drawing itself, which needs a connected MCP client (for example Claude Code). None is connected right now.",
"timeout": "⚠️ The AI service did not respond in time. It may be busy — try again in a moment.",
"serverDown": "⚠️ The AI service is temporarily unavailable. Try again shortly; your credits were not charged."
},
"signInTitle": "Sign in to use the assistant",
"signInSub": "Summarizing, translating and Q&A run on your OpenAEC AI server and are billed to your credits. PDF editing keeps working offline.",
"signIn": "Sign in with OpenAEC",
"signingIn": "Signing in…"
},
"account": {
"callbackOkTitle": "Signed in to OpenAEC",
"callbackOkBody": "You can close this window and return to Open PDF Studio.",
"callbackCancelledTitle": "Sign-in cancelled",
"callbackCancelledBody": "You can close this window."
}
}
Loading
Loading