update the test_gitea
@@ -37,6 +37,8 @@ env:
|
||||
jobs:
|
||||
build-and-push:
|
||||
runs-on: ubuntu-latest
|
||||
container:
|
||||
image: gitea-modal-runner:latest
|
||||
timeout-minutes: 30
|
||||
steps:
|
||||
- name: Checkout repository
|
||||
|
||||
@@ -6,9 +6,9 @@
|
||||
#
|
||||
# Required Gitea Repository Secrets:
|
||||
# MODAL_TOKEN - Modal API token (from modal.com/settings)
|
||||
# AWS_ACCESS_KEY_ID - For ECR push
|
||||
# AWS_SECRET_ACCESS_KEY
|
||||
# AWS_REGION - e.g., us-east-1
|
||||
#
|
||||
# Required Modal Secrets (configured in Modal dashboard, NOT Gitea):
|
||||
# aws-secrets - AWS_ACCESS_KEY_ID, AWS_SECRET_ACCESS_KEY, AWS_REGION
|
||||
#
|
||||
# Required Gitea Repository Variables:
|
||||
# ECR_PUBLIC_REGISTRY_ALIAS - Your public.ecr.aws alias (e.g., vkist-project)
|
||||
@@ -61,35 +61,15 @@ env:
|
||||
jobs:
|
||||
build-via-modal:
|
||||
name: Build & Push via Modal
|
||||
runs-on: ubuntu-latest
|
||||
runs-on: [self-hosted, linux, amd64]
|
||||
container:
|
||||
image: gitea-modal-runner:latest
|
||||
timeout-minutes: 20
|
||||
|
||||
steps:
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- name: Set up Python
|
||||
uses: actions/setup-python@v5
|
||||
with:
|
||||
python-version: '3.12'
|
||||
|
||||
- name: Install Modal CLI
|
||||
run: pip install modal
|
||||
|
||||
- name: Configure AWS credentials
|
||||
uses: aws-actions/configure-aws-credentials@v4
|
||||
with:
|
||||
aws-access-key-id: ${{ secrets.AWS_ACCESS_KEY_ID }}
|
||||
aws-secret-access-key: ${{ secrets.AWS_SECRET_ACCESS_KEY }}
|
||||
aws-region: ${{ env.AWS_REGION || 'us-east-1' }}
|
||||
|
||||
- name: Login to Amazon ECR Public
|
||||
id: login-ecr
|
||||
uses: aws-actions/amazon-ecr-login@v2
|
||||
with:
|
||||
registry-type: public
|
||||
mask-password: 'true'
|
||||
|
||||
- name: Determine image tag
|
||||
id: tag
|
||||
run: |
|
||||
@@ -114,11 +94,6 @@ jobs:
|
||||
--platform linux/amd64 \
|
||||
--push
|
||||
|
||||
- name: Print image URI
|
||||
run: |
|
||||
echo "Successfully pushed: ${{ env.ECR_REGISTRY }}/${{ env.ECR_REPOSITORY }}:${{ steps.tag.outputs.tag }}"
|
||||
echo "image=${{ env.ECR_REGISTRY }}/${{ env.ECR_REPOSITORY }}:${{ steps.tag.outputs.tag }}" >> $GITHUB_OUTPUT
|
||||
|
||||
# Optional: Trigger dependent deployments
|
||||
# trigger-triton:
|
||||
# needs: build-via-modal
|
||||
|
||||
@@ -17,7 +17,7 @@ permissions:
|
||||
env:
|
||||
FRONTEND_DIR: workspace/sprint_1_2/CODEBASE/frontend/implementation
|
||||
ECR_REGISTRY: public.ecr.aws
|
||||
ECR_REPOSITORY: ${{ vars.ECR_PUBLIC_REGISTRY_ALIAS }}/lumina-frontend
|
||||
ECR_REPOSITORY: ${{ vars.ECR_PUBLIC_REGISTRY_ALIAS }}/msk_lumina_frontend
|
||||
|
||||
jobs:
|
||||
build-and-push:
|
||||
1
.gitignore
vendored
@@ -240,3 +240,4 @@ workspace/sprint_1_2/CODEBASE/knowledge/implementation/model/
|
||||
logs/
|
||||
*.rdb
|
||||
dist/
|
||||
distribute_asset/*
|
||||
4
workspace/sprint_1_2/CODEBASE/frontend/distribute_asset/.gitignore
vendored
Normal file
@@ -0,0 +1,4 @@
|
||||
# Ignore everything in this directory
|
||||
*
|
||||
# Except this .gitignore file itself, which forces Git to keep the folder
|
||||
!.gitignore
|
||||
@@ -1,15 +0,0 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 640 480">
|
||||
<defs>
|
||||
<radialGradient id="heat" cx="55%" cy="42%" r="35%">
|
||||
<stop offset="0%" stop-color="#ff4444" stop-opacity="0.8"/>
|
||||
<stop offset="50%" stop-color="#ffaa00" stop-opacity="0.4"/>
|
||||
<stop offset="100%" stop-color="#000000" stop-opacity="0"/>
|
||||
</radialGradient>
|
||||
<radialGradient id="heat2" cx="38%" cy="55%" r="25%">
|
||||
<stop offset="0%" stop-color="#ff6600" stop-opacity="0.5"/>
|
||||
<stop offset="100%" stop-color="#000000" stop-opacity="0"/>
|
||||
</radialGradient>
|
||||
</defs>
|
||||
<ellipse cx="350" cy="210" rx="120" ry="80" fill="url(#heat)"/>
|
||||
<ellipse cx="250" cy="270" rx="70" ry="50" fill="url(#heat2)"/>
|
||||
</svg>
|
||||
|
Before Width: | Height: | Size: 711 B |
@@ -1,14 +0,0 @@
|
||||
<svg width="20" height="20" viewBox="0 0 20 20" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<!-- Figma MSK_Workspace_UIX-Design node 244:2415 -->
|
||||
<path
|
||||
d="M5.25 12.75C3.75 11.25 3.25 9.25 3.75 7.25C4.5 5 6.75 3.5 9.5 3.75C12.25 4 14.25 5.75 14.75 8.25C15.25 10.75 14.25 13 12.5 14.25M11.75 13.5C12.15 13 12.4 12.4 12.5 11.75"
|
||||
stroke="currentColor"
|
||||
stroke-width="1.5"
|
||||
stroke-linecap="round"
|
||||
stroke-linejoin="round"
|
||||
/>
|
||||
<circle cx="6.75" cy="7.75" r="1.05" fill="currentColor" />
|
||||
<circle cx="9.25" cy="6.5" r="1.05" fill="currentColor" />
|
||||
<circle cx="11.75" cy="6.5" r="1.05" fill="currentColor" />
|
||||
<circle cx="14.25" cy="7.75" r="1.05" fill="currentColor" />
|
||||
</svg>
|
||||
|
Before Width: | Height: | Size: 702 B |
@@ -1 +0,0 @@
|
||||
:root{--color-primary: #056b58;--color-on-primary: #ffffff;--color-primary-container: #76c8b1;--color-on-primary-container: #005344;--color-secondary: #23686c;--color-on-secondary: #ffffff;--color-secondary-container: #adeef2;--color-tertiary: #5f5983;--color-tertiary-container: #bab3e2;--color-error: #ba1a1a;--color-error-container: #ffdad6;--color-surface: #f5faf9;--color-surface-container: #eaefee;--color-surface-container-high: #e4e9e8;--color-on-surface: #171d1c;--color-on-surface-variant: #3e4945;--color-outline: #6f7975;--color-outline-variant: #bec9c4;--glass-bg: rgba(255, 255, 255, .55);--glass-border: rgba(5, 107, 88, .18);--glass-shadow: 0 10px 30px rgba(77, 141, 145, .08);--blur-glass: blur(12px);--radius-sm: .25rem;--radius-md: .75rem;--radius-lg: 1rem;--radius-full: 9999px;--font-headline: "Manrope", system-ui, sans-serif;--font-body: "Work Sans", system-ui, sans-serif;--space-sm: 8px;--space-md: 16px;--space-lg: 24px;--topbar-h: 56px;--bottombar-h: 40px}*,*:before,*:after{box-sizing:border-box}html,body,#root{height:100%;margin:0}body{font-family:var(--font-body);font-size:16px;line-height:1.5;color:var(--color-on-surface);background:radial-gradient(ellipse at 20% 0%,rgba(118,200,177,.25),transparent 50%),radial-gradient(ellipse at 80% 100%,rgba(173,238,242,.2),transparent 45%),var(--color-surface);-webkit-font-smoothing:antialiased}h1,h2,h3,h4{font-family:var(--font-headline);margin:0}button,input,textarea,select{font:inherit}a{color:inherit;text-decoration:none}.glass{background:var(--glass-bg);backdrop-filter:var(--blur-glass);-webkit-backdrop-filter:var(--blur-glass);border:1px solid var(--glass-border);box-shadow:var(--glass-shadow)}.glass-elevated{background:#ffffffb8;backdrop-filter:var(--blur-glass);-webkit-backdrop-filter:var(--blur-glass);border:1px solid rgba(5,107,88,.22);box-shadow:0 16px 40px #4d8d911f}.tnum{font-variant-numeric:tabular-nums}.app-shell{display:flex;flex-direction:column;min-height:100%}.app-main{flex:1;display:flex;flex-direction:column;min-height:0}.btn{display:inline-flex;align-items:center;justify-content:center;gap:6px;padding:10px 18px;border-radius:var(--radius-md);border:none;cursor:pointer;font-weight:600;font-size:14px;transition:transform .15s,box-shadow .15s,opacity .15s}.btn:active{transform:scale(.98)}.btn-primary{background:var(--color-secondary);color:var(--color-on-secondary);box-shadow:inset 0 1px #fff3}.btn-primary:hover{background:#1a5559}.btn-secondary{background:var(--glass-bg);-webkit-backdrop-filter:var(--blur-glass);backdrop-filter:var(--blur-glass);color:var(--color-primary);border:1px solid var(--color-primary)}.btn-ghost{background:transparent;color:var(--color-on-surface-variant);border:1px solid var(--color-outline-variant)}.chip{display:inline-flex;align-items:center;padding:4px 10px;border-radius:var(--radius-full);font-size:12px;font-weight:600;letter-spacing:.02em}.chip-urgent{background:var(--color-error-container);color:var(--color-error)}.chip-processing{background:var(--color-secondary-container);color:var(--color-secondary)}.chip-followup{background:var(--color-tertiary-container);color:var(--color-tertiary)}.chip-success{background:var(--color-primary-container);color:var(--color-on-primary-container)}.icon-btn{width:40px;height:40px;border-radius:var(--radius-full);border:1px solid var(--glass-border);background:var(--glass-bg);-webkit-backdrop-filter:var(--blur-glass);backdrop-filter:var(--blur-glass);display:inline-flex;align-items:center;justify-content:center;cursor:pointer;color:var(--color-secondary)}.icon-btn:hover{border-color:var(--color-primary)}.sr-only{position:absolute;width:1px;height:1px;padding:0;margin:-1px;overflow:hidden;clip:rect(0,0,0,0);border:0}
|
||||
|
Before Width: | Height: | Size: 85 KiB |
|
Before Width: | Height: | Size: 109 KiB |
@@ -1,79 +0,0 @@
|
||||
import{j as a}from"./index-BCa3bqPy.js";function m({title:e="Lumina MSK",subtitle:t,showBack:n,onBack:r,actions:o,phase:s}){return a.jsxs("header",{className:"topbar glass",children:[a.jsxs("div",{className:"topbar__left",children:[n&&a.jsx("button",{type:"button",className:"icon-btn",onClick:r,"aria-label":"Quay lại",children:"←"}),a.jsxs("div",{className:"topbar__brand",children:[a.jsx("span",{className:"topbar__logo",children:"◈"}),a.jsxs("div",{children:[a.jsx("h1",{className:"topbar__title",children:e}),t&&a.jsx("p",{className:"topbar__subtitle",children:t})]})]})]}),a.jsx("div",{className:"topbar__center",children:s&&a.jsx("span",{className:"topbar__phase chip chip-success",children:s})}),a.jsx("div",{className:"topbar__actions",children:o}),a.jsx("style",{children:`
|
||||
.topbar {
|
||||
height: var(--topbar-h);
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
padding: 0 var(--space-md);
|
||||
gap: var(--space-md);
|
||||
border-radius: 0;
|
||||
border-left: none;
|
||||
border-right: none;
|
||||
border-top: none;
|
||||
position: sticky;
|
||||
top: 0;
|
||||
z-index: 100;
|
||||
}
|
||||
.topbar__left {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: var(--space-sm);
|
||||
min-width: 0;
|
||||
}
|
||||
.topbar__brand {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 10px;
|
||||
min-width: 0;
|
||||
}
|
||||
.topbar__logo {
|
||||
color: var(--color-primary);
|
||||
font-size: 20px;
|
||||
}
|
||||
.topbar__title {
|
||||
font-size: 16px;
|
||||
font-weight: 700;
|
||||
line-height: 1.2;
|
||||
white-space: nowrap;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
}
|
||||
.topbar__subtitle {
|
||||
margin: 0;
|
||||
font-size: 12px;
|
||||
color: var(--color-on-surface-variant);
|
||||
white-space: nowrap;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
}
|
||||
.topbar__center {
|
||||
flex-shrink: 0;
|
||||
}
|
||||
.topbar__phase {
|
||||
font-size: 11px;
|
||||
}
|
||||
.topbar__actions {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: var(--space-sm);
|
||||
flex-shrink: 0;
|
||||
}
|
||||
`})]})}function b(){return a.jsxs("footer",{className:"bottombar glass",children:[a.jsx("span",{className:"bottombar__copyright",children:"© 2026, by VKIST"}),a.jsx("style",{children:`
|
||||
.bottombar {
|
||||
height: var(--bottombar-h);
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
padding: 0 var(--space-md);
|
||||
font-size: 12px;
|
||||
color: var(--color-on-surface-variant);
|
||||
border-radius: 0;
|
||||
border-left: none;
|
||||
border-right: none;
|
||||
border-bottom: none;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
.bottombar__copyright {
|
||||
letter-spacing: 0.02em;
|
||||
}
|
||||
`})]})}function N({children:e,topBar:t}){return a.jsxs("div",{className:"app-shell",children:[a.jsx(m,{...t}),a.jsx("main",{className:"app-main",children:e}),a.jsx(b,{})]})}const d="poc-v2-spec-cv-seg-norm",h=1440*60*1e3;function x(e,t,n,r=d){return`${e}|${t}|${n}|${r}`}function S(e,t,n=d){const r=[...t].sort().join(",");return`${e}|${n}|analyze:${r}`}async function E(e){const t=await fetch(e);if(!t.ok)throw new Error(`Failed to load image for hash (${t.status})`);const n=await t.arrayBuffer(),r=await crypto.subtle.digest("SHA-256",n);return Array.from(new Uint8Array(r)).map(o=>o.toString(16).padStart(2,"0")).join("")}const y="lumina-msk-ml",_=1,i="frame-inference-cache";function p(){return new Promise((e,t)=>{const n=indexedDB.open(y,_);n.onerror=()=>t(n.error??new Error("Failed to open ML cache IndexedDB")),n.onsuccess=()=>e(n.result),n.onupgradeneeded=()=>{const r=n.result;if(!r.objectStoreNames.contains(i)){const o=r.createObjectStore(i,{keyPath:"cacheKey"});o.createIndex("patientMrn","patientMrn",{unique:!1}),o.createIndex("frameId","frameId",{unique:!1})}}})}function l(e){return new Promise((t,n)=>{e.onsuccess=()=>t(e.result),e.onerror=()=>n(e.error??new Error("IndexedDB request failed"))})}function f(e){return e.pipelineVersion!==d?!1:Date.now()-e.cachedAt<h}async function g(e){try{const t=await p(),n=await l(t.transaction(i,"readonly").objectStore(i).get(e));return t.close(),!n||!f(n)?null:n}catch{return null}}async function I(e,t,n,r){try{const o=x(e,t,n),s=await g(o),c={cacheKey:o,patientMrn:e,frameId:t,contentHash:n,pipelineVersion:d,cachedAt:Date.now(),segmentation:r.segmentation!==void 0?r.segmentation:(s==null?void 0:s.segmentation)??null,angle:r.angle!==void 0?r.angle:(s==null?void 0:s.angle)??null},u=await p();await l(u.transaction(i,"readwrite").objectStore(i).put(c)),u.close()}catch{}}function v(e){const t=e==null?void 0:e.severity;if(!t||typeof t!="object")return null;const n=t.level;return typeof n!="number"||!Number.isFinite(n)?null:Math.min(3,Math.max(0,Math.round(n)))}async function w(e){var t;try{const n=await p(),r=await l(n.transaction(i,"readonly").objectStore(i).index("patientMrn").getAll(e));n.close();let o=null;for(const s of r){if(!f(s))continue;const c=v((t=s.segmentation)==null?void 0:t.raw);c!=null&&(o===null||c>o)&&(o=c)}return o}catch{return null}}async function M(e){const t=[...new Set(e)],n=await Promise.all(t.map(async r=>[r,await w(r)]));return Object.fromEntries(n)}async function A(e){try{const t=await p(),r=t.transaction(i,"readwrite").objectStore(i),o=r.index("patientMrn"),s=await l(o.getAllKeys(e));for(const c of s)await l(r.delete(c));t.close()}catch{}}export{N as A,x as a,S as b,g as c,A as d,E as h,M as l,I as m};
|
||||
|
Before Width: | Height: | Size: 98 KiB |
|
Before Width: | Height: | Size: 122 KiB |
|
Before Width: | Height: | Size: 138 KiB |
|
Before Width: | Height: | Size: 130 KiB |
|
Before Width: | Height: | Size: 128 KiB |
|
Before Width: | Height: | Size: 98 KiB |
|
Before Width: | Height: | Size: 95 KiB |
|
Before Width: | Height: | Size: 111 KiB |
|
Before Width: | Height: | Size: 137 KiB |
|
Before Width: | Height: | Size: 113 KiB |
|
Before Width: | Height: | Size: 85 KiB |
|
Before Width: | Height: | Size: 122 KiB |
|
Before Width: | Height: | Size: 138 KiB |
|
Before Width: | Height: | Size: 130 KiB |
|
Before Width: | Height: | Size: 114 KiB |
|
Before Width: | Height: | Size: 98 KiB |
|
Before Width: | Height: | Size: 122 KiB |
|
Before Width: | Height: | Size: 111 KiB |
|
Before Width: | Height: | Size: 130 KiB |
|
Before Width: | Height: | Size: 127 KiB |
@@ -1,21 +0,0 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 640 480">
|
||||
<path
|
||||
d="M200 220 Q300 160 420 210 T500 300 Q380 380 260 350 T190 270 Z"
|
||||
fill="none"
|
||||
stroke="#056b58"
|
||||
stroke-width="3"
|
||||
stroke-dasharray="8 4"
|
||||
opacity="0.85"
|
||||
/>
|
||||
<ellipse
|
||||
cx="340"
|
||||
cy="230"
|
||||
rx="55"
|
||||
ry="35"
|
||||
fill="#76c8b1"
|
||||
opacity="0.35"
|
||||
stroke="#056b58"
|
||||
stroke-width="2"
|
||||
/>
|
||||
<text x="310" y="235" fill="#ffffff" font-family="sans-serif" font-size="11" font-weight="600">M<EFBFBD>ng H</text>
|
||||
</svg>
|
||||
@@ -1,35 +0,0 @@
|
||||
# Stitch Project Assets
|
||||
|
||||
**Project:** MSK Collaborative Workspace
|
||||
**Project ID:** `17628337625105266704`
|
||||
|
||||
| Screen | Screen ID | Route mapping |
|
||||
|--------|-----------|---------------|
|
||||
| Danh sách công việc - Lumina MSK | `9d638112797c4b8d8a100d526bab43f2` | `/` (Patient Worklist) |
|
||||
| Chế độ Kiểm tra Lâm sàng - Lumina MSK (VN) | `fa207d6a4f684f32a3b99706275daad9` | `/workspace/:id` (Clinical Review) |
|
||||
| Không gian chẩn đoán - AI Review (VN) | `03c138643dd9427aaa59a6e16d089793` | Safety overlay state |
|
||||
| Chế độ siêu âm gốc - Metadata Tinh gọn (Refined Raw View) | `85ece6c4ed6f4c179042b59813237d44` | Segmentation legend bubble on `DiagnosticCanvas` |
|
||||
| Annotation Workspace with AI Hub | `3bdfcf6ba964447d88b98c8b9b0df9f1` | Floating `AnnotationRibbon` on `DiagnosticCanvas` |
|
||||
|
||||
## Fetching assets
|
||||
|
||||
Stitch API requires OAuth2 (`STITCH_ACCESS_TOKEN` + `GOOGLE_CLOUD_PROJECT`), not a generic API key.
|
||||
|
||||
```bash
|
||||
# Using @google/stitch-sdk (after OAuth setup)
|
||||
npm install @google/stitch-sdk
|
||||
STITCH_ACCESS_TOKEN=... GOOGLE_CLOUD_PROJECT=... node scripts/fetch-stitch.mjs
|
||||
```
|
||||
|
||||
Or use Stitch MCP `get_screen` with `projectId` and `screenId`, then download:
|
||||
- `htmlCode.downloadUrl` → save to `public/assets/stitch/<screen>/index.html`
|
||||
- `screenshot.downloadUrl` → save to `public/assets/stitch/<screen>/screenshot.png`
|
||||
|
||||
## Local placeholders
|
||||
|
||||
This mockup uses handcrafted SVG assets in `public/assets/` aligned with `spec/DESIGN.md` tokens, since Stitch OAuth was unavailable during build.
|
||||
|
||||
Reference thumbnails (design intent):
|
||||
- `worklist-reference.svg`
|
||||
- `clinical-review-reference.svg`
|
||||
- `ai-review-reference.svg`
|
||||
@@ -1,10 +0,0 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 400 300">
|
||||
<rect width="400" height="300" fill="#f5faf9" rx="12"/>
|
||||
<rect x="0" y="0" width="400" height="40" fill="rgba(255,255,255,0.55)"/>
|
||||
<rect x="16" y="52" width="240" height="232" fill="#0a0f0e" rx="12" opacity="0.45"/>
|
||||
<rect x="60" y="80" width="280" height="180" fill="rgba(255,255,255,0.85)" stroke="#ba1a1a" stroke-width="2" rx="12"/>
|
||||
<text x="80" y="110" fill="#ba1a1a" font-family="sans-serif" font-size="12" font-weight="700">Ch<EFBFBD> <11> Leo thang An to<74>n</text>
|
||||
<text x="80" y="140" fill="#3e4945" font-family="sans-serif" font-size="11"><10>i tho<68>i Socratic</text>
|
||||
<rect x="80" y="155" width="240" height="40" fill="#f0f5f4" rx="6" stroke="#bec9c4"/>
|
||||
<rect x="0" y="276" width="400" height="24" fill="rgba(255,255,255,0.72)"/>
|
||||
</svg>
|
||||
@@ -1,10 +0,0 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 400 300">
|
||||
<rect width="400" height="300" fill="#f5faf9" rx="12"/>
|
||||
<rect x="0" y="0" width="400" height="40" fill="rgba(255,255,255,0.55)" stroke="rgba(5,107,88,0.18)"/>
|
||||
<rect x="16" y="52" width="240" height="232" fill="#0a0f0e" rx="12" stroke="rgba(5,107,88,0.18)"/>
|
||||
<rect x="268" y="52" width="116" height="232" fill="rgba(255,255,255,0.72)" stroke="rgba(5,107,88,0.22)" rx="12"/>
|
||||
<text x="280" y="80" fill="#23686c" font-family="sans-serif" font-size="10" font-weight="600"><10> XU<58>T AI</text>
|
||||
<circle cx="326" cy="110" r="20" fill="#e8a838"/>
|
||||
<text x="320" y="115" fill="#fff" font-family="sans-serif" font-size="16" font-weight="700">2</text>
|
||||
<rect x="0" y="276" width="400" height="24" fill="rgba(255,255,255,0.72)" stroke="rgba(5,107,88,0.18)"/>
|
||||
</svg>
|
||||
|
Before Width: | Height: | Size: 827 B |
@@ -1,12 +0,0 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 400 300">
|
||||
<rect width="400" height="300" fill="#f5faf9" rx="12"/>
|
||||
<rect x="16" y="16" width="368" height="40" fill="rgba(255,255,255,0.55)" stroke="rgba(5,107,88,0.18)" rx="8"/>
|
||||
<text x="32" y="42" fill="#23686c" font-family="sans-serif" font-size="14" font-weight="700">Danh s<>ch c<>ng vi<76>c</text>
|
||||
<rect x="16" y="68" width="110" height="70" fill="rgba(255,255,255,0.55)" stroke="rgba(5,107,88,0.18)" rx="12"/>
|
||||
<rect x="136" y="68" width="110" height="70" fill="rgba(255,255,255,0.55)" stroke="rgba(5,107,88,0.18)" rx="12"/>
|
||||
<rect x="256" y="68" width="128" height="70" fill="rgba(255,255,255,0.55)" stroke="rgba(5,107,88,0.18)" rx="12"/>
|
||||
<rect x="16" y="154" width="180" height="130" fill="rgba(255,255,255,0.55)" stroke="rgba(5,107,88,0.18)" rx="12"/>
|
||||
<rect x="204" y="154" width="180" height="130" fill="rgba(255,255,255,0.55)" stroke="rgba(5,107,88,0.18)" rx="12"/>
|
||||
<rect x="0" y="276" width="400" height="24" fill="rgba(255,255,255,0.72)" stroke="rgba(5,107,88,0.18)"/>
|
||||
<text x="16" y="292" fill="#6f7975" font-family="sans-serif" font-size="10">NFR <20> EMR</text>
|
||||
</svg>
|
||||
|
Before Width: | Height: | Size: 1.1 KiB |
|
Before Width: | Height: | Size: 98 KiB |
@@ -1 +0,0 @@
|
||||
function w(n){return[...n].sort((t,e)=>t.relTime!==e.relTime?t.relTime-e.relTime:t.timestamp-e.timestamp)}const f="UIX_Agent_Recorder",a="pending_chunks",p=1,I=1440*60*1e3;function i(n){return new Promise((t,e)=>{n.onsuccess=()=>t(n.result),n.onerror=()=>e(n.error??new Error("IndexedDB request failed"))})}function d(){return new Promise((n,t)=>{const e=indexedDB.open(f,p);e.onerror=()=>t(e.error??new Error("Failed to open UIX WAL IndexedDB")),e.onupgradeneeded=()=>{const o=e.result;if(!o.objectStoreNames.contains(a)){const r=o.createObjectStore(a,{keyPath:"chunkId"});r.createIndex("patientId","patientId",{unique:!1}),r.createIndex("sessionId","sessionId",{unique:!1}),r.createIndex("createdAt","createdAt",{unique:!1})}},e.onsuccess=()=>n(e.result)})}async function k(n){const t=await d();try{const e=t.transaction(a,"readwrite");await i(e.objectStore(a).put(n)),await new Promise((o,r)=>{e.oncomplete=()=>o(),e.onerror=()=>r(e.error??new Error("WAL put transaction failed"))})}finally{t.close()}}async function y(n){const t=await d();try{const e=t.transaction(a,"readwrite");await i(e.objectStore(a).delete(n)),await new Promise((o,r)=>{e.oncomplete=()=>o(),e.onerror=()=>r(e.error??new Error("WAL delete transaction failed"))})}finally{t.close()}}async function _(n){const t=await d();try{const e=t.transaction(a,"readonly"),o=e.objectStore(a).index("patientId"),r=await i(o.getAll(n));return await new Promise((c,s)=>{e.oncomplete=()=>c(),e.onerror=()=>s(e.error??new Error("WAL read transaction failed"))}),r.sort((c,s)=>c.createdAt-s.createdAt)}finally{t.close()}}async function h(){const n=Date.now()-I,t=await d();let e=0;try{const o=t.transaction(a,"readwrite"),r=o.objectStore(a),c=await i(r.getAll());for(const s of c)s.createdAt<n&&(await i(r.delete(s.chunkId)),e+=1);await new Promise((s,l)=>{o.oncomplete=()=>s(),o.onerror=()=>l(o.error??new Error("WAL purge transaction failed"))})}finally{t.close()}return e}async function m(n){return!0}async function u(n){try{await m(n)&&await y(n.chunkId)}catch{}}async function A(n){const t=w(n.chunk.events),e={...n.chunk,events:t};await k(e),self.postMessage({type:"wal_committed",requestId:n.requestId,chunkId:e.chunkId,events:t}),u(e)}async function g(n){const t=await _(n.patientId);for(const e of t)u(e);self.postMessage({type:"wal_recovered",requestId:n.requestId,patientId:n.patientId,chunks:t})}async function b(n){const t=await h();self.postMessage({type:"wal_purge_stale_done",requestId:n.requestId,removedCount:t})}self.onmessage=n=>{const t=n.data;if(!(t!=null&&t.requestId))return;const e=r=>{self.postMessage({type:"wal_error",requestId:t.requestId,message:r})};(async()=>{switch(t.type){case"wal_flush":await A(t);break;case"wal_recover_patient":await g(t);break;case"wal_purge_stale":await b(t);break;default:e("Unknown WAL worker message type")}})().catch(r=>{e(r instanceof Error?r.message:"WAL worker operation failed")})};
|
||||
@@ -1,20 +0,0 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 640 480" role="img">
|
||||
<defs>
|
||||
<radialGradient id="us-bg" cx="50%" cy="45%" r="55%">
|
||||
<stop offset="0%" stop-color="#2a3533"/>
|
||||
<stop offset="100%" stop-color="#0a0f0e"/>
|
||||
</radialGradient>
|
||||
<filter id="noise">
|
||||
<feTurbulence type="fractalNoise" baseFrequency="0.9" numOctaves="4" stitchTiles="stitch"/>
|
||||
<feColorMatrix type="saturate" values="0"/>
|
||||
<feBlend in="SourceGraphic" mode="multiply"/>
|
||||
</filter>
|
||||
</defs>
|
||||
<rect width="640" height="480" fill="url(#us-bg)"/>
|
||||
<ellipse cx="320" cy="240" rx="220" ry="170" fill="#1a2422" opacity="0.6"/>
|
||||
<path d="M180 200 Q260 120 400 180 T520 280 Q420 360 280 340 T160 260 Z" fill="#3d4a47" opacity="0.5" filter="url(#noise)"/>
|
||||
<path d="M200 220 Q300 160 420 210 T500 300 Q380 380 260 350 T190 270 Z" fill="#5a6b66" opacity="0.35"/>
|
||||
<ellipse cx="340" cy="230" rx="45" ry="28" fill="#8a9a94" opacity="0.25"/>
|
||||
<text x="20" y="30" fill="#76c8b1" font-family="monospace" font-size="14">SI<EFBFBD>U <20>M <20> KH<4B>P G<>I</text>
|
||||
<text x="20" y="460" fill="#6f7975" font-family="monospace" font-size="12">12.5 MHz <20> Gain 62</text>
|
||||
</svg>
|
||||
|
Before Width: | Height: | Size: 1.1 KiB |
@@ -1 +0,0 @@
|
||||
|
||||
@@ -1,20 +0,0 @@
|
||||
<!doctype html>
|
||||
<html lang="vi">
|
||||
<head>
|
||||
<meta charset="UTF-8" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||
<title>Lumina MSK — Không gian Lâm sàng</title>
|
||||
<link rel="preconnect" href="https://fonts.googleapis.com" />
|
||||
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin />
|
||||
<link
|
||||
href="https://fonts.googleapis.com/css2?family=Manrope:wght@600;700&family=Work+Sans:wght@400;500;600&display=swap"
|
||||
rel="stylesheet"
|
||||
/>
|
||||
<script type="module" crossorigin src="/assets/index-BCa3bqPy.js"></script>
|
||||
<link rel="modulepreload" crossorigin href="/assets/vendor-react-BWW3uBCb.js">
|
||||
<link rel="stylesheet" crossorigin href="/assets/index-LQC2ade3.css">
|
||||
</head>
|
||||
<body>
|
||||
<div id="root"></div>
|
||||
</body>
|
||||
</html>
|
||||