Skip to content

Commit 7254238

Browse files
committed
Füge Unterstützung für ein entferntes Ankündigungsbanner hinzu, das Nachrichten vom Server abruft und anzeigt; implementiere die Anzeige und Verwaltung von Nachrichtenverlauf in den Optionen.
1 parent 62999d9 commit 7254238

10 files changed

Lines changed: 721 additions & 0 deletions

File tree

README.md

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -48,6 +48,7 @@ This release marks the first stable version of **AI Chat Pro Client** and introd
4848
- [`options/options.js`](options/options.js) — Provider configuration, model checks, backup I/O
4949
- [`background/background.js`](background/background.js) — Provider API routing in the service worker
5050
- [`shared/i18n.js`](shared/i18n.js) — Translation dictionary and helpers (EN / DE / RU)
51+
- [`shared/announcement.js`](shared/announcement.js) — Remote announcement banner: fetch, cache, version filter, render
5152

5253
## Installation (Chrome / Edge)
5354

@@ -85,6 +86,40 @@ This release marks the first stable version of **AI Chat Pro Client** and introd
8586
- Each provider has its own API key field. Keys are stored locally and never shared.
8687
- The *Base URL (advanced)* field lets you point to OpenAI-compatible proxies or self-hosted gateways.
8788

89+
## Remote Announcements
90+
91+
The extension fetches an optional announcement banner from `https://schiller.pw/AIChatProClientMessage/main.json` once per hour and displays it above the chat area. This is used for release notifications, security advisories, or general news. The fetch is cached locally and never sends any data about the user.
92+
93+
The endpoint serves a JSON file with the following shape:
94+
95+
```json
96+
{
97+
"id": "2026-04-10-v1.1.0",
98+
"type": "update",
99+
"title": { "en": "Update available", "de": "Update verfügbar", "ru": "Доступно обновление" },
100+
"body": { "en": "Version 1.1.0 …", "de": "Version 1.1.0 …", "ru": "Доступна версия 1.1.0 …" },
101+
"linkLabel": { "en": "Download", "de": "Herunterladen", "ru": "Скачать" },
102+
"link": "https://github.com/dan17612/AI_Chat_Pro_Browser_Extension/releases/latest",
103+
"targetVersion": "1.1.0",
104+
"minVersion": null,
105+
"dismissable": true,
106+
"expiresAt": null
107+
}
108+
```
109+
110+
| Field | Description |
111+
| --- | --- |
112+
| `id` | Unique identifier. Bump this value to re-show the banner to users who previously dismissed it. |
113+
| `type` | One of `info`, `update`, `warning`. Controls the banner color. |
114+
| `title` / `body` / `linkLabel` | Either a plain string or an object keyed by language code (`en`, `de`, `ru`). Falls back to `en` if the active language has no entry. |
115+
| `link` | Optional URL. When present, a button is rendered. |
116+
| `targetVersion` | The banner is shown only if the installed extension version is **older** than this value. Users on the latest version will not see it; once a new release with a higher `targetVersion` is published, the banner reappears. |
117+
| `minVersion` | Optional lower bound. The banner is hidden for installs below this version. |
118+
| `dismissable` | If `false`, the banner cannot be closed until the `id` changes or it expires. |
119+
| `expiresAt` | Optional ISO-8601 timestamp. The banner disappears automatically afterwards. |
120+
121+
A ready-to-host example lives in [`examples/announcement-main.json`](examples/announcement-main.json).
122+
88123
## Development
89124

90125
- No build pipeline — this is a plain browser extension project.

examples/announcement-main.json

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
{
2+
"id": "2026-04-10-v1.1.0",
3+
"type": "update",
4+
"title": {
5+
"en": "Update available",
6+
"de": "Update verfügbar",
7+
"ru": "Доступно обновление"
8+
},
9+
"body": {
10+
"en": "Version 1.1.0 of AI Chat Pro Client is now available with new features and improvements.",
11+
"de": "Version 1.1.0 von AI Chat Pro Client ist jetzt verfügbar – mit neuen Funktionen und Verbesserungen.",
12+
"ru": "Доступна версия 1.1.0 AI Chat Pro Client с новыми функциями и улучшениями."
13+
},
14+
"link": "https://github.com/dan17612/AI_Chat_Pro_Browser_Extension/releases/latest",
15+
"linkLabel": {
16+
"en": "Download",
17+
"de": "Herunterladen",
18+
"ru": "Скачать"
19+
},
20+
"targetVersion": "1.1.0",
21+
"minVersion": null,
22+
"dismissable": true,
23+
"expiresAt": null
24+
}

options/options.css

Lines changed: 87 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -700,6 +700,93 @@ body {
700700
margin-top: 16px;
701701
}
702702

