@jini-ai/ui 0.3.12 → 0.3.14
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/features/i18n/dictionaries/settings-dialog.ar.js +2 -2
- package/dist/features/i18n/dictionaries/settings-dialog.bn.js +2 -2
- package/dist/features/i18n/dictionaries/settings-dialog.de.js +2 -2
- package/dist/features/i18n/dictionaries/settings-dialog.en.js +2 -2
- package/dist/features/i18n/dictionaries/settings-dialog.es.js +2 -2
- package/dist/features/i18n/dictionaries/settings-dialog.fa.js +2 -2
- package/dist/features/i18n/dictionaries/settings-dialog.fr.js +2 -2
- package/dist/features/i18n/dictionaries/settings-dialog.hi.js +2 -2
- package/dist/features/i18n/dictionaries/settings-dialog.hu.js +2 -2
- package/dist/features/i18n/dictionaries/settings-dialog.id.js +2 -2
- package/dist/features/i18n/dictionaries/settings-dialog.it.js +2 -2
- package/dist/features/i18n/dictionaries/settings-dialog.ja.js +2 -2
- package/dist/features/i18n/dictionaries/settings-dialog.ko.js +2 -2
- package/dist/features/i18n/dictionaries/settings-dialog.pl.js +2 -2
- package/dist/features/i18n/dictionaries/settings-dialog.pt-BR.js +2 -2
- package/dist/features/i18n/dictionaries/settings-dialog.ru.js +2 -2
- package/dist/features/i18n/dictionaries/settings-dialog.th.js +2 -2
- package/dist/features/i18n/dictionaries/settings-dialog.tr.js +2 -2
- package/dist/features/i18n/dictionaries/settings-dialog.uk.js +2 -2
- package/dist/features/i18n/dictionaries/settings-dialog.ur.js +2 -2
- package/dist/features/i18n/dictionaries/settings-dialog.zh-CN.js +2 -2
- package/dist/features/i18n/dictionaries/settings-dialog.zh-TW.js +2 -2
- package/dist/features/mcp-ui/surfaces/bridge.d.ts +2 -1
- package/dist/features/mcp-ui/surfaces/bridge.js +17 -7
- package/dist/features/mcp-ui/surfaces/confirmation.d.ts +20 -0
- package/dist/features/mcp-ui/surfaces/confirmation.js +38 -6
- package/dist/features/mcp-ui/surfaces/document.d.ts +7 -1
- package/dist/features/mcp-ui/surfaces/document.js +19 -1
- package/dist/features/mcp-ui/surfaces/text-input.js +4 -4
- package/package.json +4 -4
|
@@ -48,8 +48,8 @@ export const SETTINGS_DIALOG_AR = {
|
|
|
48
48
|
'Copy': 'نسخ',
|
|
49
49
|
'Copy to clipboard': 'نسخ إلى الحافظة',
|
|
50
50
|
'Could not load live models: {message}': 'تعذّر تحميل النماذج المباشرة: {message}',
|
|
51
|
-
'No API key
|
|
52
|
-
'No API key
|
|
51
|
+
'No API key saved. Save one to test the connection.': 'لا يوجد مفتاح API محفوظ. احفظ مفتاحًا لاختبار الاتصال.',
|
|
52
|
+
'No API key saved. Save one to load models.': 'لا يوجد مفتاح API محفوظ. احفظ مفتاحًا لتحميل النماذج.',
|
|
53
53
|
'Could not load skills: {error}': 'تعذّر تحميل المهارات: {error}',
|
|
54
54
|
'Could not reach the daemon: {error}': 'تعذّر الوصول إلى الخدمة الخلفية: {error}',
|
|
55
55
|
'Could not reach the server. Showing local changes only.': 'تعذّر الوصول إلى الخادم. تُعرض التغييرات المحلية فقط.',
|
|
@@ -48,8 +48,8 @@ export const SETTINGS_DIALOG_BN = {
|
|
|
48
48
|
'Copy': 'কপি করুন',
|
|
49
49
|
'Copy to clipboard': 'ক্লিপবোর্ডে কপি করুন',
|
|
50
50
|
'Could not load live models: {message}': 'লাইভ মডেল লোড করা যায়নি: {message}',
|
|
51
|
-
'No API key
|
|
52
|
-
'No API key
|
|
51
|
+
'No API key saved. Save one to test the connection.': 'কোনো API কী সংরক্ষিত নেই। সংযোগ পরীক্ষা করতে একটি কী সংরক্ষণ করুন।',
|
|
52
|
+
'No API key saved. Save one to load models.': 'কোনো API কী সংরক্ষিত নেই। মডেল লোড করতে একটি কী সংরক্ষণ করুন।',
|
|
53
53
|
'Could not load skills: {error}': 'স্কিল লোড করা যায়নি: {error}',
|
|
54
54
|
'Could not reach the daemon: {error}': 'ডিমনে পৌঁছানো যায়নি: {error}',
|
|
55
55
|
'Could not reach the server. Showing local changes only.': 'সার্ভারে পৌঁছানো যায়নি। শুধু স্থানীয় পরিবর্তন দেখানো হচ্ছে।',
|
|
@@ -48,8 +48,8 @@ export const SETTINGS_DIALOG_DE = {
|
|
|
48
48
|
'Copy': 'Kopieren',
|
|
49
49
|
'Copy to clipboard': 'In die Zwischenablage kopieren',
|
|
50
50
|
'Could not load live models: {message}': 'Live-Modelle konnten nicht geladen werden: {message}',
|
|
51
|
-
'No API key
|
|
52
|
-
'No API key
|
|
51
|
+
'No API key saved. Save one to test the connection.': 'Kein API-Schlüssel gespeichert. Speichern Sie einen, um die Verbindung zu testen.',
|
|
52
|
+
'No API key saved. Save one to load models.': 'Kein API-Schlüssel gespeichert. Speichern Sie einen, um Modelle zu laden.',
|
|
53
53
|
'Could not load skills: {error}': 'Skills konnten nicht geladen werden: {error}',
|
|
54
54
|
'Could not reach the daemon: {error}': 'Der Daemon konnte nicht erreicht werden: {error}',
|
|
55
55
|
'Could not reach the server. Showing local changes only.': 'Der Server konnte nicht erreicht werden. Es werden nur lokale Änderungen angezeigt.',
|
|
@@ -48,8 +48,8 @@ export const SETTINGS_DIALOG_EN = {
|
|
|
48
48
|
'Copy': 'Copy',
|
|
49
49
|
'Copy to clipboard': 'Copy to clipboard',
|
|
50
50
|
'Could not load live models: {message}': 'Could not load live models: {message}',
|
|
51
|
-
'No API key
|
|
52
|
-
'No API key
|
|
51
|
+
'No API key saved. Save one to test the connection.': 'No API key saved. Save one to test the connection.',
|
|
52
|
+
'No API key saved. Save one to load models.': 'No API key saved. Save one to load models.',
|
|
53
53
|
'Could not load skills: {error}': 'Could not load skills: {error}',
|
|
54
54
|
'Could not reach the daemon: {error}': 'Could not reach the daemon: {error}',
|
|
55
55
|
'Could not reach the server. Showing local changes only.': 'Could not reach the server. Showing local changes only.',
|
|
@@ -48,8 +48,8 @@ export const SETTINGS_DIALOG_ES = {
|
|
|
48
48
|
'Copy': 'Copiar',
|
|
49
49
|
'Copy to clipboard': 'Copiar al portapapeles',
|
|
50
50
|
'Could not load live models: {message}': 'No se pudieron cargar los modelos en vivo: {message}',
|
|
51
|
-
'No API key
|
|
52
|
-
'No API key
|
|
51
|
+
'No API key saved. Save one to test the connection.': 'No hay ninguna clave de API guardada. Guarde una para probar la conexión.',
|
|
52
|
+
'No API key saved. Save one to load models.': 'No hay ninguna clave de API guardada. Guarde una para cargar los modelos.',
|
|
53
53
|
'Could not load skills: {error}': 'No se pudieron cargar las skills: {error}',
|
|
54
54
|
'Could not reach the daemon: {error}': 'No se pudo contactar con el daemon: {error}',
|
|
55
55
|
'Could not reach the server. Showing local changes only.': 'No se pudo contactar con el servidor. Mostrando solo cambios locales.',
|
|
@@ -48,8 +48,8 @@ export const SETTINGS_DIALOG_FA = {
|
|
|
48
48
|
'Copy': 'کپی',
|
|
49
49
|
'Copy to clipboard': 'کپی در کلیپبورد',
|
|
50
50
|
'Could not load live models: {message}': 'بارگذاری مدلهای زنده ممکن نشد: {message}',
|
|
51
|
-
'No API key
|
|
52
|
-
'No API key
|
|
51
|
+
'No API key saved. Save one to test the connection.': 'هیچ کلید API ذخیره نشده است. برای آزمون اتصال یک کلید ذخیره کنید.',
|
|
52
|
+
'No API key saved. Save one to load models.': 'هیچ کلید API ذخیره نشده است. برای بارگیری مدلها یک کلید ذخیره کنید.',
|
|
53
53
|
'Could not load skills: {error}': 'بارگذاری مهارتها ممکن نشد: {error}',
|
|
54
54
|
'Could not reach the daemon: {error}': 'ارتباط با دیمون برقرار نشد: {error}',
|
|
55
55
|
'Could not reach the server. Showing local changes only.': 'ارتباط با سرور برقرار نشد. فقط تغییرات محلی نمایش داده میشود.',
|
|
@@ -48,8 +48,8 @@ export const SETTINGS_DIALOG_FR = {
|
|
|
48
48
|
'Copy': 'Copier',
|
|
49
49
|
'Copy to clipboard': 'Copier dans le presse-papiers',
|
|
50
50
|
'Could not load live models: {message}': 'Impossible de charger les modèles en direct : {message}',
|
|
51
|
-
'No API key
|
|
52
|
-
'No API key
|
|
51
|
+
'No API key saved. Save one to test the connection.': 'Aucune clé API enregistrée. Enregistrez-en une pour tester la connexion.',
|
|
52
|
+
'No API key saved. Save one to load models.': 'Aucune clé API enregistrée. Enregistrez-en une pour charger les modèles.',
|
|
53
53
|
'Could not load skills: {error}': 'Impossible de charger les compétences : {error}',
|
|
54
54
|
'Could not reach the daemon: {error}': 'Impossible de contacter le daemon : {error}',
|
|
55
55
|
'Could not reach the server. Showing local changes only.': 'Impossible de contacter le serveur. Affichage des modifications locales uniquement.',
|
|
@@ -48,8 +48,8 @@ export const SETTINGS_DIALOG_HI = {
|
|
|
48
48
|
'Copy': 'कॉपी करें',
|
|
49
49
|
'Copy to clipboard': 'क्लिपबोर्ड पर कॉपी करें',
|
|
50
50
|
'Could not load live models: {message}': 'लाइव मॉडल लोड नहीं हो सके: {message}',
|
|
51
|
-
'No API key
|
|
52
|
-
'No API key
|
|
51
|
+
'No API key saved. Save one to test the connection.': 'कोई API कुंजी सहेजी नहीं गई है। कनेक्शन जाँचने के लिए एक कुंजी सहेजें।',
|
|
52
|
+
'No API key saved. Save one to load models.': 'कोई API कुंजी सहेजी नहीं गई है। मॉडल लोड करने के लिए एक कुंजी सहेजें।',
|
|
53
53
|
'Could not load skills: {error}': 'स्किल्स लोड नहीं हो सकीं: {error}',
|
|
54
54
|
'Could not reach the daemon: {error}': 'डेमन तक नहीं पहुंच सके: {error}',
|
|
55
55
|
'Could not reach the server. Showing local changes only.': 'सर्वर तक नहीं पहुंच सके। केवल स्थानीय बदलाव दिखाए जा रहे हैं।',
|
|
@@ -48,8 +48,8 @@ export const SETTINGS_DIALOG_HU = {
|
|
|
48
48
|
'Copy': 'Másolás',
|
|
49
49
|
'Copy to clipboard': 'Másolás vágólapra',
|
|
50
50
|
'Could not load live models: {message}': 'Nem sikerült betölteni az élő modelleket: {message}',
|
|
51
|
-
'No API key
|
|
52
|
-
'No API key
|
|
51
|
+
'No API key saved. Save one to test the connection.': 'Nincs mentett API-kulcs. Mentsen egyet a kapcsolat teszteléséhez.',
|
|
52
|
+
'No API key saved. Save one to load models.': 'Nincs mentett API-kulcs. Mentsen egyet a modellek betöltéséhez.',
|
|
53
53
|
'Could not load skills: {error}': 'Nem sikerült betölteni a képességeket: {error}',
|
|
54
54
|
'Could not reach the daemon: {error}': 'Nem sikerült elérni a démont: {error}',
|
|
55
55
|
'Could not reach the server. Showing local changes only.': 'Nem sikerült elérni a szervert. Csak a helyi módosítások jelennek meg.',
|
|
@@ -48,8 +48,8 @@ export const SETTINGS_DIALOG_ID = {
|
|
|
48
48
|
'Copy': 'Salin',
|
|
49
49
|
'Copy to clipboard': 'Salin ke clipboard',
|
|
50
50
|
'Could not load live models: {message}': 'Tidak dapat memuat model live: {message}',
|
|
51
|
-
'No API key
|
|
52
|
-
'No API key
|
|
51
|
+
'No API key saved. Save one to test the connection.': 'Belum ada kunci API yang disimpan. Simpan kunci untuk menguji koneksi.',
|
|
52
|
+
'No API key saved. Save one to load models.': 'Belum ada kunci API yang disimpan. Simpan kunci untuk memuat model.',
|
|
53
53
|
'Could not load skills: {error}': 'Tidak dapat memuat skill: {error}',
|
|
54
54
|
'Could not reach the daemon: {error}': 'Tidak dapat menjangkau daemon: {error}',
|
|
55
55
|
'Could not reach the server. Showing local changes only.': 'Tidak dapat menjangkau server. Hanya menampilkan perubahan lokal.',
|
|
@@ -48,8 +48,8 @@ export const SETTINGS_DIALOG_IT = {
|
|
|
48
48
|
'Copy': 'Copia',
|
|
49
49
|
'Copy to clipboard': 'Copia negli appunti',
|
|
50
50
|
'Could not load live models: {message}': 'Impossibile caricare i modelli in tempo reale: {message}',
|
|
51
|
-
'No API key
|
|
52
|
-
'No API key
|
|
51
|
+
'No API key saved. Save one to test the connection.': 'Nessuna chiave API salvata. Salvane una per testare la connessione.',
|
|
52
|
+
'No API key saved. Save one to load models.': 'Nessuna chiave API salvata. Salvane una per caricare i modelli.',
|
|
53
53
|
'Could not load skills: {error}': 'Impossibile caricare le competenze: {error}',
|
|
54
54
|
'Could not reach the daemon: {error}': 'Impossibile contattare il daemon: {error}',
|
|
55
55
|
'Could not reach the server. Showing local changes only.': 'Impossibile contattare il server. Vengono mostrate solo le modifiche locali.',
|
|
@@ -48,8 +48,8 @@ export const SETTINGS_DIALOG_JA = {
|
|
|
48
48
|
'Copy': 'コピー',
|
|
49
49
|
'Copy to clipboard': 'クリップボードにコピー',
|
|
50
50
|
'Could not load live models: {message}': 'ライブモデルを読み込めませんでした:{message}',
|
|
51
|
-
'No API key
|
|
52
|
-
'No API key
|
|
51
|
+
'No API key saved. Save one to test the connection.': 'API キーが保存されていません。接続をテストするにはキーを保存してください。',
|
|
52
|
+
'No API key saved. Save one to load models.': 'API キーが保存されていません。モデルを読み込むにはキーを保存してください。',
|
|
53
53
|
'Could not load skills: {error}': 'スキルを読み込めませんでした:{error}',
|
|
54
54
|
'Could not reach the daemon: {error}': 'デーモンに接続できませんでした:{error}',
|
|
55
55
|
'Could not reach the server. Showing local changes only.': 'サーバーに接続できませんでした。ローカルの変更のみ表示しています。',
|
|
@@ -48,8 +48,8 @@ export const SETTINGS_DIALOG_KO = {
|
|
|
48
48
|
'Copy': '복사',
|
|
49
49
|
'Copy to clipboard': '클립보드에 복사',
|
|
50
50
|
'Could not load live models: {message}': '실시간 모델을 불러올 수 없습니다: {message}',
|
|
51
|
-
'No API key
|
|
52
|
-
'No API key
|
|
51
|
+
'No API key saved. Save one to test the connection.': '저장된 API 키가 없습니다. 연결을 테스트하려면 키를 저장하세요.',
|
|
52
|
+
'No API key saved. Save one to load models.': '저장된 API 키가 없습니다. 모델을 불러오려면 키를 저장하세요.',
|
|
53
53
|
'Could not load skills: {error}': '스킬을 불러올 수 없습니다: {error}',
|
|
54
54
|
'Could not reach the daemon: {error}': '데몬에 연결할 수 없습니다: {error}',
|
|
55
55
|
'Could not reach the server. Showing local changes only.': '서버에 연결할 수 없습니다. 로컬 변경 사항만 표시합니다.',
|
|
@@ -48,8 +48,8 @@ export const SETTINGS_DIALOG_PL = {
|
|
|
48
48
|
'Copy': 'Kopiuj',
|
|
49
49
|
'Copy to clipboard': 'Kopiuj do schowka',
|
|
50
50
|
'Could not load live models: {message}': 'Nie udało się wczytać modeli na żywo: {message}',
|
|
51
|
-
'No API key
|
|
52
|
-
'No API key
|
|
51
|
+
'No API key saved. Save one to test the connection.': 'Nie zapisano klucza API. Zapisz klucz, aby przetestować połączenie.',
|
|
52
|
+
'No API key saved. Save one to load models.': 'Nie zapisano klucza API. Zapisz klucz, aby wczytać modele.',
|
|
53
53
|
'Could not load skills: {error}': 'Nie udało się wczytać umiejętności: {error}',
|
|
54
54
|
'Could not reach the daemon: {error}': 'Nie udało się połączyć z demonem: {error}',
|
|
55
55
|
'Could not reach the server. Showing local changes only.': 'Nie udało się połączyć z serwerem. Wyświetlane są tylko zmiany lokalne.',
|
|
@@ -48,8 +48,8 @@ export const SETTINGS_DIALOG_PT_BR = {
|
|
|
48
48
|
'Copy': 'Copiar',
|
|
49
49
|
'Copy to clipboard': 'Copiar para a área de transferência',
|
|
50
50
|
'Could not load live models: {message}': 'Não foi possível carregar os modelos em tempo real: {message}',
|
|
51
|
-
'No API key
|
|
52
|
-
'No API key
|
|
51
|
+
'No API key saved. Save one to test the connection.': 'Nenhuma chave de API salva. Salve uma para testar a conexão.',
|
|
52
|
+
'No API key saved. Save one to load models.': 'Nenhuma chave de API salva. Salve uma para carregar os modelos.',
|
|
53
53
|
'Could not load skills: {error}': 'Não foi possível carregar as skills: {error}',
|
|
54
54
|
'Could not reach the daemon: {error}': 'Não foi possível contatar o daemon: {error}',
|
|
55
55
|
'Could not reach the server. Showing local changes only.': 'Não foi possível contatar o servidor. Mostrando apenas as alterações locais.',
|
|
@@ -48,8 +48,8 @@ export const SETTINGS_DIALOG_RU = {
|
|
|
48
48
|
'Copy': 'Копировать',
|
|
49
49
|
'Copy to clipboard': 'Копировать в буфер обмена',
|
|
50
50
|
'Could not load live models: {message}': 'Не удалось загрузить актуальные модели: {message}',
|
|
51
|
-
'No API key
|
|
52
|
-
'No API key
|
|
51
|
+
'No API key saved. Save one to test the connection.': 'API-ключ не сохранён. Сохраните ключ, чтобы проверить подключение.',
|
|
52
|
+
'No API key saved. Save one to load models.': 'API-ключ не сохранён. Сохраните ключ, чтобы загрузить модели.',
|
|
53
53
|
'Could not load skills: {error}': 'Не удалось загрузить навыки: {error}',
|
|
54
54
|
'Could not reach the daemon: {error}': 'Не удалось связаться с демоном: {error}',
|
|
55
55
|
'Could not reach the server. Showing local changes only.': 'Не удалось связаться с сервером. Показаны только локальные изменения.',
|
|
@@ -48,8 +48,8 @@ export const SETTINGS_DIALOG_TH = {
|
|
|
48
48
|
'Copy': 'คัดลอก',
|
|
49
49
|
'Copy to clipboard': 'คัดลอกไปยังคลิปบอร์ด',
|
|
50
50
|
'Could not load live models: {message}': 'ไม่สามารถโหลดโมเดลแบบเรียลไทม์ได้: {message}',
|
|
51
|
-
'No API key
|
|
52
|
-
'No API key
|
|
51
|
+
'No API key saved. Save one to test the connection.': 'ยังไม่ได้บันทึกคีย์ API บันทึกคีย์ก่อนเพื่อทดสอบการเชื่อมต่อ',
|
|
52
|
+
'No API key saved. Save one to load models.': 'ยังไม่ได้บันทึกคีย์ API บันทึกคีย์ก่อนเพื่อโหลดโมเดล',
|
|
53
53
|
'Could not load skills: {error}': 'ไม่สามารถโหลดทักษะได้: {error}',
|
|
54
54
|
'Could not reach the daemon: {error}': 'ไม่สามารถติดต่อดีมอนได้: {error}',
|
|
55
55
|
'Could not reach the server. Showing local changes only.': 'ไม่สามารถติดต่อเซิร์ฟเวอร์ได้ กำลังแสดงเฉพาะการเปลี่ยนแปลงในเครื่อง',
|
|
@@ -48,8 +48,8 @@ export const SETTINGS_DIALOG_TR = {
|
|
|
48
48
|
'Copy': 'Kopyala',
|
|
49
49
|
'Copy to clipboard': 'Panoya kopyala',
|
|
50
50
|
'Could not load live models: {message}': 'Canlı modeller yüklenemedi: {message}',
|
|
51
|
-
'No API key
|
|
52
|
-
'No API key
|
|
51
|
+
'No API key saved. Save one to test the connection.': 'Kayıtlı API anahtarı yok. Bağlantıyı test etmek için bir anahtar kaydedin.',
|
|
52
|
+
'No API key saved. Save one to load models.': 'Kayıtlı API anahtarı yok. Modelleri yüklemek için bir anahtar kaydedin.',
|
|
53
53
|
'Could not load skills: {error}': 'Beceriler yüklenemedi: {error}',
|
|
54
54
|
'Could not reach the daemon: {error}': 'Arka plan sürecine ulaşılamadı: {error}',
|
|
55
55
|
'Could not reach the server. Showing local changes only.': 'Sunucuya ulaşılamadı. Yalnızca yerel değişiklikler gösteriliyor.',
|
|
@@ -48,8 +48,8 @@ export const SETTINGS_DIALOG_UK = {
|
|
|
48
48
|
'Copy': 'Копіювати',
|
|
49
49
|
'Copy to clipboard': 'Копіювати в буфер обміну',
|
|
50
50
|
'Could not load live models: {message}': 'Не вдалося завантажити актуальні моделі: {message}',
|
|
51
|
-
'No API key
|
|
52
|
-
'No API key
|
|
51
|
+
'No API key saved. Save one to test the connection.': 'API-ключ не збережено. Збережіть ключ, щоб перевірити підключення.',
|
|
52
|
+
'No API key saved. Save one to load models.': 'API-ключ не збережено. Збережіть ключ, щоб завантажити моделі.',
|
|
53
53
|
'Could not load skills: {error}': 'Не вдалося завантажити навички: {error}',
|
|
54
54
|
'Could not reach the daemon: {error}': 'Не вдалося з\'єднатися з демоном: {error}',
|
|
55
55
|
'Could not reach the server. Showing local changes only.': 'Не вдалося з\'єднатися з сервером. Показано лише локальні зміни.',
|
|
@@ -48,8 +48,8 @@ export const SETTINGS_DIALOG_UR = {
|
|
|
48
48
|
'Copy': 'کاپی کریں',
|
|
49
49
|
'Copy to clipboard': 'کلپ بورڈ پر کاپی کریں',
|
|
50
50
|
'Could not load live models: {message}': 'لائیو ماڈلز لوڈ نہیں ہو سکے: {message}',
|
|
51
|
-
'No API key
|
|
52
|
-
'No API key
|
|
51
|
+
'No API key saved. Save one to test the connection.': 'کوئی API کلید محفوظ نہیں ہے۔ کنکشن ٹیسٹ کرنے کے لیے ایک کلید محفوظ کریں۔',
|
|
52
|
+
'No API key saved. Save one to load models.': 'کوئی API کلید محفوظ نہیں ہے۔ ماڈلز لوڈ کرنے کے لیے ایک کلید محفوظ کریں۔',
|
|
53
53
|
'Could not load skills: {error}': 'اسکلز لوڈ نہیں ہو سکیں: {error}',
|
|
54
54
|
'Could not reach the daemon: {error}': 'ڈیمن تک رسائی نہیں ہو سکی: {error}',
|
|
55
55
|
'Could not reach the server. Showing local changes only.': 'سرور تک رسائی نہیں ہو سکی۔ صرف مقامی تبدیلیاں دکھائی جا رہی ہیں۔',
|
|
@@ -48,8 +48,8 @@ export const SETTINGS_DIALOG_ZH_CN = {
|
|
|
48
48
|
'Copy': '复制',
|
|
49
49
|
'Copy to clipboard': '复制到剪贴板',
|
|
50
50
|
'Could not load live models: {message}': '无法加载实时模型列表:{message}',
|
|
51
|
-
'No API key
|
|
52
|
-
'No API key
|
|
51
|
+
'No API key saved. Save one to test the connection.': '未保存 API 密钥。请先保存密钥再测试连接。',
|
|
52
|
+
'No API key saved. Save one to load models.': '未保存 API 密钥。请先保存密钥再加载模型。',
|
|
53
53
|
'Could not load skills: {error}': '无法加载技能:{error}',
|
|
54
54
|
'Could not reach the daemon: {error}': '无法连接到守护进程:{error}',
|
|
55
55
|
'Could not reach the server. Showing local changes only.': '无法连接到服务器,仅显示本地更改。',
|
|
@@ -48,8 +48,8 @@ export const SETTINGS_DIALOG_ZH_TW = {
|
|
|
48
48
|
'Copy': '複製',
|
|
49
49
|
'Copy to clipboard': '複製到剪貼簿',
|
|
50
50
|
'Could not load live models: {message}': '無法載入即時模型清單:{message}',
|
|
51
|
-
'No API key
|
|
52
|
-
'No API key
|
|
51
|
+
'No API key saved. Save one to test the connection.': '尚未儲存 API 金鑰。請先儲存金鑰再測試連線。',
|
|
52
|
+
'No API key saved. Save one to load models.': '尚未儲存 API 金鑰。請先儲存金鑰再載入模型。',
|
|
53
53
|
'Could not load skills: {error}': '無法載入技能:{error}',
|
|
54
54
|
'Could not reach the daemon: {error}': '無法連上背景程序:{error}',
|
|
55
55
|
'Could not reach the server. Showing local changes only.': '無法連上伺服器,僅顯示本機變更。',
|
|
@@ -26,7 +26,8 @@ export interface BridgeScriptSpec {
|
|
|
26
26
|
* rejected with the Host's own error message on a JSON-RPC error response.
|
|
27
27
|
* - `notify(method, params)` → a raw fire-and-forget notification, for the lifecycle messages a
|
|
28
28
|
* surface may want beyond what the bridge sends itself.
|
|
29
|
-
* - `openLink(url)` → `
|
|
29
|
+
* - `openLink(url)` → `Promise` of the Host's answer to the `ui/open-link` REQUEST (queued until the
|
|
30
|
+
* handshake completes, like `callTool`) — the only way an isolated frame can navigate anything.
|
|
30
31
|
* - `requestTeardown()` → `ui/notifications/request-teardown`, the draft-spec way for a "Done"
|
|
31
32
|
* button to ask the Host to remove the frame.
|
|
32
33
|
* - `whenReady(fn)` → runs `fn` after the handshake, immediately if it already completed.
|
|
@@ -60,7 +60,8 @@ export const SURFACE_HANDSHAKE_FAILED_ATTRIBUTE = 'data-mcpui-handshake-failed';
|
|
|
60
60
|
* rejected with the Host's own error message on a JSON-RPC error response.
|
|
61
61
|
* - `notify(method, params)` → a raw fire-and-forget notification, for the lifecycle messages a
|
|
62
62
|
* surface may want beyond what the bridge sends itself.
|
|
63
|
-
* - `openLink(url)` → `
|
|
63
|
+
* - `openLink(url)` → `Promise` of the Host's answer to the `ui/open-link` REQUEST (queued until the
|
|
64
|
+
* handshake completes, like `callTool`) — the only way an isolated frame can navigate anything.
|
|
64
65
|
* - `requestTeardown()` → `ui/notifications/request-teardown`, the draft-spec way for a "Done"
|
|
65
66
|
* button to ask the Host to remove the frame.
|
|
66
67
|
* - `whenReady(fn)` → runs `fn` after the handshake, immediately if it already completed.
|
|
@@ -115,14 +116,23 @@ export function renderBridgeScript(spec) {
|
|
|
115
116
|
// tools/call with a JSON-RPC invalidRequest, so sending early would turn "the user clicked a
|
|
116
117
|
// little too fast" into a refusal the surface would have to explain. A call held when the
|
|
117
118
|
// handshake FAILS is rejected rather than held forever, so the dialog can say so.
|
|
118
|
-
function
|
|
119
|
-
|
|
120
|
-
if (ready) return request("tools/call", params);
|
|
119
|
+
function requestWhenReady(method, params) {
|
|
120
|
+
if (ready) return request(method, params);
|
|
121
121
|
return new Promise(function (resolve, reject) {
|
|
122
|
-
queuedCalls.push({ resolve: resolve, reject: reject, params: params });
|
|
122
|
+
queuedCalls.push({ resolve: resolve, reject: reject, method: method, params: params });
|
|
123
123
|
});
|
|
124
124
|
}
|
|
125
125
|
|
|
126
|
+
function callTool(name, args) {
|
|
127
|
+
return requestWhenReady("tools/call", { name: name, arguments: args || {} });
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
// A REQUEST, not a notification: the spec defines ui/open-link as a request, and @mcp-ui/client's
|
|
131
|
+
// AppBridge registers it with setRequestHandler, so an id-less message is silently dropped.
|
|
132
|
+
function openLink(url) {
|
|
133
|
+
return requestWhenReady("ui/open-link", { url: url });
|
|
134
|
+
}
|
|
135
|
+
|
|
126
136
|
function reportSize() {
|
|
127
137
|
var root = document.documentElement;
|
|
128
138
|
notify("ui/notifications/size-changed", { width: root.scrollWidth, height: root.scrollHeight });
|
|
@@ -164,7 +174,7 @@ export function renderBridgeScript(spec) {
|
|
|
164
174
|
var handlers = readyHandlers;
|
|
165
175
|
queuedCalls = [];
|
|
166
176
|
readyHandlers = [];
|
|
167
|
-
for (var i = 0; i < calls.length; i++) request(
|
|
177
|
+
for (var i = 0; i < calls.length; i++) request(calls[i].method, calls[i].params).then(calls[i].resolve, calls[i].reject);
|
|
168
178
|
for (var j = 0; j < handlers.length; j++) handlers[j]();
|
|
169
179
|
if (typeof ResizeObserver === "function") new ResizeObserver(reportSize).observe(document.documentElement);
|
|
170
180
|
}
|
|
@@ -190,7 +200,7 @@ export function renderBridgeScript(spec) {
|
|
|
190
200
|
window.${global} = {
|
|
191
201
|
callTool: callTool,
|
|
192
202
|
notify: notify,
|
|
193
|
-
openLink:
|
|
203
|
+
openLink: openLink,
|
|
194
204
|
requestTeardown: function () { notify("ui/notifications/request-teardown"); },
|
|
195
205
|
whenReady: function (fn) { if (ready) fn(); else if (!settled) readyHandlers.push(fn); },
|
|
196
206
|
hostContext: function () { return hostContext; },
|
|
@@ -38,6 +38,18 @@ export interface ConfirmationToolAction {
|
|
|
38
38
|
/** Passed verbatim as the tool's `arguments`. The only safe home for a confirmation token. */
|
|
39
39
|
readonly params: Readonly<Record<string, unknown>>;
|
|
40
40
|
}
|
|
41
|
+
/**
|
|
42
|
+
* A further affirmative button beside confirm, such as "Allow for this chat" next to "Allow" — see
|
|
43
|
+
* {@link ConfirmationSurfaceSpec.alternatives}.
|
|
44
|
+
*/
|
|
45
|
+
export interface ConfirmationAlternative extends ConfirmationToolAction {
|
|
46
|
+
/**
|
|
47
|
+
* The button's `data-mcpui-action` handle. A lowercase `[a-z0-9]+(-[a-z0-9]+)*` handle, unique,
|
|
48
|
+
* and never `confirm` or `cancel`: the script dispatches on it, and an agent driving the frame
|
|
49
|
+
* finds the button by it (`mcpui-action-<id>`).
|
|
50
|
+
*/
|
|
51
|
+
readonly id: string;
|
|
52
|
+
}
|
|
41
53
|
/** One row of an optional per-item checkbox list — see {@link ConfirmationSurfaceSpec.choices}. */
|
|
42
54
|
export interface ConfirmationChoice {
|
|
43
55
|
/**
|
|
@@ -74,6 +86,14 @@ export interface ConfirmationSurfaceSpec {
|
|
|
74
86
|
/** Styles the affirmative button as destructive. Affects presentation only; it changes no behavior. */
|
|
75
87
|
readonly danger?: boolean;
|
|
76
88
|
readonly confirm: ConfirmationToolAction;
|
|
89
|
+
/**
|
|
90
|
+
* Further affirmative buttons, rendered after confirm and before cancel, each calling its own tool
|
|
91
|
+
* with its own params. Every one is held to exactly what confirm is held to: disabled until the
|
|
92
|
+
* dwell has run, only a trusted click counts, one call locks the whole dialog, and ticked
|
|
93
|
+
* {@link choices} ride along on it the way they ride on confirm. With none, the dialog renders and
|
|
94
|
+
* behaves exactly as the two-button one does.
|
|
95
|
+
*/
|
|
96
|
+
readonly alternatives?: readonly ConfirmationAlternative[];
|
|
77
97
|
/** Omit for a dialog whose cancel only dismisses. Give it a tool to burn a pending token server-side. */
|
|
78
98
|
readonly cancel?: ConfirmationToolAction | {
|
|
79
99
|
readonly label: string;
|
|
@@ -40,6 +40,17 @@ import { renderCheckbox } from './checkbox.js';
|
|
|
40
40
|
*/
|
|
41
41
|
export const CONFIRM_DWELL_MS = 1500;
|
|
42
42
|
const DEFAULT_APP = { appName: 'jini-mcp-ui-confirmation', appVersion: '1' };
|
|
43
|
+
const ALTERNATIVE_ID_PATTERN = /^[a-z0-9]+(-[a-z0-9]+)*$/;
|
|
44
|
+
/** Refuses an alternative id the script or a driver could confuse with another button. */
|
|
45
|
+
function assertAlternativeIds(alternatives) {
|
|
46
|
+
const seen = new Set(['confirm', 'cancel']);
|
|
47
|
+
for (const alternative of alternatives) {
|
|
48
|
+
if (!ALTERNATIVE_ID_PATTERN.test(alternative.id) || seen.has(alternative.id)) {
|
|
49
|
+
throw new Error(`Confirmation alternative ids must be unique lowercase handles other than "confirm" and "cancel"; ${JSON.stringify(alternative.id)} is not.`);
|
|
50
|
+
}
|
|
51
|
+
seen.add(alternative.id);
|
|
52
|
+
}
|
|
53
|
+
}
|
|
43
54
|
function isToolAction(action) {
|
|
44
55
|
return action !== undefined && 'toolName' in action;
|
|
45
56
|
}
|
|
@@ -93,6 +104,7 @@ function renderChoices(choices) {
|
|
|
93
104
|
function confirmationActions(spec) {
|
|
94
105
|
const actions = [
|
|
95
106
|
{ id: 'confirm', label: spec.confirm.label, variant: spec.danger === true ? 'danger' : 'primary' },
|
|
107
|
+
...(spec.alternatives ?? []).map((alternative) => ({ id: alternative.id, label: alternative.label, variant: 'neutral' })),
|
|
96
108
|
];
|
|
97
109
|
if (spec.cancel !== undefined)
|
|
98
110
|
actions.push({ id: 'cancel', label: spec.cancel.label, variant: 'neutral' });
|
|
@@ -106,7 +118,16 @@ export function renderConfirmationDocument(spec) {
|
|
|
106
118
|
if ((spec.choices ?? []).length > 0 && Object.prototype.hasOwnProperty.call(spec.confirm.params, choicesParam)) {
|
|
107
119
|
throw new Error(`choicesParam ${JSON.stringify(choicesParam)} collides with a key already in confirm.params.`);
|
|
108
120
|
}
|
|
121
|
+
for (const alternative of (spec.choices ?? []).length > 0 ? (spec.alternatives ?? []) : []) {
|
|
122
|
+
if (Object.prototype.hasOwnProperty.call(alternative.params, choicesParam)) {
|
|
123
|
+
throw new Error(`choicesParam ${JSON.stringify(choicesParam)} collides with a key already in the params of alternative ${JSON.stringify(alternative.id)}.`);
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
const alternatives = spec.alternatives ?? [];
|
|
127
|
+
assertAlternativeIds(alternatives);
|
|
109
128
|
const actions = confirmationActions(spec);
|
|
129
|
+
// Every affirmative button starts disabled and waits out the dwell; only cancel is live at once.
|
|
130
|
+
const affirmative = new Set(['confirm', ...alternatives.map((alternative) => alternative.id)]);
|
|
110
131
|
const warning = spec.warning === undefined ? '' : `<p class="mcpui-warning">${escapeHtml(spec.warning)}</p>`;
|
|
111
132
|
const bodyHtml = [
|
|
112
133
|
renderSurfaceHeader(spec.description === undefined ? { title: spec.title } : { title: spec.title, description: spec.description }),
|
|
@@ -114,7 +135,7 @@ export function renderConfirmationDocument(spec) {
|
|
|
114
135
|
renderChoices(spec.choices ?? []),
|
|
115
136
|
warning,
|
|
116
137
|
// Confirm starts disabled; the script enables it once the dwell has run out.
|
|
117
|
-
renderActions(actions.map((action) => (action.id
|
|
138
|
+
renderActions(actions.map((action) => (affirmative.has(action.id) ? { ...action, disabled: true } : action))),
|
|
118
139
|
renderStatusRegion(),
|
|
119
140
|
]
|
|
120
141
|
.filter((fragment) => fragment !== '')
|
|
@@ -125,6 +146,9 @@ export function renderConfirmationDocument(spec) {
|
|
|
125
146
|
const plan = {
|
|
126
147
|
confirm: { toolName: spec.confirm.toolName, params: spec.confirm.params },
|
|
127
148
|
};
|
|
149
|
+
for (const alternative of alternatives) {
|
|
150
|
+
plan[alternative.id] = { toolName: alternative.toolName, params: alternative.params };
|
|
151
|
+
}
|
|
128
152
|
if (spec.cancel !== undefined) {
|
|
129
153
|
plan['cancel'] = isToolAction(spec.cancel)
|
|
130
154
|
? { toolName: spec.cancel.toolName, params: spec.cancel.params }
|
|
@@ -137,6 +161,10 @@ ${SURFACE_SCRIPT_PRELUDE}
|
|
|
137
161
|
var TEXT = ${escapeJsValue(text)};
|
|
138
162
|
var DWELL_MS = ${CONFIRM_DWELL_MS};
|
|
139
163
|
var CHOICES_PARAM = ${escapeJsValue(choicesParam)};
|
|
164
|
+
var AFFIRMATIVE = ${escapeJsValue([...affirmative])};
|
|
165
|
+
function isAffirmative(action) {
|
|
166
|
+
return AFFIRMATIVE.indexOf(action) !== -1;
|
|
167
|
+
}
|
|
140
168
|
|
|
141
169
|
// Monotonic where available: a wall clock set backwards would otherwise stretch the dwell.
|
|
142
170
|
function now() {
|
|
@@ -150,14 +178,18 @@ ${SURFACE_SCRIPT_PRELUDE}
|
|
|
150
178
|
// has not been seen, so its dwell starts when it is shown, and restarts each time it is shown again.
|
|
151
179
|
var visibleSince = null;
|
|
152
180
|
var dwellTimer = null;
|
|
153
|
-
var
|
|
181
|
+
var affirmativeButtons = actionButtons.filter(function (node) {
|
|
182
|
+
return isAffirmative(node.getAttribute("data-mcpui-action"));
|
|
183
|
+
});
|
|
154
184
|
|
|
155
185
|
function dwellDone() {
|
|
156
186
|
return visibleSince !== null && now() - visibleSince >= DWELL_MS;
|
|
157
187
|
}
|
|
158
|
-
// Confirm is enabled exactly when the dwell has run out and no call holds
|
|
188
|
+
// Confirm (and every alternative) is enabled exactly when the dwell has run out and no call holds
|
|
189
|
+
// the dialog.
|
|
159
190
|
function syncConfirm() {
|
|
160
|
-
if (
|
|
191
|
+
if (locked) return;
|
|
192
|
+
for (var b = 0; b < affirmativeButtons.length; b++) affirmativeButtons[b].disabled = !dwellDone();
|
|
161
193
|
}
|
|
162
194
|
// Re-checks rather than trusts the timer: setTimeout and now() are different clocks, and a timer
|
|
163
195
|
// that fired a hair early must not leave confirm disabled for good.
|
|
@@ -231,7 +263,7 @@ ${SURFACE_SCRIPT_PRELUDE}
|
|
|
231
263
|
var step = PLAN[action];
|
|
232
264
|
if (step === undefined) return;
|
|
233
265
|
// Cancel is exempt: backing out early is never the harm this guards against.
|
|
234
|
-
if (action
|
|
266
|
+
if (isAffirmative(action) && !dwellDone()) return;
|
|
235
267
|
locked = true;
|
|
236
268
|
setChoicesDisabled(true);
|
|
237
269
|
if (step === null) {
|
|
@@ -243,7 +275,7 @@ ${SURFACE_SCRIPT_PRELUDE}
|
|
|
243
275
|
// Choices ride only on confirm, and only when this document has any -- with none, \`params\` stays
|
|
244
276
|
// the exact \`step.params\` reference PLAN was built from, byte-identical to before this existed.
|
|
245
277
|
var params = step.params;
|
|
246
|
-
if (action
|
|
278
|
+
if (isAffirmative(action) && choiceInputs.length > 0) {
|
|
247
279
|
var merged = {};
|
|
248
280
|
var key;
|
|
249
281
|
for (key in step.params) if (Object.prototype.hasOwnProperty.call(step.params, key)) merged[key] = step.params[key];
|
|
@@ -8,7 +8,7 @@ import { type SurfaceTokenName } from './tokens.js';
|
|
|
8
8
|
* be a color no caller could reskin. `var(--x, fallback)` appears only where a value is genuinely
|
|
9
9
|
* optional.
|
|
10
10
|
*/
|
|
11
|
-
export declare const SURFACE_BASE_CSS = "*, *::before, *::after { box-sizing: border-box; }\nhtml, body { margin: 0; padding: 0; }\nbody {\n /* Transparent, not var(--jini-mcpui-bg): this document has no page of its own to paint. It is an\n allow-scripts-only sandboxed iframe embedded directly in a host's chat transcript, and html's\n background is left at its (also transparent) initial value, so the host's own pane shows\n straight through to .mcpui-surface below rather than through a second, unrelated rectangle of\n fill color sitting between the two. --jini-mcpui-bg stays declared for a caller styling\n something else via extraCss -- nothing in this stylesheet reads it anymore. */\n background: transparent;\n color: var(--jini-mcpui-text);\n font-family: var(--jini-mcpui-font);\n font-size: 14px;\n line-height: 1.5;\n padding: 16px;\n -webkit-font-smoothing: antialiased;\n}\n.mcpui-surface {\n background: linear-gradient(135deg,\n color-mix(in srgb, var(--jini-mcpui-accent) 5%, var(--jini-mcpui-panel)) 0%,\n var(--jini-mcpui-panel) 60%);\n border: 1px solid color-mix(in srgb, var(--jini-mcpui-accent) 18%, var(--jini-mcpui-border));\n border-radius: var(--jini-mcpui-radius-md);\n box-shadow: var(--jini-mcpui-surface-shadow);\n padding: 18px 20px 16px;\n max-width: 640px;\n margin: 0 auto;\n}\n.mcpui-title {\n margin: 0;\n font-size: 16px;\n font-weight: 600;\n letter-spacing: -0.01em;\n color: var(--jini-mcpui-text-strong);\n}\n.mcpui-description { margin: 6px 0 0; color: var(--jini-mcpui-text-muted); }\n.mcpui-details {\n display: grid;\n grid-template-columns: max-content minmax(0, 1fr);\n gap: 4px 14px;\n margin: 14px 0 0;\n}\n.mcpui-details dt { color: var(--jini-mcpui-text-soft); font-weight: 500; }\n.mcpui-details dd { margin: 0; overflow-wrap: anywhere; color: var(--jini-mcpui-text); }\n.mcpui-warning {\n margin: 14px 0 0;\n padding: 9px 11px;\n border-radius: var(--jini-mcpui-radius-md);\n border: 1px solid var(--jini-mcpui-danger);\n background: var(--jini-mcpui-danger-tint);\n color: var(--jini-mcpui-text-strong);\n}\n.mcpui-fields { display: flex; flex-direction: column; gap: 12px; margin: 16px 0 0; border: 0; padding: 0; }\n.mcpui-field { display: flex; flex-direction: column; gap: 4px; }\n.mcpui-field-inline { flex-direction: row; align-items: flex-start; gap: 8px; }\n.mcpui-label { font-weight: 500; color: var(--jini-mcpui-text-strong); }\n.mcpui-required { color: var(--jini-mcpui-accent); margin-left: 2px; }\n.mcpui-hint { color: var(--jini-mcpui-text-soft); font-size: 13px; }\n.mcpui-input, .mcpui-select, .mcpui-textarea {\n font: inherit;\n color: var(--jini-mcpui-text);\n background: var(--jini-mcpui-panel);\n border: 1px solid color-mix(in srgb, var(--jini-mcpui-accent) 14%, var(--jini-mcpui-border-strong));\n border-radius: var(--jini-mcpui-radius-sm);\n padding: 7px 9px;\n width: 100%;\n min-width: 0;\n}\n.mcpui-textarea { resize: vertical; min-height: 72px; font-family: var(--jini-mcpui-font); }\n.mcpui-checkbox { margin: 3px 0 0; accent-color: var(--jini-mcpui-accent); }\n.mcpui-choice-group { border: 0; padding: 0; margin: 0; min-width: 0; }\n.mcpui-choice { display: flex; flex-direction: row; align-items: flex-start; gap: 8px; margin: 6px 0 0; }\n.mcpui-choice-label { color: var(--jini-mcpui-text); font-weight: 400; }\n.mcpui-input:focus-visible, .mcpui-select:focus-visible, .mcpui-textarea:focus-visible,\n.mcpui-checkbox:focus-visible, .mcpui-button:focus-visible {\n outline: 2px solid var(--jini-mcpui-accent-ring);\n outline-offset: 1px;\n border-color: var(--jini-mcpui-accent);\n}\n.mcpui-actions { display: flex; flex-wrap: wrap; gap: 8px; margin: 18px 0 0; }\n.mcpui-button {\n font: inherit;\n font-weight: 500;\n padding: 7px 15px;\n border-radius: var(--jini-mcpui-radius-pill);\n border: 1px solid color-mix(in srgb, var(--jini-mcpui-accent) 14%, var(--jini-mcpui-border-strong));\n background: var(--jini-mcpui-panel);\n color: var(--jini-mcpui-text);\n cursor: pointer;\n}\n.mcpui-button:hover:not([disabled]) { background: var(--jini-mcpui-accent-tint); }\n.mcpui-button-primary {\n background: var(--jini-mcpui-accent);\n border-color: var(--jini-mcpui-accent);\n color: var(--jini-mcpui-panel);\n}\n.mcpui-button-primary:hover:not([disabled]) { background: var(--jini-mcpui-accent-hover); border-color: var(--jini-mcpui-accent-hover); }\n.mcpui-button-danger {\n background: var(--jini-mcpui-danger);\n border-color: var(--jini-mcpui-danger);\n color: var(--jini-mcpui-panel);\n}\n.mcpui-button-danger:hover:not([disabled]) { background: var(--jini-mcpui-danger-hover); border-color: var(--jini-mcpui-danger-hover); }\n.mcpui-button[disabled] { opacity: 0.55; cursor: default; }\n.mcpui-status { margin: 12px 0 0; min-height: 1.4em; color: var(--jini-mcpui-text-muted); }\n.mcpui-status[data-state=\"failed\"], .mcpui-status[data-state=\"invalid\"], .mcpui-status[data-state=\"expired\"] { color: var(--jini-mcpui-danger); }\n.mcpui-status[data-state=\"done\"] { color: var(--jini-mcpui-text-strong); }\n/* Neither a plain success nor a failure \u2014 e.g. an outcome surface reporting \"uploaded, not yet\n reachable\" (outcome.ts's own state: partial). Distinct from both --jini-mcpui-danger (would\n wrongly read as \"this failed\") and --jini-mcpui-text-strong (would wrongly read as \"this is\n fully done\"); the accent token reads as \"worth a second look\" without asserting either. */\n.mcpui-status[data-state=\"partial\"] { color: var(--jini-mcpui-accent); }\n.mcpui-code { font-family: var(--jini-mcpui-font-mono); font-size: 13px; color: var(--jini-mcpui-text-faint); }";
|
|
11
|
+
export declare const SURFACE_BASE_CSS = "*, *::before, *::after { box-sizing: border-box; }\nhtml, body { margin: 0; padding: 0; }\nbody {\n /* Transparent, not var(--jini-mcpui-bg): this document has no page of its own to paint. It is an\n allow-scripts-only sandboxed iframe embedded directly in a host's chat transcript, and html's\n background is left at its (also transparent) initial value, so the host's own pane shows\n straight through to .mcpui-surface below rather than through a second, unrelated rectangle of\n fill color sitting between the two. --jini-mcpui-bg stays declared for a caller styling\n something else via extraCss -- nothing in this stylesheet reads it anymore. */\n background: transparent;\n color: var(--jini-mcpui-text);\n font-family: var(--jini-mcpui-font);\n font-size: 14px;\n line-height: 1.5;\n padding: 16px;\n -webkit-font-smoothing: antialiased;\n}\n.mcpui-surface {\n background: linear-gradient(135deg,\n color-mix(in srgb, var(--jini-mcpui-accent) 5%, var(--jini-mcpui-panel)) 0%,\n var(--jini-mcpui-panel) 60%);\n border: 1px solid color-mix(in srgb, var(--jini-mcpui-accent) 18%, var(--jini-mcpui-border));\n border-radius: var(--jini-mcpui-radius-md);\n box-shadow: var(--jini-mcpui-surface-shadow);\n padding: 18px 20px 16px;\n max-width: 640px;\n margin: 0 auto;\n}\n.mcpui-title {\n margin: 0;\n font-size: 16px;\n font-weight: 600;\n letter-spacing: -0.01em;\n color: var(--jini-mcpui-text-strong);\n}\n.mcpui-description { margin: 6px 0 0; color: var(--jini-mcpui-text-muted); }\n.mcpui-details {\n display: grid;\n grid-template-columns: max-content minmax(0, 1fr);\n gap: 4px 14px;\n margin: 14px 0 0;\n}\n.mcpui-details dt { color: var(--jini-mcpui-text-soft); font-weight: 500; }\n.mcpui-details dd { margin: 0; overflow-wrap: anywhere; color: var(--jini-mcpui-text); }\n.mcpui-code {\n margin: 0;\n white-space: pre-wrap;\n overflow-wrap: anywhere;\n max-height: 16em;\n overflow: auto;\n font-family: var(--jini-mcpui-font-mono);\n font-size: 12.5px;\n line-height: 1.45;\n padding: 6px 8px;\n border: 1px solid var(--jini-mcpui-border);\n border-radius: var(--jini-mcpui-radius-sm);\n background: var(--jini-mcpui-bg);\n}\n.mcpui-warning {\n margin: 14px 0 0;\n padding: 9px 11px;\n border-radius: var(--jini-mcpui-radius-md);\n border: 1px solid var(--jini-mcpui-danger);\n background: var(--jini-mcpui-danger-tint);\n color: var(--jini-mcpui-text-strong);\n}\n.mcpui-fields { display: flex; flex-direction: column; gap: 12px; margin: 16px 0 0; border: 0; padding: 0; }\n.mcpui-field { display: flex; flex-direction: column; gap: 4px; }\n.mcpui-field-inline { flex-direction: row; align-items: flex-start; gap: 8px; }\n.mcpui-label { font-weight: 500; color: var(--jini-mcpui-text-strong); }\n.mcpui-required { color: var(--jini-mcpui-accent); margin-left: 2px; }\n.mcpui-hint { color: var(--jini-mcpui-text-soft); font-size: 13px; }\n.mcpui-input, .mcpui-select, .mcpui-textarea {\n font: inherit;\n color: var(--jini-mcpui-text);\n background: var(--jini-mcpui-panel);\n border: 1px solid color-mix(in srgb, var(--jini-mcpui-accent) 14%, var(--jini-mcpui-border-strong));\n border-radius: var(--jini-mcpui-radius-sm);\n padding: 7px 9px;\n width: 100%;\n min-width: 0;\n}\n.mcpui-textarea { resize: vertical; min-height: 72px; font-family: var(--jini-mcpui-font); }\n.mcpui-checkbox { margin: 3px 0 0; accent-color: var(--jini-mcpui-accent); }\n.mcpui-choice-group { border: 0; padding: 0; margin: 0; min-width: 0; }\n.mcpui-choice { display: flex; flex-direction: row; align-items: flex-start; gap: 8px; margin: 6px 0 0; }\n.mcpui-choice-label { color: var(--jini-mcpui-text); font-weight: 400; }\n.mcpui-input:focus-visible, .mcpui-select:focus-visible, .mcpui-textarea:focus-visible,\n.mcpui-checkbox:focus-visible, .mcpui-button:focus-visible {\n outline: 2px solid var(--jini-mcpui-accent-ring);\n outline-offset: 1px;\n border-color: var(--jini-mcpui-accent);\n}\n.mcpui-actions { display: flex; flex-wrap: wrap; gap: 8px; margin: 18px 0 0; }\n.mcpui-button {\n font: inherit;\n font-weight: 500;\n padding: 7px 15px;\n border-radius: var(--jini-mcpui-radius-pill);\n border: 1px solid color-mix(in srgb, var(--jini-mcpui-accent) 14%, var(--jini-mcpui-border-strong));\n background: var(--jini-mcpui-panel);\n color: var(--jini-mcpui-text);\n cursor: pointer;\n}\n.mcpui-button:hover:not([disabled]) { background: var(--jini-mcpui-accent-tint); }\n.mcpui-button-primary {\n background: var(--jini-mcpui-accent);\n border-color: var(--jini-mcpui-accent);\n color: var(--jini-mcpui-panel);\n}\n.mcpui-button-primary:hover:not([disabled]) { background: var(--jini-mcpui-accent-hover); border-color: var(--jini-mcpui-accent-hover); }\n.mcpui-button-danger {\n background: var(--jini-mcpui-danger);\n border-color: var(--jini-mcpui-danger);\n color: var(--jini-mcpui-panel);\n}\n.mcpui-button-danger:hover:not([disabled]) { background: var(--jini-mcpui-danger-hover); border-color: var(--jini-mcpui-danger-hover); }\n.mcpui-button[disabled] { opacity: 0.55; cursor: default; }\n.mcpui-status { margin: 12px 0 0; min-height: 1.4em; color: var(--jini-mcpui-text-muted); }\n.mcpui-status[data-state=\"failed\"], .mcpui-status[data-state=\"invalid\"], .mcpui-status[data-state=\"expired\"] { color: var(--jini-mcpui-danger); }\n.mcpui-status[data-state=\"done\"] { color: var(--jini-mcpui-text-strong); }\n/* Neither a plain success nor a failure \u2014 e.g. an outcome surface reporting \"uploaded, not yet\n reachable\" (outcome.ts's own state: partial). Distinct from both --jini-mcpui-danger (would\n wrongly read as \"this failed\") and --jini-mcpui-text-strong (would wrongly read as \"this is\n fully done\"); the accent token reads as \"worth a second look\" without asserting either. */\n.mcpui-status[data-state=\"partial\"] { color: var(--jini-mcpui-accent); }\n.mcpui-code { font-family: var(--jini-mcpui-font-mono); font-size: 13px; color: var(--jini-mcpui-text-faint); }";
|
|
12
12
|
/**
|
|
13
13
|
* The Content-Security-Policy every generated surface declares in its own `<head>`.
|
|
14
14
|
*
|
|
@@ -66,6 +66,12 @@ export declare function renderSurfaceHeader(spec: {
|
|
|
66
66
|
export interface SurfaceDetail {
|
|
67
67
|
readonly label: string;
|
|
68
68
|
readonly value: string;
|
|
69
|
+
/**
|
|
70
|
+
* `code` renders the value in a monospace `<pre>` that keeps its line breaks and scrolls inside
|
|
71
|
+
* its own box past a fixed height, so a long SQL statement or JSON argument stays readable without
|
|
72
|
+
* stretching the dialog past its frame. Omitted (or `text`) keeps the plain inline value.
|
|
73
|
+
*/
|
|
74
|
+
readonly format?: 'text' | 'code';
|
|
69
75
|
}
|
|
70
76
|
/**
|
|
71
77
|
* Renders a `label: value` description list.
|
|
@@ -67,6 +67,20 @@ body {
|
|
|
67
67
|
}
|
|
68
68
|
.mcpui-details dt { color: var(--jini-mcpui-text-soft); font-weight: 500; }
|
|
69
69
|
.mcpui-details dd { margin: 0; overflow-wrap: anywhere; color: var(--jini-mcpui-text); }
|
|
70
|
+
.mcpui-code {
|
|
71
|
+
margin: 0;
|
|
72
|
+
white-space: pre-wrap;
|
|
73
|
+
overflow-wrap: anywhere;
|
|
74
|
+
max-height: 16em;
|
|
75
|
+
overflow: auto;
|
|
76
|
+
font-family: var(--jini-mcpui-font-mono);
|
|
77
|
+
font-size: 12.5px;
|
|
78
|
+
line-height: 1.45;
|
|
79
|
+
padding: 6px 8px;
|
|
80
|
+
border: 1px solid var(--jini-mcpui-border);
|
|
81
|
+
border-radius: var(--jini-mcpui-radius-sm);
|
|
82
|
+
background: var(--jini-mcpui-bg);
|
|
83
|
+
}
|
|
70
84
|
.mcpui-warning {
|
|
71
85
|
margin: 14px 0 0;
|
|
72
86
|
padding: 9px 11px;
|
|
@@ -209,7 +223,11 @@ export function renderDetailList(details) {
|
|
|
209
223
|
if (details.length === 0)
|
|
210
224
|
return '';
|
|
211
225
|
const rows = details
|
|
212
|
-
.map((detail) =>
|
|
226
|
+
.map((detail) => {
|
|
227
|
+
const value = escapeHtml(detail.value);
|
|
228
|
+
const body = detail.format === 'code' ? `<pre class="mcpui-code">${value}</pre>` : value;
|
|
229
|
+
return ` <dt>${escapeHtml(detail.label)}</dt><dd>${body}</dd>`;
|
|
230
|
+
})
|
|
213
231
|
.join('\n');
|
|
214
232
|
return `<dl class="mcpui-details">\n${rows}\n</dl>`;
|
|
215
233
|
}
|
|
@@ -48,10 +48,10 @@ export function renderTextInput(props) {
|
|
|
48
48
|
? `<textarea class="mcpui-textarea"${common}${optionalAttribute('rows', props.rows)}>${props.value === undefined ? '' : escapeHtml(String(props.value))}</textarea>`
|
|
49
49
|
: `<input class="mcpui-input" type="${type}"${common}` +
|
|
50
50
|
optionalAttribute('value', props.value) +
|
|
51
|
-
// A stored secret (e.g. a cloud provider's access key) must never be
|
|
52
|
-
//
|
|
53
|
-
//
|
|
54
|
-
(
|
|
51
|
+
// A stored secret (e.g. a cloud provider's access key or a database password) must never be
|
|
52
|
+
// autofilled with a saved website login. `new-password`, not `off`: Chrome ignores `off` on
|
|
53
|
+
// password inputs by design and fills the saved login anyway.
|
|
54
|
+
(type === 'password' ? optionalAttribute('autocomplete', 'new-password') : '') +
|
|
55
55
|
(isNumber
|
|
56
56
|
? optionalAttribute('min', props.min) + optionalAttribute('max', props.max) + optionalAttribute('step', props.step)
|
|
57
57
|
: '') +
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@jini-ai/ui",
|
|
3
|
-
"version": "0.3.
|
|
3
|
+
"version": "0.3.14",
|
|
4
4
|
"description": "Generic, product-neutral React UI primitives and feature domains. Framework-free types, decision rules, catalogs, and host port contracts live behind ./core (no React, no DOM). The Excalidraw and Lexical editors live behind ./sketch-editor and ./lexical-rich-text-editor; the GrapesJS-backed inline HTML editor lives behind ./html-editor.",
|
|
5
5
|
"license": "Apache-2.0",
|
|
6
6
|
"repository": {
|
|
@@ -107,9 +107,9 @@
|
|
|
107
107
|
"shiki": "^1.24.4",
|
|
108
108
|
"tailwind-merge": "^2.6.0",
|
|
109
109
|
"zod": "^3.25.76",
|
|
110
|
-
"@jini-ai/
|
|
111
|
-
"@jini-ai/
|
|
112
|
-
"@jini-ai/
|
|
110
|
+
"@jini-ai/agentic": "0.3.7",
|
|
111
|
+
"@jini-ai/protocol": "0.3.1",
|
|
112
|
+
"@jini-ai/platform": "0.3.0"
|
|
113
113
|
},
|
|
114
114
|
"devDependencies": {
|
|
115
115
|
"@excalidraw/excalidraw": "0.18.1",
|