Add company pages + HTML5 history mode
- CompanyView.vue: hero, plattform, produkte, nyx CTA, footer - ImpressumView.vue + DatenschutzView.vue: legal pages - Router: HTML5 history mode (no # URLs), company routes - Reverted vite-ssg (SSR compat needs proper refactor, planned) - Removed ssr-shim.ts Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
parent
743eddfbd1
commit
bf047d1292
2324
.vite-ssg-temp/eyyt8tgg8y/assets/AgentsView-CFS236kk.js
Normal file
2324
.vite-ssg-temp/eyyt8tgg8y/assets/AgentsView-CFS236kk.js
Normal file
File diff suppressed because it is too large
Load Diff
66
.vite-ssg-temp/eyyt8tgg8y/assets/CompanyView-9MVdsTPz.js
Normal file
66
.vite-ssg-temp/eyyt8tgg8y/assets/CompanyView-9MVdsTPz.js
Normal file
@ -0,0 +1,66 @@
|
||||
import { resolveComponent, mergeProps, withCtx, createTextVNode, useSSRContext } from "vue";
|
||||
import { ssrRenderAttrs, ssrRenderComponent } from "vue/server-renderer";
|
||||
import { _ as _export_sfc } from "../main.mjs";
|
||||
import "@unhead/vue/server";
|
||||
import "vue-router";
|
||||
import "pinia";
|
||||
import "@heroicons/vue/24/outline";
|
||||
import "@heroicons/vue/20/solid";
|
||||
import "overlayscrollbars-vue";
|
||||
import "overlayscrollbars";
|
||||
const _sfc_main = {};
|
||||
function _sfc_ssrRender(_ctx, _push, _parent, _attrs) {
|
||||
const _component_router_link = resolveComponent("router-link");
|
||||
_push(`<div${ssrRenderAttrs(mergeProps({ class: "company-page" }, _attrs))} data-v-7d8ccc13><div class="hero" data-v-7d8ccc13><h1 data-v-7d8ccc13>Wir bauen<br data-v-7d8ccc13>agentische <em data-v-7d8ccc13>Produkte</em>.</h1><p data-v-7d8ccc13>Eigene Plattform. Eigene Infrastruktur. Eigene Regeln.</p></div><hr class="divider" data-v-7d8ccc13><section id="plattform" data-v-7d8ccc13><div class="section-label" data-v-7d8ccc13>Plattform</div><h2 data-v-7d8ccc13>Was dahintersteht.</h2><div class="cards" data-v-7d8ccc13><div class="card" data-v-7d8ccc13><h3 data-v-7d8ccc13>Das Wesentliche</h3><p data-v-7d8ccc13>Jedes Produkt kennt seinen Bereich und bleibt darin. Tiefe schlägt Breite — verlässlich, jeden Tag. Keine Extras, die niemand braucht.</p></div><div class="card" data-v-7d8ccc13><h3 data-v-7d8ccc13>Produkte für Teams</h3><p data-v-7d8ccc13>Ein Produkt für das ganze Team. Alle arbeiten mit demselben Zugang — in eigenen Sitzungen, mit voller Kontrolle darüber, wer was sieht.</p></div><div class="card" data-v-7d8ccc13><h3 data-v-7d8ccc13>Sicher & konform</h3><p data-v-7d8ccc13>Alle Daten liegen in Deutschland. Mandantentrennung auf Containerebene, DSGVO-konforme Verarbeitung, Auftragsverarbeitung inklusive.</p></div></div></section><hr class="divider" data-v-7d8ccc13><section id="produkte" data-v-7d8ccc13><div class="section-label" data-v-7d8ccc13>Produkte</div><h2 data-v-7d8ccc13>Woran wir gerade bauen.</h2><div class="cards" data-v-7d8ccc13><div class="card" data-v-7d8ccc13><h3 data-v-7d8ccc13>Frühkindliche Bildung</h3><p data-v-7d8ccc13>Fachkräfte sagen, was sie brauchen — unser Produkt baut daraus fertige Bildungsangebote. Passend zur Einrichtung, zur Altersgruppe, zum Alltag.</p></div><div class="card" data-v-7d8ccc13><h3 data-v-7d8ccc13>Erwachsenen-Grundbildung</h3><p data-v-7d8ccc13>Lehrkräfte an Bildungseinrichtungen bekommen Unterstützung bei der Erstellung von Lernangeboten — strukturiert, lehrplannah, sofort einsetzbar.</p></div></div></section><hr class="divider" data-v-7d8ccc13><div class="cta" data-v-7d8ccc13><h2 data-v-7d8ccc13>nyx</h2><p data-v-7d8ccc13>Produkte kennenlernen, ausprobieren, Zugang einrichten — direkt hier, direkt mit nyx.</p>`);
|
||||
_push(ssrRenderComponent(_component_router_link, {
|
||||
to: "/agents",
|
||||
class: "btn"
|
||||
}, {
|
||||
default: withCtx((_, _push2, _parent2, _scopeId) => {
|
||||
if (_push2) {
|
||||
_push2(`nyx öffnen`);
|
||||
} else {
|
||||
return [
|
||||
createTextVNode("nyx öffnen")
|
||||
];
|
||||
}
|
||||
}),
|
||||
_: 1
|
||||
}, _parent));
|
||||
_push(`</div><footer data-v-7d8ccc13><div class="footer-links" data-v-7d8ccc13>`);
|
||||
_push(ssrRenderComponent(_component_router_link, { to: "/impressum" }, {
|
||||
default: withCtx((_, _push2, _parent2, _scopeId) => {
|
||||
if (_push2) {
|
||||
_push2(`Impressum`);
|
||||
} else {
|
||||
return [
|
||||
createTextVNode("Impressum")
|
||||
];
|
||||
}
|
||||
}),
|
||||
_: 1
|
||||
}, _parent));
|
||||
_push(ssrRenderComponent(_component_router_link, { to: "/datenschutz" }, {
|
||||
default: withCtx((_, _push2, _parent2, _scopeId) => {
|
||||
if (_push2) {
|
||||
_push2(`Datenschutz`);
|
||||
} else {
|
||||
return [
|
||||
createTextVNode("Datenschutz")
|
||||
];
|
||||
}
|
||||
}),
|
||||
_: 1
|
||||
}, _parent));
|
||||
_push(`</div><span data-v-7d8ccc13>© 2026 loop42 UG (haftungsbeschränkt)</span></footer></div>`);
|
||||
}
|
||||
const _sfc_setup = _sfc_main.setup;
|
||||
_sfc_main.setup = (props, ctx) => {
|
||||
const ssrContext = useSSRContext();
|
||||
(ssrContext.modules || (ssrContext.modules = /* @__PURE__ */ new Set())).add("src/views/CompanyView.vue");
|
||||
return _sfc_setup ? _sfc_setup(props, ctx) : void 0;
|
||||
};
|
||||
const CompanyView = /* @__PURE__ */ _export_sfc(_sfc_main, [["ssrRender", _sfc_ssrRender], ["__scopeId", "data-v-7d8ccc13"]]);
|
||||
export {
|
||||
CompanyView as default
|
||||
};
|
||||
50
.vite-ssg-temp/eyyt8tgg8y/assets/DatenschutzView-CmUxA-7Z.js
Normal file
50
.vite-ssg-temp/eyyt8tgg8y/assets/DatenschutzView-CmUxA-7Z.js
Normal file
@ -0,0 +1,50 @@
|
||||
import { resolveComponent, mergeProps, withCtx, createTextVNode, useSSRContext } from "vue";
|
||||
import { ssrRenderAttrs, ssrRenderComponent } from "vue/server-renderer";
|
||||
import { _ as _export_sfc } from "../main.mjs";
|
||||
import "@unhead/vue/server";
|
||||
import "vue-router";
|
||||
import "pinia";
|
||||
import "@heroicons/vue/24/outline";
|
||||
import "@heroicons/vue/20/solid";
|
||||
import "overlayscrollbars-vue";
|
||||
import "overlayscrollbars";
|
||||
const _sfc_main = {};
|
||||
function _sfc_ssrRender(_ctx, _push, _parent, _attrs) {
|
||||
const _component_router_link = resolveComponent("router-link");
|
||||
_push(`<div${ssrRenderAttrs(mergeProps({ class: "legal-page" }, _attrs))} data-v-2cc9352e><h1 data-v-2cc9352e>Datenschutzerklärung</h1><h2 data-v-2cc9352e>1. Verantwortlicher</h2><p data-v-2cc9352e> loop42 UG (haftungsbeschränkt)<br data-v-2cc9352e> [NAME]<br data-v-2cc9352e> [STRASSE HAUSNUMMER], [PLZ ORT] </p><h2 data-v-2cc9352e>2. Erhebung und Speicherung personenbezogener Daten</h2><p data-v-2cc9352e> Beim Besuch dieser Website werden automatisch Informationen allgemeiner Natur erfasst (sog. Server-Logfiles). Diese umfassen u. a. den Browsertyp, das verwendete Betriebssystem, den Referrer, die IP-Adresse sowie Datum und Uhrzeit des Zugriffs. Diese Daten sind nicht bestimmten Personen zuordbar und werden nicht mit anderen Datenquellen zusammengeführt. </p><p data-v-2cc9352e> Eine darüber hinausgehende Erhebung personenbezogener Daten findet auf dieser Website derzeit nicht statt. Sobald weitere Dienste (z. B. Kontaktformular, Nutzerkonten) eingeführt werden, wird diese Erklärung entsprechend aktualisiert. </p><h2 data-v-2cc9352e>3. Hosting und Infrastruktur</h2><p data-v-2cc9352e> Diese Website wird auf einem Server in einem deutschen Rechenzentrum betrieben. Der Anbieter ist IONOS SE, Elgendorfer Str. 57, 56410 Montabaur, Deutschland. Weitere Informationen zum Datenschutz bei IONOS finden Sie unter <a href="https://www.ionos.de/terms-gtc/datenschutzerklaerung/" target="_blank" rel="noopener" data-v-2cc9352e>ionos.de</a>. </p><h2 data-v-2cc9352e>4. Cookies</h2><p data-v-2cc9352e>Diese Website verwendet keine Cookies.</p><h2 data-v-2cc9352e>5. Analyse-Tools und Tracking</h2><p data-v-2cc9352e> Diese Website verwendet keine Analyse-Tools, kein Tracking und keine eingebetteten Inhalte Dritter (z. B. Google Fonts, Social-Media-Widgets). Es werden keine Daten an Dritte übermittelt. </p><h2 data-v-2cc9352e>6. Ihre Rechte</h2><p data-v-2cc9352e>Sie haben gegenüber uns folgende Rechte hinsichtlich Ihrer personenbezogenen Daten:</p><ul data-v-2cc9352e><li data-v-2cc9352e>Recht auf Auskunft (Art. 15 DSGVO)</li><li data-v-2cc9352e>Recht auf Berichtigung (Art. 16 DSGVO)</li><li data-v-2cc9352e>Recht auf Löschung (Art. 17 DSGVO)</li><li data-v-2cc9352e>Recht auf Einschränkung der Verarbeitung (Art. 18 DSGVO)</li><li data-v-2cc9352e>Recht auf Datenübertragbarkeit (Art. 20 DSGVO)</li><li data-v-2cc9352e>Recht auf Widerspruch gegen die Verarbeitung (Art. 21 DSGVO)</li></ul><p data-v-2cc9352e>Zur Ausübung Ihrer Rechte wenden Sie sich bitte an: [E-MAIL]</p><h2 data-v-2cc9352e>7. Beschwerderecht bei der Aufsichtsbehörde</h2><p data-v-2cc9352e> Sie haben das Recht, sich bei einer Datenschutz-Aufsichtsbehörde über die Verarbeitung Ihrer personenbezogenen Daten durch uns zu beschweren. Die zuständige Aufsichtsbehörde richtet sich nach dem Bundesland des Unternehmenssitzes. </p><h2 data-v-2cc9352e>8. Aktualität und Änderungen</h2><p data-v-2cc9352e> Diese Datenschutzerklärung ist aktuell gültig und hat den Stand März 2026. Durch die Weiterentwicklung unserer Website oder aufgrund geänderter gesetzlicher Vorgaben kann es notwendig werden, diese Datenschutzerklärung anzupassen. </p><footer data-v-2cc9352e><div class="footer-links" data-v-2cc9352e>`);
|
||||
_push(ssrRenderComponent(_component_router_link, { to: "/impressum" }, {
|
||||
default: withCtx((_, _push2, _parent2, _scopeId) => {
|
||||
if (_push2) {
|
||||
_push2(`Impressum`);
|
||||
} else {
|
||||
return [
|
||||
createTextVNode("Impressum")
|
||||
];
|
||||
}
|
||||
}),
|
||||
_: 1
|
||||
}, _parent));
|
||||
_push(ssrRenderComponent(_component_router_link, { to: "/datenschutz" }, {
|
||||
default: withCtx((_, _push2, _parent2, _scopeId) => {
|
||||
if (_push2) {
|
||||
_push2(`Datenschutz`);
|
||||
} else {
|
||||
return [
|
||||
createTextVNode("Datenschutz")
|
||||
];
|
||||
}
|
||||
}),
|
||||
_: 1
|
||||
}, _parent));
|
||||
_push(`</div><span data-v-2cc9352e>© 2026 loop42 UG (haftungsbeschränkt)</span></footer></div>`);
|
||||
}
|
||||
const _sfc_setup = _sfc_main.setup;
|
||||
_sfc_main.setup = (props, ctx) => {
|
||||
const ssrContext = useSSRContext();
|
||||
(ssrContext.modules || (ssrContext.modules = /* @__PURE__ */ new Set())).add("src/views/DatenschutzView.vue");
|
||||
return _sfc_setup ? _sfc_setup(props, ctx) : void 0;
|
||||
};
|
||||
const DatenschutzView = /* @__PURE__ */ _export_sfc(_sfc_main, [["ssrRender", _sfc_ssrRender], ["__scopeId", "data-v-2cc9352e"]]);
|
||||
export {
|
||||
DatenschutzView as default
|
||||
};
|
||||
738
.vite-ssg-temp/eyyt8tgg8y/assets/DevView-CqaEvdwT.js
Normal file
738
.vite-ssg-temp/eyyt8tgg8y/assets/DevView-CqaEvdwT.js
Normal file
@ -0,0 +1,738 @@
|
||||
import { defineComponent, ref, computed, onMounted, nextTick, watch, onUnmounted, resolveComponent, unref, mergeProps, withCtx, createVNode, createTextVNode, resolveDynamicComponent, openBlock, createBlock, withDirectives, vModelCheckbox, toDisplayString, createCommentVNode, Fragment, renderList, Teleport, withModifiers, useSSRContext } from "vue";
|
||||
import { ssrRenderComponent, ssrIncludeBooleanAttr, ssrRenderClass, ssrRenderVNode, ssrLooseContain, ssrRenderStyle, ssrInterpolate, ssrRenderList, ssrRenderTeleport, ssrRenderAttrs } from "vue/server-renderer";
|
||||
import { useRoute } from "vue-router";
|
||||
import { t as takeover, g as getApiBase, w as ws, b as auth, s as scrollbarOptions, T as THEME_ICONS, f as useTheme, h as useDevFlags, _ as _export_sfc } from "../main.mjs";
|
||||
import { OverlayScrollbarsComponent } from "overlayscrollbars-vue";
|
||||
import { HomeIcon, BoltIcon, SignalSlashIcon, LinkIcon, XMarkIcon, CameraIcon, LockClosedIcon } from "@heroicons/vue/20/solid";
|
||||
import "@unhead/vue/server";
|
||||
import "pinia";
|
||||
import "@heroicons/vue/24/outline";
|
||||
import "overlayscrollbars";
|
||||
const SESSION_KEY = "nyx_session";
|
||||
const _sfc_main = /* @__PURE__ */ defineComponent({
|
||||
...{ name: "DevView" },
|
||||
__name: "DevView",
|
||||
__ssrInlineRender: true,
|
||||
setup(__props) {
|
||||
const route = useRoute();
|
||||
const { isLoggedIn } = auth;
|
||||
const { connected, send: wsSend, onMessage } = ws;
|
||||
const { theme, setTheme } = useTheme();
|
||||
const devFlags = useDevFlags();
|
||||
const takeoverToken = takeover.token;
|
||||
const captureActive = takeover.capture.isActive;
|
||||
const breakoutReq = takeover.breakout.pendingRequest;
|
||||
function handleInitTakeover() {
|
||||
takeover.init();
|
||||
}
|
||||
function handleRevoke() {
|
||||
takeover.revoke();
|
||||
}
|
||||
function confirmBreakout() {
|
||||
breakoutReq.value?.resolve(true);
|
||||
}
|
||||
function denyBreakout() {
|
||||
breakoutReq.value?.resolve(false);
|
||||
}
|
||||
async function handleToggleCapture() {
|
||||
if (captureActive.value) {
|
||||
takeover.capture.disable();
|
||||
} else {
|
||||
await takeover.capture.enable();
|
||||
}
|
||||
}
|
||||
const SESSION_TOKEN = () => localStorage.getItem(SESSION_KEY) ?? "";
|
||||
const systemRequests = ref([]);
|
||||
const systemGranted = ref(null);
|
||||
async function fetchPendingSystemRequests() {
|
||||
try {
|
||||
const res = await fetch(`${getApiBase()}/api/system/pending`, {
|
||||
headers: { Authorization: `Bearer ${SESSION_TOKEN()}` }
|
||||
});
|
||||
if (res.ok) {
|
||||
const { pending } = await res.json();
|
||||
systemRequests.value = pending ?? [];
|
||||
}
|
||||
} catch {
|
||||
}
|
||||
}
|
||||
async function approveSystemRequest(requestId) {
|
||||
const res = await fetch(`${getApiBase()}/api/system/approve`, {
|
||||
method: "POST",
|
||||
headers: { "Content-Type": "application/json" },
|
||||
body: JSON.stringify({ sessionToken: SESSION_TOKEN(), requestId })
|
||||
});
|
||||
if (res.ok) systemRequests.value = systemRequests.value.filter((r) => r.requestId !== requestId);
|
||||
}
|
||||
async function denySystemRequest(requestId) {
|
||||
await fetch(`${getApiBase()}/api/system/deny`, {
|
||||
method: "POST",
|
||||
headers: { "Content-Type": "application/json" },
|
||||
body: JSON.stringify({ sessionToken: SESSION_TOKEN(), requestId })
|
||||
});
|
||||
systemRequests.value = systemRequests.value.filter((r) => r.requestId !== requestId);
|
||||
}
|
||||
const counterValue = ref(Math.floor(Math.random() * 11));
|
||||
const counterBusy = ref(false);
|
||||
const counterMuted = ref(true);
|
||||
const challengeMessage = ref("");
|
||||
const challengeTimer = ref(0);
|
||||
let challengeInterval = null;
|
||||
let challengeGen = 0;
|
||||
const actionPicker = ref({ title: "", options: [] });
|
||||
const actionPickerBusy = ref(false);
|
||||
async function pickAction(id) {
|
||||
actionPickerBusy.value = true;
|
||||
actionPicker.value = { title: "", options: [] };
|
||||
try {
|
||||
await fetch(`${getApiBase()}/api/dev/counter`, {
|
||||
method: "POST",
|
||||
headers: { "Content-Type": "application/json", Authorization: `Bearer ${SESSION_TOKEN()}` },
|
||||
body: JSON.stringify({ action: "pick", pick: id })
|
||||
});
|
||||
} catch {
|
||||
}
|
||||
actionPickerBusy.value = false;
|
||||
}
|
||||
async function counterAction(action) {
|
||||
counterMuted.value = true;
|
||||
challengeGen++;
|
||||
if (challengeInterval) {
|
||||
clearInterval(challengeInterval);
|
||||
challengeInterval = null;
|
||||
}
|
||||
challengeMessage.value = "";
|
||||
challengeTimer.value = 0;
|
||||
counterBusy.value = true;
|
||||
try {
|
||||
await fetch(`${getApiBase()}/api/dev/counter`, {
|
||||
method: "POST",
|
||||
headers: { "Content-Type": "application/json", Authorization: `Bearer ${SESSION_TOKEN()}` },
|
||||
body: JSON.stringify({ action })
|
||||
});
|
||||
} catch {
|
||||
}
|
||||
counterBusy.value = false;
|
||||
}
|
||||
const loading = ref(false);
|
||||
const statsError = ref("");
|
||||
const stats = ref(null);
|
||||
function fmt(v) {
|
||||
if (v === null || v === void 0) return "—";
|
||||
return v.toFixed(2);
|
||||
}
|
||||
const usedPct = computed(() => {
|
||||
const t = stats.value?.credits?.total;
|
||||
const u = stats.value?.credits?.used;
|
||||
if (!t) return 0;
|
||||
return Math.min(100, u / t * 100);
|
||||
});
|
||||
function load() {
|
||||
if (!connected.value) return;
|
||||
loading.value = true;
|
||||
statsError.value = "";
|
||||
wsSend({ type: "stats_request" });
|
||||
}
|
||||
const discoing = ref(false);
|
||||
const discoChatting = ref(false);
|
||||
function disco() {
|
||||
if (!connected.value) return;
|
||||
discoing.value = true;
|
||||
wsSend({ type: "disco_request" });
|
||||
setTimeout(() => {
|
||||
discoing.value = false;
|
||||
}, 2e3);
|
||||
}
|
||||
function discoChat() {
|
||||
if (!connected.value) return;
|
||||
discoChatting.value = true;
|
||||
wsSend({ type: "disco_chat_request" });
|
||||
setTimeout(() => {
|
||||
discoChatting.value = false;
|
||||
}, 3e3);
|
||||
}
|
||||
let unsubscribeWs = null;
|
||||
let refreshInterval;
|
||||
onMounted(() => {
|
||||
fetchPendingSystemRequests();
|
||||
unsubscribeWs = onMessage((data) => {
|
||||
if (data.type === "system_access_request") {
|
||||
if (!systemRequests.value.find((r) => r.requestId === data.requestId))
|
||||
systemRequests.value.push(data);
|
||||
return;
|
||||
}
|
||||
if (data.type === "counter_update") {
|
||||
counterValue.value = data.value ?? counterValue.value;
|
||||
return;
|
||||
}
|
||||
if (data.type === "counter_challenge") {
|
||||
challengeMessage.value = data.message || "DECIDE NOW!";
|
||||
challengeTimer.value = data.timeout || 30;
|
||||
counterMuted.value = false;
|
||||
nextTick(() => {
|
||||
const el = document.querySelector(".counter-widget");
|
||||
if (el) {
|
||||
el.classList.remove("flash");
|
||||
void el.offsetWidth;
|
||||
el.classList.add("flash");
|
||||
el.scrollIntoView({ behavior: "smooth", block: "center" });
|
||||
}
|
||||
});
|
||||
if (challengeInterval) clearInterval(challengeInterval);
|
||||
const cid = ++challengeGen;
|
||||
challengeInterval = setInterval(() => {
|
||||
if (cid !== challengeGen) {
|
||||
clearInterval(challengeInterval);
|
||||
return;
|
||||
}
|
||||
challengeTimer.value--;
|
||||
if (challengeTimer.value <= 0) {
|
||||
if (challengeInterval) {
|
||||
clearInterval(challengeInterval);
|
||||
challengeInterval = null;
|
||||
}
|
||||
counterMuted.value = true;
|
||||
challengeMessage.value = "";
|
||||
fetch(`${getApiBase()}/api/dev/counter`, {
|
||||
method: "POST",
|
||||
headers: { "Content-Type": "application/json", Authorization: `Bearer ${SESSION_TOKEN()}` },
|
||||
body: JSON.stringify({ action: "timeout" })
|
||||
});
|
||||
}
|
||||
}, 1e3);
|
||||
return;
|
||||
}
|
||||
if (data.type === "confetti") {
|
||||
const container = document.createElement("div");
|
||||
container.className = "confetti-container";
|
||||
const colors = ["#ff6b6b", "#ffd93d", "#6bcb77", "#4d96ff", "#ff922b", "#cc5de8"];
|
||||
for (let i = 0; i < 60; i++) {
|
||||
const piece = document.createElement("div");
|
||||
piece.className = "confetti-piece";
|
||||
piece.style.left = Math.random() * 100 + "%";
|
||||
piece.style.background = colors[Math.floor(Math.random() * colors.length)];
|
||||
piece.style.animationDelay = Math.random() * 2 + "s";
|
||||
piece.style.borderRadius = Math.random() > 0.5 ? "50%" : "0";
|
||||
container.appendChild(piece);
|
||||
}
|
||||
document.body.appendChild(container);
|
||||
setTimeout(() => container.remove(), 5e3);
|
||||
return;
|
||||
}
|
||||
if (data.type === "action_picker") {
|
||||
actionPicker.value = { title: data.title || "Next?", options: data.options || [] };
|
||||
actionPickerBusy.value = false;
|
||||
return;
|
||||
}
|
||||
if (data.type === "counter_mute") {
|
||||
counterMuted.value = true;
|
||||
challengeMessage.value = data.message || "";
|
||||
if (challengeInterval) {
|
||||
clearInterval(challengeInterval);
|
||||
challengeInterval = null;
|
||||
}
|
||||
challengeTimer.value = 0;
|
||||
return;
|
||||
}
|
||||
if (data.type === "stats") {
|
||||
loading.value = false;
|
||||
if (data.error) {
|
||||
statsError.value = data.error;
|
||||
} else {
|
||||
stats.value = data;
|
||||
}
|
||||
}
|
||||
});
|
||||
const startRefresh = () => {
|
||||
load();
|
||||
refreshInterval = setInterval(load, 15e3);
|
||||
};
|
||||
if (connected.value) {
|
||||
startRefresh();
|
||||
} else {
|
||||
const stop = watch(connected, (val) => {
|
||||
if (val) {
|
||||
stop();
|
||||
startRefresh();
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
watch(() => route.name, (name, prev) => {
|
||||
if (name === "dev" && !refreshInterval && connected.value) {
|
||||
load();
|
||||
refreshInterval = setInterval(load, 15e3);
|
||||
} else if (prev === "dev" && name !== "dev" && refreshInterval) {
|
||||
clearInterval(refreshInterval);
|
||||
refreshInterval = void 0;
|
||||
}
|
||||
});
|
||||
onUnmounted(() => {
|
||||
if (unsubscribeWs) unsubscribeWs();
|
||||
if (refreshInterval) clearInterval(refreshInterval);
|
||||
});
|
||||
return (_ctx, _push, _parent, _attrs) => {
|
||||
const _component_RouterLink = resolveComponent("RouterLink");
|
||||
if (unref(isLoggedIn)) {
|
||||
_push(ssrRenderComponent(unref(OverlayScrollbarsComponent), mergeProps({
|
||||
class: "dev-view",
|
||||
options: unref(scrollbarOptions),
|
||||
element: "div"
|
||||
}, _attrs), {
|
||||
default: withCtx((_, _push2, _parent2, _scopeId) => {
|
||||
if (_push2) {
|
||||
_push2(`<div class="page" data-v-6fcff79f${_scopeId}><div class="dev-header" data-v-6fcff79f${_scopeId}><h2 data-v-6fcff79f${_scopeId}>/dev</h2><div class="dev-actions" data-v-6fcff79f${_scopeId}>`);
|
||||
_push2(ssrRenderComponent(_component_RouterLink, {
|
||||
to: "/agents",
|
||||
class: "dev-disco-btn"
|
||||
}, {
|
||||
default: withCtx((_2, _push3, _parent3, _scopeId2) => {
|
||||
if (_push3) {
|
||||
_push3(ssrRenderComponent(unref(HomeIcon), { class: "w-4 h-4 inline" }, null, _parent3, _scopeId2));
|
||||
_push3(` Home`);
|
||||
} else {
|
||||
return [
|
||||
createVNode(unref(HomeIcon), { class: "w-4 h-4 inline" }),
|
||||
createTextVNode(" Home")
|
||||
];
|
||||
}
|
||||
}),
|
||||
_: 1
|
||||
}, _parent2, _scopeId));
|
||||
_push2(`<button class="dev-disco-btn"${ssrIncludeBooleanAttr(discoing.value) ? " disabled" : ""} data-v-6fcff79f${_scopeId}>`);
|
||||
_push2(ssrRenderComponent(unref(BoltIcon), { class: "w-4 h-4 inline" }, null, _parent2, _scopeId));
|
||||
_push2(` Disconnect Gateway</button><button class="dev-disco-btn"${ssrIncludeBooleanAttr(discoChatting.value) ? " disabled" : ""} data-v-6fcff79f${_scopeId}>`);
|
||||
_push2(ssrRenderComponent(unref(SignalSlashIcon), { class: "w-4 h-4 inline" }, null, _parent2, _scopeId));
|
||||
_push2(` Disconnect Chat</button></div></div><div class="content" data-v-6fcff79f${_scopeId}><div class="dev-section" data-v-6fcff79f${_scopeId}><h3 data-v-6fcff79f${_scopeId}>Theme</h3><div class="dev-actions" data-v-6fcff79f${_scopeId}><button class="${ssrRenderClass([{ active: unref(theme) === "loop42" }, "dev-theme-btn"])}" data-v-6fcff79f${_scopeId}>`);
|
||||
ssrRenderVNode(_push2, createVNode(resolveDynamicComponent(unref(THEME_ICONS).loop42), { class: "w-4 h-4 inline" }, null), _parent2, _scopeId);
|
||||
_push2(` loop42</button><button class="${ssrRenderClass([{ active: unref(theme) === "titan" }, "dev-theme-btn"])}" data-v-6fcff79f${_scopeId}>`);
|
||||
ssrRenderVNode(_push2, createVNode(resolveDynamicComponent(unref(THEME_ICONS).titan), { class: "w-4 h-4 inline" }, null), _parent2, _scopeId);
|
||||
_push2(` Titan</button><button class="${ssrRenderClass([{ active: unref(theme) === "eras" }, "dev-theme-btn"])}" data-v-6fcff79f${_scopeId}>`);
|
||||
ssrRenderVNode(_push2, createVNode(resolveDynamicComponent(unref(THEME_ICONS).eras), { class: "w-4 h-4 inline" }, null), _parent2, _scopeId);
|
||||
_push2(` ERAS</button></div></div><div class="dev-section" data-v-6fcff79f${_scopeId}><h3 data-v-6fcff79f${_scopeId}>Dev Flags</h3><div class="dev-flags" data-v-6fcff79f${_scopeId}><label class="dev-flag" data-v-6fcff79f${_scopeId}><input type="checkbox"${ssrIncludeBooleanAttr(Array.isArray(unref(devFlags).showGrid) ? ssrLooseContain(unref(devFlags).showGrid, null) : unref(devFlags).showGrid) ? " checked" : ""} data-v-6fcff79f${_scopeId}><span data-v-6fcff79f${_scopeId}>showGrid</span></label><label class="dev-flag" data-v-6fcff79f${_scopeId}><input type="checkbox"${ssrIncludeBooleanAttr(Array.isArray(unref(devFlags).showDebugInfo) ? ssrLooseContain(unref(devFlags).showDebugInfo, null) : unref(devFlags).showDebugInfo) ? " checked" : ""} data-v-6fcff79f${_scopeId}><span data-v-6fcff79f${_scopeId}>showDebugInfo</span></label><label class="dev-flag" data-v-6fcff79f${_scopeId}><input type="checkbox"${ssrIncludeBooleanAttr(Array.isArray(unref(devFlags).showHud) ? ssrLooseContain(unref(devFlags).showHud, null) : unref(devFlags).showHud) ? " checked" : ""} data-v-6fcff79f${_scopeId}><span data-v-6fcff79f${_scopeId}>showHud</span></label></div></div><div class="dev-section" data-v-6fcff79f${_scopeId}><h3 data-v-6fcff79f${_scopeId}>Takeover</h3>`);
|
||||
if (!unref(takeoverToken)) {
|
||||
_push2(`<div class="dev-actions" data-v-6fcff79f${_scopeId}><button class="dev-theme-btn" data-v-6fcff79f${_scopeId}>`);
|
||||
_push2(ssrRenderComponent(unref(LinkIcon), { class: "w-4 h-4 inline" }, null, _parent2, _scopeId));
|
||||
_push2(` Enable Takeover</button></div>`);
|
||||
} else {
|
||||
_push2(`<div class="dev-actions" style="${ssrRenderStyle({ "gap": "var(--space-page)", "align-items": "center", "flex-wrap": "wrap" })}" data-v-6fcff79f${_scopeId}><code class="takeover-token" data-v-6fcff79f${_scopeId}>${ssrInterpolate(unref(takeoverToken))}</code><button class="dev-theme-btn active" data-v-6fcff79f${_scopeId}>`);
|
||||
_push2(ssrRenderComponent(unref(XMarkIcon), { class: "w-4 h-4 inline" }, null, _parent2, _scopeId));
|
||||
_push2(` Revoke</button><button class="${ssrRenderClass([{ active: unref(captureActive) }, "dev-theme-btn"])}" data-v-6fcff79f${_scopeId}>`);
|
||||
_push2(ssrRenderComponent(unref(CameraIcon), { class: "w-4 h-4 inline" }, null, _parent2, _scopeId));
|
||||
_push2(` ${ssrInterpolate(unref(captureActive) ? "Capture ON" : "Enable Capture")}</button></div>`);
|
||||
}
|
||||
_push2(`</div>`);
|
||||
if (systemRequests.value.length > 0 || systemGranted.value) {
|
||||
_push2(`<div class="dev-section" data-v-6fcff79f${_scopeId}><h3 data-v-6fcff79f${_scopeId}>System Access</h3>`);
|
||||
if (systemGranted.value) {
|
||||
_push2(`<div class="dev-system-granted" data-v-6fcff79f${_scopeId}> Access granted to ${ssrInterpolate(systemGranted.value.user)} — active until session end. </div>`);
|
||||
} else {
|
||||
_push2(`<!---->`);
|
||||
}
|
||||
_push2(`<!--[-->`);
|
||||
ssrRenderList(systemRequests.value, (req) => {
|
||||
_push2(`<div class="dev-system-request" data-v-6fcff79f${_scopeId}><div class="dev-system-code" data-v-6fcff79f${_scopeId}>${ssrInterpolate(req.userCode)}</div><div class="dev-system-desc" data-v-6fcff79f${_scopeId}>${ssrInterpolate(req.description)}</div><div class="dev-system-expiry" data-v-6fcff79f${_scopeId}>expires in ${ssrInterpolate(Math.max(0, Math.ceil((req.expiresAt - Date.now()) / 1e3)))}s</div><div class="dev-actions" style="${ssrRenderStyle({ "margin-top": "8px" })}" data-v-6fcff79f${_scopeId}><button class="dev-theme-btn" data-v-6fcff79f${_scopeId}>Deny</button><button class="dev-theme-btn active" data-v-6fcff79f${_scopeId}>Approve</button></div></div>`);
|
||||
});
|
||||
_push2(`<!--]--></div>`);
|
||||
} else {
|
||||
_push2(`<!---->`);
|
||||
}
|
||||
_push2(`<div class="dev-section" data-v-6fcff79f${_scopeId}><h3 data-v-6fcff79f${_scopeId}>MCP Counter</h3><div class="${ssrRenderClass([{ muted: counterMuted.value }, "counter-widget"])}" data-v-6fcff79f${_scopeId}><div class="counter-controls" data-v-6fcff79f${_scopeId}><button class="counter-btn"${ssrIncludeBooleanAttr(counterMuted.value || counterBusy.value) ? " disabled" : ""} data-v-6fcff79f${_scopeId}>−</button><span class="counter-value" id="mcp-counter-value" data-v-6fcff79f${_scopeId}>${ssrInterpolate(counterValue.value)}</span><button class="counter-btn"${ssrIncludeBooleanAttr(counterMuted.value || counterBusy.value) ? " disabled" : ""} data-v-6fcff79f${_scopeId}>+</button></div>`);
|
||||
if (challengeMessage.value) {
|
||||
_push2(`<div class="counter-challenge" data-v-6fcff79f${_scopeId}><span class="counter-message" data-v-6fcff79f${_scopeId}>${ssrInterpolate(challengeMessage.value)}</span>`);
|
||||
if (challengeTimer.value > 0) {
|
||||
_push2(`<span class="counter-timer" data-v-6fcff79f${_scopeId}>${ssrInterpolate(challengeTimer.value)}s</span>`);
|
||||
} else {
|
||||
_push2(`<!---->`);
|
||||
}
|
||||
_push2(`</div>`);
|
||||
} else {
|
||||
_push2(`<div class="counter-hint" data-v-6fcff79f${_scopeId}>Waiting for Claude...</div>`);
|
||||
}
|
||||
_push2(`</div></div>`);
|
||||
if (actionPicker.value.title) {
|
||||
_push2(`<div class="dev-section" data-v-6fcff79f${_scopeId}><h3 data-v-6fcff79f${_scopeId}>${ssrInterpolate(actionPicker.value.title)}</h3><div class="action-picker" data-v-6fcff79f${_scopeId}><!--[-->`);
|
||||
ssrRenderList(actionPicker.value.options, (opt, i) => {
|
||||
_push2(`<button class="action-pick-btn"${ssrIncludeBooleanAttr(actionPickerBusy.value) ? " disabled" : ""} data-v-6fcff79f${_scopeId}>${ssrInterpolate(opt.label)}</button>`);
|
||||
});
|
||||
_push2(`<!--]--></div></div>`);
|
||||
} else {
|
||||
_push2(`<!---->`);
|
||||
}
|
||||
_push2(`<div class="dev-section" data-v-6fcff79f${_scopeId}><h3 data-v-6fcff79f${_scopeId}>OpenRouter Credits</h3>`);
|
||||
if (loading.value && !stats.value) {
|
||||
_push2(`<div class="dev-loading" data-v-6fcff79f${_scopeId}>Loading…</div>`);
|
||||
} else if (statsError.value) {
|
||||
_push2(`<div class="dev-error" data-v-6fcff79f${_scopeId}>${ssrInterpolate(statsError.value)}</div>`);
|
||||
} else if (stats.value) {
|
||||
_push2(`<div class="credits-widget" data-v-6fcff79f${_scopeId}><div class="credits-bar-track" data-v-6fcff79f${_scopeId}><div class="credits-bar-fill" style="${ssrRenderStyle({ width: usedPct.value + "%" })}" data-v-6fcff79f${_scopeId}></div></div><div class="credits-row" data-v-6fcff79f${_scopeId}><div class="credits-stat" data-v-6fcff79f${_scopeId}><span class="credits-label" data-v-6fcff79f${_scopeId}>Used</span><span class="credits-amount credits-used" data-v-6fcff79f${_scopeId}>$${ssrInterpolate(fmt(stats.value.credits.used))}</span></div><div class="credits-stat" data-v-6fcff79f${_scopeId}><span class="credits-label" data-v-6fcff79f${_scopeId}>Remaining</span><span class="credits-amount credits-remaining" data-v-6fcff79f${_scopeId}>$${ssrInterpolate(fmt(stats.value.credits.remaining))}</span></div><div class="credits-stat" data-v-6fcff79f${_scopeId}><span class="credits-label" data-v-6fcff79f${_scopeId}>Total</span><span class="credits-amount" data-v-6fcff79f${_scopeId}>$${ssrInterpolate(fmt(stats.value.credits.total))}</span></div></div></div>`);
|
||||
} else {
|
||||
_push2(`<!---->`);
|
||||
}
|
||||
_push2(`</div>`);
|
||||
if (stats.value) {
|
||||
_push2(`<div class="dev-section" data-v-6fcff79f${_scopeId}><h3 data-v-6fcff79f${_scopeId}>Agents</h3><div class="dev-table-wrap" data-v-6fcff79f${_scopeId}><table class="dev-table" data-v-6fcff79f${_scopeId}><thead data-v-6fcff79f${_scopeId}><tr data-v-6fcff79f${_scopeId}><th data-v-6fcff79f${_scopeId}>Agent</th><th data-v-6fcff79f${_scopeId}>Model</th><th data-v-6fcff79f${_scopeId}>Context</th><th data-v-6fcff79f${_scopeId}>Prompt / 1M</th><th data-v-6fcff79f${_scopeId}>Completion / 1M</th></tr></thead><tbody data-v-6fcff79f${_scopeId}><!--[-->`);
|
||||
ssrRenderList(stats.value.agents, (a) => {
|
||||
_push2(`<tr data-v-6fcff79f${_scopeId}><td class="agent-id" data-v-6fcff79f${_scopeId}>${ssrInterpolate(a.id)}</td><td data-v-6fcff79f${_scopeId}><code data-v-6fcff79f${_scopeId}>${ssrInterpolate(a.modelId || a.model)}</code></td><td data-v-6fcff79f${_scopeId}>${ssrInterpolate(a.contextLength ? (a.contextLength / 1e3).toFixed(0) + "k" : "—")}</td><td data-v-6fcff79f${_scopeId}>${ssrInterpolate(a.promptPrice !== null ? "$" + a.promptPrice.toFixed(3) : "—")}</td><td data-v-6fcff79f${_scopeId}>${ssrInterpolate(a.completionPrice !== null ? "$" + a.completionPrice.toFixed(3) : "—")}</td></tr>`);
|
||||
});
|
||||
_push2(`<!--]--></tbody></table></div></div>`);
|
||||
} else {
|
||||
_push2(`<!---->`);
|
||||
}
|
||||
_push2(`</div></div>`);
|
||||
ssrRenderTeleport(_push2, (_push3) => {
|
||||
if (unref(breakoutReq)) {
|
||||
_push3(`<div class="breakout-modal-overlay" data-v-6fcff79f${_scopeId}><div class="breakout-modal" data-v-6fcff79f${_scopeId}><h3 data-v-6fcff79f${_scopeId}>Open Breakout</h3><p data-v-6fcff79f${_scopeId}>Name: <strong data-v-6fcff79f${_scopeId}>${ssrInterpolate(unref(breakoutReq).name)}</strong></p><p data-v-6fcff79f${_scopeId}>Size: <strong data-v-6fcff79f${_scopeId}>${ssrInterpolate(unref(breakoutReq).preset)}</strong></p><p class="breakout-nonce" data-v-6fcff79f${_scopeId}>${ssrInterpolate(unref(breakoutReq).nonce)}</p><div class="breakout-modal-actions" data-v-6fcff79f${_scopeId}><button class="dev-theme-btn" data-v-6fcff79f${_scopeId}>Cancel</button><button class="dev-theme-btn active" data-v-6fcff79f${_scopeId}>Confirm</button></div></div></div>`);
|
||||
} else {
|
||||
_push3(`<!---->`);
|
||||
}
|
||||
}, "body", false, _parent2);
|
||||
} else {
|
||||
return [
|
||||
createVNode("div", { class: "page" }, [
|
||||
createVNode("div", { class: "dev-header" }, [
|
||||
createVNode("h2", null, "/dev"),
|
||||
createVNode("div", { class: "dev-actions" }, [
|
||||
createVNode(_component_RouterLink, {
|
||||
to: "/agents",
|
||||
class: "dev-disco-btn"
|
||||
}, {
|
||||
default: withCtx(() => [
|
||||
createVNode(unref(HomeIcon), { class: "w-4 h-4 inline" }),
|
||||
createTextVNode(" Home")
|
||||
]),
|
||||
_: 1
|
||||
}),
|
||||
createVNode("button", {
|
||||
class: "dev-disco-btn",
|
||||
onClick: disco,
|
||||
disabled: discoing.value
|
||||
}, [
|
||||
createVNode(unref(BoltIcon), { class: "w-4 h-4 inline" }),
|
||||
createTextVNode(" Disconnect Gateway")
|
||||
], 8, ["disabled"]),
|
||||
createVNode("button", {
|
||||
class: "dev-disco-btn",
|
||||
onClick: discoChat,
|
||||
disabled: discoChatting.value
|
||||
}, [
|
||||
createVNode(unref(SignalSlashIcon), { class: "w-4 h-4 inline" }),
|
||||
createTextVNode(" Disconnect Chat")
|
||||
], 8, ["disabled"])
|
||||
])
|
||||
]),
|
||||
createVNode("div", { class: "content" }, [
|
||||
createVNode("div", { class: "dev-section" }, [
|
||||
createVNode("h3", null, "Theme"),
|
||||
createVNode("div", { class: "dev-actions" }, [
|
||||
createVNode("button", {
|
||||
class: ["dev-theme-btn", { active: unref(theme) === "loop42" }],
|
||||
onClick: ($event) => unref(setTheme)("loop42")
|
||||
}, [
|
||||
(openBlock(), createBlock(resolveDynamicComponent(unref(THEME_ICONS).loop42), { class: "w-4 h-4 inline" })),
|
||||
createTextVNode(" loop42")
|
||||
], 10, ["onClick"]),
|
||||
createVNode("button", {
|
||||
class: ["dev-theme-btn", { active: unref(theme) === "titan" }],
|
||||
onClick: ($event) => unref(setTheme)("titan")
|
||||
}, [
|
||||
(openBlock(), createBlock(resolveDynamicComponent(unref(THEME_ICONS).titan), { class: "w-4 h-4 inline" })),
|
||||
createTextVNode(" Titan")
|
||||
], 10, ["onClick"]),
|
||||
createVNode("button", {
|
||||
class: ["dev-theme-btn", { active: unref(theme) === "eras" }],
|
||||
onClick: ($event) => unref(setTheme)("eras")
|
||||
}, [
|
||||
(openBlock(), createBlock(resolveDynamicComponent(unref(THEME_ICONS).eras), { class: "w-4 h-4 inline" })),
|
||||
createTextVNode(" ERAS")
|
||||
], 10, ["onClick"])
|
||||
])
|
||||
]),
|
||||
createVNode("div", { class: "dev-section" }, [
|
||||
createVNode("h3", null, "Dev Flags"),
|
||||
createVNode("div", { class: "dev-flags" }, [
|
||||
createVNode("label", { class: "dev-flag" }, [
|
||||
withDirectives(createVNode("input", {
|
||||
type: "checkbox",
|
||||
"onUpdate:modelValue": ($event) => unref(devFlags).showGrid = $event
|
||||
}, null, 8, ["onUpdate:modelValue"]), [
|
||||
[vModelCheckbox, unref(devFlags).showGrid]
|
||||
]),
|
||||
createVNode("span", null, "showGrid")
|
||||
]),
|
||||
createVNode("label", { class: "dev-flag" }, [
|
||||
withDirectives(createVNode("input", {
|
||||
type: "checkbox",
|
||||
"onUpdate:modelValue": ($event) => unref(devFlags).showDebugInfo = $event
|
||||
}, null, 8, ["onUpdate:modelValue"]), [
|
||||
[vModelCheckbox, unref(devFlags).showDebugInfo]
|
||||
]),
|
||||
createVNode("span", null, "showDebugInfo")
|
||||
]),
|
||||
createVNode("label", { class: "dev-flag" }, [
|
||||
withDirectives(createVNode("input", {
|
||||
type: "checkbox",
|
||||
"onUpdate:modelValue": ($event) => unref(devFlags).showHud = $event
|
||||
}, null, 8, ["onUpdate:modelValue"]), [
|
||||
[vModelCheckbox, unref(devFlags).showHud]
|
||||
]),
|
||||
createVNode("span", null, "showHud")
|
||||
])
|
||||
])
|
||||
]),
|
||||
createVNode("div", { class: "dev-section" }, [
|
||||
createVNode("h3", null, "Takeover"),
|
||||
!unref(takeoverToken) ? (openBlock(), createBlock("div", {
|
||||
key: 0,
|
||||
class: "dev-actions"
|
||||
}, [
|
||||
createVNode("button", {
|
||||
class: "dev-theme-btn",
|
||||
onClick: handleInitTakeover
|
||||
}, [
|
||||
createVNode(unref(LinkIcon), { class: "w-4 h-4 inline" }),
|
||||
createTextVNode(" Enable Takeover")
|
||||
])
|
||||
])) : (openBlock(), createBlock("div", {
|
||||
key: 1,
|
||||
class: "dev-actions",
|
||||
style: { "gap": "var(--space-page)", "align-items": "center", "flex-wrap": "wrap" }
|
||||
}, [
|
||||
createVNode("code", { class: "takeover-token" }, toDisplayString(unref(takeoverToken)), 1),
|
||||
createVNode("button", {
|
||||
class: "dev-theme-btn active",
|
||||
onClick: handleRevoke
|
||||
}, [
|
||||
createVNode(unref(XMarkIcon), { class: "w-4 h-4 inline" }),
|
||||
createTextVNode(" Revoke")
|
||||
]),
|
||||
createVNode("button", {
|
||||
class: ["dev-theme-btn", { active: unref(captureActive) }],
|
||||
onClick: handleToggleCapture
|
||||
}, [
|
||||
createVNode(unref(CameraIcon), { class: "w-4 h-4 inline" }),
|
||||
createTextVNode(" " + toDisplayString(unref(captureActive) ? "Capture ON" : "Enable Capture"), 1)
|
||||
], 2)
|
||||
]))
|
||||
]),
|
||||
systemRequests.value.length > 0 || systemGranted.value ? (openBlock(), createBlock("div", {
|
||||
key: 0,
|
||||
class: "dev-section"
|
||||
}, [
|
||||
createVNode("h3", null, "System Access"),
|
||||
systemGranted.value ? (openBlock(), createBlock("div", {
|
||||
key: 0,
|
||||
class: "dev-system-granted"
|
||||
}, " Access granted to " + toDisplayString(systemGranted.value.user) + " — active until session end. ", 1)) : createCommentVNode("", true),
|
||||
(openBlock(true), createBlock(Fragment, null, renderList(systemRequests.value, (req) => {
|
||||
return openBlock(), createBlock("div", {
|
||||
key: req.requestId,
|
||||
class: "dev-system-request"
|
||||
}, [
|
||||
createVNode("div", { class: "dev-system-code" }, toDisplayString(req.userCode), 1),
|
||||
createVNode("div", { class: "dev-system-desc" }, toDisplayString(req.description), 1),
|
||||
createVNode("div", { class: "dev-system-expiry" }, "expires in " + toDisplayString(Math.max(0, Math.ceil((req.expiresAt - Date.now()) / 1e3))) + "s", 1),
|
||||
createVNode("div", {
|
||||
class: "dev-actions",
|
||||
style: { "margin-top": "8px" }
|
||||
}, [
|
||||
createVNode("button", {
|
||||
class: "dev-theme-btn",
|
||||
onClick: ($event) => denySystemRequest(req.requestId)
|
||||
}, "Deny", 8, ["onClick"]),
|
||||
createVNode("button", {
|
||||
class: "dev-theme-btn active",
|
||||
onClick: ($event) => approveSystemRequest(req.requestId)
|
||||
}, "Approve", 8, ["onClick"])
|
||||
])
|
||||
]);
|
||||
}), 128))
|
||||
])) : createCommentVNode("", true),
|
||||
createVNode("div", { class: "dev-section" }, [
|
||||
createVNode("h3", null, "MCP Counter"),
|
||||
createVNode("div", {
|
||||
class: ["counter-widget", { muted: counterMuted.value }]
|
||||
}, [
|
||||
createVNode("div", { class: "counter-controls" }, [
|
||||
createVNode("button", {
|
||||
class: "counter-btn",
|
||||
onClick: ($event) => counterAction("decrement"),
|
||||
disabled: counterMuted.value || counterBusy.value
|
||||
}, "−", 8, ["onClick", "disabled"]),
|
||||
createVNode("span", {
|
||||
class: "counter-value",
|
||||
id: "mcp-counter-value"
|
||||
}, toDisplayString(counterValue.value), 1),
|
||||
createVNode("button", {
|
||||
class: "counter-btn",
|
||||
onClick: ($event) => counterAction("increment"),
|
||||
disabled: counterMuted.value || counterBusy.value
|
||||
}, "+", 8, ["onClick", "disabled"])
|
||||
]),
|
||||
challengeMessage.value ? (openBlock(), createBlock("div", {
|
||||
key: 0,
|
||||
class: "counter-challenge"
|
||||
}, [
|
||||
createVNode("span", { class: "counter-message" }, toDisplayString(challengeMessage.value), 1),
|
||||
challengeTimer.value > 0 ? (openBlock(), createBlock("span", {
|
||||
key: 0,
|
||||
class: "counter-timer"
|
||||
}, toDisplayString(challengeTimer.value) + "s", 1)) : createCommentVNode("", true)
|
||||
])) : (openBlock(), createBlock("div", {
|
||||
key: 1,
|
||||
class: "counter-hint"
|
||||
}, "Waiting for Claude..."))
|
||||
], 2)
|
||||
]),
|
||||
actionPicker.value.title ? (openBlock(), createBlock("div", {
|
||||
key: 1,
|
||||
class: "dev-section"
|
||||
}, [
|
||||
createVNode("h3", null, toDisplayString(actionPicker.value.title), 1),
|
||||
createVNode("div", { class: "action-picker" }, [
|
||||
(openBlock(true), createBlock(Fragment, null, renderList(actionPicker.value.options, (opt, i) => {
|
||||
return openBlock(), createBlock("button", {
|
||||
key: i,
|
||||
class: "action-pick-btn",
|
||||
disabled: actionPickerBusy.value,
|
||||
onClick: ($event) => pickAction(opt.id)
|
||||
}, toDisplayString(opt.label), 9, ["disabled", "onClick"]);
|
||||
}), 128))
|
||||
])
|
||||
])) : createCommentVNode("", true),
|
||||
createVNode("div", { class: "dev-section" }, [
|
||||
createVNode("h3", null, "OpenRouter Credits"),
|
||||
loading.value && !stats.value ? (openBlock(), createBlock("div", {
|
||||
key: 0,
|
||||
class: "dev-loading"
|
||||
}, "Loading…")) : statsError.value ? (openBlock(), createBlock("div", {
|
||||
key: 1,
|
||||
class: "dev-error"
|
||||
}, toDisplayString(statsError.value), 1)) : stats.value ? (openBlock(), createBlock("div", {
|
||||
key: 2,
|
||||
class: "credits-widget"
|
||||
}, [
|
||||
createVNode("div", { class: "credits-bar-track" }, [
|
||||
createVNode("div", {
|
||||
class: "credits-bar-fill",
|
||||
style: { width: usedPct.value + "%" }
|
||||
}, null, 4)
|
||||
]),
|
||||
createVNode("div", { class: "credits-row" }, [
|
||||
createVNode("div", { class: "credits-stat" }, [
|
||||
createVNode("span", { class: "credits-label" }, "Used"),
|
||||
createVNode("span", { class: "credits-amount credits-used" }, "$" + toDisplayString(fmt(stats.value.credits.used)), 1)
|
||||
]),
|
||||
createVNode("div", { class: "credits-stat" }, [
|
||||
createVNode("span", { class: "credits-label" }, "Remaining"),
|
||||
createVNode("span", { class: "credits-amount credits-remaining" }, "$" + toDisplayString(fmt(stats.value.credits.remaining)), 1)
|
||||
]),
|
||||
createVNode("div", { class: "credits-stat" }, [
|
||||
createVNode("span", { class: "credits-label" }, "Total"),
|
||||
createVNode("span", { class: "credits-amount" }, "$" + toDisplayString(fmt(stats.value.credits.total)), 1)
|
||||
])
|
||||
])
|
||||
])) : createCommentVNode("", true)
|
||||
]),
|
||||
stats.value ? (openBlock(), createBlock("div", {
|
||||
key: 2,
|
||||
class: "dev-section"
|
||||
}, [
|
||||
createVNode("h3", null, "Agents"),
|
||||
createVNode("div", { class: "dev-table-wrap" }, [
|
||||
createVNode("table", { class: "dev-table" }, [
|
||||
createVNode("thead", null, [
|
||||
createVNode("tr", null, [
|
||||
createVNode("th", null, "Agent"),
|
||||
createVNode("th", null, "Model"),
|
||||
createVNode("th", null, "Context"),
|
||||
createVNode("th", null, "Prompt / 1M"),
|
||||
createVNode("th", null, "Completion / 1M")
|
||||
])
|
||||
]),
|
||||
createVNode("tbody", null, [
|
||||
(openBlock(true), createBlock(Fragment, null, renderList(stats.value.agents, (a) => {
|
||||
return openBlock(), createBlock("tr", {
|
||||
key: a.id
|
||||
}, [
|
||||
createVNode("td", { class: "agent-id" }, toDisplayString(a.id), 1),
|
||||
createVNode("td", null, [
|
||||
createVNode("code", null, toDisplayString(a.modelId || a.model), 1)
|
||||
]),
|
||||
createVNode("td", null, toDisplayString(a.contextLength ? (a.contextLength / 1e3).toFixed(0) + "k" : "—"), 1),
|
||||
createVNode("td", null, toDisplayString(a.promptPrice !== null ? "$" + a.promptPrice.toFixed(3) : "—"), 1),
|
||||
createVNode("td", null, toDisplayString(a.completionPrice !== null ? "$" + a.completionPrice.toFixed(3) : "—"), 1)
|
||||
]);
|
||||
}), 128))
|
||||
])
|
||||
])
|
||||
])
|
||||
])) : createCommentVNode("", true)
|
||||
])
|
||||
]),
|
||||
(openBlock(), createBlock(Teleport, { to: "body" }, [
|
||||
unref(breakoutReq) ? (openBlock(), createBlock("div", {
|
||||
key: 0,
|
||||
class: "breakout-modal-overlay",
|
||||
onClick: withModifiers(denyBreakout, ["self"])
|
||||
}, [
|
||||
createVNode("div", { class: "breakout-modal" }, [
|
||||
createVNode("h3", null, "Open Breakout"),
|
||||
createVNode("p", null, [
|
||||
createTextVNode("Name: "),
|
||||
createVNode("strong", null, toDisplayString(unref(breakoutReq).name), 1)
|
||||
]),
|
||||
createVNode("p", null, [
|
||||
createTextVNode("Size: "),
|
||||
createVNode("strong", null, toDisplayString(unref(breakoutReq).preset), 1)
|
||||
]),
|
||||
createVNode("p", { class: "breakout-nonce" }, toDisplayString(unref(breakoutReq).nonce), 1),
|
||||
createVNode("div", { class: "breakout-modal-actions" }, [
|
||||
createVNode("button", {
|
||||
class: "dev-theme-btn",
|
||||
onClick: denyBreakout
|
||||
}, "Cancel"),
|
||||
createVNode("button", {
|
||||
class: "dev-theme-btn active",
|
||||
onClick: confirmBreakout
|
||||
}, "Confirm")
|
||||
])
|
||||
])
|
||||
])) : createCommentVNode("", true)
|
||||
]))
|
||||
];
|
||||
}
|
||||
}),
|
||||
_: 1
|
||||
}, _parent));
|
||||
} else {
|
||||
_push(`<div${ssrRenderAttrs(mergeProps({ class: "not-logged-in" }, _attrs))} data-v-6fcff79f><p data-v-6fcff79f>`);
|
||||
_push(ssrRenderComponent(unref(LockClosedIcon), { class: "w-5 h-5 inline" }, null, _parent));
|
||||
_push(` Not logged in</p>`);
|
||||
_push(ssrRenderComponent(_component_RouterLink, { to: "/login" }, {
|
||||
default: withCtx((_, _push2, _parent2, _scopeId) => {
|
||||
if (_push2) {
|
||||
_push2(`Sign in →`);
|
||||
} else {
|
||||
return [
|
||||
createTextVNode("Sign in →")
|
||||
];
|
||||
}
|
||||
}),
|
||||
_: 1
|
||||
}, _parent));
|
||||
_push(`</div>`);
|
||||
}
|
||||
};
|
||||
}
|
||||
});
|
||||
const _sfc_setup = _sfc_main.setup;
|
||||
_sfc_main.setup = (props, ctx) => {
|
||||
const ssrContext = useSSRContext();
|
||||
(ssrContext.modules || (ssrContext.modules = /* @__PURE__ */ new Set())).add("src/views/DevView.vue");
|
||||
return _sfc_setup ? _sfc_setup(props, ctx) : void 0;
|
||||
};
|
||||
const DevView = /* @__PURE__ */ _export_sfc(_sfc_main, [["__scopeId", "data-v-6fcff79f"]]);
|
||||
export {
|
||||
DevView as default
|
||||
};
|
||||
50
.vite-ssg-temp/eyyt8tgg8y/assets/ImpressumView-B77dj09a.js
Normal file
50
.vite-ssg-temp/eyyt8tgg8y/assets/ImpressumView-B77dj09a.js
Normal file
@ -0,0 +1,50 @@
|
||||
import { resolveComponent, mergeProps, withCtx, createTextVNode, useSSRContext } from "vue";
|
||||
import { ssrRenderAttrs, ssrRenderComponent } from "vue/server-renderer";
|
||||
import { _ as _export_sfc } from "../main.mjs";
|
||||
import "@unhead/vue/server";
|
||||
import "vue-router";
|
||||
import "pinia";
|
||||
import "@heroicons/vue/24/outline";
|
||||
import "@heroicons/vue/20/solid";
|
||||
import "overlayscrollbars-vue";
|
||||
import "overlayscrollbars";
|
||||
const _sfc_main = {};
|
||||
function _sfc_ssrRender(_ctx, _push, _parent, _attrs) {
|
||||
const _component_router_link = resolveComponent("router-link");
|
||||
_push(`<div${ssrRenderAttrs(mergeProps({ class: "legal-page" }, _attrs))} data-v-d8c150db><h1 data-v-d8c150db>Impressum</h1><h2 data-v-d8c150db>Angaben gemäß § 5 TMG</h2><p data-v-d8c150db> loop42 UG (haftungsbeschränkt)<br data-v-d8c150db> [STRASSE HAUSNUMMER]<br data-v-d8c150db> [PLZ ORT]<br data-v-d8c150db> Deutschland </p><h2 data-v-d8c150db>Vertreten durch</h2><p data-v-d8c150db>[NAME]</p><h2 data-v-d8c150db>Kontakt</h2><p data-v-d8c150db>Telefon: [TELEFONNUMMER]</p><h2 data-v-d8c150db>Registereintrag</h2><p data-v-d8c150db> Eintragung im Handelsregister.<br data-v-d8c150db> Registergericht: [AMTSGERICHT]<br data-v-d8c150db> Registernummer: [HRB-NUMMER] </p><h2 data-v-d8c150db>Umsatzsteuer-ID</h2><p data-v-d8c150db> Umsatzsteuer-Identifikationsnummer gemäß § 27 a Umsatzsteuergesetz:<br data-v-d8c150db> [UST-ID-NUMMER] </p><h2 data-v-d8c150db>Verantwortlich für den Inhalt nach § 55 Abs. 2 RStV</h2><p data-v-d8c150db> [NAME]<br data-v-d8c150db> [STRASSE HAUSNUMMER]<br data-v-d8c150db> [PLZ ORT] </p><h2 data-v-d8c150db>Streitschlichtung</h2><p data-v-d8c150db> Die Europäische Kommission stellt eine Plattform zur Online-Streitbeilegung (OS) bereit: <a href="https://ec.europa.eu/consumers/odr/" target="_blank" rel="noopener" data-v-d8c150db>https://ec.europa.eu/consumers/odr/</a>.<br data-v-d8c150db> Unsere E-Mail-Adresse finden Sie oben im Impressum. </p><p data-v-d8c150db> Wir sind nicht bereit oder verpflichtet, an Streitbeilegungsverfahren vor einer Verbraucherschlichtungsstelle teilzunehmen. </p><footer data-v-d8c150db><div class="footer-links" data-v-d8c150db>`);
|
||||
_push(ssrRenderComponent(_component_router_link, { to: "/impressum" }, {
|
||||
default: withCtx((_, _push2, _parent2, _scopeId) => {
|
||||
if (_push2) {
|
||||
_push2(`Impressum`);
|
||||
} else {
|
||||
return [
|
||||
createTextVNode("Impressum")
|
||||
];
|
||||
}
|
||||
}),
|
||||
_: 1
|
||||
}, _parent));
|
||||
_push(ssrRenderComponent(_component_router_link, { to: "/datenschutz" }, {
|
||||
default: withCtx((_, _push2, _parent2, _scopeId) => {
|
||||
if (_push2) {
|
||||
_push2(`Datenschutz`);
|
||||
} else {
|
||||
return [
|
||||
createTextVNode("Datenschutz")
|
||||
];
|
||||
}
|
||||
}),
|
||||
_: 1
|
||||
}, _parent));
|
||||
_push(`</div><span data-v-d8c150db>© 2026 loop42 UG (haftungsbeschränkt)</span></footer></div>`);
|
||||
}
|
||||
const _sfc_setup = _sfc_main.setup;
|
||||
_sfc_main.setup = (props, ctx) => {
|
||||
const ssrContext = useSSRContext();
|
||||
(ssrContext.modules || (ssrContext.modules = /* @__PURE__ */ new Set())).add("src/views/ImpressumView.vue");
|
||||
return _sfc_setup ? _sfc_setup(props, ctx) : void 0;
|
||||
};
|
||||
const ImpressumView = /* @__PURE__ */ _export_sfc(_sfc_main, [["ssrRender", _sfc_ssrRender], ["__scopeId", "data-v-d8c150db"]]);
|
||||
export {
|
||||
ImpressumView as default
|
||||
};
|
||||
445
.vite-ssg-temp/eyyt8tgg8y/assets/ViewerView-5go7ZmZ8.js
Normal file
445
.vite-ssg-temp/eyyt8tgg8y/assets/ViewerView-5go7ZmZ8.js
Normal file
@ -0,0 +1,445 @@
|
||||
import { computed, ref, onMounted, watch, onUnmounted, defineComponent, mergeProps, unref, withCtx, openBlock, createBlock, createVNode, toDisplayString, Fragment, renderList, createCommentVNode, createTextVNode, useSSRContext } from "vue";
|
||||
import { ssrRenderAttrs, ssrRenderList, ssrRenderClass, ssrInterpolate, ssrRenderAttr, ssrRenderComponent } from "vue/server-renderer";
|
||||
import { OverlayScrollbarsComponent } from "overlayscrollbars-vue";
|
||||
import { i as useViewerStore, g as getApiBase, w as ws, l as lastViewerPath, s as scrollbarOptions, _ as _export_sfc } from "../main.mjs";
|
||||
import { ArrowDownTrayIcon, FolderOpenIcon, FolderIcon, DocumentIcon } from "@heroicons/vue/20/solid";
|
||||
import { useRoute, useRouter } from "vue-router";
|
||||
import { marked } from "marked";
|
||||
import "@unhead/vue/server";
|
||||
import "pinia";
|
||||
import "@heroicons/vue/24/outline";
|
||||
import "overlayscrollbars";
|
||||
function useViewer() {
|
||||
const route = useRoute();
|
||||
const router = useRouter();
|
||||
const { onMessage } = ws;
|
||||
const viewerStore = useViewerStore();
|
||||
const fstoken = computed(() => viewerStore.fstoken);
|
||||
const viewerRoots = computed(() => viewerStore.roots);
|
||||
const token = fstoken;
|
||||
function normalizePath(path) {
|
||||
if (!path) return path;
|
||||
const roots = viewerStore.roots;
|
||||
if (!roots.length) return path;
|
||||
const [prefix, ...rest] = path.split("/");
|
||||
if (roots.includes(prefix)) return path;
|
||||
const canonical = roots.find((r) => r === `workspace-${prefix}` || r.endsWith(`-${prefix}`));
|
||||
return canonical ? [canonical, ...rest].join("/") : path;
|
||||
}
|
||||
const currentPath = ref(normalizePath(
|
||||
route.query.path || localStorage.getItem("viewer_last_path") || ""
|
||||
));
|
||||
const sidebarCollapsed = ref(window.innerWidth < 768);
|
||||
const content = ref("");
|
||||
const fileType = ref("");
|
||||
const dirFiles = ref([]);
|
||||
const dirDirs = ref([]);
|
||||
const loading = ref(false);
|
||||
const showLoading = ref(false);
|
||||
let loadingTimer = null;
|
||||
const fetchError = ref("");
|
||||
const pdfCacheBust = ref(Date.now());
|
||||
const mdRaw = ref(false);
|
||||
const mdLineCount = computed(() => {
|
||||
if (!content.value) return 0;
|
||||
return content.value.split("\n").length;
|
||||
});
|
||||
const renderedMd = computed(() => {
|
||||
if (fileType.value !== "md" || !content.value) return "";
|
||||
return marked.parse(content.value);
|
||||
});
|
||||
const pdfSrc = computed(() => {
|
||||
if (fileType.value !== "pdf" || !currentPath.value || !token.value) return "";
|
||||
const base = getBaseUrl();
|
||||
return `${base}/api/viewer/file?path=${encodeURIComponent(currentPath.value)}&token=${encodeURIComponent(token.value)}&t=${pdfCacheBust.value}`;
|
||||
});
|
||||
function getBaseUrl() {
|
||||
return getApiBase();
|
||||
}
|
||||
function extOf(p) {
|
||||
const m = p.match(/\.([^./]+)$/);
|
||||
return m ? m[1].toLowerCase() : "";
|
||||
}
|
||||
function startLoading(silent) {
|
||||
if (silent) return;
|
||||
loading.value = true;
|
||||
showLoading.value = false;
|
||||
if (loadingTimer) clearTimeout(loadingTimer);
|
||||
loadingTimer = setTimeout(() => {
|
||||
showLoading.value = true;
|
||||
}, 3e3);
|
||||
}
|
||||
function stopLoading() {
|
||||
loading.value = false;
|
||||
showLoading.value = false;
|
||||
if (loadingTimer) {
|
||||
clearTimeout(loadingTimer);
|
||||
loadingTimer = null;
|
||||
}
|
||||
}
|
||||
async function fetchContent(path, silent = false, _retry = false) {
|
||||
startLoading(silent);
|
||||
try {
|
||||
const base = getBaseUrl();
|
||||
const url = `${base}/api/viewer/file?path=${encodeURIComponent(path)}&token=${encodeURIComponent(token.value)}`;
|
||||
const res = await fetch(url);
|
||||
if (res.status === 401 && !_retry) {
|
||||
viewerStore.invalidate();
|
||||
await viewerStore.acquire(true);
|
||||
return fetchContent(path, silent, true);
|
||||
}
|
||||
if (!res.ok) {
|
||||
fetchError.value = `${res.status}: ${await res.text()}`;
|
||||
return;
|
||||
}
|
||||
fetchError.value = "";
|
||||
content.value = await res.text();
|
||||
} catch (e) {
|
||||
fetchError.value = e.message || "Fetch failed";
|
||||
} finally {
|
||||
stopLoading();
|
||||
}
|
||||
}
|
||||
async function openFile(path) {
|
||||
path = normalizePath(path);
|
||||
currentPath.value = path;
|
||||
localStorage.setItem("viewer_last_path", path);
|
||||
lastViewerPath.value = path;
|
||||
if (route.query.path !== path) {
|
||||
router.push({ name: "viewer", query: { path } });
|
||||
}
|
||||
const ext = extOf(path);
|
||||
if (!path) {
|
||||
fileType.value = "dir";
|
||||
content.value = "";
|
||||
fetchError.value = "";
|
||||
dirDirs.value = viewerRoots.value.length ? viewerRoots.value : ["shared", "workspace-titan"];
|
||||
dirFiles.value = [];
|
||||
return;
|
||||
}
|
||||
if (!ext) {
|
||||
fileType.value = "dir";
|
||||
content.value = "";
|
||||
fetchError.value = "";
|
||||
startLoading(false);
|
||||
try {
|
||||
const base = getBaseUrl();
|
||||
const url = `${base}/api/viewer/tree?root=${encodeURIComponent(path)}&token=${encodeURIComponent(token.value)}`;
|
||||
const res = await fetch(url);
|
||||
if (!res.ok) throw new Error(`${res.status} ${res.statusText}`);
|
||||
const data = await res.json();
|
||||
dirDirs.value = data.dirs || [];
|
||||
dirFiles.value = data.files || [];
|
||||
} catch (e) {
|
||||
dirDirs.value = [];
|
||||
dirFiles.value = [];
|
||||
fetchError.value = e.message || "Failed to load directory";
|
||||
} finally {
|
||||
stopLoading();
|
||||
}
|
||||
return;
|
||||
}
|
||||
if (ext === "pdf") {
|
||||
fileType.value = "pdf";
|
||||
content.value = "";
|
||||
fetchError.value = "";
|
||||
pdfCacheBust.value = Date.now();
|
||||
fetch(`${getBaseUrl()}/api/viewer/file?path=${encodeURIComponent(path)}&token=${encodeURIComponent(token.value)}`, { method: "HEAD" }).catch(() => {
|
||||
});
|
||||
return;
|
||||
}
|
||||
fileType.value = ext === "md" ? "md" : "text";
|
||||
fetchError.value = "";
|
||||
await fetchContent(path, false);
|
||||
}
|
||||
function onCopy(e) {
|
||||
const sel = window.getSelection();
|
||||
if (!sel || sel.isCollapsed) return;
|
||||
const html = (() => {
|
||||
const div = document.createElement("div");
|
||||
div.appendChild(sel.getRangeAt(0).cloneContents());
|
||||
return div.innerHTML;
|
||||
})();
|
||||
const clean = html.replace(/background(-color)?:[^;"]*(;|(?="))/gi, "");
|
||||
const plain = sel.toString();
|
||||
e.clipboardData.setData("text/html", clean);
|
||||
e.clipboardData.setData("text/plain", plain);
|
||||
e.preventDefault();
|
||||
}
|
||||
async function refreshFile(path) {
|
||||
const ext = extOf(path);
|
||||
if (ext === "pdf") {
|
||||
pdfCacheBust.value = Date.now();
|
||||
return;
|
||||
}
|
||||
await fetchContent(path, true);
|
||||
}
|
||||
let unsubscribe = null;
|
||||
onMounted(() => {
|
||||
unsubscribe = onMessage((data) => {
|
||||
if (data.type === "viewer_file_changed" && data.path === currentPath.value) {
|
||||
refreshFile(currentPath.value);
|
||||
}
|
||||
if (data.type === "viewer_tree_changed" && data.path === currentPath.value && fileType.value === "dir") {
|
||||
openFile(currentPath.value);
|
||||
}
|
||||
});
|
||||
viewerStore.acquire();
|
||||
if (currentPath.value) openFile(currentPath.value);
|
||||
});
|
||||
watch(() => route.query.path, (newPath) => {
|
||||
if (newPath && newPath !== currentPath.value) {
|
||||
openFile(newPath);
|
||||
}
|
||||
});
|
||||
onUnmounted(() => {
|
||||
if (unsubscribe) unsubscribe();
|
||||
});
|
||||
return {
|
||||
fstoken,
|
||||
viewerRoots,
|
||||
currentPath,
|
||||
sidebarCollapsed,
|
||||
content,
|
||||
fileType,
|
||||
loading,
|
||||
fetchError,
|
||||
mdRaw,
|
||||
mdLineCount,
|
||||
renderedMd,
|
||||
pdfSrc,
|
||||
openFile,
|
||||
onCopy,
|
||||
dirFiles,
|
||||
dirDirs,
|
||||
showLoading
|
||||
};
|
||||
}
|
||||
const _sfc_main = /* @__PURE__ */ defineComponent({
|
||||
...{ name: "ViewerView" },
|
||||
__name: "ViewerView",
|
||||
__ssrInlineRender: true,
|
||||
setup(__props) {
|
||||
const {
|
||||
fstoken,
|
||||
currentPath,
|
||||
content,
|
||||
fileType,
|
||||
showLoading,
|
||||
fetchError,
|
||||
mdRaw,
|
||||
renderedMd,
|
||||
pdfSrc,
|
||||
openFile,
|
||||
onCopy,
|
||||
dirFiles,
|
||||
dirDirs
|
||||
} = useViewer();
|
||||
const contentLines = computed(() => content.value.split("\n"));
|
||||
const fileName = computed(() => {
|
||||
if (!currentPath.value) return "";
|
||||
return currentPath.value.split("/").pop() || "";
|
||||
});
|
||||
const downloadUrl = computed(() => {
|
||||
if (!currentPath.value || !fstoken.value) return "";
|
||||
return `${getApiBase()}/api/viewer/file?path=${encodeURIComponent(currentPath.value)}&token=${encodeURIComponent(fstoken.value)}&dl=1`;
|
||||
});
|
||||
const breadcrumbs = computed(() => {
|
||||
const crumbs = [{ label: "files", path: "" }];
|
||||
if (!currentPath.value) return crumbs;
|
||||
const parts = currentPath.value.split("/");
|
||||
for (let i = 0; i < parts.length; i++) {
|
||||
crumbs.push({ label: parts[i], path: parts.slice(0, i + 1).join("/") });
|
||||
}
|
||||
return crumbs;
|
||||
});
|
||||
return (_ctx, _push, _parent, _attrs) => {
|
||||
_push(`<div${ssrRenderAttrs(mergeProps({ class: "viewer-layout h-full overflow-hidden" }, _attrs))} data-v-6b5cb038><main class="viewer-pane min-w-0 flex flex-col h-full overflow-hidden" data-v-6b5cb038>`);
|
||||
if (unref(currentPath) || unref(fileType) === "dir") {
|
||||
_push(`<div class="viewer-toolbar" data-v-6b5cb038><div class="viewer-panel breadcrumb-panel" data-v-6b5cb038><!--[-->`);
|
||||
ssrRenderList(breadcrumbs.value, (crumb, i) => {
|
||||
_push(`<span data-v-6b5cb038>`);
|
||||
if (i > 0) {
|
||||
_push(`<span class="breadcrumb-sep" data-v-6b5cb038>/</span>`);
|
||||
} else {
|
||||
_push(`<!---->`);
|
||||
}
|
||||
_push(`<span class="${ssrRenderClass([{ active: i === breadcrumbs.value.length - 1 }, "breadcrumb-item"])}" data-v-6b5cb038>${ssrInterpolate(crumb.label)}</span></span>`);
|
||||
});
|
||||
_push(`<!--]--></div><div class="viewer-toolbar-spacer" data-v-6b5cb038></div>`);
|
||||
if (unref(fileType) === "md") {
|
||||
_push(`<div class="viewer-panel toggle-panel" data-v-6b5cb038><button class="${ssrRenderClass([{ active: !unref(mdRaw) }, "md-toggle-btn"])}" data-v-6b5cb038>Rendered</button><button class="${ssrRenderClass([{ active: unref(mdRaw) }, "md-toggle-btn"])}" data-v-6b5cb038>Raw</button></div>`);
|
||||
} else {
|
||||
_push(`<!---->`);
|
||||
}
|
||||
if (unref(fileType) && unref(fileType) !== "dir") {
|
||||
_push(`<a class="viewer-panel download-panel"${ssrRenderAttr("href", downloadUrl.value)}${ssrRenderAttr("download", fileName.value)} title="Download" data-v-6b5cb038>`);
|
||||
_push(ssrRenderComponent(unref(ArrowDownTrayIcon), { class: "w-4 h-4" }, null, _parent));
|
||||
_push(`</a>`);
|
||||
} else {
|
||||
_push(`<!---->`);
|
||||
}
|
||||
_push(`</div>`);
|
||||
} else {
|
||||
_push(`<!---->`);
|
||||
}
|
||||
_push(ssrRenderComponent(unref(OverlayScrollbarsComponent), {
|
||||
class: "content flex-1 min-h-0",
|
||||
options: unref(scrollbarOptions),
|
||||
element: "div"
|
||||
}, {
|
||||
default: withCtx((_, _push2, _parent2, _scopeId) => {
|
||||
if (_push2) {
|
||||
if (!unref(currentPath) && unref(fileType) !== "dir") {
|
||||
_push2(`<div class="viewer-empty" data-v-6b5cb038${_scopeId}>`);
|
||||
_push2(ssrRenderComponent(unref(FolderOpenIcon), { class: "w-8 h-8 text-text-dim" }, null, _parent2, _scopeId));
|
||||
_push2(`<p data-v-6b5cb038${_scopeId}>Select a file from the tree</p></div>`);
|
||||
} else if (unref(showLoading)) {
|
||||
_push2(`<div class="viewer-loading" data-v-6b5cb038${_scopeId}>loading…</div>`);
|
||||
} else if (unref(fetchError)) {
|
||||
_push2(`<div class="viewer-error" data-v-6b5cb038${_scopeId}>${ssrInterpolate(unref(fetchError))}</div>`);
|
||||
} else if (unref(fileType) === "dir") {
|
||||
_push2(`<div class="viewer-dir" data-v-6b5cb038${_scopeId}><!--[-->`);
|
||||
ssrRenderList(unref(dirDirs), (d) => {
|
||||
_push2(`<div class="dir-entry dir-entry--dir" data-v-6b5cb038${_scopeId}>`);
|
||||
_push2(ssrRenderComponent(unref(FolderIcon), { class: "w-4 h-4" }, null, _parent2, _scopeId));
|
||||
_push2(`<span data-v-6b5cb038${_scopeId}>${ssrInterpolate(d)}/</span></div>`);
|
||||
});
|
||||
_push2(`<!--]--><!--[-->`);
|
||||
ssrRenderList(unref(dirFiles), (f) => {
|
||||
_push2(`<div class="dir-entry dir-entry--file" data-v-6b5cb038${_scopeId}>`);
|
||||
_push2(ssrRenderComponent(unref(DocumentIcon), { class: "w-4 h-4" }, null, _parent2, _scopeId));
|
||||
_push2(`<span data-v-6b5cb038${_scopeId}>${ssrInterpolate(f.name)}</span></div>`);
|
||||
});
|
||||
_push2(`<!--]-->`);
|
||||
if (!unref(dirDirs).length && !unref(dirFiles).length) {
|
||||
_push2(`<div class="viewer-empty" data-v-6b5cb038${_scopeId}><p data-v-6b5cb038${_scopeId}>Empty directory</p></div>`);
|
||||
} else {
|
||||
_push2(`<!---->`);
|
||||
}
|
||||
_push2(`</div>`);
|
||||
} else if (unref(fileType) === "pdf") {
|
||||
_push2(`<iframe class="viewer-pdf"${ssrRenderAttr("src", unref(pdfSrc))} frameborder="0" data-v-6b5cb038${_scopeId}></iframe>`);
|
||||
} else if (unref(fileType) === "md" && !unref(mdRaw)) {
|
||||
_push2(`<div class="viewer-md md-body" data-v-6b5cb038${_scopeId}>${unref(renderedMd) ?? ""}</div>`);
|
||||
} else if (unref(fileType) === "md" && unref(mdRaw)) {
|
||||
_push2(`<div class="viewer-raw-md" data-v-6b5cb038${_scopeId}><pre class="viewer-raw-code" data-v-6b5cb038${_scopeId}><code data-v-6b5cb038${_scopeId}><!--[-->`);
|
||||
ssrRenderList(contentLines.value, (line, i) => {
|
||||
_push2(`<span class="code-line" data-v-6b5cb038${_scopeId}><span class="line-num" data-v-6b5cb038${_scopeId}>${ssrInterpolate(i + 1)}</span>${ssrInterpolate(line)}
|
||||
</span>`);
|
||||
});
|
||||
_push2(`<!--]--></code></pre></div>`);
|
||||
} else {
|
||||
_push2(`<pre class="viewer-raw-code" data-v-6b5cb038${_scopeId}><code data-v-6b5cb038${_scopeId}><!--[-->`);
|
||||
ssrRenderList(contentLines.value, (line, i) => {
|
||||
_push2(`<span class="code-line" data-v-6b5cb038${_scopeId}><span class="line-num" data-v-6b5cb038${_scopeId}>${ssrInterpolate(i + 1)}</span>${ssrInterpolate(line)}
|
||||
</span>`);
|
||||
});
|
||||
_push2(`<!--]--></code></pre>`);
|
||||
}
|
||||
} else {
|
||||
return [
|
||||
!unref(currentPath) && unref(fileType) !== "dir" ? (openBlock(), createBlock("div", {
|
||||
key: 0,
|
||||
class: "viewer-empty"
|
||||
}, [
|
||||
createVNode(unref(FolderOpenIcon), { class: "w-8 h-8 text-text-dim" }),
|
||||
createVNode("p", null, "Select a file from the tree")
|
||||
])) : unref(showLoading) ? (openBlock(), createBlock("div", {
|
||||
key: 1,
|
||||
class: "viewer-loading"
|
||||
}, "loading…")) : unref(fetchError) ? (openBlock(), createBlock("div", {
|
||||
key: 2,
|
||||
class: "viewer-error"
|
||||
}, toDisplayString(unref(fetchError)), 1)) : unref(fileType) === "dir" ? (openBlock(), createBlock("div", {
|
||||
key: 3,
|
||||
class: "viewer-dir"
|
||||
}, [
|
||||
(openBlock(true), createBlock(Fragment, null, renderList(unref(dirDirs), (d) => {
|
||||
return openBlock(), createBlock("div", {
|
||||
key: d,
|
||||
class: "dir-entry dir-entry--dir",
|
||||
onClick: ($event) => unref(openFile)(unref(currentPath) ? unref(currentPath) + "/" + d : d)
|
||||
}, [
|
||||
createVNode(unref(FolderIcon), { class: "w-4 h-4" }),
|
||||
createVNode("span", null, toDisplayString(d) + "/", 1)
|
||||
], 8, ["onClick"]);
|
||||
}), 128)),
|
||||
(openBlock(true), createBlock(Fragment, null, renderList(unref(dirFiles), (f) => {
|
||||
return openBlock(), createBlock("div", {
|
||||
key: f.path,
|
||||
class: "dir-entry dir-entry--file",
|
||||
onClick: ($event) => unref(openFile)(f.path)
|
||||
}, [
|
||||
createVNode(unref(DocumentIcon), { class: "w-4 h-4" }),
|
||||
createVNode("span", null, toDisplayString(f.name), 1)
|
||||
], 8, ["onClick"]);
|
||||
}), 128)),
|
||||
!unref(dirDirs).length && !unref(dirFiles).length ? (openBlock(), createBlock("div", {
|
||||
key: 0,
|
||||
class: "viewer-empty"
|
||||
}, [
|
||||
createVNode("p", null, "Empty directory")
|
||||
])) : createCommentVNode("", true)
|
||||
])) : unref(fileType) === "pdf" ? (openBlock(), createBlock("iframe", {
|
||||
key: 4,
|
||||
class: "viewer-pdf",
|
||||
src: unref(pdfSrc),
|
||||
frameborder: "0"
|
||||
}, null, 8, ["src"])) : unref(fileType) === "md" && !unref(mdRaw) ? (openBlock(), createBlock("div", {
|
||||
key: 5,
|
||||
class: "viewer-md md-body",
|
||||
innerHTML: unref(renderedMd),
|
||||
onCopy: unref(onCopy)
|
||||
}, null, 40, ["innerHTML", "onCopy"])) : unref(fileType) === "md" && unref(mdRaw) ? (openBlock(), createBlock("div", {
|
||||
key: 6,
|
||||
class: "viewer-raw-md"
|
||||
}, [
|
||||
createVNode("pre", { class: "viewer-raw-code" }, [
|
||||
createVNode("code", null, [
|
||||
(openBlock(true), createBlock(Fragment, null, renderList(contentLines.value, (line, i) => {
|
||||
return openBlock(), createBlock("span", {
|
||||
key: i,
|
||||
class: "code-line"
|
||||
}, [
|
||||
createVNode("span", { class: "line-num" }, toDisplayString(i + 1), 1),
|
||||
createTextVNode(toDisplayString(line) + "\n", 1)
|
||||
]);
|
||||
}), 128))
|
||||
])
|
||||
])
|
||||
])) : (openBlock(), createBlock("pre", {
|
||||
key: 7,
|
||||
class: "viewer-raw-code"
|
||||
}, [
|
||||
createVNode("code", null, [
|
||||
(openBlock(true), createBlock(Fragment, null, renderList(contentLines.value, (line, i) => {
|
||||
return openBlock(), createBlock("span", {
|
||||
key: i,
|
||||
class: "code-line"
|
||||
}, [
|
||||
createVNode("span", { class: "line-num" }, toDisplayString(i + 1), 1),
|
||||
createTextVNode(toDisplayString(line) + "\n", 1)
|
||||
]);
|
||||
}), 128))
|
||||
])
|
||||
]))
|
||||
];
|
||||
}
|
||||
}),
|
||||
_: 1
|
||||
}, _parent));
|
||||
_push(`</main></div>`);
|
||||
};
|
||||
}
|
||||
});
|
||||
const _sfc_setup = _sfc_main.setup;
|
||||
_sfc_main.setup = (props, ctx) => {
|
||||
const ssrContext = useSSRContext();
|
||||
(ssrContext.modules || (ssrContext.modules = /* @__PURE__ */ new Set())).add("src/views/ViewerView.vue");
|
||||
return _sfc_setup ? _sfc_setup(props, ctx) : void 0;
|
||||
};
|
||||
const ViewerView = /* @__PURE__ */ _export_sfc(_sfc_main, [["__scopeId", "data-v-6b5cb038"]]);
|
||||
export {
|
||||
ViewerView as default
|
||||
};
|
||||
11
.vite-ssg-temp/eyyt8tgg8y/favicon-eras.svg
Normal file
11
.vite-ssg-temp/eyyt8tgg8y/favicon-eras.svg
Normal file
@ -0,0 +1,11 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 32 32">
|
||||
<!-- Sun circle -->
|
||||
<circle cx="16" cy="16" r="10" fill="none" stroke="#005e83" stroke-width="2"/>
|
||||
<!-- Sun rays -->
|
||||
<line x1="16" y1="2" x2="16" y2="6" stroke="#e25303" stroke-width="2" stroke-linecap="round"/>
|
||||
<line x1="16" y1="26" x2="16" y2="30" stroke="#e25303" stroke-width="2" stroke-linecap="round"/>
|
||||
<line x1="2" y1="16" x2="6" y2="16" stroke="#e25303" stroke-width="2" stroke-linecap="round"/>
|
||||
<line x1="26" y1="16" x2="30" y2="16" stroke="#e25303" stroke-width="2" stroke-linecap="round"/>
|
||||
<!-- Inner dot -->
|
||||
<circle cx="16" cy="16" r="4" fill="#005e83"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 653 B |
9
.vite-ssg-temp/eyyt8tgg8y/favicon-loop42.svg
Normal file
9
.vite-ssg-temp/eyyt8tgg8y/favicon-loop42.svg
Normal file
@ -0,0 +1,9 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 32 32">
|
||||
<!-- Rounded square background -->
|
||||
<rect x="2" y="2" width="28" height="28" rx="6" fill="#1A212C" stroke="#1D7872" stroke-width="1.5"/>
|
||||
<!-- Loop symbol: two interlinked arcs -->
|
||||
<path d="M10,16 a5,5 0 1,1 6,0 a5,5 0 1,1 -6,0" fill="none" stroke="#71B095" stroke-width="2"/>
|
||||
<path d="M16,16 a5,5 0 1,1 6,0 a5,5 0 1,1 -6,0" fill="none" stroke="#1D7872" stroke-width="2"/>
|
||||
<!-- 42 text -->
|
||||
<text x="16" y="27" font-family="monospace" font-size="6" font-weight="bold" fill="#71B095" text-anchor="middle">42</text>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 593 B |
12
.vite-ssg-temp/eyyt8tgg8y/favicon-titan.svg
Normal file
12
.vite-ssg-temp/eyyt8tgg8y/favicon-titan.svg
Normal file
@ -0,0 +1,12 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 32 32">
|
||||
<!-- Monitor body -->
|
||||
<rect x="2" y="4" width="28" height="18" rx="2.5" ry="2.5" fill="#1a1a2e" stroke="#7c6ff7" stroke-width="1.5"/>
|
||||
<!-- Screen inner -->
|
||||
<rect x="4.5" y="6.5" width="23" height="13" rx="1" fill="#0d0d1a"/>
|
||||
<!-- Stand neck -->
|
||||
<rect x="14" y="22" width="4" height="4" fill="#7c6ff7"/>
|
||||
<!-- Stand base -->
|
||||
<rect x="10" y="26" width="12" height="2.5" rx="1.2" fill="#7c6ff7"/>
|
||||
<!-- Lightning bolt on screen -->
|
||||
<polygon points="18,9 14,16 16.5,16 14,23 20,15 17,15" fill="#f0c040"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 587 B |
12
.vite-ssg-temp/eyyt8tgg8y/favicon.svg
Normal file
12
.vite-ssg-temp/eyyt8tgg8y/favicon.svg
Normal file
@ -0,0 +1,12 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 32 32">
|
||||
<!-- Monitor body -->
|
||||
<rect x="2" y="4" width="28" height="18" rx="2.5" ry="2.5" fill="#1a1a2e" stroke="#7c6ff7" stroke-width="1.5"/>
|
||||
<!-- Screen inner -->
|
||||
<rect x="4.5" y="6.5" width="23" height="13" rx="1" fill="#0d0d1a"/>
|
||||
<!-- Stand neck -->
|
||||
<rect x="14" y="22" width="4" height="4" fill="#7c6ff7"/>
|
||||
<!-- Stand base -->
|
||||
<rect x="10" y="26" width="12" height="2.5" rx="1.2" fill="#7c6ff7"/>
|
||||
<!-- Lightning bolt on screen -->
|
||||
<polygon points="18,9 14,16 16.5,16 14,23 20,15 17,15" fill="#f0c040"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 587 B |
Binary file not shown.
Binary file not shown.
2890
.vite-ssg-temp/eyyt8tgg8y/main.mjs
Normal file
2890
.vite-ssg-temp/eyyt8tgg8y/main.mjs
Normal file
File diff suppressed because it is too large
Load Diff
85
package-lock.json
generated
85
package-lock.json
generated
@ -567,6 +567,19 @@
|
||||
"node": ">=6.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@jridgewell/source-map": {
|
||||
"version": "0.3.11",
|
||||
"resolved": "https://registry.npmjs.org/@jridgewell/source-map/-/source-map-0.3.11.tgz",
|
||||
"integrity": "sha512-ZMp1V8ZFcPG5dIWnQLr3NSI1MiCU7UETdS/A0G8V/XWHvJv3ZsFqutJn1Y5RPmAPX6F3BiE397OqveU/9NCuIA==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"@jridgewell/gen-mapping": "^0.3.5",
|
||||
"@jridgewell/trace-mapping": "^0.3.25"
|
||||
}
|
||||
},
|
||||
"node_modules/@jridgewell/sourcemap-codec": {
|
||||
"version": "1.5.5",
|
||||
"resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.5.tgz",
|
||||
@ -1495,6 +1508,15 @@
|
||||
"url": "https://github.com/sponsors/antfu"
|
||||
}
|
||||
},
|
||||
"node_modules/buffer-from": {
|
||||
"version": "1.1.2",
|
||||
"resolved": "https://registry.npmjs.org/buffer-from/-/buffer-from-1.1.2.tgz",
|
||||
"integrity": "sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"peer": true
|
||||
},
|
||||
"node_modules/chokidar": {
|
||||
"version": "5.0.0",
|
||||
"resolved": "https://registry.npmjs.org/chokidar/-/chokidar-5.0.0.tgz",
|
||||
@ -2329,6 +2351,18 @@
|
||||
"integrity": "sha512-6FtHJEvt+pVMIB9IBY+IcCJ6Z5f1iQnytgyfKMhDKgmzYG+TeH/wx1y3l27rshSbLiSanrR9ffZDrEsmjlQF2g==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/source-map": {
|
||||
"version": "0.6.1",
|
||||
"resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz",
|
||||
"integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==",
|
||||
"dev": true,
|
||||
"license": "BSD-3-Clause",
|
||||
"optional": true,
|
||||
"peer": true,
|
||||
"engines": {
|
||||
"node": ">=0.10.0"
|
||||
}
|
||||
},
|
||||
"node_modules/source-map-js": {
|
||||
"version": "1.2.1",
|
||||
"resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.1.tgz",
|
||||
@ -2338,6 +2372,19 @@
|
||||
"node": ">=0.10.0"
|
||||
}
|
||||
},
|
||||
"node_modules/source-map-support": {
|
||||
"version": "0.5.21",
|
||||
"resolved": "https://registry.npmjs.org/source-map-support/-/source-map-support-0.5.21.tgz",
|
||||
"integrity": "sha512-uBHU3L3czsIyYXKX88fdrGovxdSCoTGDRZ6SYXtSRxLZUzHg5P/66Ht6uoUlHu9EZod+inXhKo3qQgwXUT/y1w==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"buffer-from": "^1.0.0",
|
||||
"source-map": "^0.6.0"
|
||||
}
|
||||
},
|
||||
"node_modules/speakingurl": {
|
||||
"version": "14.0.1",
|
||||
"resolved": "https://registry.npmjs.org/speakingurl/-/speakingurl-14.0.1.tgz",
|
||||
@ -2380,6 +2427,36 @@
|
||||
"url": "https://opencollective.com/webpack"
|
||||
}
|
||||
},
|
||||
"node_modules/terser": {
|
||||
"version": "5.46.1",
|
||||
"resolved": "https://registry.npmjs.org/terser/-/terser-5.46.1.tgz",
|
||||
"integrity": "sha512-vzCjQO/rgUuK9sf8VJZvjqiqiHFaZLnOiimmUuOKODxWL8mm/xua7viT7aqX7dgPY60otQjUotzFMmCB4VdmqQ==",
|
||||
"dev": true,
|
||||
"license": "BSD-2-Clause",
|
||||
"optional": true,
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"@jridgewell/source-map": "^0.3.3",
|
||||
"acorn": "^8.15.0",
|
||||
"commander": "^2.20.0",
|
||||
"source-map-support": "~0.5.20"
|
||||
},
|
||||
"bin": {
|
||||
"terser": "bin/terser"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=10"
|
||||
}
|
||||
},
|
||||
"node_modules/terser/node_modules/commander": {
|
||||
"version": "2.20.3",
|
||||
"resolved": "https://registry.npmjs.org/commander/-/commander-2.20.3.tgz",
|
||||
"integrity": "sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"peer": true
|
||||
},
|
||||
"node_modules/tinyglobby": {
|
||||
"version": "0.2.15",
|
||||
"resolved": "https://registry.npmjs.org/tinyglobby/-/tinyglobby-0.2.15.tgz",
|
||||
@ -2396,6 +2473,14 @@
|
||||
"url": "https://github.com/sponsors/SuperchupuDev"
|
||||
}
|
||||
},
|
||||
"node_modules/tslib": {
|
||||
"version": "2.8.1",
|
||||
"resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz",
|
||||
"integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==",
|
||||
"dev": true,
|
||||
"license": "0BSD",
|
||||
"optional": true
|
||||
},
|
||||
"node_modules/typescript": {
|
||||
"version": "5.9.3",
|
||||
"resolved": "https://registry.npmjs.org/typescript/-/typescript-5.9.3.tgz",
|
||||
|
||||
@ -32,15 +32,16 @@ export interface Agent {
|
||||
const _allAgents: Ref<Agent[]> = ref([]);
|
||||
// Restore agent from URL or sessionStorage at init (before WS auth fires)
|
||||
const _initAgent = (() => {
|
||||
if (typeof window === 'undefined') return '';
|
||||
const url = new URLSearchParams(window.location.hash.split('?')[1] || '').get('agent');
|
||||
if (url) return url;
|
||||
const saved = sessionStorage.getItem('agent');
|
||||
if (saved) return saved;
|
||||
return ''; // truly first visit — server will provide default via ready message
|
||||
return '';
|
||||
})();
|
||||
const _selectedAgent: Ref<string> = ref(_initAgent);
|
||||
// Restore mode from URL or sessionStorage at init (before WS auth fires)
|
||||
const _initMode = (() => {
|
||||
if (typeof window === 'undefined') return 'private';
|
||||
const url = new URLSearchParams(window.location.hash.split('?')[1] || '').get('mode');
|
||||
if (url === 'public' || url === 'private') return url;
|
||||
const saved = sessionStorage.getItem('agent_mode');
|
||||
|
||||
@ -56,7 +56,7 @@ export function useBreakout() {
|
||||
pendingRequest.value = null;
|
||||
if (!confirmed) { resolve({ error: 'rejected by user' }); return; }
|
||||
const token = deriveToken(parentToken, name);
|
||||
const url = `${window.location.origin}${window.location.pathname}?breakout_token=${token}#/agents`;
|
||||
const url = `${window.location.origin}${window.location.pathname}?breakout_token=${token}/agents`;
|
||||
const popup = window.open(url, `hermes_breakout_${name}`, `width=${pw},height=${ph},resizable=yes,scrollbars=yes`);
|
||||
if (!popup) { resolve({ error: 'popup blocked' }); return; }
|
||||
windows.set(name, popup);
|
||||
@ -70,7 +70,7 @@ export function useBreakout() {
|
||||
function openDirect(name: string, presetStr: string, parentToken: string) {
|
||||
const [w, h] = presetStr.split('x').map(Number);
|
||||
const token = deriveToken(parentToken, name);
|
||||
const url = `${window.location.origin}${window.location.pathname}?breakout_token=${token}#/agents`;
|
||||
const url = `${window.location.origin}${window.location.pathname}?breakout_token=${token}/agents`;
|
||||
const popup = window.open(url, `hermes_breakout_${name}`, `width=${w},height=${h},resizable=yes,scrollbars=yes`);
|
||||
if (!popup) { alert('Popup blocked -- allow popups for this site'); return; }
|
||||
windows.set(name, popup);
|
||||
|
||||
@ -23,7 +23,10 @@ export interface HermesRuntime {
|
||||
captureStream: MediaStream | null;
|
||||
}
|
||||
|
||||
const _ssrFallback = {} as HermesRuntime;
|
||||
|
||||
export function useHermes(): HermesRuntime {
|
||||
if (typeof window === 'undefined') return _ssrFallback;
|
||||
const w = window as any;
|
||||
if (!w.__hermes) w.__hermes = {};
|
||||
return w.__hermes;
|
||||
|
||||
@ -13,7 +13,7 @@ import { useHermes } from './useHermes';
|
||||
const TAKEOVER_KEY = 'hermes_takeover_token';
|
||||
|
||||
// ── Module-level (survives HMR) ──
|
||||
let currentToken = sessionStorage.getItem(TAKEOVER_KEY) || '';
|
||||
let currentToken = (typeof sessionStorage !== 'undefined') ? sessionStorage.getItem(TAKEOVER_KEY) || '' : '';
|
||||
|
||||
export function useTakeover(wsSend: (msg: any) => void) {
|
||||
const token = ref(currentToken);
|
||||
|
||||
@ -170,8 +170,8 @@ function connect(
|
||||
sessionStorage.removeItem('agent');
|
||||
status.value = 'Logged out';
|
||||
// Redirect to login — avoids broken UI with stale state
|
||||
if (window.location.hash !== '#/login') {
|
||||
window.location.hash = '#/login';
|
||||
if (window.location.pathname !== '/login') {
|
||||
window.location.pathname = '/login';
|
||||
}
|
||||
} else {
|
||||
scheduleReconnect();
|
||||
|
||||
@ -1,6 +1,4 @@
|
||||
// ── window.__hermes: HMR-safe runtime store ──
|
||||
// Ensure object exists (may have been created by composable imports first).
|
||||
// Console hook only installs once (guarded by _origConsole).
|
||||
const _h = (window as any).__hermes || ((window as any).__hermes = {});
|
||||
if (!_h._origConsole) {
|
||||
const MAX = 200;
|
||||
|
||||
@ -1,11 +1,13 @@
|
||||
import { createRouter, createWebHashHistory } from 'vue-router';
|
||||
import { createRouter, createWebHistory } from 'vue-router';
|
||||
import LoginView from './views/LoginView.vue';
|
||||
import { THEME_NAMES, useTheme } from './composables/useTheme';
|
||||
|
||||
const router = createRouter({
|
||||
history: createWebHashHistory(),
|
||||
history: createWebHistory(),
|
||||
routes: [
|
||||
{ path: '/', name: 'home', component: () => import('./views/HomeView.vue'), meta: { suffix: 'Home' } },
|
||||
{ path: '/', name: 'home', component: () => import('./views/CompanyView.vue'), meta: { suffix: '' } },
|
||||
{ path: '/impressum', name: 'impressum', component: () => import('./views/ImpressumView.vue'), meta: { suffix: 'Impressum' } },
|
||||
{ path: '/datenschutz', name: 'datenschutz', component: () => import('./views/DatenschutzView.vue'), meta: { suffix: 'Datenschutz' } },
|
||||
{ path: '/login', name: 'login', component: LoginView, meta: { suffix: 'Login' } },
|
||||
{ path: '/agents', name: 'agents', component: () => import('./views/AgentsView.vue'), meta: { suffix: 'Home', requiresSocket: true } },
|
||||
{ path: '/chat', redirect: '/agents' },
|
||||
@ -17,7 +19,7 @@ const router = createRouter({
|
||||
|
||||
router.afterEach((to) => {
|
||||
const { theme } = useTheme();
|
||||
const brand = THEME_NAMES[theme.value] || 'Hermes';
|
||||
const brand = THEME_NAMES[theme.value] || 'loop42';
|
||||
const suffix = (to.meta?.suffix as string) || '';
|
||||
document.title = suffix ? `${brand} - ${suffix}` : brand;
|
||||
});
|
||||
|
||||
@ -12,7 +12,7 @@ import { useAuth } from './composables/auth';
|
||||
import { useViewerStore } from './store/viewer';
|
||||
|
||||
// Viewer last-selected path — reactive singleton so App.vue nav link stays current
|
||||
export const lastViewerPath = ref(localStorage.getItem('viewer_last_path') || '');
|
||||
export const lastViewerPath = ref(typeof localStorage !== 'undefined' ? localStorage.getItem('viewer_last_path') || '' : '');
|
||||
|
||||
// Viewer tree open-state — Set of paths that are currently expanded
|
||||
// In-memory only; survives tab switches, resets on full page reload
|
||||
|
||||
224
src/views/CompanyView.vue
Normal file
224
src/views/CompanyView.vue
Normal file
@ -0,0 +1,224 @@
|
||||
<template>
|
||||
<div class="company-page">
|
||||
<div class="hero">
|
||||
<h1>Wir bauen<br>agentische <em>Produkte</em>.</h1>
|
||||
<p>Eigene Plattform. Eigene Infrastruktur. Eigene Regeln.</p>
|
||||
</div>
|
||||
|
||||
<hr class="divider">
|
||||
|
||||
<section id="plattform">
|
||||
<div class="section-label">Plattform</div>
|
||||
<h2>Was dahintersteht.</h2>
|
||||
<div class="cards">
|
||||
<div class="card">
|
||||
<h3>Das Wesentliche</h3>
|
||||
<p>Jedes Produkt kennt seinen Bereich und bleibt darin. Tiefe schlägt Breite — verlässlich, jeden Tag. Keine Extras, die niemand braucht.</p>
|
||||
</div>
|
||||
<div class="card">
|
||||
<h3>Produkte für Teams</h3>
|
||||
<p>Ein Produkt für das ganze Team. Alle arbeiten mit demselben Zugang — in eigenen Sitzungen, mit voller Kontrolle darüber, wer was sieht.</p>
|
||||
</div>
|
||||
<div class="card">
|
||||
<h3>Sicher & konform</h3>
|
||||
<p>Alle Daten liegen in Deutschland. Mandantentrennung auf Containerebene, DSGVO-konforme Verarbeitung, Auftragsverarbeitung inklusive.</p>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<hr class="divider">
|
||||
|
||||
<section id="produkte">
|
||||
<div class="section-label">Produkte</div>
|
||||
<h2>Woran wir gerade bauen.</h2>
|
||||
<div class="cards">
|
||||
<div class="card">
|
||||
<h3>Frühkindliche Bildung</h3>
|
||||
<p>Fachkräfte sagen, was sie brauchen — unser Produkt baut daraus fertige Bildungsangebote. Passend zur Einrichtung, zur Altersgruppe, zum Alltag.</p>
|
||||
</div>
|
||||
<div class="card">
|
||||
<h3>Erwachsenen-Grundbildung</h3>
|
||||
<p>Lehrkräfte an Bildungseinrichtungen bekommen Unterstützung bei der Erstellung von Lernangeboten — strukturiert, lehrplannah, sofort einsetzbar.</p>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<hr class="divider">
|
||||
|
||||
<div class="cta">
|
||||
<h2>nyx</h2>
|
||||
<p>Produkte kennenlernen, ausprobieren, Zugang einrichten — direkt hier, direkt mit nyx.</p>
|
||||
<router-link to="/agents" class="btn">nyx öffnen</router-link>
|
||||
</div>
|
||||
|
||||
<footer>
|
||||
<div class="footer-links">
|
||||
<router-link to="/impressum">Impressum</router-link>
|
||||
<router-link to="/datenschutz">Datenschutz</router-link>
|
||||
</div>
|
||||
<span>© 2026 loop42 UG (haftungsbeschränkt)</span>
|
||||
</footer>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style scoped>
|
||||
.company-page {
|
||||
--cp-bg: #0a0a0a;
|
||||
--cp-bg2: #111;
|
||||
--cp-border: #1e1e1e;
|
||||
--cp-text: #e8e8e8;
|
||||
--cp-muted: rgba(232,232,232,0.4);
|
||||
--cp-accent: #7c6af7;
|
||||
--cp-accent-dim: rgba(124,106,247,0.12);
|
||||
|
||||
color: var(--cp-text);
|
||||
overflow-y: auto;
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
.hero {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
text-align: center;
|
||||
padding: 6rem 2rem 4rem;
|
||||
min-height: 60vh;
|
||||
}
|
||||
|
||||
.hero h1 {
|
||||
font-size: clamp(2rem, 5vw, 3.5rem);
|
||||
font-weight: 300;
|
||||
letter-spacing: 0.05em;
|
||||
margin: 0 0 1.5rem;
|
||||
line-height: 1.15;
|
||||
}
|
||||
|
||||
.hero h1 em {
|
||||
font-style: normal;
|
||||
color: var(--cp-accent);
|
||||
}
|
||||
|
||||
.hero p {
|
||||
font-size: 1.1rem;
|
||||
color: var(--cp-muted);
|
||||
max-width: 480px;
|
||||
margin: 0 auto 2.5rem;
|
||||
}
|
||||
|
||||
.divider {
|
||||
border: none;
|
||||
border-top: 1px solid var(--cp-border);
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
section {
|
||||
padding: 4rem 2rem;
|
||||
max-width: 860px;
|
||||
margin: 0 auto;
|
||||
}
|
||||
|
||||
.section-label {
|
||||
font-size: 0.75rem;
|
||||
letter-spacing: 0.15em;
|
||||
text-transform: uppercase;
|
||||
color: var(--cp-accent);
|
||||
margin-bottom: 1rem;
|
||||
}
|
||||
|
||||
section h2 {
|
||||
font-size: clamp(1.5rem, 3vw, 2.2rem);
|
||||
font-weight: 300;
|
||||
margin: 0 0 1rem;
|
||||
}
|
||||
|
||||
.cards {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(auto-fit, minmax(240px, 1fr));
|
||||
gap: 1.5rem;
|
||||
margin-top: 2rem;
|
||||
}
|
||||
|
||||
.card {
|
||||
background: var(--cp-bg2);
|
||||
border: 1px solid var(--cp-border);
|
||||
border-radius: 10px;
|
||||
padding: 1.5rem;
|
||||
}
|
||||
|
||||
.card h3 {
|
||||
font-size: 1rem;
|
||||
font-weight: 500;
|
||||
margin: 0 0 0.75rem;
|
||||
}
|
||||
|
||||
.card p {
|
||||
font-size: 0.9rem;
|
||||
color: var(--cp-muted);
|
||||
margin: 0;
|
||||
line-height: 1.6;
|
||||
}
|
||||
|
||||
.cta {
|
||||
text-align: center;
|
||||
padding: 4rem 2rem;
|
||||
background: var(--cp-accent-dim);
|
||||
border-top: 1px solid var(--cp-border);
|
||||
border-bottom: 1px solid var(--cp-border);
|
||||
}
|
||||
|
||||
.cta h2 {
|
||||
font-size: clamp(1.5rem, 3vw, 2rem);
|
||||
font-weight: 300;
|
||||
margin: 0 0 1rem;
|
||||
}
|
||||
|
||||
.cta p {
|
||||
color: var(--cp-muted);
|
||||
margin: 0 auto 2rem;
|
||||
max-width: 480px;
|
||||
}
|
||||
|
||||
.btn {
|
||||
display: inline-block;
|
||||
padding: 0.75rem 2rem;
|
||||
background: var(--cp-accent);
|
||||
color: #fff;
|
||||
border-radius: 6px;
|
||||
font-size: 0.9rem;
|
||||
letter-spacing: 0.05em;
|
||||
transition: opacity 0.2s;
|
||||
text-decoration: none;
|
||||
}
|
||||
.btn:hover { opacity: 0.85; }
|
||||
|
||||
footer {
|
||||
text-align: center;
|
||||
padding: 2rem;
|
||||
font-size: 0.8rem;
|
||||
color: var(--cp-muted);
|
||||
border-top: 1px solid var(--cp-border);
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 0.75rem;
|
||||
}
|
||||
|
||||
.footer-links {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
gap: 1.5rem;
|
||||
}
|
||||
|
||||
.footer-links a {
|
||||
color: var(--cp-muted);
|
||||
font-size: 0.8rem;
|
||||
text-decoration: none;
|
||||
}
|
||||
.footer-links a:hover { color: var(--cp-text); }
|
||||
|
||||
@media (max-width: 600px) {
|
||||
section { padding: 3rem 1.25rem; }
|
||||
.hero { padding: 4rem 1.25rem 3rem; }
|
||||
.cta { padding: 3rem 1.25rem; }
|
||||
}
|
||||
</style>
|
||||
115
src/views/DatenschutzView.vue
Normal file
115
src/views/DatenschutzView.vue
Normal file
@ -0,0 +1,115 @@
|
||||
<template>
|
||||
<div class="legal-page">
|
||||
<h1>Datenschutzerklärung</h1>
|
||||
|
||||
<h2>1. Verantwortlicher</h2>
|
||||
<p>
|
||||
loop42 UG (haftungsbeschränkt)<br>
|
||||
[NAME]<br>
|
||||
[STRASSE HAUSNUMMER], [PLZ ORT]
|
||||
</p>
|
||||
|
||||
<h2>2. Erhebung und Speicherung personenbezogener Daten</h2>
|
||||
<p>
|
||||
Beim Besuch dieser Website werden automatisch Informationen allgemeiner Natur erfasst
|
||||
(sog. Server-Logfiles). Diese umfassen u. a. den Browsertyp, das verwendete Betriebssystem,
|
||||
den Referrer, die IP-Adresse sowie Datum und Uhrzeit des Zugriffs. Diese Daten sind nicht
|
||||
bestimmten Personen zuordbar und werden nicht mit anderen Datenquellen zusammengeführt.
|
||||
</p>
|
||||
<p>
|
||||
Eine darüber hinausgehende Erhebung personenbezogener Daten findet auf dieser Website
|
||||
derzeit nicht statt. Sobald weitere Dienste (z. B. Kontaktformular, Nutzerkonten)
|
||||
eingeführt werden, wird diese Erklärung entsprechend aktualisiert.
|
||||
</p>
|
||||
|
||||
<h2>3. Hosting und Infrastruktur</h2>
|
||||
<p>
|
||||
Diese Website wird auf einem Server in einem deutschen Rechenzentrum betrieben.
|
||||
Der Anbieter ist IONOS SE, Elgendorfer Str. 57, 56410 Montabaur, Deutschland.
|
||||
Weitere Informationen zum Datenschutz bei IONOS finden Sie unter
|
||||
<a href="https://www.ionos.de/terms-gtc/datenschutzerklaerung/" target="_blank" rel="noopener">ionos.de</a>.
|
||||
</p>
|
||||
|
||||
<h2>4. Cookies</h2>
|
||||
<p>Diese Website verwendet keine Cookies.</p>
|
||||
|
||||
<h2>5. Analyse-Tools und Tracking</h2>
|
||||
<p>
|
||||
Diese Website verwendet keine Analyse-Tools, kein Tracking und keine eingebetteten
|
||||
Inhalte Dritter (z. B. Google Fonts, Social-Media-Widgets). Es werden keine Daten
|
||||
an Dritte übermittelt.
|
||||
</p>
|
||||
|
||||
<h2>6. Ihre Rechte</h2>
|
||||
<p>Sie haben gegenüber uns folgende Rechte hinsichtlich Ihrer personenbezogenen Daten:</p>
|
||||
<ul>
|
||||
<li>Recht auf Auskunft (Art. 15 DSGVO)</li>
|
||||
<li>Recht auf Berichtigung (Art. 16 DSGVO)</li>
|
||||
<li>Recht auf Löschung (Art. 17 DSGVO)</li>
|
||||
<li>Recht auf Einschränkung der Verarbeitung (Art. 18 DSGVO)</li>
|
||||
<li>Recht auf Datenübertragbarkeit (Art. 20 DSGVO)</li>
|
||||
<li>Recht auf Widerspruch gegen die Verarbeitung (Art. 21 DSGVO)</li>
|
||||
</ul>
|
||||
<p>Zur Ausübung Ihrer Rechte wenden Sie sich bitte an: [E-MAIL]</p>
|
||||
|
||||
<h2>7. Beschwerderecht bei der Aufsichtsbehörde</h2>
|
||||
<p>
|
||||
Sie haben das Recht, sich bei einer Datenschutz-Aufsichtsbehörde über die Verarbeitung
|
||||
Ihrer personenbezogenen Daten durch uns zu beschweren. Die zuständige Aufsichtsbehörde
|
||||
richtet sich nach dem Bundesland des Unternehmenssitzes.
|
||||
</p>
|
||||
|
||||
<h2>8. Aktualität und Änderungen</h2>
|
||||
<p>
|
||||
Diese Datenschutzerklärung ist aktuell gültig und hat den Stand März 2026.
|
||||
Durch die Weiterentwicklung unserer Website oder aufgrund geänderter gesetzlicher
|
||||
Vorgaben kann es notwendig werden, diese Datenschutzerklärung anzupassen.
|
||||
</p>
|
||||
|
||||
<footer>
|
||||
<div class="footer-links">
|
||||
<router-link to="/impressum">Impressum</router-link>
|
||||
<router-link to="/datenschutz">Datenschutz</router-link>
|
||||
</div>
|
||||
<span>© 2026 loop42 UG (haftungsbeschränkt)</span>
|
||||
</footer>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style scoped>
|
||||
.legal-page {
|
||||
--cp-border: #1e1e1e;
|
||||
--cp-muted: rgba(232,232,232,0.4);
|
||||
--cp-accent: #7c6af7;
|
||||
--cp-text: #e8e8e8;
|
||||
|
||||
padding: 3rem 2rem 2rem;
|
||||
max-width: 720px;
|
||||
margin: 0 auto;
|
||||
color: var(--cp-text);
|
||||
overflow-y: auto;
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
h1 { font-size: 2rem; font-weight: 300; margin-bottom: 2rem; }
|
||||
h2 { font-size: 1.1rem; font-weight: 500; margin: 2rem 0 0.5rem; }
|
||||
p, li { color: var(--cp-muted); font-size: 0.95rem; line-height: 1.7; }
|
||||
ul { padding-left: 1.5rem; }
|
||||
a { color: var(--cp-accent); text-decoration: none; }
|
||||
a:hover { text-decoration: underline; }
|
||||
|
||||
footer {
|
||||
text-align: center;
|
||||
padding: 3rem 0 1rem;
|
||||
font-size: 0.8rem;
|
||||
color: var(--cp-muted);
|
||||
border-top: 1px solid var(--cp-border);
|
||||
margin-top: 3rem;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 0.75rem;
|
||||
}
|
||||
.footer-links { display: flex; justify-content: center; gap: 1.5rem; }
|
||||
.footer-links a { color: var(--cp-muted); font-size: 0.8rem; }
|
||||
.footer-links a:hover { color: var(--cp-text); }
|
||||
</style>
|
||||
96
src/views/ImpressumView.vue
Normal file
96
src/views/ImpressumView.vue
Normal file
@ -0,0 +1,96 @@
|
||||
<template>
|
||||
<div class="legal-page">
|
||||
<h1>Impressum</h1>
|
||||
|
||||
<h2>Angaben gemäß § 5 TMG</h2>
|
||||
<p>
|
||||
loop42 UG (haftungsbeschränkt)<br>
|
||||
[STRASSE HAUSNUMMER]<br>
|
||||
[PLZ ORT]<br>
|
||||
Deutschland
|
||||
</p>
|
||||
|
||||
<h2>Vertreten durch</h2>
|
||||
<p>[NAME]</p>
|
||||
|
||||
<h2>Kontakt</h2>
|
||||
<p>Telefon: [TELEFONNUMMER]</p>
|
||||
|
||||
<h2>Registereintrag</h2>
|
||||
<p>
|
||||
Eintragung im Handelsregister.<br>
|
||||
Registergericht: [AMTSGERICHT]<br>
|
||||
Registernummer: [HRB-NUMMER]
|
||||
</p>
|
||||
|
||||
<h2>Umsatzsteuer-ID</h2>
|
||||
<p>
|
||||
Umsatzsteuer-Identifikationsnummer gemäß § 27 a Umsatzsteuergesetz:<br>
|
||||
[UST-ID-NUMMER]
|
||||
</p>
|
||||
|
||||
<h2>Verantwortlich für den Inhalt nach § 55 Abs. 2 RStV</h2>
|
||||
<p>
|
||||
[NAME]<br>
|
||||
[STRASSE HAUSNUMMER]<br>
|
||||
[PLZ ORT]
|
||||
</p>
|
||||
|
||||
<h2>Streitschlichtung</h2>
|
||||
<p>
|
||||
Die Europäische Kommission stellt eine Plattform zur Online-Streitbeilegung (OS) bereit:
|
||||
<a href="https://ec.europa.eu/consumers/odr/" target="_blank" rel="noopener">https://ec.europa.eu/consumers/odr/</a>.<br>
|
||||
Unsere E-Mail-Adresse finden Sie oben im Impressum.
|
||||
</p>
|
||||
<p>
|
||||
Wir sind nicht bereit oder verpflichtet, an Streitbeilegungsverfahren vor einer
|
||||
Verbraucherschlichtungsstelle teilzunehmen.
|
||||
</p>
|
||||
|
||||
<footer>
|
||||
<div class="footer-links">
|
||||
<router-link to="/impressum">Impressum</router-link>
|
||||
<router-link to="/datenschutz">Datenschutz</router-link>
|
||||
</div>
|
||||
<span>© 2026 loop42 UG (haftungsbeschränkt)</span>
|
||||
</footer>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style scoped>
|
||||
.legal-page {
|
||||
--cp-border: #1e1e1e;
|
||||
--cp-muted: rgba(232,232,232,0.4);
|
||||
--cp-accent: #7c6af7;
|
||||
--cp-text: #e8e8e8;
|
||||
|
||||
padding: 3rem 2rem 2rem;
|
||||
max-width: 720px;
|
||||
margin: 0 auto;
|
||||
color: var(--cp-text);
|
||||
overflow-y: auto;
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
h1 { font-size: 2rem; font-weight: 300; margin-bottom: 2rem; }
|
||||
h2 { font-size: 1.1rem; font-weight: 500; margin: 2rem 0 0.5rem; }
|
||||
p, li { color: var(--cp-muted); font-size: 0.95rem; line-height: 1.7; }
|
||||
ul { padding-left: 1.5rem; }
|
||||
a { color: var(--cp-accent); text-decoration: none; }
|
||||
a:hover { text-decoration: underline; }
|
||||
|
||||
footer {
|
||||
text-align: center;
|
||||
padding: 3rem 0 1rem;
|
||||
font-size: 0.8rem;
|
||||
color: var(--cp-muted);
|
||||
border-top: 1px solid var(--cp-border);
|
||||
margin-top: 3rem;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 0.75rem;
|
||||
}
|
||||
.footer-links { display: flex; justify-content: center; gap: 1.5rem; }
|
||||
.footer-links a { color: var(--cp-muted); font-size: 0.8rem; }
|
||||
.footer-links a:hover { color: var(--cp-text); }
|
||||
</style>
|
||||
Reference in New Issue
Block a user