703+
/* Message history (announcements) */
704+
.message-list {
705+
display: flex;
706+
flex-direction: column;
707+
gap: 10px;
708+
}
709+
710+
.message-empty {
711+
padding: 24px;
712+
text-align: center;
713+
color: var(--color-text-tertiary);
714+
font-size: 13px;
715+
border: 1px dashed var(--color-border);
716+
border-radius: var(--radius-md);
717+
background: var(--color-bg-secondary);
718+
}
719+
720+
.message-card {
721+
padding: 12px 14px;
722+
border-radius: var(--radius-md);
723+
border: 1px solid var(--color-border);
724+
background: var(--color-bg-secondary);
725+
}
726+
727+
.message-card.message-info {
728+
border-color: rgba(59, 130, 246, 0.4);
729+
background: rgba(59, 130, 246, 0.06);
730+
}
731+
732+
.message-card.message-update {
733+
border-color: rgba(34, 197, 94, 0.4);
734+
background: rgba(34, 197, 94, 0.06);
735+
}
736+
737+
.message-card.message-warning {
738+
border-color: rgba(234, 179, 8, 0.5);
739+
background: rgba(234, 179, 8, 0.08);
740+
}
741+
742+
.message-card-header {
743+
display: flex;
744+
align-items: center;
745+
gap: 8px;
746+
margin-bottom: 6px;
747+
}
748+
749+
.message-icon {
750+
font-size: 16px;
751+
line-height: 1;
752+
}
753+
754+
.message-card-title {
755+
flex: 1 1 auto;
756+
font-weight: 600;
757+
color: var(--color-text-primary);
758+
font-size: 14px;
759+
}
760+
761+
.message-card-time {
762+
flex: 0 0 auto;
763+
font-size: 11px;
764+
color: var(--color-text-tertiary);
765+
}
766+
767+
.message-card-body {
768+
font-size: 13px;
769+
line-height: 1.5;
770+
color: var(--color-text-secondary);
771+
word-wrap: break-word;
772+
}
773+
774+
.message-card-link {
775+
display: inline-block;
776+
margin-top: 8px;
777+
padding: 4px 12px;
778+
background: var(--color-accent);
779+
color: #fff;
780+
border-radius: var(--radius-sm);
781+
font-size: 12px;
782+
font-weight: 600;
783+
text-decoration: none;
784+
}
785+
786+
.message-card-link:hover {
787+
filter: brightness(1.1);
788+
}
789+
703790
/* Animations */
704791
@keyframes fadeIn {
705792
from {

options/options.html

Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@
1515
</script>
1616
<link rel="stylesheet" href="options.css" />
1717
<script src="../shared/i18n.js"></script>
18+
<script src="../shared/announcement.js"></script>
1819
</head>
1920
<body>
2021
<div id="app" class="theme-dark">
@@ -126,6 +127,22 @@ <h1><span data-i18n="app.name">AI Chat Pro Client</span> <span class="version">v
126127
</svg>
127128
<span data-i18n="nav.behavior">Behavior</span>
128129
</button>
130+
<button class="nav-item" data-section="messages">
131+
<svg
132+
width="16"
133+
height="16"
134+
viewBox="0 0 24 24"
135+
fill="none"
136+
stroke="currentColor"
137+
stroke-width="2"
138+
>
139+
<path d="M22 12h-6l-2 3h-4l-2-3H2" />
140+
<path
141+
d="M5.45 5.11 2 12v6a2 2 0 0 0 2 2h16a2 2 0 0 0 2-2v-6l-3.45-6.89A2 2 0 0 0 16.76 4H7.24a2 2 0 0 0-1.79 1.11z"
142+
/>
143+
</svg>
144+
<span data-i18n="nav.messages">Messages</span>
145+
</button>
129146
<button class="nav-item" data-section="data">
130147
<svg
131148
width="16"
@@ -538,6 +555,36 @@ <h2 data-i18n="behavior.title">Behavior</h2>
538555
</section>
539556

540557
<!-- Data Section -->
558+
<!-- Messages Section -->
559+
<section id="section-messages" class="settings-section">
560+
<div class="section-header">
561+
<h2 data-i18n="messages.title">Messages</h2>
562+
<p data-i18n="messages.subtitle">
563+
Recent announcements from the developer. Messages you dismissed in the chat banner are still listed here.
564+
</p>
565+
</div>
566+
567+
<div class="setting-group">
568+
<div id="message-list" class="message-list"></div>
569+
<button id="btn-clear-messages" class="btn btn-secondary" style="margin-top: 12px;">
570+
<svg
571+
width="14"
572+
height="14"
573+
viewBox="0 0 24 24"
574+
fill="none"
575+
stroke="currentColor"
576+
stroke-width="2"
577+
>
578+
<polyline points="3 6 5 6 21 6" />
579+
<path
580+
d="M19 6v14a2 2 0 0 1-2 2H7a2 2 0 0 1-2-2V6m3 0V4a2 2 0 0 1 2-2h4a2 2 0 0 1 2 2v2"
581+
/>
582+
</svg>
583+
<span data-i18n="messages.clear">Clear message history</span>
584+
</button>
585+
</div>
586+
</section>
587+
541588
<section id="section-data" class="settings-section">
542589
<div class="section-header">
543590
<h2 data-i18n="data.title">Data & Storage</h2>

options/options.js

Lines changed: 106 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -174,6 +174,8 @@
174174
importFileInput: $("#import-file-input"),
175175
btnClearAll: $("#btn-clear-all"),
176176
btnResetSettings: $("#btn-reset-settings"),
177+
messageList: $("#message-list"),
178+
btnClearMessages: $("#btn-clear-messages"),
177179
};
178180

179181
// ---- Init ----
@@ -184,6 +186,13 @@
184186
populateUI();
185187
bindEvents();
186188
loadStats();
189+
// Trigger a fresh announcement fetch (keeps the history up to date even
190+
// if the user only ever opens the options page) and render whatever we have.
191+
if (window.announcement) {
192+
window.announcement.fetch(false).finally(() => renderMessageHistory());
193+
} else {
194+
renderMessageHistory();
195+
}
187196
}
188197

