diff --git a/bricks/api_doc.js b/bricks/api_doc.js
new file mode 100644
index 0000000..88ffed6
--- /dev/null
+++ b/bricks/api_doc.js
@@ -0,0 +1,305 @@
+var bricks = window.bricks || {};
+/*
+ * ApiDoc widget - renders API documentation from markdown
+ * Depends on: marked (CDN), highlight.js (CDN, optional)
+ * Options:
+ * md_url: string - URL to fetch markdown from
+ * title: string (optional) - page title
+ * base_url: string (optional) - API base URL for display
+ */
+
+bricks.ApiDoc = class extends bricks.VBox {
+ constructor(options) {
+ if (!options) options = {};
+ super(options);
+ this.md_url = bricks.absurl(this.opts.md_url);
+ this.api_title = this.opts.title || 'API Documentation';
+ this.api_base_url = this.opts.base_url || '';
+ this.sections = [];
+ this.active_section = null;
+ this._build_layout();
+ var self = this;
+ schedule_once(function() { self._load(); }, 0.1);
+ }
+
+ _build_layout() {
+ var d = this.dom_element;
+ d.classList.add('bricks-apidoc');
+ d.innerHTML = '';
+
+ // Header
+ var header = document.createElement('div');
+ header.className = 'bricks-apidoc-header';
+ header.innerHTML = '
' +
+ this.api_title + '
' +
+ (this.api_base_url ? '' +
+ this.api_base_url + '
' : '');
+ d.appendChild(header);
+
+ // Body: sidebar + content
+ var body = document.createElement('div');
+ body.className = 'bricks-apidoc-body';
+ d.appendChild(body);
+
+ // Sidebar
+ var sidebar = document.createElement('div');
+ sidebar.className = 'bricks-apidoc-sidebar';
+ body.appendChild(sidebar);
+ this.sidebar_el = sidebar;
+
+ // Content
+ var content = document.createElement('div');
+ content.className = 'bricks-apidoc-content';
+ body.appendChild(content);
+ this.content_el = content;
+
+ // Scroll spy
+ var self = this;
+ content.addEventListener('scroll', function() {
+ self._scroll_spy();
+ });
+ }
+
+ async _load() {
+ if (!this.md_url) return;
+ try {
+ var md_text = await bricks.tget(this.md_url);
+ this._render(md_text);
+ } catch(e) {
+ this.content_el.innerHTML =
+ 'Failed to load: ' +
+ this.md_url + '
';
+ bricks.error('ApiDoc load error:', e);
+ }
+ }
+
+ _render(md_text) {
+ // Use marked to parse the full markdown
+ var html = marked.parse(md_text);
+
+ // Create a temporary container to parse the DOM
+ var tmp = document.createElement('div');
+ tmp.innerHTML = html;
+
+ // Extract sections (h2 elements = API endpoints)
+ var sections = [];
+ var current = null;
+ var children = tmp.children;
+
+ for (var i = 0; i < children.length; i++) {
+ var el = children[i];
+ if (el.tagName === 'H2') {
+ if (current) sections.push(current);
+ current = {
+ heading: el.textContent.trim(),
+ id: 'api-sec-' + sections.length,
+ elements: []
+ };
+ } else if (current) {
+ current.elements.push(el);
+ }
+ }
+ if (current) sections.push(current);
+
+ this.sections = sections;
+ this._build_sidebar();
+ this._build_content(sections);
+
+ // Highlight code blocks
+ if (typeof hljs !== 'undefined') {
+ var blocks = this.content_el.querySelectorAll('pre code');
+ blocks.forEach(function(block) {
+ hljs.highlightElement(block);
+ });
+ }
+
+ // Add copy buttons to code blocks
+ this._add_copy_buttons();
+
+ // Activate first section
+ if (sections.length > 0) {
+ this._activate_section(sections[0].id);
+ }
+ }
+
+ _build_sidebar() {
+ var sidebar = this.sidebar_el;
+ sidebar.innerHTML = '';
+
+ var self = this;
+ for (var i = 0; i < this.sections.length; i++) {
+ var sec = this.sections[i];
+ var item = document.createElement('div');
+ item.className = 'bricks-apidoc-nav-item';
+ item.setAttribute('data-section', sec.id);
+
+ // Parse method from heading (e.g. "POST /v1/chat/completions")
+ var parts = sec.heading.split(' ');
+ if (parts.length >= 2) {
+ var method = parts[0].toUpperCase();
+ var path = parts.slice(1).join(' ');
+ var badge = document.createElement('span');
+ badge.className = 'bricks-apidoc-method-badge';
+ badge.className += ' bricks-apidoc-method-' + method.toLowerCase();
+ badge.textContent = method;
+ item.appendChild(badge);
+
+ var pathEl = document.createElement('span');
+ pathEl.className = 'bricks-apidoc-nav-path';
+ pathEl.textContent = path.replace('/v1/', '/');
+ item.appendChild(pathEl);
+ } else {
+ item.textContent = sec.heading;
+ }
+
+ item.onclick = (function(sid) {
+ return function() { self._scroll_to(sid); };
+ })(sec.id);
+
+ sidebar.appendChild(item);
+ }
+ }
+
+ _build_content(sections) {
+ var content = this.content_el;
+ content.innerHTML = '';
+
+ for (var i = 0; i < sections.length; i++) {
+ var sec = sections[i];
+
+ // Section container
+ var section_el = document.createElement('div');
+ section_el.className = 'bricks-apidoc-section';
+ section_el.id = sec.id;
+ content.appendChild(section_el);
+
+ // Section heading with method badge
+ var heading = document.createElement('h2');
+ heading.className = 'bricks-apidoc-section-title';
+
+ var parts = sec.heading.split(' ');
+ if (parts.length >= 2) {
+ var method = parts[0].toUpperCase();
+ var path = parts.slice(1).join(' ');
+ heading.innerHTML = '' + method +
+ '' + path + '';
+ } else {
+ heading.textContent = sec.heading;
+ }
+ section_el.appendChild(heading);
+
+ // Section content elements
+ for (var j = 0; j < sec.elements.length; j++) {
+ var el = sec.elements[j].cloneNode(true);
+ this._enhance_element(el);
+ section_el.appendChild(el);
+ }
+ }
+ }
+
+ _enhance_element(el) {
+ // Add classes to tables
+ if (el.tagName === 'TABLE') {
+ el.className = 'bricks-apidoc-table';
+ }
+
+ // Add classes to h3 subsections
+ if (el.tagName === 'H3') {
+ el.className = 'bricks-apidoc-subsection';
+ }
+
+ // Add classes to pre/code blocks
+ if (el.tagName === 'PRE') {
+ var wrapper = document.createElement('div');
+ wrapper.className = 'bricks-apidoc-code-wrapper';
+ el.parentNode && el.parentNode.insertBefore(wrapper, el);
+ wrapper.appendChild(el);
+ }
+
+ // Add classes to inline tables in parent divs
+ if (el.tagName === 'DIV') {
+ var tables = el.querySelectorAll('table');
+ tables.forEach(function(t) { t.className = 'bricks-apidoc-table'; });
+ }
+ }
+
+ _add_copy_buttons() {
+ var wrappers = this.content_el.querySelectorAll('.bricks-apidoc-code-wrapper');
+ wrappers.forEach(function(wrapper) {
+ var pre = wrapper.querySelector('pre');
+ if (!pre) return;
+
+ var btn = document.createElement('button');
+ btn.className = 'bricks-apidoc-copy-btn';
+ btn.textContent = 'Copy';
+ btn.onclick = function() {
+ var code = pre.querySelector('code');
+ var text = code ? code.textContent : pre.textContent;
+ navigator.clipboard.writeText(text).then(function() {
+ btn.textContent = 'Copied!';
+ setTimeout(function() { btn.textContent = 'Copy'; }, 2000);
+ });
+ };
+ wrapper.insertBefore(btn, pre);
+ });
+ }
+
+ _scroll_to(section_id) {
+ var el = document.getElementById(section_id);
+ if (!el) return;
+ el.scrollIntoView({ behavior: 'smooth', block: 'start' });
+ this._activate_section(section_id);
+ }
+
+ _activate_section(section_id) {
+ if (this.active_section === section_id) return;
+ this.active_section = section_id;
+
+ // Update sidebar active state
+ var items = this.sidebar_el.querySelectorAll('.bricks-apidoc-nav-item');
+ items.forEach(function(item) {
+ item.classList.remove('active');
+ if (item.getAttribute('data-section') === section_id) {
+ item.classList.add('active');
+ }
+ });
+ }
+
+ _scroll_spy() {
+ var content = this.content_el;
+ var sections = content.querySelectorAll('.bricks-apidoc-section');
+ var scrollTop = content.scrollTop;
+ var found = null;
+
+ for (var i = 0; i < sections.length; i++) {
+ var sec = sections[i];
+ if (sec.offsetTop - content.offsetTop <= scrollTop + 60) {
+ found = sec.id;
+ }
+ }
+
+ if (found) {
+ this._activate_section(found);
+ }
+ }
+
+ setValue(v) {
+ if (typeof v === 'string') {
+ this._render(v);
+ }
+ }
+
+ getValue() {
+ return this.md_url;
+ }
+
+ render(params) {
+ if (params && params.md_url) {
+ this.md_url = bricks.absurl(params.md_url);
+ this._load();
+ }
+ }
+}
+
+bricks.Factory.register('ApiDoc', bricks.ApiDoc);
diff --git a/bricks/build.sh b/bricks/build.sh
index 43b67c0..45c8ebe 100755
--- a/bricks/build.sh
+++ b/bricks/build.sh
@@ -12,7 +12,7 @@ SOURCES=" page_data_loader.js factory.js uitypesdef.js utils.js uitype.js \
line.js pie.js bar.js gobang.js period.js iconbarpage.js \
keypress.js asr.js webspeech.js countdown.js progressbar.js \
qaframe.js svg.js videoplayer.js scatter.js radar.js kline.js \
- heatmap.js map.js qr.js textfiles.js agent.js "
+ heatmap.js map.js qr.js textfiles.js agent.js api_doc.js "
echo ${SOURCES}
cat ${SOURCES} > ../dist/bricks.js
# uglifyjs --compress --mangle -- ../dist/bricks.js > ../dist/bricks.min.js
diff --git a/bricks/css/api_doc.css b/bricks/css/api_doc.css
new file mode 100644
index 0000000..bcc62fa
--- /dev/null
+++ b/bricks/css/api_doc.css
@@ -0,0 +1,285 @@
+/* ApiDoc Widget Styles */
+
+.bricks-apidoc {
+ display: flex;
+ flex-direction: column;
+ height: 100%;
+ width: 100%;
+ overflow: hidden;
+ background: var(--bg-card, #1a1a2e);
+ color: var(--text-primary, #e0e0e0);
+ font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
+}
+
+.bricks-apidoc-header {
+ padding: 16px 20px;
+ border-bottom: 1px solid var(--border-color, #2a2a4a);
+ flex-shrink: 0;
+}
+
+.bricks-apidoc-title {
+ font-size: 1.4em;
+ font-weight: 700;
+ color: var(--text-primary, #fff);
+}
+
+.bricks-apidoc-baseurl {
+ font-size: 0.85em;
+ color: var(--text-dim, #888);
+ margin-top: 4px;
+ font-family: 'SFMono-Regular', Consolas, monospace;
+}
+
+/* Body: sidebar + content */
+.bricks-apidoc-body {
+ display: flex;
+ flex: 1;
+ overflow: hidden;
+}
+
+/* Sidebar */
+.bricks-apidoc-sidebar {
+ width: 240px;
+ min-width: 200px;
+ border-right: 1px solid var(--border-color, #2a2a4a);
+ overflow-y: auto;
+ padding: 8px 0;
+ flex-shrink: 0;
+}
+
+.bricks-apidoc-nav-item {
+ display: flex;
+ align-items: center;
+ gap: 8px;
+ padding: 8px 16px;
+ cursor: pointer;
+ transition: background 0.15s;
+ font-size: 0.88em;
+ border-left: 3px solid transparent;
+}
+
+.bricks-apidoc-nav-item:hover {
+ background: var(--bg-hover, rgba(255,255,255,0.05));
+}
+
+.bricks-apidoc-nav-item.active {
+ background: var(--bg-active, rgba(100,140,255,0.1));
+ border-left-color: var(--accent-blue, #648cff);
+}
+
+.bricks-apidoc-nav-path {
+ overflow: hidden;
+ text-overflow: ellipsis;
+ white-space: nowrap;
+ color: var(--text-secondary, #bbb);
+}
+
+.bricks-apidoc-nav-item.active .bricks-apidoc-nav-path {
+ color: var(--text-primary, #fff);
+}
+
+/* Method badges */
+.bricks-apidoc-method-badge {
+ display: inline-block;
+ padding: 2px 8px;
+ border-radius: 4px;
+ font-size: 0.75em;
+ font-weight: 700;
+ letter-spacing: 0.5px;
+ text-transform: uppercase;
+ flex-shrink: 0;
+}
+
+.bricks-apidoc-method-get {
+ background: rgba(34,197,94,0.15);
+ color: #22c55e;
+}
+
+.bricks-apidoc-method-post {
+ background: rgba(59,130,246,0.15);
+ color: #3b82f6;
+}
+
+.bricks-apidoc-method-put {
+ background: rgba(249,115,22,0.15);
+ color: #f97316;
+}
+
+.bricks-apidoc-method-delete {
+ background: rgba(239,68,68,0.15);
+ color: #ef4444;
+}
+
+.bricks-apidoc-method-patch {
+ background: rgba(168,85,247,0.15);
+ color: #a855f7;
+}
+
+/* Content area */
+.bricks-apidoc-content {
+ flex: 1;
+ overflow-y: auto;
+ padding: 20px 28px;
+ scroll-behavior: smooth;
+}
+
+/* Section */
+.bricks-apidoc-section {
+ margin-bottom: 40px;
+ padding-bottom: 32px;
+ border-bottom: 1px solid var(--border-color, #2a2a4a);
+}
+
+.bricks-apidoc-section:last-child {
+ border-bottom: none;
+}
+
+.bricks-apidoc-section-title {
+ display: flex;
+ align-items: center;
+ gap: 12px;
+ margin-bottom: 16px;
+ padding-bottom: 12px;
+ border-bottom: 1px solid var(--border-color, rgba(255,255,255,0.08));
+}
+
+.bricks-apidoc-section-title .bricks-apidoc-method-badge {
+ font-size: 0.8em;
+ padding: 4px 10px;
+}
+
+.bricks-apidoc-path {
+ font-family: 'SFMono-Regular', Consolas, monospace;
+ font-size: 0.95em;
+ color: var(--text-primary, #fff);
+ word-break: break-all;
+}
+
+/* Subsection headings (h3) */
+.bricks-apidoc-subsection,
+.bricks-apidoc-section h3 {
+ font-size: 1em;
+ font-weight: 600;
+ color: var(--text-secondary, #ccc);
+ margin: 20px 0 10px 0;
+ padding-bottom: 6px;
+ border-bottom: 1px solid var(--border-color, rgba(255,255,255,0.06));
+}
+
+/* Tables */
+.bricks-apidoc-table,
+.bricks-apidoc-section table {
+ width: 100%;
+ border-collapse: collapse;
+ margin: 10px 0;
+ font-size: 0.88em;
+}
+
+.bricks-apidoc-table th,
+.bricks-apidoc-section table th {
+ background: var(--bg-elevated, rgba(255,255,255,0.04));
+ text-align: left;
+ padding: 8px 12px;
+ border: 1px solid var(--border-color, #2a2a4a);
+ color: var(--text-secondary, #aaa);
+ font-weight: 600;
+}
+
+.bricks-apidoc-table td,
+.bricks-apidoc-section table td {
+ padding: 8px 12px;
+ border: 1px solid var(--border-color, #2a2a4a);
+ color: var(--text-primary, #ddd);
+}
+
+.bricks-apidoc-table tr:hover td,
+.bricks-apidoc-section table tr:hover td {
+ background: var(--bg-hover, rgba(255,255,255,0.03));
+}
+
+/* Inline code */
+.bricks-apidoc-section code {
+ background: var(--bg-elevated, rgba(255,255,255,0.08));
+ padding: 1px 6px;
+ border-radius: 3px;
+ font-size: 0.9em;
+ font-family: 'SFMono-Regular', Consolas, monospace;
+ color: #e0a8ff;
+}
+
+/* Code blocks */
+.bricks-apidoc-code-wrapper {
+ position: relative;
+ margin: 12px 0;
+}
+
+.bricks-apidoc-code-wrapper pre {
+ background: var(--bg-code, #0d1117) !important;
+ border: 1px solid var(--border-color, #2a2a4a);
+ border-radius: 6px;
+ padding: 14px 16px;
+ overflow-x: auto;
+ font-size: 0.85em;
+ line-height: 1.5;
+ margin: 0;
+}
+
+.bricks-apidoc-code-wrapper pre code {
+ background: none;
+ padding: 0;
+ color: #c9d1d9;
+ font-family: 'SFMono-Regular', Consolas, monospace;
+}
+
+/* Copy button */
+.bricks-apidoc-copy-btn {
+ position: absolute;
+ top: 8px;
+ right: 8px;
+ background: var(--bg-elevated, rgba(255,255,255,0.1));
+ border: 1px solid var(--border-color, #2a2a4a);
+ color: var(--text-secondary, #aaa);
+ padding: 3px 10px;
+ border-radius: 4px;
+ font-size: 0.75em;
+ cursor: pointer;
+ transition: all 0.15s;
+ opacity: 0;
+}
+
+.bricks-apidoc-code-wrapper:hover .bricks-apidoc-copy-btn {
+ opacity: 1;
+}
+
+.bricks-apidoc-copy-btn:hover {
+ background: var(--bg-hover, rgba(255,255,255,0.15));
+ color: var(--text-primary, #fff);
+}
+
+/* Description paragraphs */
+.bricks-apidoc-section p {
+ margin: 8px 0;
+ line-height: 1.6;
+ color: var(--text-secondary, #ccc);
+}
+
+/* Horizontal rules */
+.bricks-apidoc-section hr {
+ border: none;
+ border-top: 1px solid var(--border-color, #2a2a4a);
+ margin: 16px 0;
+}
+
+/* Error state */
+.bricks-apidoc-error {
+ padding: 20px;
+ color: #ef4444;
+ text-align: center;
+}
+
+/* Responsive: hide sidebar on narrow widths */
+@media (max-width: 700px) {
+ .bricks-apidoc-sidebar {
+ display: none;
+ }
+}