Add image model (regolo-qwen-image) to modelSpecs, enabling UI image‑generation button
This commit is contained in:
commit
b2badebad6
4 files changed
+283
No files matched your search
+10
@@ -0,0 +1,10 @@
|
|||||||
|
FROM cloudron/ai.librechat.cloudronapp:202610150224320000
|
||||||
|
|
||||||
|
# Copy custom configuration and patches into the image.
|
||||||
|
COPY librechat.yaml /app/data/librechat.yaml
|
||||||
|
COPY patches/keyBroker.js /app/code/librechat/api/server/controllers/auth/keyBroker.js
|
||||||
|
COPY patches/oauth.js /app/code/librechat/api/server/controllers/auth/oauth.js
|
||||||
|
|
||||||
|
RUN chmod 644 /app/data/librechat.yaml \
|
||||||
|
&& chmod 644 /app/code/librechat/api/server/controllers/auth/keyBroker.js \
|
||||||
|
&& chmod 644 /app/code/librechat/api/server/controllers/auth/oauth.js
|
||||||
@@ -0,0 +1,32 @@
|
|||||||
|
# LibreChat sandbox maintenance
|
||||||
|
|
||||||
|
**Current image**
|
||||||
|
- Docker image: `cloudron/ai.librechat.cloudronapp:202610150224320000`
|
||||||
|
- Deployed as app **0752e430‑aa36‑436e‑8668‑ef18da474f47** (LibreChat sandbox at `https://librechat-test.inference.coop`).
|
||||||
|
|
||||||
|
**Automatic updates**
|
||||||
|
- The "Automatic updates" toggle in the Cloudron UI has been turned **off** (as of 2026‑10‑18). This prevents the sandbox from pulling newer Docker images automatically.
|
||||||
|
|
||||||
|
**Version‑pinning workflow**
|
||||||
|
1. **Create a version tag** when a new LibreChat release is approved (e.g.
|
||||||
|
`cloudron/ai.librechat.cloudronapp:202610250100000000`).
|
||||||
|
2. **Push the tagged image** to the co‑op registry (e.g. `registry.inference.coop/librechat:sandbox‑2026‑10‑25`).
|
||||||
|
```bash
|
||||||
|
docker pull cloudron/ai.librechat.cloudronapp:<new‑tag>
|
||||||
|
docker tag cloudron/ai.librechat.cloudronapp:<new‑tag> registry.inference.coop/librechat:sandbox‑<date>
|
||||||
|
docker login registry.inference.coop # use registry‑bot credentials
|
||||||
|
docker push registry.inference.coop/librechat:sandbox‑<date>
|
||||||
|
```
|
||||||
|
3. **Redeploy** the sandbox with the pinned tag:
|
||||||
|
```bash
|
||||||
|
cloudron update --app 0752e430-aa36-436e-8668-ef18da474f47 \
|
||||||
|
--image registry.inference.coop/librechat:sandbox‑<date>
|
||||||
|
```
|
||||||
|
4. **Verify** the app is running (`cloudron status --app <id>`) and that the image field reflects the new tag.
|
||||||
|
5. **Record** the change in this document (date, tag, reason) and optionally open a GitHub/Gitea issue linking to the upstream LibreChat release.
|
||||||
|
|
||||||
|
**Rollback**
|
||||||
|
- To roll back, repeat step 3 with the previous image tag.
|
||||||
|
|
||||||
|
**Future maintainers**
|
||||||
|
- All steps are captured in this doc and in the corresponding issue in `co‑op/support` (Issue #24). The `co‑op/docs` repository should contain this file; keep it up‑to‑date whenever the sandbox image changes or the automatic‑update flag is toggled.
|
||||||
+165
@@ -0,0 +1,165 @@
|
|||||||
|
version: 1.3.0
|
||||||
|
|
||||||
|
# Voice: server-side STT/TTS via the co-op gateway (Tinfoil TEE).
|
||||||
|
# Server STT works in every browser (Firefox has no Web Speech API).
|
||||||
|
speech:
|
||||||
|
stt:
|
||||||
|
openai:
|
||||||
|
url: "https://gateway.inference.coop/v1/audio/transcriptions"
|
||||||
|
apiKey: "${GATEWAY_KEY}"
|
||||||
|
model: "tinfoil/whisper-large-v3-turbo"
|
||||||
|
tts:
|
||||||
|
openai:
|
||||||
|
url: "https://gateway.inference.coop/v1/audio/speech"
|
||||||
|
apiKey: "${GATEWAY_KEY}"
|
||||||
|
model: "tinfoil/voxtral-tts"
|
||||||
|
voices: ["neutral_female", "neutral_male", "casual_female", "casual_male", "cheerful_female"]
|
||||||
|
cache: true
|
||||||
|
|
||||||
|
# Inference Co-op gateway — PER-USER keys (production pattern): no server-side
|
||||||
|
# apiKey here, so each user pastes their own dashboard key once. Usage then
|
||||||
|
# bills to that member's own LiteLLM team/budget, exactly like the API path.
|
||||||
|
endpoints:
|
||||||
|
custom:
|
||||||
|
- name: "Inference Co-op"
|
||||||
|
iconURL: "/assets/logo-coop.png"
|
||||||
|
apiKey: "user_provided"
|
||||||
|
baseURL: "https://gateway.inference.coop/v1"
|
||||||
|
models:
|
||||||
|
fetch: true
|
||||||
|
default:
|
||||||
|
- greenpt/deepseek-v4.1-flash
|
||||||
|
- tinfoil/deepseek-v4-1-flash
|
||||||
|
- tinfoil/gpt-oss-120b
|
||||||
|
- tinfoil/glm-5.3
|
||||||
|
- greenpt/glm-5.3-flash
|
||||||
|
- greenpt/glm-5.3
|
||||||
|
- regolo/apertus-70b
|
||||||
|
- regolo/qwen3.5-122b
|
||||||
|
titleConvo: true
|
||||||
|
titleModel: "greenpt/glm-5.3-flash"
|
||||||
|
titleMethod: "completion"
|
||||||
|
|
||||||
|
# Web search via the co-op's self-hosted SearXNG (public URL, JSON enabled for OWUI already).
|
||||||
|
# scraperProvider keenable = keyless page-fetch, so members get one-click search
|
||||||
|
# with no Firecrawl prompt. (Production note: keenable is an external keyless
|
||||||
|
# service — if that offends self-host values, swap in a self-hosted Firecrawl.)
|
||||||
|
webSearch:
|
||||||
|
searchProvider: "searxng"
|
||||||
|
scraperProvider: "keenable"
|
||||||
|
searxngInstanceUrl: "${SEARXNG_INSTANCE_URL}"
|
||||||
|
searxngSearchOptions:
|
||||||
|
engines: "google,bing,duckduckgo"
|
||||||
|
language: "en"
|
||||||
|
timeout: 15000
|
||||||
|
|
||||||
|
# MCP: the co-op docs, opt-in per conversation (stdlib stdio server in /app/data).
|
||||||
|
mcpServers:
|
||||||
|
inference-docs:
|
||||||
|
type: stdio
|
||||||
|
command: python3
|
||||||
|
args:
|
||||||
|
- /app/data/inference-docs-mcp/server.py
|
||||||
|
|
||||||
|
# Emoji-ified model picker (same feature-tier convention as models.md / OWUI):
|
||||||
|
# 🌱 = renewable-powered (GreenPT + Regolo), 🔒 = TEE-architected private (Tinfoil).
|
||||||
|
# Utility models (audio STT/TTS, image generation) are excluded from chat.
|
||||||
|
modelSpecs:
|
||||||
|
enforce: true
|
||||||
|
prioritize: true
|
||||||
|
list:
|
||||||
|
- name: tinfoil-deepseek-v4-1-flash
|
||||||
|
iconURL: "/assets/logo-coop.png"
|
||||||
|
label: "🔒 tinfoil/deepseek-v4-1-flash"
|
||||||
|
softDefault: true
|
||||||
|
preset:
|
||||||
|
endpoint: "Inference Co-op"
|
||||||
|
model: "tinfoil/deepseek-v4-1-flash"
|
||||||
|
- name: tinfoil-gpt-oss-120b
|
||||||
|
iconURL: "/assets/logo-coop.png"
|
||||||
|
label: "🔒 tinfoil/gpt-oss-120b"
|
||||||
|
preset:
|
||||||
|
endpoint: "Inference Co-op"
|
||||||
|
model: "tinfoil/gpt-oss-120b"
|
||||||
|
- name: tinfoil-glm-5-3
|
||||||
|
iconURL: "/assets/logo-coop.png"
|
||||||
|
label: "🔒 tinfoil/glm-5.3"
|
||||||
|
preset:
|
||||||
|
endpoint: "Inference Co-op"
|
||||||
|
model: "tinfoil/glm-5.3"
|
||||||
|
- name: greenpt-deepseek-v4-1-flash
|
||||||
|
iconURL: "/assets/logo-coop.png"
|
||||||
|
label: "🌱 greenpt/deepseek-v4.1-flash"
|
||||||
|
preset:
|
||||||
|
endpoint: "Inference Co-op"
|
||||||
|
model: "greenpt/deepseek-v4.1-flash"
|
||||||
|
- name: greenpt-glm-5-3-flash
|
||||||
|
iconURL: "/assets/logo-coop.png"
|
||||||
|
label: "🌱 greenpt/glm-5.3-flash"
|
||||||
|
preset:
|
||||||
|
endpoint: "Inference Co-op"
|
||||||
|
model: "greenpt/glm-5.3-flash"
|
||||||
|
- name: greenpt-glm-5-3
|
||||||
|
iconURL: "/assets/logo-coop.png"
|
||||||
|
label: "🌱 greenpt/glm-5.3"
|
||||||
|
preset:
|
||||||
|
endpoint: "Inference Co-op"
|
||||||
|
model: "greenpt/glm-5.3"
|
||||||
|
- name: regolo-apertus-70b
|
||||||
|
iconURL: "/assets/logo-coop.png"
|
||||||
|
label: "🌱 regolo/apertus-70b"
|
||||||
|
preset:
|
||||||
|
endpoint: "Inference Co-op"
|
||||||
|
model: "regolo/apertus-70b"
|
||||||
|
- name: regolo-qwen3-5-122b
|
||||||
|
iconURL: "/assets/logo-coop.png"
|
||||||
|
label: "🌱 regolo/qwen3.5-122b"
|
||||||
|
preset:
|
||||||
|
endpoint: "Inference Co-op"
|
||||||
|
model: "regolo/qwen3.5-122b"
|
||||||
|
- name: regolo-qwen-image
|
||||||
|
iconURL: "/assets/logo-coop.png"
|
||||||
|
label: "🖼️ regolo/qwen-image"
|
||||||
|
preset:
|
||||||
|
endpoint: "Inference Co-op"
|
||||||
|
model: "regolo/qwen-image"
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
# Inference Cooperative branding
|
||||||
|
interface:
|
||||||
|
customWelcome: "Welcome to your co-op AI."
|
||||||
|
theme:
|
||||||
|
version: 1
|
||||||
|
name: inference
|
||||||
|
modes:
|
||||||
|
light:
|
||||||
|
colors:
|
||||||
|
rgb-surface-primary: '250 248 245'
|
||||||
|
rgb-surface-secondary: '241 237 230'
|
||||||
|
rgb-surface-tertiary: '232 227 217'
|
||||||
|
rgb-surface-hover: '236 234 217'
|
||||||
|
rgb-surface-submit: '91 140 90'
|
||||||
|
rgb-surface-submit-hover: '74 122 74'
|
||||||
|
rgb-accent-primary: '91 140 90'
|
||||||
|
rgb-accent-primary-hover: '74 122 74'
|
||||||
|
rgb-ring-primary: '91 140 90'
|
||||||
|
rgb-link: '74 122 74'
|
||||||
|
rgb-text-primary: '36 48 32'
|
||||||
|
rgb-text-secondary: '90 107 82'
|
||||||
|
rgb-border-light: '213 221 208'
|
||||||
|
dark:
|
||||||
|
colors:
|
||||||
|
rgb-surface-primary: '31 36 27'
|
||||||
|
rgb-surface-secondary: '38 45 34'
|
||||||
|
rgb-surface-tertiary: '46 54 40'
|
||||||
|
rgb-surface-hover: '49 57 45'
|
||||||
|
rgb-surface-submit: '122 184 122'
|
||||||
|
rgb-surface-submit-hover: '142 200 142'
|
||||||
|
rgb-accent-primary: '122 184 122'
|
||||||
|
rgb-accent-primary-hover: '142 200 142'
|
||||||
|
rgb-ring-primary: '142 200 142'
|
||||||
|
rgb-link: '142 200 142'
|
||||||
|
rgb-text-primary: '236 233 226'
|
||||||
|
rgb-text-secondary: '168 173 158'
|
||||||
|
rgb-border-light: '74 82 64'
|
||||||
@@ -0,0 +1,76 @@
|
|||||||
|
/**
|
||||||
|
* Key broker sync — automatically provision a member's gateway key at SSO login.
|
||||||
|
*
|
||||||
|
* Inference Cooperative patch (MIT, same as LibreChat). When KEY_BROKER_URL is
|
||||||
|
* set, an OpenID (Cloudron SSO) login fetches-or-creates the member's chat key
|
||||||
|
* via the member portal's broker and stores it as the user's LibreChat key for
|
||||||
|
* the configured endpoint — so members never paste a key and all usage bills
|
||||||
|
* to their own LiteLLM team/budget.
|
||||||
|
*
|
||||||
|
* Env:
|
||||||
|
* KEY_BROKER_URL e.g. https://portal.inference.coop
|
||||||
|
* KEY_BROKER_SECRET the portal's BROKER_SECRET (x-broker-secret header)
|
||||||
|
* KEY_BROKER_KEY_NAME portal-side key name, default "chat"
|
||||||
|
* KEY_BROKER_ENDPOINT LibreChat endpoint (user-key) name, default "Inference Co-op"
|
||||||
|
*
|
||||||
|
* Semantics: create-on-first-login. If the member already has a portal-side
|
||||||
|
* "chat" key but LibreChat lost its stored copy, we deliberately do NOT rotate
|
||||||
|
* (rotation would silently break the member's own API scripts); they can
|
||||||
|
* rotate from the dashboard and log in again.
|
||||||
|
*/
|
||||||
|
const { updateUserKey, getUserKeyExpiry } = require('~/models');
|
||||||
|
const { logger } = require('@librechat/data-schemas');
|
||||||
|
|
||||||
|
async function syncBrokerUserKey(user) {
|
||||||
|
const base = process.env.KEY_BROKER_URL;
|
||||||
|
const secret = process.env.KEY_BROKER_SECRET;
|
||||||
|
const userId = user?._id?.toString?.() || user?.id;
|
||||||
|
if (!base || !secret || !userId || !user?.email) return;
|
||||||
|
const keyName = process.env.KEY_BROKER_KEY_NAME || 'chat';
|
||||||
|
const endpointName = process.env.KEY_BROKER_ENDPOINT || 'Inference Co-op';
|
||||||
|
const headers = {
|
||||||
|
'x-broker-secret': secret,
|
||||||
|
'x-member-email': user.email,
|
||||||
|
};
|
||||||
|
|
||||||
|
// Does the member already have a portal-side chat key?
|
||||||
|
const listRes = await fetch(`${base}/broker/keys`, { headers });
|
||||||
|
if (!listRes.ok) throw new Error(`broker list ${listRes.status}`);
|
||||||
|
const list = await listRes.json();
|
||||||
|
const exists = (list.keys || []).some((k) => k.name === keyName);
|
||||||
|
|
||||||
|
if (!exists) {
|
||||||
|
const createRes = await fetch(`${base}/broker/keys`, {
|
||||||
|
method: 'POST',
|
||||||
|
headers: { ...headers, 'content-type': 'application/json' },
|
||||||
|
body: JSON.stringify({ name: keyName }),
|
||||||
|
});
|
||||||
|
if (!createRes.ok) throw new Error(`broker create ${createRes.status}`);
|
||||||
|
const created = await createRes.json();
|
||||||
|
if (!created.sk_token) throw new Error('broker returned no sk_token');
|
||||||
|
// Store the key in LibreChat (JSON string as required by LibreChat storage)
|
||||||
|
await updateUserKey({
|
||||||
|
userId: userId,
|
||||||
|
name: endpointName,
|
||||||
|
value: JSON.stringify({ apiKey: created.sk_token }),
|
||||||
|
expiresAt: null,
|
||||||
|
});
|
||||||
|
logger.info(`[KeyBroker] provisioned "${keyName}" key for ${user.email} (endpoint: ${endpointName})`);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Portal-side key exists — check whether LibreChat still holds a stored copy.
|
||||||
|
try {
|
||||||
|
const stored = await getUserKeyExpiry({ userId: user.id, name: endpointName });
|
||||||
|
if (!stored) {
|
||||||
|
logger.warn(
|
||||||
|
`[KeyBroker] ${user.email} has a portal "${keyName}" key but no stored copy for endpoint "${endpointName}". ` +
|
||||||
|
'Rotate the key on the dashboard and log in again (manual rotation avoids breaking their API scripts).',
|
||||||
|
);
|
||||||
|
}
|
||||||
|
} catch (e) {
|
||||||
|
logger.warn(`[KeyBroker] stored-key check failed for ${user.email}: ${e.message}`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
module.exports = { syncBrokerUserKey };
|
||||||
Reference in new issue
Block a user