Skip to content

Commit 5a48fbf

Browse files
authored
Merge pull request #117 from OpenAF/codex/add-browser-context-for-svg-generation
Add browserContext hints for SVG/vector rendering
2 parents 87e2ea5 + 5a564aa commit 5a48fbf

8 files changed

Lines changed: 137 additions & 11 deletions

File tree

CHEATSHEET.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -414,7 +414,7 @@ mini-a goal="Comprehensive analysis of renewable energy trends 2024" \
414414
| `useascii` | boolean | `false` | Encourage enhanced UTF-8/ANSI visual output with colors and emojis |
415415
| `usemaps` | boolean | `false` | Encourage Leaflet JSON blocks for interactive maps (renders in console transcript and web UI) |
416416
| `usesvg` | boolean | `false` | Encourage raw SVG blocks rendered securely as image data URIs in the web UI |
417-
| `usevectors` | boolean | `false` | Enable infographic-focused vector guidance bundle (`usesvg=true` + `usediagrams=true`) |
417+
| `usevectors` | boolean | `false` | Enable vector guidance bundle (`usesvg=true` + `usediagrams=true`), preferring Mermaid for structural diagrams and SVG for infographics/custom visuals |
418418
| `format` | string | `md` | Output format (`md` or `json`) |
419419
| `outputfile` | string | - | Alternative key for `outfile`, used mainly during plan conversions |
420420

USAGE.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -896,6 +896,8 @@ Only when every stage returns an empty list (or errors) does Mini-A log the issu
896896
#### Visual Guidance
897897
- **`usediagrams`** (boolean, default: false): Ask the model to produce Mermaid diagrams when sketching workflows or structures
898898
- **`usecharts`** (boolean, default: false): Hint the model to provide Chart.js snippets for data visualization tasks. When combined with `usesvg=true` or `usevectors=true`, supported charts should still be emitted as chart configs instead of being drawn manually as SVG/vector art; SVG remains the fallback for unsupported chart forms or custom illustrations.
899+
- **`usesvg`** (boolean, default: false): Prime the model to emit raw `svg` fenced blocks for infographics, annotated summaries, custom artwork, and other self-contained illustrations. Standard structural diagrams should still prefer Mermaid when supported.
900+
- **`usevectors`** (boolean, default: false): Enable the combined vector bundle (`usesvg=true` + `usediagrams=true`). In practice this should prefer Mermaid for structural diagrams and SVG for infographics or custom visuals.
899901
- **`useascii`** (boolean, default: false): Encourage enhanced UTF-8/ANSI visual output for rich terminal displays. When enabled, Mini-A guides the model to use:
900902
- **Full UTF-8 characters**: Box-drawing (┌─┐│└┘├┤┬┴┼╔═╗║╚╝╠╣╦╩╬), arrows (→←↑↓⇒⇐⇑⇓➔➜➡), bullets (•●○◦◉◎◘◙), shapes (▪▫▬▭▮▯■□▲△▼▽◆◇), and mathematical symbols (∞≈≠≤≥±×÷√∑∏∫∂∇)
901903
- **Strategic emoji**: Status indicators (✅❌⚠️🔴🟢🟡), workflow symbols (🔄🔁⏸️▶️⏹️), category icons (📁📂📄🔧⚙️🔑🔒), and semantic markers (💡🎯🚀⭐🏆)