189198
function applyLanguage() {
@@ -521,9 +530,22 @@
521530
btn.classList.add("active");
522531
$$(".settings-section").forEach((s) => s.classList.remove("active"));
523532
$(`#section-${btn.dataset.section}`).classList.add("active");
533+
if (btn.dataset.section === "messages") {
534+
renderMessageHistory();
535+
}
524536
});
525537
});
526538

539+
// Clear message history
540+
if (dom.btnClearMessages) {
541+
dom.btnClearMessages.addEventListener("click", async () => {
542+
if (window.announcement) {
543+
await window.announcement.clearHistory();
544+
}
545+
renderMessageHistory();
546+
});
547+
}
548+
527549
// Provider
528550
dom.providerSelect.addEventListener("change", () => {
529551
settings.provider = dom.providerSelect.value;
@@ -794,6 +816,90 @@
794816
dom.statMessages.textContent = totalMessages;
795817
}
796818

819+
// ---- Message history (announcements) ----
820+
async function renderMessageHistory() {
821+
if (!dom.messageList) return;
822+
dom.messageList.innerHTML = "";
823+
824+
if (!window.announcement) {
825+
const empty = document.createElement("div");
826+
empty.className = "message-empty";
827+
empty.textContent = t("messages.empty");
828+
dom.messageList.appendChild(empty);
829+
return;
830+
}
831+
832+
const history = await window.announcement.getHistory();
833+
if (!history.length) {
834+
const empty = document.createElement("div");
835+
empty.className = "message-empty";
836+
empty.textContent = t("messages.empty");
837+
dom.messageList.appendChild(empty);
838+
return;
839+
}
840+
841+
const lang = window.i18n ? window.i18n.i18nGetLang() : "en";
842+
const fmt = new Intl.DateTimeFormat(lang === "en" ? undefined : lang, {
843+
year: "numeric",
844+
month: "2-digit",
845+
day: "2-digit",
846+
hour: "2-digit",
847+
minute: "2-digit",
848+
});
849+
850+
history.forEach((entry) => {
851+
const type = ["info", "update", "warning"].includes(entry.type)
852+
? entry.type
853+
: "info";
854+
855+
const card = document.createElement("div");
856+
card.className = `message-card message-${type}`;
857+
858+
const header = document.createElement("div");
859+
header.className = "message-card-header";
860+
861+
const icon = document.createElement("span");
862+
icon.className = "message-icon";
863+
icon.textContent =
864+
type === "warning" ? "⚠️" : type === "update" ? "⬆️" : "ℹ️";
865+
header.appendChild(icon);
866+
867+
const title = document.createElement("div");
868+
title.className = "message-card-title";
869+
title.textContent = window.announcement.pickLocalized(entry.title, lang);
870+
header.appendChild(title);
871+
872+
const time = document.createElement("span");
873+
time.className = "message-card-time";
874+
time.textContent = entry.receivedAt ? fmt.format(entry.receivedAt) : "";
875+
header.appendChild(time);
876+
877+
card.appendChild(header);
878+
879+
const body = window.announcement.pickLocalized(entry.body, lang);
880+
if (body) {
881+
const bodyEl = document.createElement("div");
882+
bodyEl.className = "message-card-body";
883+
bodyEl.textContent = body;
884+
card.appendChild(bodyEl);
885+
}
886+
887+
if (entry.link) {
888+
const link = document.createElement("a");
889+
link.className = "message-card-link";
890+
link.href = entry.link;
891+
link.target = "_blank";
892+
link.rel = "noopener noreferrer";
893+
link.textContent =
894+
window.announcement.pickLocalized(entry.linkLabel, lang) ||
895+
entry.link;
896+
card.appendChild(link);
897+
}
898+
899+
dom.messageList.appendChild(card);
900+
});
901+
}
902+
797903
/**
798904
* Full backup: conversations + settings (includes custom models, base URLs,
799905
* API keys, model check cache, language, etc).

0 commit comments

Comments
 (0)