Refactor: remove desktop/Tauri bundle, add i18n, CI/CD pipeline
Nightly Build / build (push) Failing after 6s

- Remove desktop (Tauri) bundle: docs/desktop.md, icons/, tauri.conf.json,
  src/desktop/mod.rs, gen/schemas/
- Remove build.rs (no longer needed)
- Add i18n system (crates/core-api, plugin-mobile-connector, web)
- Refactor config system (src/config.rs, boot_format.rs)
- Add mobile connector features (app, router, device pairing)
- Plugin system improvements (skald-core)
- Update dependencies (Cargo.lock, Cargo.toml)
- CI/CD: Gitea Actions workflows (nightly + release), package.sh,
  verify-version.sh, builds.skaldagent.net config
This commit is contained in:
2026-07-19 22:35:06 +01:00
parent ba911ae8cb
commit fb3eeeeec6
54 changed files with 823 additions and 8002 deletions
+3 -1
View File
@@ -389,6 +389,7 @@ export class AppCopilot extends I18nMixin(ChatSession) {
</div>
<div class="copilot-input-area">
${this._renderNoModelsBanner()}
<div class="copilot-composer"
@dragover=${(e) => e.preventDefault()}
@drop=${(e) => this._onDrop(e)}>
@@ -416,6 +417,7 @@ export class AppCopilot extends I18nMixin(ChatSession) {
<textarea
class="copilot-textarea"
rows="1"
?disabled=${this._noModels}
placeholder=${t('chat.placeholder')}
@keydown=${this._composerKeydown}
@input=${(e) => { this._autoResize(e.target); this._updateCmdMenu(e.target.value); }}
@@ -469,7 +471,7 @@ export class AppCopilot extends I18nMixin(ChatSession) {
<i class="bi bi-stop-fill"></i>
</button>`
: nothing}
<button class="copilot-send-btn" @click=${() => this._send()} title=${t('chat.send')}>
<button class="copilot-send-btn" ?disabled=${this._noModels} @click=${() => this._send()} title=${t('chat.send')}>
<i class="bi bi-send-fill"></i>
</button>
</div>
+3
View File
@@ -139,6 +139,7 @@ export class ChatPage extends ChatSession {
</div>
<div class="chat-page-input-area">
${this._renderNoModelsBanner()}
<div class="chat-page-composer"
@dragover=${(e) => e.preventDefault()}
@drop=${(e) => this._onDrop(e)}>
@@ -153,6 +154,7 @@ export class ChatPage extends ChatSession {
<textarea
class="chat-page-textarea"
rows="1"
?disabled=${this._noModels}
placeholder=${t('chat.mobile.placeholder')}
@input=${(e) => this._autoResize(e.target)}
@paste=${(e) => this._onPaste(e)}
@@ -195,6 +197,7 @@ export class ChatPage extends ChatSession {
: nothing}
<button
class="chat-page-send"
?disabled=${this._noModels}
@click=${() => this._send()}
title=${t('chat.send')}
><i class="bi bi-send-fill"></i></button>
+8
View File
@@ -6,6 +6,14 @@
flex-shrink: 0;
}
.chat-no-models {
margin-bottom: 0.6rem;
padding: 0.7rem 0.9rem;
font-size: 0.82rem;
}
.chat-no-models .home-banner-icon { font-size: 1.2rem; }
.chat-no-models a { color: var(--bs-danger); font-weight: 600; }
.copilot-composer {
position: relative;
display: flex;
+24
View File
@@ -1,3 +1,4 @@
import { html, nothing } from 'lit';
import { LightElement } from './base.js';
import { t } from './i18n.js';
@@ -29,6 +30,7 @@ export class ChatSession extends LightElement {
_expanded: { state: true },
_providers: { state: true },
_selectedClient: { state: true },
_providersLoaded: { state: true },
_rejectingId: { state: true },
_rejectNote: { state: true },
_clarificationAnswer: { state: true },
@@ -54,6 +56,7 @@ export class ChatSession extends LightElement {
this._ws = null;
this._providers = [];
this._selectedClient = null;
this._providersLoaded = false;
this._rejectingId = null;
this._rejectNote = '';
this._clarificationAnswer = '';
@@ -116,9 +119,30 @@ export class ChatSession extends LightElement {
this._selectedClient = def;
} catch (e) {
console.error('Failed to load LLM models:', e);
} finally {
this._providersLoaded = true;
}
}
get _noModels() {
return this._providersLoaded
&& this._providers.filter(p => p !== 'auto').length === 0;
}
_renderNoModelsBanner() {
if (!this._noModels) return nothing;
return html`
<div class="home-banner home-banner--error chat-no-models">
<div class="home-banner-icon"><i class="bi bi-cpu-fill"></i></div>
<div class="home-banner-body">
<strong>${t('dashboard.banner.no_models.title')}</strong>
${t('dashboard.banner.no_models.desc')}
<a href="#llm-providers">${t('dashboard.banner.no_models.action')}</a>
</div>
</div>
`;
}
async _loadHistory() {
try {
const res = await fetch(`/api/${this._source}/messages`);
+16
View File
@@ -23,6 +23,22 @@ export function t(key, params) {
return s;
}
/**
* Merge additional strings into the dictionaries — the registration seam for
* plugin page fragments, which ship their own `{ en, it, fr }` table and call
* this at module-load time (before their first render). Keys MUST be namespaced
* (`plugin.<id>.<key>`) so a plugin never clobbers a core key or another
* plugin's. Unknown locales are created on demand; missing keys still fall back
* through `t()` (locale → en → key). Merging alone does not re-render — the
* fragment registers before it mounts, and later `locale-changed` events drive
* updates as usual via `I18nMixin`.
*/
export function addStrings(dicts) {
for (const [loc, map] of Object.entries(dicts || {})) {
DICTS[loc] = { ...(DICTS[loc] || {}), ...map };
}
}
export function getLocale() { return _locale; }
export function setLocale(locale, { persist = false } = {}) {