mini-a-con.js

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2419,6 +2419,9 @@ try {
24192419
if (isUnDef(args.usediagrams) || !args.usediagrams) args.usediagrams = args.usevectors
24202420
delete args.usevectors
24212421
}
2422+
if (args.usesvg === true && isUnDef(args.browsercontext)) {
2423+
args.browsercontext = true
2424+
}
24222425
if (isDef(args.valgoal)) {
24232426
if (isUnDef(args.validationgoal)) args.validationgoal = args.valgoal
24242427
delete args.valgoal

mini-a-modes.yaml

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -100,14 +100,17 @@ modes:
100100
usemaps : true
101101
useascii : false
102102
usemath : true
103+
usevectors : true
103104
usetools : true
104105
usehistory : true
105106
useattach : true
106107
usestream : true
107108
historykeep: true
108109
useplanning: false
109110
mcpproxy : true
110-
mcp :
111+
mcpproxythreshold: 51200
112+
mcpproxytoon : true
113+
mcp :
111114
- type : ojob
112115
options:
113116
job: mcps/mcp-web.yaml

mini-a-web.yaml

Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -466,6 +466,12 @@ todo:
466466
})
467467
// Only pass conversation parameter for new conversations to avoid overwriting in-memory state
468468
if (isNewConversation) {
469+
if (isMap(postData.browserContext)) {
470+
startArgs.browsercontext = jsonParse(stringify(postData.browserContext, __, ""), __, __, true)
471+
} else if (isString(postData.browserContext) && postData.browserContext.trim().length > 0) {
472+
var parsedPostBrowserContext = af.fromJSSLON(postData.browserContext)
473+
if (isMap(parsedPostBrowserContext)) startArgs.browsercontext = parsedPostBrowserContext
474+
}
469475
startArgs.conversation = global._mini_a_web_loadFile(_hfile)
470476
}
471477
var _rma = lma.start(startArgs)
@@ -835,6 +841,7 @@ jobs:
835841
usediagrams : toBoolean().isBoolean().default(false)
836842
usecharts : toBoolean().isBoolean().default(false)
837843
usevectors : toBoolean().isBoolean().default(false)
844+
browsercontext : isString().default(__)
838845
useascii : toBoolean().isBoolean().default(false)
839846
usemath : toBoolean().isBoolean().default(false)
840847
historys3bucket : isString().default(__)
@@ -918,6 +925,36 @@ jobs:
918925
}
919926
args.__interaction_source = "mini-a-web"
920927
928+
var browserContext = __
929+
if (isMap(args.browsercontext)) {
930+
browserContext = jsonParse(stringify(args.browsercontext, __, ""), __, __, true)
931+
} else if (isString(args.browsercontext) && args.browsercontext.trim().length > 0) {
932+
var parsedBrowserContext = af.fromJSSLON(args.browsercontext)
933+
if (isMap(parsedBrowserContext)) {
934+
browserContext = parsedBrowserContext
935+
} else if (toBoolean(args.browsercontext)) {
936+
browserContext = true
937+
}
938+
} else if (toBoolean(args.browsercontext)) {
939+
browserContext = true
940+
}
941+
942+
if ((args.usevectors || args.usesvg) && isUnDef(browserContext)) {
943+
browserContext = true
944+
}
945+
946+
if ((args.usevectors || args.usesvg) && isNumber(args.onport) && browserContext === true) {
947+
browserContext = {
948+
viewportWidth: 1280,
949+
panelWidth: 720,
950+
fontSize: "16px",
951+
colorScheme: "dark",
952+
devicePixelRatio: 2,
953+
serverPort: args.onport
954+
}
955+
}
956+
957+
args.browsercontext = browserContext
921958
global.maArgs = merge(args, {})
922959
var needsWorkerRegBootstrap = isNumber(args.workerreg)
923960
if (!toBoolean(args.mcplazy) || needsWorkerRegBootstrap) {

mini-a.js

Lines changed: 50 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -703,6 +703,7 @@ MiniA.buildVisualKnowledge = function(options) {
703703
var useMaps = _$(toBoolean(options.useMaps), "options.useMaps").isBoolean().default(false)
704704
var useMath = _$(toBoolean(options.useMath), "options.useMath").isBoolean().default(false)
705705
var useSvg = _$(toBoolean(options.useSvg), "options.useSvg").isBoolean().default(false)
706+
var browserContext = isMap(options.browserContext) ? options.browserContext : __
706707

707708
if (!useDiagrams && !useCharts && !useAscii && !useMaps && !useMath && !useSvg) return ""
708709

@@ -723,12 +724,18 @@ MiniA.buildVisualKnowledge = function(options) {
723724

724725
var visualParts = []
725726

726-
visualParts.push(
727-
"Visual output guidance (concise):\n\n" +
728-
"- Default to including a diagram, chart, or UTF-8/ANSI visual whenever structure, flow, hierarchy, metrics, or comparisons are involved.\n" +
729-
"- Always pair the visual with a short caption (1-2 sentences) summarizing the insight.\n" +
727+
var introLines = [
728+
"Visual output guidance (concise):\n",
729+
"- Default to including a diagram, chart, or UTF-8/ANSI visual whenever structure, flow, hierarchy, metrics, or comparisons are involved.",
730+
"- Always pair the visual with a short caption (1-2 sentences) summarizing the insight.",
730731
"- In your explanatory text and captions, refer only to the visual type (e.g., 'diagram', 'chart', 'table', 'map') without mentioning the technical implementation (Mermaid, Chart.js, Leaflet, ANSI codes, etc.)."
731-
)
732+
]
733+
if (useSvg && (useCharts || useDiagrams)) {
734+
introLines.push(
735+
"- INTENT OVERRIDE — When the user's request contains words like 'infographic', 'poster', 'banner', 'flyer', 'draw', 'design', 'illustrate', 'layout', 'mockup', or 'wireframe', treat SVG as the primary output format immediately, regardless of other enabled modes. Do not default to a chart or Mermaid diagram for these requests."
736+
)
737+
}
738+
visualParts.push(introLines.join("\n"))
732739

733740
if (useDiagrams) {
734741
visualParts.push(
@@ -871,7 +878,7 @@ MiniA.buildVisualKnowledge = function(options) {
871878
visualParts.push(
872879
"Illustrations and custom visuals:\n" +
873880
" - For custom illustrations, output a ```svg``` fenced block with complete `<svg>...</svg>` markup.\n" +
874-
" - In vector/infographic mode, default to a polished SVG infographic whenever visuals improve understanding.\n" +
881+
" - In vector/infographic mode, use SVG primarily for infographics, annotated summaries, custom artwork, technical drawings, and UI mockups rather than standard structural diagrams.\n" +
875882
" - If chart rendering guidance is also enabled, do NOT draw ordinary charts in SVG/vector form when a supported chart type can be expressed with the chart fence; reserve SVG for unsupported chart designs or non-chart custom visuals.\n" +
876883
" - Build the infographic for fast scanning: headline, clear sections, visual hierarchy, concise labels, and callouts.\n" +
877884
" - Prefer infographic structures (panels, KPI cards, legends, timelines, comparisons, process steps, annotated layouts, icon-supported summaries) over standalone art.\n" +
@@ -881,12 +888,28 @@ MiniA.buildVisualKnowledge = function(options) {
881888
" - Never include `<script>`, event handler attributes (`on*`), `<foreignObject>`, `javascript:` URIs, or external resource references.\n" +
882889
" - Allowed tags: svg, g, path, rect, circle, ellipse, line, polyline, polygon, text, tspan, defs, linearGradient, radialGradient, clipPath, mask, pattern, use (internal `#id` only), marker, symbol, title, desc.\n" +
883890
" - Use this format for custom illustrations, icons, technical drawings, annotated diagrams, infographics, geometric patterns, and UI mockups.\n" +
884-
" - Prefer Mermaid for standard flow/sequence/entity/timeline-style diagrams when Mermaid types apply."
891+
" - Prefer Mermaid for standard flow, sequence, entity, architecture, dependency, and timeline-style structural diagrams when Mermaid types apply."
885892
)
893+
894+
if (isMap(browserContext) && Object.keys(browserContext).length > 0) {
895+
visualParts.push(
896+
"Browser context hints for SVG/vector rendering:\n" +
897+
" - Use this browser context to tune layout density, typography scale, and contrast for the expected viewport.\n" +
898+
" - Keep SVG dimensions and composition aligned with the available panel width to avoid clipping.\n" +
899+
" - browserContext:\n" +
900+
" ```json\n" +
901+
stringify(browserContext, __, " ") + "\n" +
902+
" ```"
903+
)
904+
}
886905
}
887906

888907
var checklist = "\n\nVisual selection checklist:"
889908
var nextIndex = 1
909+
if (useSvg && (useCharts || useDiagrams)) {
910+
checklist += "\n" + nextIndex + ". User says 'infographic', 'poster', 'banner', 'flyer', 'draw', 'design', 'illustrate', 'layout', 'mockup', or 'wireframe' -> SVG is the primary output; charts or Mermaid diagrams may appear as embedded sub-elements only if they genuinely help."
911+
nextIndex++
912+
}
890913
if (useDiagrams) {
891914
checklist += "\n" + nextIndex + ". Relationships or flows -> diagram with graph or sequence."
892915
nextIndex++
@@ -926,7 +949,7 @@ MiniA.buildVisualKnowledge = function(options) {
926949
if (useSvg) {
927950
checklist += "\n" + nextIndex + ". Rich infographic, annotated summary, or custom illustration -> use a self-contained SVG block with safe static elements only."
928951
nextIndex++
929-
checklist += "\n" + nextIndex + ". Standard process/flow/timeline diagrams -> prefer Mermaid when a supported type exists; otherwise use a custom illustration."
952+
checklist += "\n" + nextIndex + ". Standard process/flow/timeline/architecture diagrams -> prefer Mermaid when a supported type exists; otherwise use a custom illustration."
930953
nextIndex++
931954
}
932955
checklist += "\n\nIf no visual type above applies to the user's request (e.g., purely narrative or conversational queries), you may provide text-only output without explanation."
@@ -10120,6 +10143,23 @@ MiniA.prototype.init = function(args) {
1012010143
args.usesvg = true
1012110144
args.usediagrams = true
1012210145
}
10146+
if (isMap(args.browsercontext)) {
10147+
args.browsercontext = jsonParse(stringify(args.browsercontext, __, ""), __, __, true)
10148+
} else if (isString(args.browsercontext) && args.browsercontext.trim().length > 0) {
10149+
var parsedBrowserContext = af.fromJSSLON(args.browsercontext)
10150+
if (isMap(parsedBrowserContext)) {
10151+
args.browsercontext = parsedBrowserContext
10152+
} else if (toBoolean(args.browsercontext) === true) {
10153+
args.browsercontext = true
10154+
} else {
10155+
args.browsercontext = __
10156+
}
10157+
} else if (toBoolean(args.browsercontext) === true) {
10158+
args.browsercontext = true
10159+
} else {
10160+
args.browsercontext = __
10161+
}
10162+
if ((args.usesvg === true || args.usevectors === true) && isUnDef(args.browsercontext)) args.browsercontext = true
1012310163
args.usejsontool = _$(toBoolean(args.usejsontool), "args.usejsontool").isBoolean().default(false)
1012410164
args.chatbotmode = _$(toBoolean(args.chatbotmode), "args.chatbotmode").isBoolean().default(args.chatbotmode)
1012510165
args.useplanning = _$(toBoolean(args.useplanning), "args.useplanning").isBoolean().default(args.useplanning)
@@ -10250,6 +10290,7 @@ MiniA.prototype.init = function(args) {
1025010290
useMaps: args.usemaps,
1025110291
useMath: args.usemath,
1025210292
useSvg: args.usesvg,
10293+
browserContext: args.browsercontext,
1025310294
existingKnowledge: baseKnowledge
1025410295
})
1025510296
if (visualKnowledge.length > 0) {
@@ -11174,6 +11215,7 @@ MiniA.prototype._startInternal = function(args, sessionStartTime) {
1117411215
args.usesvg = true
1117511216
args.usediagrams = true
1117611217
}
11218+
if ((args.usesvg === true || args.usevectors === true) && isUnDef(args.browsercontext)) args.browsercontext = true
1117711219
args.usejsontool = _$(toBoolean(args.usejsontool), "args.usejsontool").isBoolean().default(false)
1117811220
this._autoEnableJsonToolForOssModels(args, useJsonToolWasDefined)
1117911221
args.usestream = _$(toBoolean(args.usestream), "args.usestream").isBoolean().default(false)

mini-a.yaml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -795,6 +795,7 @@ jobs:
795795
if (isUnDef(args.usesvg) || !args.usesvg) args.usesvg = true
796796
if (isUnDef(args.usediagrams) || !args.usediagrams) args.usediagrams = true
797797
}
798+
if (args.usesvg === true && isUnDef(args.browsercontext)) args.browsercontext = true
798799
if (isDef(args.valgoal) && isUnDef(args.validationgoal)) args.validationgoal = args.valgoal
799800
if (isDef(args.rtm) && isUnDef(args.rpm)) args.rpm = args.rtm
800801
if (isDef(args.maxcontent) && isUnDef(args.maxcontext)) args.maxcontext = args.maxcontent

public/index.md

Lines changed: 39 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4983,6 +4983,42 @@
49834983
return html;
49844984
}
49854985

4986+
function collectBrowserContext() {
4987+
const colorScheme = (typeof window !== 'undefined' && window.matchMedia && window.matchMedia('(prefers-color-scheme: dark)').matches)
4988+
? 'dark'
4989+
: 'light';
4990+
const promptStyles = (typeof window !== 'undefined' && promptInput) ? window.getComputedStyle(promptInput) : null;
4991+
const resultsRect = resultsDiv ? resultsDiv.getBoundingClientRect() : null;
4992+
const rawViewportWidth = (typeof window !== 'undefined' && window.innerWidth) ? window.innerWidth : 0;
4993+
const rawViewportHeight = (typeof window !== 'undefined' && window.innerHeight) ? window.innerHeight : 0;
4994+
const locationPort = (typeof window !== 'undefined' && window.location && window.location.port)
4995+
? parseInt(window.location.port, 10)
4996+
: NaN;
4997+
4998+
const conversationViewportWidth = resultsRect
4999+
? Math.max(0, Math.min(resultsRect.right, rawViewportWidth) - Math.max(resultsRect.left, 0))
5000+
: 0;
5001+
const conversationViewportHeight = resultsRect
5002+
? Math.max(0, Math.min(resultsRect.bottom, rawViewportHeight) - Math.max(resultsRect.top, 0))
5003+
: 0;
5004+
5005+
return {
5006+
viewportWidth: rawViewportWidth,
5007+
viewportHeight: rawViewportHeight,
5008+
panelWidth: resultsRect ? Math.round(resultsRect.width) : 0,
5009+
panelHeight: resultsRect ? Math.round(resultsRect.height) : 0,
5010+
conversationViewportWidth: Math.round(conversationViewportWidth),
5011+
conversationViewportHeight: Math.round(conversationViewportHeight),
5012+
conversationScrollTop: resultsDiv ? Math.round(resultsDiv.scrollTop) : 0,
5013+
conversationScrollHeight: resultsDiv ? Math.round(resultsDiv.scrollHeight) : 0,
5014+
conversationClientHeight: resultsDiv ? Math.round(resultsDiv.clientHeight) : 0,
5015+
fontSize: promptStyles ? promptStyles.fontSize : '',
5016+
colorScheme,
5017+
devicePixelRatio: (typeof window !== 'undefined' && window.devicePixelRatio) ? window.devicePixelRatio : 1,
5018+
serverPort: Number.isFinite(locationPort) ? locationPort : 0
5019+
};
5020+
}
5021+
49865022
async function handleSubmit() {
49875023
if (isProcessing) {
49885024
await stopProcessing();
@@ -4999,6 +5035,8 @@
49995035

50005036
try {
50015037
if (!currentSessionUuid) currentSessionUuid = getOrCreateSessionUuid();
5038+
const shouldSendBrowserContext = !lastRawContent && (!Array.isArray(lastKnownHistory) || lastKnownHistory.length === 0);
5039+
const browserContext = shouldSendBrowserContext ? collectBrowserContext() : null;
50025040

50035041
// Add user prompt to display immediately
50045042
const userPromptHtml = formatUserPromptHtml(finalPrompt);
@@ -5009,7 +5047,7 @@
50095047
const response = await fetch('/prompt', {
50105048
method: 'POST',
50115049
headers: { 'Content-Type': 'application/json; charset=utf-8' },
5012-
body: JSON.stringify({ prompt: finalPrompt, uuid: currentSessionUuid })
5050+
body: JSON.stringify({ prompt: finalPrompt, uuid: currentSessionUuid, browserContext })
50135051
});
50145052

50155053
if (!response.ok) throw new Error('Failed to submit prompt');

0 commit comments

Comments
 (0)