Compare commits
No commits in common. "main" and "chatgpt_web_custom_gpt_project" have entirely different histories.
main
...
chatgpt_we
7
.github/ISSUE_TEMPLATE/bug_report.yml
vendored
|
|
@ -8,7 +8,7 @@ body:
|
|||
|
||||
If you have a feature or enhancement request, please use the [feature request][fr] form.
|
||||
|
||||
[fr]: https://github.com/micz/ThunderAI/issues/new?assignees=&labels=&projects=&template=feature_request.yml&title=
|
||||
[fr]: https://github.com/micz/ThunderAI/issues/new?assignees=&labels=&projects=&template=feature_request.md&title=
|
||||
- type: textarea
|
||||
validations:
|
||||
required: true
|
||||
|
|
@ -36,7 +36,7 @@ body:
|
|||
attributes:
|
||||
label: Which version of Thunderbird are you using?
|
||||
description: >
|
||||
Thunderbird version like 140.0 or 147.0.1.
|
||||
Thunderbird version like 115.14.0 or 128.1.
|
||||
- type: input
|
||||
id: version
|
||||
validations:
|
||||
|
|
@ -52,11 +52,8 @@ body:
|
|||
attributes:
|
||||
label: Which integration are you using?
|
||||
options:
|
||||
- -- Select an option --
|
||||
- ChatGPT Web Interface
|
||||
- OpenAI ChatGPT API
|
||||
- Google Gemini API
|
||||
- Claude API
|
||||
- Ollama API
|
||||
- OpenAI Compatible API
|
||||
- type: markdown
|
||||
|
|
|
|||
17
.github/ISSUE_TEMPLATE/feature_request.md
vendored
Normal file
|
|
@ -0,0 +1,17 @@
|
|||
---
|
||||
name: Feature Request
|
||||
about: Suggest an idea for this project
|
||||
title: ''
|
||||
labels: ''
|
||||
assignees: ''
|
||||
|
||||
---
|
||||
|
||||
**Is your feature request related to a problem? Please describe.**
|
||||
A clear and concise description of what the problem is. Ex. I'm always frustrated when [...]
|
||||
|
||||
**Describe the solution you'd like**
|
||||
A clear and concise description of what you want to happen.
|
||||
|
||||
**Additional context**
|
||||
Add any other context or screenshots about the feature request here.
|
||||
40
.github/ISSUE_TEMPLATE/feature_request.yml
vendored
|
|
@ -1,40 +0,0 @@
|
|||
name: Feature Request
|
||||
description: Suggest a new idea for ThunderAI
|
||||
body:
|
||||
- type: markdown
|
||||
attributes:
|
||||
value: |
|
||||
This issue form is for suggesting new ideas only!
|
||||
|
||||
If you need to report a bug, please use the [bug][bg] form.
|
||||
|
||||
[bg]: https://github.com/micz/ThunderAI/issues/new?template=bug_report.yml
|
||||
- type: textarea
|
||||
validations:
|
||||
required: true
|
||||
id: idea
|
||||
attributes:
|
||||
label: Your idea
|
||||
description: >-
|
||||
Describe the idea you'd like to see implemented in ThunderAI.
|
||||
|
||||
Provide a clear and concise description.
|
||||
- type: markdown
|
||||
attributes:
|
||||
value: |
|
||||
# Details
|
||||
- type: textarea
|
||||
id: value
|
||||
attributes:
|
||||
label: Value
|
||||
description: >-
|
||||
What value does this idea bring to ThunderAI? How does it improve the
|
||||
user experience or functionality?
|
||||
- type: textarea
|
||||
id: additional
|
||||
attributes:
|
||||
label: Additional information
|
||||
description: >
|
||||
If you have any additional information, use the field below.
|
||||
Please note, you can attach screenshots or screen recordings here, by
|
||||
dragging and dropping files in the field below.
|
||||
80
.github/scripts/tom-select-update.js
vendored
|
|
@ -1,80 +0,0 @@
|
|||
const fs = require('fs');
|
||||
const https = require('https');
|
||||
const path = require('path');
|
||||
|
||||
// --- Utility: fetch URL following redirects ---
|
||||
function fetchUrl(url) {
|
||||
return new Promise((resolve, reject) => {
|
||||
https.get(url, { headers: { 'User-Agent': 'ThunderAI-vendor-updater' } }, (res) => {
|
||||
if (res.statusCode === 301 || res.statusCode === 302) {
|
||||
return fetchUrl(res.headers.location).then(resolve).catch(reject);
|
||||
}
|
||||
const chunks = [];
|
||||
res.on('data', chunk => chunks.push(chunk));
|
||||
res.on('end', () => resolve({ status: res.statusCode, body: Buffer.concat(chunks) }));
|
||||
}).on('error', reject);
|
||||
});
|
||||
}
|
||||
|
||||
// --- Read VENDORS.md ---
|
||||
const vendorsPath = 'VENDORS.md';
|
||||
const content = fs.readFileSync(vendorsPath, 'utf8');
|
||||
|
||||
// --- Find all entries: file + source ---
|
||||
const entryRegex = /file:\s*(.+?)\r?\nsource:\s*(https?:\/\/.+)/g;
|
||||
const entries = [];
|
||||
let match;
|
||||
while ((match = entryRegex.exec(content)) !== null) {
|
||||
entries.push({
|
||||
file: match[1].trim(),
|
||||
source: match[2].trim(),
|
||||
});
|
||||
}
|
||||
|
||||
// --- Filter tom-select entries only ---
|
||||
const filteredEntries = entries.filter(e => e.file.toLowerCase().includes('tom-select'));
|
||||
|
||||
if (filteredEntries.length === 0) {
|
||||
console.log('No entries found in VENDORS.md');
|
||||
process.exit(0);
|
||||
}
|
||||
|
||||
// --- Main ---
|
||||
(async () => {
|
||||
let hasErrors = false;
|
||||
|
||||
for (const entry of filteredEntries) {
|
||||
const filePath = entry.file.replace(/\\/g, '/');
|
||||
console.log(`\nProcessing: ${filePath}`);
|
||||
console.log(` Source: ${entry.source}`);
|
||||
|
||||
// Create directory if it doesn't exist
|
||||
const dir = path.dirname(filePath);
|
||||
if (!fs.existsSync(dir)) {
|
||||
fs.mkdirSync(dir, { recursive: true });
|
||||
console.log(` → Directory created: ${dir}`);
|
||||
}
|
||||
|
||||
// Download file
|
||||
try {
|
||||
const res = await fetchUrl(entry.source);
|
||||
if (res.status === 200) {
|
||||
fs.writeFileSync(filePath, res.body);
|
||||
console.log(` ✓ File updated (${res.body.length} bytes)`);
|
||||
} else {
|
||||
console.error(` ✗ Download failed: HTTP ${res.status}`);
|
||||
hasErrors = true;
|
||||
}
|
||||
} catch (err) {
|
||||
console.error(` ✗ Error: ${err.message}`);
|
||||
hasErrors = true;
|
||||
}
|
||||
}
|
||||
|
||||
if (hasErrors) {
|
||||
console.error('\nSome files were not updated.');
|
||||
process.exit(1);
|
||||
} else {
|
||||
console.log('\nAll files updated successfully.');
|
||||
}
|
||||
})();
|
||||
117
.github/workflows/auto-mark-released.yml
vendored
|
|
@ -1,117 +0,0 @@
|
|||
name: Mark released issues on close
|
||||
|
||||
on:
|
||||
issues:
|
||||
types: [closed]
|
||||
|
||||
permissions:
|
||||
contents: read
|
||||
issues: write
|
||||
|
||||
jobs:
|
||||
mark-released:
|
||||
# Run only if closed as completed AND has at least one "status:" label
|
||||
if: ${{ github.event.issue.state_reason == 'completed' && contains(toJSON(github.event.issue.labels), 'status:') }}
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Set "released" label when closed completed and was ready for release
|
||||
uses: actions/github-script@v7
|
||||
with:
|
||||
script: |
|
||||
// Helpers
|
||||
const owner = context.repo.owner;
|
||||
const repo = context.repo.repo;
|
||||
const issue = context.payload.issue;
|
||||
if (!issue) {
|
||||
core.info('No issue payload found, skipping.');
|
||||
return;
|
||||
}
|
||||
|
||||
// Only proceed if closed as "completed" (not "not_planned")
|
||||
const isCompleted = issue.state_reason === 'completed';
|
||||
if (!isCompleted) {
|
||||
core.info('Issue state_reason is not "completed"; skipping.');
|
||||
return;
|
||||
}
|
||||
|
||||
const getName = (l) => typeof l === 'string' ? l : l.name;
|
||||
const labels = (issue.labels || []).map(getName);
|
||||
|
||||
// Status detection (robust to case/spacing)
|
||||
const isStatusLabel = (name) => /^status:\s*/i.test(String(name || ''));
|
||||
const hasAnyStatus = labels.some(isStatusLabel);
|
||||
if (!hasAnyStatus) {
|
||||
core.info('Issue has no "status:*" label; skipping.');
|
||||
return;
|
||||
}
|
||||
|
||||
const READY_REGEX = /^status:\s*ready\s*for\s*release$/i;
|
||||
const hasReady = labels.some((n) => READY_REGEX.test(String(n || '')));
|
||||
if (!hasReady) {
|
||||
core.info('Issue is not "status: ready for release"; nothing to do.');
|
||||
return;
|
||||
}
|
||||
|
||||
const releasedLabel = 'status: released';
|
||||
|
||||
// Ensure "released" label exists
|
||||
try {
|
||||
await github.rest.issues.getLabel({ owner, repo, name: releasedLabel });
|
||||
} catch (e) {
|
||||
if (e.status === 404) {
|
||||
core.info(`Label "${releasedLabel}" not found. Creating it.`);
|
||||
await github.rest.issues.createLabel({
|
||||
owner,
|
||||
repo,
|
||||
name: releasedLabel,
|
||||
color: '184738', // deep green
|
||||
description: 'Feature released.'
|
||||
});
|
||||
} else {
|
||||
throw e;
|
||||
}
|
||||
}
|
||||
|
||||
// Remove all status:* labels, add "status: released"
|
||||
const filtered = labels.filter((name) => !isStatusLabel(name));
|
||||
const newLabels = [...new Set([...filtered, releasedLabel])];
|
||||
|
||||
await github.rest.issues.update({
|
||||
owner,
|
||||
repo,
|
||||
issue_number: issue.number,
|
||||
labels: newLabels
|
||||
});
|
||||
|
||||
core.info(`Issue #${issue.number} relabeled as "${releasedLabel}".`);
|
||||
|
||||
// === NEW: add release comment with milestone if present ===
|
||||
const milestone = issue.milestone;
|
||||
if (!milestone || !milestone.title) {
|
||||
core.info('Issue has no milestone; no release comment added.');
|
||||
return;
|
||||
}
|
||||
|
||||
const commentBody = `Released in version ${milestone.title}.`;
|
||||
|
||||
// Avoid duplicate comment if the action reruns
|
||||
const existingComments = await github.paginate(
|
||||
github.rest.issues.listComments,
|
||||
{ owner, repo, issue_number: issue.number, per_page: 100 }
|
||||
);
|
||||
|
||||
const alreadyCommented = existingComments.some(c =>
|
||||
typeof c.body === 'string' && c.body.trim() === commentBody
|
||||
);
|
||||
|
||||
if (alreadyCommented) {
|
||||
core.info('Release comment already present; skipping comment creation.');
|
||||
} else {
|
||||
await github.rest.issues.createComment({
|
||||
owner,
|
||||
repo,
|
||||
issue_number: issue.number,
|
||||
body: commentBody
|
||||
});
|
||||
core.info('Release comment added.');
|
||||
}
|
||||
|
|
@ -1,75 +0,0 @@
|
|||
name: Issues Validate Integration Field
|
||||
|
||||
on:
|
||||
issues:
|
||||
types: [opened, edited]
|
||||
|
||||
permissions:
|
||||
issues: write
|
||||
|
||||
jobs:
|
||||
check-integration:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Validate dropdown selection (only for bug report form)
|
||||
uses: actions/github-script@v7
|
||||
with:
|
||||
script: |
|
||||
const issue = context.payload.issue;
|
||||
const bodyText = issue.body || "";
|
||||
|
||||
// ---- Guard: only act if this looks like a Bug Report form ----
|
||||
if (!bodyText.includes("### The problem")) {
|
||||
core.info("Skipped: This issue does not contain '### The problem', so it is not a Bug Report.");
|
||||
return;
|
||||
}
|
||||
core.info("Detected a Bug Report issue. Proceeding with validation...");
|
||||
|
||||
// Extract selection under "Which integration are you using?"
|
||||
const header = "### Which integration are you using?";
|
||||
const nextHeaderRegex = /\n### /;
|
||||
let selection = "";
|
||||
|
||||
const startIdx = bodyText.indexOf(header);
|
||||
if (startIdx !== -1) {
|
||||
const afterHeader = bodyText.slice(startIdx + header.length).replace(/^\r?\n+/, "");
|
||||
const nextHeaderIdx = afterHeader.search(nextHeaderRegex);
|
||||
selection = (nextHeaderIdx === -1 ? afterHeader : afterHeader.slice(0, nextHeaderIdx)).trim();
|
||||
}
|
||||
|
||||
core.info(`Integration field extracted: "${selection}"`);
|
||||
|
||||
const isInvalid = !selection || selection.startsWith("-- Select an option");
|
||||
if (isInvalid) {
|
||||
core.info("Integration field is invalid or not selected.");
|
||||
} else {
|
||||
core.info("Integration field is valid. Nothing to do.");
|
||||
}
|
||||
|
||||
const owner = context.repo.owner;
|
||||
const repo = context.repo.repo;
|
||||
const issue_number = issue.number;
|
||||
|
||||
// Avoid duplicate comments
|
||||
const marker = "please specify **Which integration are you using?**";
|
||||
const { data: comments } = await github.rest.issues.listComments({ owner, repo, issue_number });
|
||||
const alreadyCommented = comments.some(c => c.body && c.body.includes(marker));
|
||||
|
||||
if (isInvalid) {
|
||||
if (!alreadyCommented) {
|
||||
core.info("No existing reminder found. Posting a new comment...");
|
||||
await github.rest.issues.createComment({
|
||||
owner, repo, issue_number,
|
||||
body: [
|
||||
`👋 Hello @${issue.user.login}!`,
|
||||
`To complete your bug report, please specify which integration you are using.`,
|
||||
"",
|
||||
"Right now the field `**Which integration are you using?**` is set to `-- Select an option --`.",
|
||||
"",
|
||||
"Click **Edit** at the top right of the issue, choose a valid option from the dropdown, and save.",
|
||||
].join("\n")
|
||||
});
|
||||
} else {
|
||||
core.info("Reminder comment already exists. Skipping new comment.");
|
||||
}
|
||||
}
|
||||
103
.github/workflows/prerelease_comment_issue.yml
vendored
|
|
@ -1,103 +0,0 @@
|
|||
name: pre-release comment issues
|
||||
|
||||
on:
|
||||
release:
|
||||
types: [published]
|
||||
|
||||
concurrency:
|
||||
group: ${{ github.workflow }}
|
||||
cancel-in-progress: false
|
||||
|
||||
permissions:
|
||||
contents: read
|
||||
issues: write
|
||||
|
||||
jobs:
|
||||
comment-milestone-issues:
|
||||
# allow manual run for testing; in production, only prerelease
|
||||
if: ${{ github.event.release.prerelease == true }}
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Debug context (no checkout)
|
||||
run: |
|
||||
echo "Event: ${{ github.event_name }}"
|
||||
echo "Default branch: ${{ github.event.repository.default_branch }}"
|
||||
echo "Release tag: ${{ github.event.release.tag_name || 'N/A' }}"
|
||||
echo "This workflow file lives on MAIN and is executed as-is (no checkout)."
|
||||
|
||||
- name: Add comment to issues of matching milestone (filtered by labels)
|
||||
uses: actions/github-script@v7
|
||||
with:
|
||||
script: |
|
||||
// core, github, context are already available in github-script
|
||||
await (async () => {
|
||||
const parsePrereleaseTag = (tag) => {
|
||||
const m = /^v?(\d+\.\d+\.\d+)pre(\d+)$/.exec(tag ?? "");
|
||||
return m ? { milestoneTitle: m[1], preNumber: Number(m[2]) } : null;
|
||||
};
|
||||
|
||||
const { owner, repo } = context.repo;
|
||||
|
||||
// If this is a manual run, exit with a clear message
|
||||
if (context.eventName !== 'release') {
|
||||
core.notice("Manual run: no release payload, nothing to do.");
|
||||
return;
|
||||
}
|
||||
|
||||
const tagName = context.payload.release?.tag_name;
|
||||
if (!tagName) { core.notice("No tag_name found on release event."); return; }
|
||||
|
||||
const parsed = parsePrereleaseTag(tagName);
|
||||
if (!parsed) { core.notice(`Tag '${tagName}' does not match vX.Y.ZpreN. Skipping.`); return; }
|
||||
|
||||
const milestoneTitle = parsed.milestoneTitle; // es. "3.7.0"
|
||||
const prereleaseTagDisplay = tagName.startsWith("v") ? tagName : `v${tagName}`;
|
||||
const commentBody = `This feature is available for testing in the pre-release version [${prereleaseTagDisplay}](https://github.com/micz/ThunderAI/releases).`;
|
||||
core.info(`(MAIN) Looking for milestone '${milestoneTitle}' in ${owner}/${repo}`);
|
||||
|
||||
// Find milestone (open or closed)
|
||||
const milestones = await github.paginate(
|
||||
github.rest.issues.listMilestones,
|
||||
{ owner, repo, state: "all", per_page: 100 }
|
||||
);
|
||||
const milestone = milestones.find(m => m.title === milestoneTitle);
|
||||
if (!milestone) { core.warning(`Milestone '${milestoneTitle}' not found.`); return; }
|
||||
|
||||
// All issues in the milestone
|
||||
const issues = await github.paginate(
|
||||
github.rest.issues.listForRepo,
|
||||
{ owner, repo, milestone: milestone.number, state: "all", per_page: 100 }
|
||||
);
|
||||
|
||||
// Only issues (no PRs) with the requested labels
|
||||
const allowed = new Set(["status: in review", "status: ready for release"]);
|
||||
const targets = issues.filter(i =>
|
||||
!i.pull_request &&
|
||||
Array.isArray(i.labels) &&
|
||||
i.labels.some(l => allowed.has(l.name))
|
||||
);
|
||||
|
||||
core.info(`Candidate issues: ${targets.length}`);
|
||||
|
||||
let commented = 0;
|
||||
for (const issue of targets) {
|
||||
const comments = await github.paginate(
|
||||
github.rest.issues.listComments,
|
||||
{ owner, repo, issue_number: issue.number, per_page: 100 }
|
||||
);
|
||||
|
||||
const exists = comments.some(c => (c.body || "").trim() === commentBody);
|
||||
if (exists) {
|
||||
core.info(`#${issue.number}: comment already present, skip.`);
|
||||
continue;
|
||||
}
|
||||
|
||||
await github.rest.issues.createComment({
|
||||
owner, repo, issue_number: issue.number, body: commentBody
|
||||
});
|
||||
commented++;
|
||||
core.info(`#${issue.number}: comment added.`);
|
||||
}
|
||||
|
||||
core.info(`Done. Added comment to ${commented} issue(s).`);
|
||||
})();
|
||||
61
.github/workflows/tom-select-release-check.yml
vendored
|
|
@ -1,61 +0,0 @@
|
|||
name: Check Tom-Select Latest Release
|
||||
|
||||
on:
|
||||
schedule:
|
||||
- cron: '0 19 * * *' # Check every day at 19:00
|
||||
workflow_dispatch: # Allows manual triggering for testing purposes
|
||||
|
||||
permissions:
|
||||
contents: read
|
||||
issues: write
|
||||
|
||||
jobs:
|
||||
check-and-notify:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Get latest release from external repo
|
||||
id: get_release
|
||||
run: |
|
||||
REPO_TARGET="orchidjs/tom-select"
|
||||
LATEST_TAG=$(curl -s https://api.github.com/repos/$REPO_TARGET/releases/latest | jq -r .tag_name)
|
||||
|
||||
if [ "$LATEST_TAG" == "null" ] || [ -z "$LATEST_TAG" ]; then
|
||||
echo "Error: unable to fetch tag or no release found."
|
||||
exit 1
|
||||
fi
|
||||
|
||||
echo "tag=$LATEST_TAG" >> $GITHUB_OUTPUT
|
||||
echo "target=$REPO_TARGET" >> $GITHUB_OUTPUT
|
||||
|
||||
- name: Check if Issue already exists and create it
|
||||
uses: actions/github-script@v7
|
||||
with:
|
||||
script: |
|
||||
const latestTag = "${{ steps.get_release.outputs.tag }}";
|
||||
const targetRepo = "${{ steps.get_release.outputs.target }}";
|
||||
const { owner, repo } = context.repo;
|
||||
const issueTitle = `Update Tom Select to version ${latestTag}`;
|
||||
|
||||
// Search through issues (both open and closed for safety)
|
||||
const response = await github.rest.search.issuesAndPullRequests({
|
||||
q: `repo:${owner}/${repo} is:issue "${issueTitle}"`,
|
||||
});
|
||||
|
||||
if (response.data.total_count > 0) {
|
||||
core.info(`The issue for version ${latestTag} already exists. No action taken.`);
|
||||
} else {
|
||||
await github.rest.issues.create({
|
||||
owner,
|
||||
repo,
|
||||
title: issueTitle,
|
||||
labels: ['enhancement'],
|
||||
body: `Update Tom Select to version **${latestTag}**.
|
||||
|
||||
https://cdn.jsdelivr.net/npm/tom-select@${latestTag}/dist/js/tom-select.base.js
|
||||
|
||||
https://cdn.jsdelivr.net/npm/tom-select@${latestTag}/dist/css/tom-select.default.min.css
|
||||
|
||||
Change the VENDORS.md file to trigger the library update.`
|
||||
});
|
||||
core.info(`New issue created for version ${latestTag}.`);
|
||||
}
|
||||
81
.github/workflows/tom-select-update.yml
vendored
|
|
@ -1,81 +0,0 @@
|
|||
name: Update Tom Select Files
|
||||
|
||||
on:
|
||||
workflow_dispatch:
|
||||
push:
|
||||
paths:
|
||||
- 'VENDORS.md'
|
||||
|
||||
jobs:
|
||||
tom-select-update:
|
||||
runs-on: ubuntu-latest
|
||||
permissions:
|
||||
contents: write
|
||||
pull-requests: write
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
with:
|
||||
ref: ${{ github.ref }}
|
||||
token: ${{ secrets.GITHUB_TOKEN }}
|
||||
|
||||
- uses: actions/setup-node@v4
|
||||
with:
|
||||
node-version: '20'
|
||||
|
||||
- name: Check and update vendor files
|
||||
run: node .github/scripts/tom-select-update.js
|
||||
|
||||
- name: Commit and push to PR branch
|
||||
id: commit
|
||||
run: |
|
||||
BASE_BRANCH="${{ github.ref_name }}"
|
||||
PR_BRANCH="tom-select-update-${BASE_BRANCH}"
|
||||
|
||||
git config user.name "github-actions[bot]"
|
||||
git config user.email "github-actions[bot]@users.noreply.github.com"
|
||||
|
||||
# If there are no changes, exit without doing anything
|
||||
if git diff --quiet; then
|
||||
echo "No changes to vendor files."
|
||||
echo "has_changes=false" >> $GITHUB_OUTPUT
|
||||
exit 0
|
||||
fi
|
||||
|
||||
# Crea o aggiorna la branch PR
|
||||
git checkout -B "$PR_BRANCH"
|
||||
git add -A
|
||||
git commit -m "Update Tom Select files from VENDORS.md"
|
||||
git push origin "$PR_BRANCH" --force
|
||||
|
||||
echo "has_changes=true" >> $GITHUB_OUTPUT
|
||||
echo "pr_branch=$PR_BRANCH" >> $GITHUB_OUTPUT
|
||||
echo "base_branch=$BASE_BRANCH" >> $GITHUB_OUTPUT
|
||||
|
||||
- name: Create Pull Request via API
|
||||
if: steps.commit.outputs.has_changes == 'true'
|
||||
run: |
|
||||
# Try to create the PR; if it already exists (422) ignore the error
|
||||
HTTP_CODE=$(curl -s -o /tmp/pr_response.json -w "%{http_code}" \
|
||||
-X POST \
|
||||
-H "Authorization: Bearer ${{ secrets.GITHUB_TOKEN }}" \
|
||||
-H "Accept: application/vnd.github+json" \
|
||||
-H "X-GitHub-Api-Version: 2022-11-28" \
|
||||
https://api.github.com/repos/${{ github.repository }}/pulls \
|
||||
-d "{
|
||||
\"title\": \"Update Tom Select files from VENDORS.md\",
|
||||
\"body\": \"Tom Select files updated automatically after a change to \`VENDORS.md\`.\n\nPlease review the downloaded files before merging.\",
|
||||
\"head\": \"${{ steps.commit.outputs.pr_branch }}\",
|
||||
\"base\": \"${{ steps.commit.outputs.base_branch }}\"
|
||||
}")
|
||||
|
||||
cat /tmp/pr_response.json
|
||||
|
||||
if [ "$HTTP_CODE" = "201" ]; then
|
||||
echo "PR created successfully."
|
||||
elif [ "$HTTP_CODE" = "422" ]; then
|
||||
echo "PR already exists, updated with the new commit."
|
||||
else
|
||||
echo "Error creating PR: HTTP $HTTP_CODE"
|
||||
exit 1
|
||||
fi
|
||||
30
.gitignore
vendored
|
|
@ -1,30 +0,0 @@
|
|||
# Logs
|
||||
logs
|
||||
*.log
|
||||
npm-debug.log*
|
||||
yarn-debug.log*
|
||||
yarn-error.log*
|
||||
pnpm-debug.log*
|
||||
lerna-debug.log*
|
||||
|
||||
node_modules
|
||||
.DS_Store
|
||||
dist
|
||||
dist-ssr
|
||||
coverage
|
||||
*.local
|
||||
|
||||
# Editor directories and files
|
||||
.vscode
|
||||
.vscode/*
|
||||
.idea
|
||||
*.suo
|
||||
*.ntvs*
|
||||
*.njsproj
|
||||
*.sln
|
||||
*.sw?
|
||||
|
||||
*.tsbuildinfo
|
||||
dist
|
||||
|
||||
.claude
|
||||
260
CHANGELOG.md
|
|
@ -3,272 +3,20 @@
|
|||
|
||||
|
||||
|
||||
<h2>Version 4.1.0 - 13/05/2026</h2>
|
||||
|
||||
<h2>Version 3.4.0 - ??/??/2025</h2>
|
||||
<ul>
|
||||
<li>Antispam information are now permanently saved for each message [<a href="https://github.com/micz/ThunderAI/issues/675">#675</a>].</li>
|
||||
<li><i>[All APIs]</i> A summary has been added above the mail content [<a href="https://github.com/micz/ThunderAI/issues/580">#580</a>].</li>
|
||||
<li><i>[All APIs]</i> Added inline auto translation for emails [<a href="https://github.com/micz/ThunderAI/issues/247">#247</a>].</li>
|
||||
<li>Custom menus configuration added. Now it's possibile to define which prompts show in the ThunderAI menu, which ones in the context menu and in which order [<a href="https://github.com/micz/ThunderAI/issues/49">#49</a>, <a href="https://github.com/micz/ThunderAI/issues/184">#184</a>, <a href="https://github.com/micz/ThunderAI/issues/680">#680</a>].</li>
|
||||
<li>Now the popup menu closes immediatly and the working indicator is in the button icon [<a href="https://github.com/micz/ThunderAI/issues/247">#677</a>].</li>
|
||||
<li><i>[All APIs]</i> Error messages added also for background operations when the API has not been configured correctly [<a href="https://github.com/micz/ThunderAI/issues/766">#766</a>].</li>
|
||||
<li><i>[Ollama API]</i> Added <i>format: json</i> option [<a href="https://github.com/micz/ThunderAI/issues/703">#703</a>].</li>
|
||||
<li>Fix: The "Important Information" section in the options page now updates correctly when choosing an integration [<a href="https://github.com/micz/ThunderAI/issues/730">#730</a>].</li>
|
||||
<li>In the options page now is visible if a special prompt is using a specific API integration [<a href="https://github.com/micz/ThunderAI/issues/676">#676</a>].</li>
|
||||
<li>Added an antispam skip list to ensure messages from designated addresses are not forwarded to the AI [<a href="https://github.com/micz/ThunderAI/issues/743">#743</a>].</li>
|
||||
<li>Fix: Correctly setting the end date for a new calendar event [<a href="https://github.com/micz/ThunderAI/issues/750">#750</a>].</li>
|
||||
<li>Now it's possibile to use different date and time formats in the AI output when creating a calendar event [<a href="https://github.com/micz/ThunderAI/issues/737">#737</a>].</li>
|
||||
<li>Added the <i>{%mail_full_headers%}</i> placeholder to retrieve all the email headers at once [<a href="https://github.com/micz/ThunderAI/issues/713">#713</a>].</li>
|
||||
<li><i>[All APIs]</i> In the API webchat the status messages have different colors [<a href="https://github.com/micz/ThunderAI/issues/3">#3</a>].</li>
|
||||
<li>Account exclusion lists for add tags and antispam are enforced only for automatic analysis of incoming emails and not for the context menu action that is always executed [<a href="https://github.com/micz/ThunderAI/issues/749">#749</a>].</li>
|
||||
</ul>
|
||||
<h2>Version 4.0.7 - 17/04/2026</h2>
|
||||
<ul>
|
||||
<li>Fix: Correctly parsing the body of HTML base64 encoded mails [<a href="https://github.com/micz/ThunderAI/issues/757">#757</a>].</li>
|
||||
</ul>
|
||||
<h2>Version 4.0.6 - 01/04/2026</h2>
|
||||
<ul>
|
||||
<li>Fix: Now it's possibile to create a tag also with accented characters in the label [<a href="https://github.com/micz/ThunderAI/issues/738">#738</a>].</li>
|
||||
</ul>
|
||||
<h2>Version 4.0.5 - 27/03/2026</h2>
|
||||
<ul>
|
||||
<li>Fix: HTML part of the mail body used in prompt is displayed as HTML code and it is not rendered. This a display fix, there is no change on how the prompt is sent to the AI [<a href="https://github.com/micz/ThunderAI/issues/711">#711</a>].</li>
|
||||
<li>Fix: HTML elements added by ThunderAI (like the antispam banner) are now not present in HTML or text data placeholders [<a href="https://github.com/micz/ThunderAI/issues/710">#710</a>].</li>
|
||||
<li><i>[All APIs]</i> The API webchat window now has a dynamic title [<a href="https://github.com/micz/ThunderAI/issues/696">#696</a>]</li>
|
||||
</ul>
|
||||
<h2>Version 4.0.4 - 26/03/2026</h2>
|
||||
<ul>
|
||||
<li>Fix: Correctly showing the selected model in the special prompt pages.</li>
|
||||
</ul>
|
||||
<h2>Version 4.0.3 - 20/03/2026</h2>
|
||||
<ul>
|
||||
<li>Fixed a bug in creating new tags [<a href="https://github.com/micz/ThunderAI/issues/698">#698</a>].</li>
|
||||
</ul>
|
||||
<h2>Version 4.0.2 - 11/03/2026</h2>
|
||||
<ul>
|
||||
<li>Now it's possible to automatically save the AI window position [<a href="https://github.com/micz/ThunderAI/issues/685">#685</a>].</li>
|
||||
<li><i>[OpenAI API]</i> Fix: Correctly showing failed response errors during streaming [<a href="https://github.com/micz/ThunderAI/issues/690">#690</a>].</li>
|
||||
<li>Fix: Correctly adding tags with non-ASCII characters [<a href="https://github.com/micz/ThunderAI/issues/689">#689</a>].</li>
|
||||
<li>Improved the spacing between lines when displaying the AI response in the API webchat [<a href="https://github.com/micz/ThunderAI/issues/686">#686</a>].</li>
|
||||
<li>Some minor improvments.</li>
|
||||
</ul>
|
||||
<h2>Version 4.0.1 - 27/02/2026</h2>
|
||||
<ul>
|
||||
<li>Fix: Correctly handling additional text without a placeholder [<a href="https://github.com/micz/ThunderAI/issues/681">#681</a>].</li>
|
||||
</ul>
|
||||
<h2>Version 4.0.0 - 24/02/2026</h2>
|
||||
<ul>
|
||||
<li>ThunderAI is now compatible only with Thunderbird 140 and later [<a href="https://github.com/micz/ThunderAI/issues/616">#616</a>].</li>
|
||||
<li><i>[All APIs]</i> It's now possibile to define a specific API integration for calendar and task recognition [<a href="https://github.com/micz/ThunderAI/issues/498">#498</a>].</li>
|
||||
<li>Added a new model selector with a search functionality to dynamically filter the list [<a href="https://github.com/micz/ThunderAI/issues/603">#603</a>].</li>
|
||||
<li><i>[All APIs]</i> Added a special prompt to summarize one or more emails, using a context menu command [<a href="https://github.com/micz/ThunderAI/issues/615">#615</a>]. Thanks to <a href="https://github.com/gdkrmr">Guido Kraemer</a> for his great work on this feature.</li>
|
||||
<li><i>[All APIs]</i> "Analyze for spam" and "Add tags" context menu items are always shown when the corresponding feature is enabled [<a href="https://github.com/micz/ThunderAI/issues/609">#609</a>].</li>
|
||||
<li><i>[OpenAI Comp API][Ollama API]</i> Asking for the single host for permission to avoid CORS errors, instead of <i>all_urls</i>, as requested by the Thunderbird Review Team [<a href="https://github.com/micz/ThunderAI/issues/524">#524</a>].</li>
|
||||
<li>Fix: Using also the mail folder owner to search for the right identity to use when composing a reply [<a href="https://github.com/micz/ThunderAI/issues/627">#627</a>].</li>
|
||||
<li>Context menu items are always ordered alfabetically [<a href="https://github.com/micz/ThunderAI/issues/630">#630</a>].</li>
|
||||
<li>The prompt export now includes an option to incorporate specific API settings, when present [<a href="https://github.com/micz/ThunderAI/issues/624">#624</a>].</li>
|
||||
<li>Added the <i>{%mail_text_body_or_selected%}</i> placeholder to retrieve the selected text or the full text body of the email if no selection is present [<a href="https://github.com/micz/ThunderAI/issues/641">#641</a>].</li>
|
||||
<li>Added the <i>{%mail_html_body_or_selected%}</i> placeholder to retrieve the selected HTML or the full HTML body of the email if no selection is present [<a href="https://github.com/micz/ThunderAI/issues/641">#641</a>].</li>
|
||||
<li><i>[All APIs]</i> Added an option to get a calendar event without selecting some text, but using the full text body of the email [<a href="https://github.com/micz/ThunderAI/issues/518">#518</a>].</li>
|
||||
<li><i>[All APIs]</i> Added a new menu item to create a calendar event from the text saved in the clipboard [<a href="https://github.com/micz/ThunderAI/issues/362">#362</a>].</li>
|
||||
<li>Added a button to copy a prompt in the Custom Prompts page [<a href="https://github.com/micz/ThunderAI/issues/598">#598</a>].</li>
|
||||
<li><i>[All APIs]</i> Showing the spam filter info at the top of the message. The data is saved only for the session in which the message has been checked for spam [<a href="https://github.com/micz/ThunderAI/issues/506">#506</a>, <a href="https://github.com/micz/ThunderAI/issues/658">#658</a>].</li>
|
||||
<li>Fix: Now it's possibile to use multiple <i>additional_text</i> placeholders in a single prompt, also using custom placeholders [<a href="https://github.com/micz/ThunderAI/issues/554">#554</a>].</li>
|
||||
<li>When using the <i>additional_text</i> placeholder is now possibile to specify an ID that will be shown in the form asking for the text [<a href="https://github.com/micz/ThunderAI/issues/525">#525</a>].</li>
|
||||
<li><i>[ChatGPT Web]</i> Added an option to define a custom time to wait for the page load. Sometimes, on slow PCs, the ChatGPT page loads slowly and ThunderAI inject its content too early. With this option you can adjust the waiting time [<a href="https://github.com/micz/ThunderAI/issues/634">#634</a>].</li>
|
||||
</ul>
|
||||
<h2>Version 3.8.5 - 22/02/2026</h2>
|
||||
<ul>
|
||||
<li>Fix: Correctly showing email addresses when using mail headers in data placeholders [<a href="https://github.com/micz/ThunderAI/issues/672">#672</a>].</li>
|
||||
</ul>
|
||||
<h2>Version 3.8.4 - 10/02/2026</h2>
|
||||
<ul>
|
||||
<li><i>[ChatGPT Web]</i> Fix: Correctly importing the selected text into the compose windows also when ChatGPT shows the advanced mail editor in the response [<a href="https://github.com/micz/ThunderAI/issues/646">#646</a>].</li>
|
||||
</ul>
|
||||
<h2>Version 3.8.3 - 22/01/2026</h2>
|
||||
<ul>
|
||||
<li>Fix: Correctly saving the API settings in new custom prompts [<a href="https://github.com/micz/ThunderAI/issues/623">#623</a>].</li>
|
||||
<li>Japanese (ja) translation added, thanks to <a href="https://hosted.weblate.org/user/watya1/">Taichi Ito</a>.</li>
|
||||
</ul>
|
||||
<h2>Version 3.8.2 - 20/01/2026</h2>
|
||||
<ul>
|
||||
<li>Fix: Correctly saving the enabled status in custom prompts [<a href="https://github.com/micz/ThunderAI/issues/621">#621</a>].</li>
|
||||
</ul>
|
||||
<h2>Version 3.8.1 - 20/01/2026</h2>
|
||||
<ul>
|
||||
<li><i>[OpenAI API]</i> Fix: Correctly sending the prompt after opening the chat window [<a href="https://github.com/micz/ThunderAI/issues/620">#620</a>].</li>
|
||||
</ul>
|
||||
<h2>Version 3.8.0 - 16/01/2026</h2>
|
||||
<ul>
|
||||
<li><i>[All APIs]</i> Now it is possible to define an API and its settings for any custom prompt. This allows anyone to use different AI providers for different prompts [<a href="https://github.com/micz/ThunderAI/pull/102">#102</a>].</li>
|
||||
<li><i>[All APIs]</i> When using special prompts (like automatically adding tags or the spam filter) with a specific API integration, all the settings for that integration can be specific. In this way you can use different api keys for the same integration, or different system prompt or temperature [<a href="https://github.com/micz/ThunderAI/pull/590">#590</a>].</li>
|
||||
<li><i>[All APIs]</i> Added the temperature parameter [<a href="https://github.com/micz/ThunderAI/issues/561">#561</a>].</li>
|
||||
<li><i>[OpenAI API]</i> Model filtering improved when choosing a model in the options page.</li>
|
||||
<li><i>[OpenAI API]</i> Now using the new Responses API [<a href="https://github.com/micz/ThunderAI/issues/407">#407</a>].</li>
|
||||
<li>It is now possible to define a custom placeholder with dynamic data to retrieve any header present in the current email [<a href="https://github.com/micz/ThunderAI/issues/527">#527</a>].</li>
|
||||
<li><i>[All APIs]</i> The configuration information reported in the webchat API has been improved for all integrations.</li>
|
||||
<li>Spanish (es) translation added, thanks to <a href="https://hosted.weblate.org/user/gerardo.sobarzo/">Gerardo Sobarzo</a>, <a href="https://hosted.weblate.org/user/arendon/">Andrés Rendón Hernández</a>, <a href="https://hosted.weblate.org/user/ErickLimonG/">Erick Limon</a>.</li>
|
||||
<li>Swedish (sv) translation added, thanks to <a href="https://hosted.weblate.org/user/Andy_tb/">Andreas Pettersson</a>.</li>
|
||||
<li>Various fixes.</li>
|
||||
</ul>
|
||||
<h2>Version 3.7.9 - 06/01/2026</h2>
|
||||
<ul>
|
||||
<li><i>[ChatGPT Web]</i> Fix: Correctly getting the job completion [<a href="https://github.com/micz/ThunderAI/issues/607">#607</a>].</li>
|
||||
</ul>
|
||||
<h2>Version 3.7.8 - 18/12/2025</h2>
|
||||
<ul>
|
||||
<li>Greek (el) translation added, thanks to <a href="https://github.com/christoskaterini">ChristosK.</a>.</li>
|
||||
</ul>
|
||||
<h2>Version 3.7.7 - 14/12/2025</h2>
|
||||
<ul>
|
||||
<li><i>[Claude API][OpenAI API]</i> Fix: asking required permissions before fetching models [<a href="https://github.com/micz/ThunderAI/issues/558">#558</a>].</li>
|
||||
<li><i>[Google Gemini API][Ollama API][OpenAI API][OpenAI Comp API]</i> Fix: improved error handling when parsing responses [<a href="https://github.com/micz/ThunderAI/issues/550">#550</a>].</li>
|
||||
</ul>
|
||||
<h2>Version 3.7.6 - 08/12/2025</h2>
|
||||
<ul>
|
||||
<li><i>[Claude API]</i> Added the System Prompt configuration option [<a href="https://github.com/micz/ThunderAI/issues/549">#549</a>].</li>
|
||||
<li><i>[ChatGPT Web]</i> Fix: correctly showing the input field after an update in the HTML page from OpenAI [<a href="https://github.com/micz/ThunderAI/issues/556">#556</a>].</li></li>
|
||||
</ul>
|
||||
<h2>Version 3.7.5 - 22/10/2025</h2>
|
||||
<ul>
|
||||
<li><i>[OpenAI API]</i> Fixed a bug when handling responses without choices [<a href="https://github.com/micz/ThunderAI/issues/535">#535</a>].</li>
|
||||
</ul>
|
||||
<h2>Version 3.7.4 - 20/10/2025</h2>
|
||||
<ul>
|
||||
<li><i>[ChatGPT Web]</i> Fixed a bug preventing the ChatGPT web interface from working in new installs [<a href="https://github.com/micz/ThunderAI/issues/534">#534</a>].</li>
|
||||
</ul>
|
||||
<h2>Version 3.7.3 - 16/10/2025</h2>
|
||||
<ul>
|
||||
<li><i>[ChatGPT Web]</i> Fix: Correctly managing custom projects in any condition [<a href="https://github.com/micz/ThunderAI/issues/520">#520</a>].</li>
|
||||
<li><i>[OpenAI API]</i> Added an optional permission for the OpenAI API endpoint to avoid a CORS errors [<a href="https://github.com/micz/ThunderAI/issues/529">#529</a>].</li>
|
||||
</ul>
|
||||
<h2>Version 3.7.2 - 03/10/2025</h2>
|
||||
<ul>
|
||||
<li><i>[ChatGPT Web]</i> Fix: Not showing the force complete hint if the prompt has not been sent.</li>
|
||||
<li><i>[ChatGPT Web]</i> Fix: Correctly getting when ChatGPT has finished sending the response even when using custom projects.</li>
|
||||
<li><i>[ChatGPT Web]</i> Fix: Under certain conditions, asking for additional text prevents ThunderAI from sending the prompt to ChatGPT [<a href="https://github.com/micz/ThunderAI/issues/522">#522</a>].</li>
|
||||
</ul>
|
||||
<h2>Version 3.7.1 - 26/09/2025</h2>
|
||||
<ul>
|
||||
<li><i>[Google Gemini API]</i> Fix: Correctly handling empty responses [<a href="https://github.com/micz/ThunderAI/issues/514">#514</a>].</li>
|
||||
</ul>
|
||||
<h2>Version 3.7.0 - 18/09/2025</h2>
|
||||
<ul>
|
||||
<li><i>[All APIs]</i> It's now possibile to define a list of tags to be used when autotagging received emails [<a href="https://github.com/micz/ThunderAI/issues/436">#436</a>]. The tags are are now shown in the information header in the AI API chat [<a href="https://github.com/micz/ThunderAI/issues/289">#289</a>].</li>
|
||||
<li><i>[All APIs]</i> The prompt id and name are now shown in the information header in the AI API chat [<a href="https://github.com/micz/ThunderAI/issues/436">#436</a>].</li>
|
||||
<li><i>[All APIs]</i> It's now possibile to define a specific API integration for spamfilter and auto tagging [<a href="https://github.com/micz/ThunderAI/issues/438">#438</a>].</li>
|
||||
<li>Added the <i>{%mail_attachments_info%}</i> placeholder to retrieve the name, type and file size of the mail attachments [<a href="https://github.com/micz/ThunderAI/issues/446">#446</a>].</li>
|
||||
<li><i>[Google Gemini API]</i> Support for the thinkingBudget parameter has been added [<a href="https://github.com/micz/ThunderAI/issues/494">#494</a>].</li>
|
||||
<li><i>[OpenAI Comp API]</i> Added DeepSeek configuration [<a href="https://github.com/micz/ThunderAI/issues/486">#486</a>].</li>
|
||||
<li><i>[ChatGPT Web]</i> Added a message to explain to click on "Force completion" if the ChatGPT job is not done after 7 seconds [<a href="https://github.com/micz/ThunderAI/issues/419">#419</a>].</li>
|
||||
<li>Anthropic API renamed to Claude API [<a href="https://github.com/micz/ThunderAI/issues/510">#510</a>].</li>
|
||||
<li>Various code improvements and minor bugs fixed.</li>
|
||||
</ul>
|
||||
<h2>Version 3.6.1 - 23/08/2025</h2>
|
||||
<ul>
|
||||
<li><i>[OpenAI Comp API]</i> Perplexity configuration fixed [<a href="https://github.com/micz/ThunderAI/issues/461">#461</a>].</li>
|
||||
<li><i>[OpenAI Comp API]</i> Added a button to clear the models list [<a href="https://github.com/micz/ThunderAI/issues/472">#472</a>].</li>
|
||||
<li><i>[ChatGPT Web]</i> Fix: Any <br> tag in the response is now replaced with a line break [<a href="https://github.com/micz/ThunderAI/issues/462">#462</a>].</li>
|
||||
<li><i>[ChatGPT Web]</i> Models list updated. [<a href="https://github.com/micz/ThunderAI/issues/464">#464</a>].</li>
|
||||
<li><i>[ChatGPT Web]</i> Improved how the line breaks are rendered in the ChatGPT web page [<a href="https://github.com/micz/ThunderAI/issues/482">#482</a>].</li>
|
||||
<li><i>[All APIs]</i> Fix: If the incoming email has no text only part, the html part is used [<a href="https://github.com/micz/ThunderAI/issues/470">#470</a>].</li>
|
||||
<li><i>[All APIs]</i> Fix: Improved the extraction of the plain text from html emails [<a href="https://github.com/micz/ThunderAI/issues/469">#469</a>].</li>
|
||||
<li><i>[All APIs]</i> Fix: Improved how the line breaks are rendered in the AI chat [<a href="https://github.com/micz/ThunderAI/issues/469">#469</a>].</li>
|
||||
<li><i>[All APIs]</i> If the option to use only existing tags is enabled, this now works also when adding tags manually using the ThunderAI menu [<a href="https://github.com/micz/ThunderAI/issues/475">#475</a>].</li>
|
||||
<li><i>[All APIs][ChatGPT Web]</i> Fix: Correctly showing and hiding the context menu when changing connection type.</li>
|
||||
</ul>
|
||||
<h2>Version 3.6.0 - 30/07/2025</h2>
|
||||
<ul>
|
||||
<li>Now it's possibile to define custom data placeholders to be used in custom prompts [<a href="https://github.com/micz/ThunderAI/issues/156">#156</a>].</li>
|
||||
<li>Improved the handling of HTML and line breaks between the email text and the AI Chat.</li>
|
||||
<li>When replying, it's now possibile to choose a different reply type (between "all" or "sender only") directly in the AI chat window [<a href="https://github.com/micz/ThunderAI/issues/372">#372</a>].</li>
|
||||
<li>Added a new default prompt for replying to emails, which asks for a custom command each time it's used [<a href="https://github.com/micz/ThunderAI/issues/444">#444</a>].</li>
|
||||
<li><i>[All APIs]</i> Added an option to choose to exclude a tag only with an exact match in the excluded words list [<a href="https://github.com/micz/ThunderAI/issues/395">#395</a>].</li>
|
||||
<li><i>[OpenAI API]</i> Added an option to enable the OpenAI storage for API requests [<a href="https://github.com/micz/ThunderAI/issues/406">#406</a>].</li>
|
||||
<li><i>[OpenAI API]</i> In the AI chat page, the initial configuration now also displays the storage setting and the "Developer Messages" [<a href="https://github.com/micz/ThunderAI/issues/430">#430</a>].</li>
|
||||
<li><i>[Ollama API]</i> Added an option to enable the thinking feature [<a href="https://github.com/micz/ThunderAI/issues/398">#398</a>].</li>
|
||||
<li><i>[Google Gemini API]</i> In the AI chat page, the initial configuration now also displays the "System Instructions". [<a href="https://github.com/micz/ThunderAI/issues/429">#429</a>].</li>
|
||||
<li><i>[OpenAI Comp API]</i> Handling responses without choices when using RAG [<a href="https://github.com/micz/ThunderAI/issues/416">#416</a>].</li>
|
||||
<li><i>[OpenAI Comp API]</i> Added Perplexity configuration [<a href="https://github.com/micz/ThunderAI/issues/405">#405</a>].</li>
|
||||
<li><i>[OpenAI Comp API]</i> Added OpenRouter configuration [<a href="https://github.com/micz/ThunderAI/issues/401">#401</a>].</li>
|
||||
<li>Traditional Chinese (zh_Hant) translation added, thanks to <a href="https://github.com/evez">evez</a>.</li>
|
||||
<li>Russian (ru) translation added, thanks to <a href="https://hosted.weblate.org/user/law820314/">Maksim</a>.</li>
|
||||
<li>Some English typing errors have been fixed [<a href="https://github.com/micz/ThunderAI/issues/422">#422</a>].</li>
|
||||
</ul>
|
||||
<h2>Version 3.5.5 - 21/07/2025</h2>
|
||||
<ul>
|
||||
<li><i>[All APIs]</i> Fix: Autotag and Antispam filter working again [<a href="https://github.com/micz/ThunderAI/issues/449">#449</a>].</li>
|
||||
</ul>
|
||||
<h2>Version 3.5.4 - 13/06/2025</h2>
|
||||
<ul>
|
||||
<li><i>[ChatGPT Web]</i> Fix: Changed again how to detect when the response is completed.</li>
|
||||
<li><i>[ChatGPT Web]</i> Fix: Auto scroll to bottom works again.</li>
|
||||
</ul>
|
||||
<h2>Version 3.5.3 - 10/06/2025</h2>
|
||||
<ul>
|
||||
<li><i>[ChatGPT Web]</i> Fix: Correctly detecting when the response is completed.</li>
|
||||
</ul>
|
||||
<h2>Version 3.5.2 - 05/06/2025</h2>
|
||||
<ul>
|
||||
<li><i>[ChatGPT Web]</i> Fix: Correctly hiding the model warning message when forcing to send the prompt [<a href="https://github.com/micz/ThunderAI/issues/410">#410</a>].</li>
|
||||
<li><i>[ChatGPT Web]</i> Fix: ThunderAI is now working also with a free account [<a href="https://github.com/micz/ThunderAI/issues/408">#408</a>].</li>
|
||||
<li><i>[ChatGPT Web]</i> Fix: Correctly showing a warning message to a not logged in user [<a href="https://github.com/micz/ThunderAI/issues/411">#411</a>].</li>
|
||||
<li><i>[ChatGPT Web]</i> Improved the model not found message [<a href="https://github.com/micz/ThunderAI/issues/413">#413</a>].</li>
|
||||
<li><i>[All APIs]</i> Fix: The selection info message in the API WebChat is shown only when needed [<a href="https://github.com/micz/ThunderAI/issues/412">#412</a>].</li>
|
||||
<li>Czech (cs) translation updated, thanks to <a href="https://hosted.weblate.org/user/jaroush/">Jaroslav Staněk</a>.</li>
|
||||
</ul>
|
||||
<h2>Version 3.5.1 - 31/05/2025</h2>
|
||||
<ul>
|
||||
<li>Fix: correctly saving text options [<a href="https://github.com/micz/ThunderAI/issues/400">#400</a>].</li>
|
||||
</ul>
|
||||
<h2>Version 3.5.0 - 30/05/2025</h2>
|
||||
<ul>
|
||||
<li>Added Anthropic API support [<a href="https://github.com/micz/ThunderAI/issues/349">#349</a>].</li>
|
||||
<li>Added the <i>{%selected_html%}</i> placeholder to retrieve the HTML portion of the selected text [<a href="https://github.com/micz/ThunderAI/issues/368">#368</a>].</li>
|
||||
<li><i>[OpenAI Comp API]</i> Added a shortcut to select configurations for known AI services. Currently, <i>Grok AI</i> and <i>Mistral AI</i> are available [<a href="https://github.com/micz/ThunderAI/issues/378">#378</a>]. <a href="https://github.com/micz/ThunderAI/issues/new?template=feature_request.md">Open an issue</a> to request additional services.</li>
|
||||
<li><i>[All APIs]</i> In the API WebChat is now possibile to select a part of the answer and use only that [<a href="https://github.com/micz/ThunderAI/issues/356">#356</a>].</li>
|
||||
<li><i>[All APIs]</i> Setting the "Max prompt length" to zero on the options page will disable the length check when sending a prompt to the AI. [<a href="https://github.com/micz/ThunderAI/issues/380">#380</a>].</li>
|
||||
<li><i>[All APIs]</i> Added a button to the options page to reset the 'Max prompt length' value to its default.</li>
|
||||
<li><i>[All APIs]</i> Adding tags automatically or with the context menu will now use also tags not created by ThunderAI [<a href="https://github.com/micz/ThunderAI/issues/390">#390</a>].</li>
|
||||
<li>Fix: in the Spamfilter page the unsaved changes warning is now correctly shown.</li>
|
||||
<li>Fix: The default keyboard shortcut is no longer enforced at every Thunderbird startup [<a href="https://github.com/micz/ThunderAI/issues/384">#384</a>].</li>
|
||||
<li>Fix: Incoming email processing now works correctly when auto-tagging is enabled and the full tagging feature is subsequently disabled.</li>
|
||||
<li>Fix: The reply type is correctly saved in the options. [<a href="https://github.com/micz/ThunderAI/issues/387">#387</a>].</li>
|
||||
</ul>
|
||||
<h2>Version 3.4.1 - 12/05/2025</h2>
|
||||
<ul>
|
||||
<li><i>[All APIs]</i> Fix: correctly assigning tags when receiving mails. Thanks to <a href="https://github.com/jdkio">jdkio</a> [<a href="https://github.com/micz/ThunderAI/issues/374">#374</a>].</li>
|
||||
</ul>
|
||||
<h2>Version 3.4.0 - 07/04/2025</h2>
|
||||
<ul>
|
||||
<li><i>[All APIs]</i> Added a special prompt to get tasks data from emails [<a href="https://github.com/micz/ThunderAI/issues/333">#333</a>]. To use this feature, you must install also the <a href="https://addons.thunderbird.net/it/thunderbird/addon/thunderai-sparks/">Sparks</a> add-on.</li>
|
||||
<li><i>[ChatGPT Web]</i> It is now possible to define a Custom GPT or a Project in the options page to be used by default, or directly in a custom prompt to be used only for that prompt [<a href="https://github.com/micz/ThunderAI/issues/168">#168</a>, <a href="https://github.com/micz/ThunderAI/issues/277">#277</a>].</li>
|
||||
<li>The chatgpt.com access permission is no more requested when installing the addon. It's mandatory to give this permission to use the ChatGPT Web Interface integration [<a href="https://github.com/micz/ThunderAI/issues/293">#293</a>].</li>
|
||||
<li>Added the <i>{%mail_quoted_text%}</i> placeholder to get the quoted text when composing a new message [<a href="https://github.com/micz/ThunderAI/issues/324">#324</a>].</li>
|
||||
<li>Fix: correctly choosing the right account when replying [<a href="https://github.com/micz/ThunderAI/issues/369">#369</a>].</li>
|
||||
<li>Fix: In the <i>{%mail_typed_text%}</i> placeholder text on different lines is now separated by a space.</li>
|
||||
<li>Fix: correctly showing text differences when using {%mail_typed_text%} placeholder.</li>
|
||||
<li>Added a default proofread prompt [<a href="https://github.com/micz/ThunderAI/issues/21">#21</a>].</li>
|
||||
<li>Added the <i>{%empty%}</i> placeholder to prevent the email body from being automatically appended at the end of the prompt [<a href="https://github.com/micz/ThunderAI/issues/345">#345</a>].</li>
|
||||
<li>Added a check for the presence of the correct version of ThunderAI Sparks [<a href="https://github.com/micz/ThunderAI/issues/315">#315</a>].</li>
|
||||
<li><i>[All APIs]</i> Improved the form asking for additional text [<a href="https://github.com/micz/ThunderAI/issues/97">#97</a>].</li>
|
||||
<li><i>[All APIs]</i> Fix: Ensure HTML body is generated from plain text if no parts are available when processing incoming messages for tags or spam.</li>
|
||||
<li>Fix a condition when using a prompt from the compose window with a "Do reply" action that is changed in "Substitute Text" [<a href="https://github.com/micz/ThunderAI/issues/353">#353</a>].</li>
|
||||
<li>Custom Prompts form improved.</li>
|
||||
<li>Various improvements.</li>
|
||||
</ul>
|
||||
<h2>Version 3.3.5 - 22/04/2025</h2>
|
||||
<ul>
|
||||
<li>Using a prompt from the compose window with a "Do reply" action is changed in "Substitute Text", asking also to insert text if none is selected [<a href="https://github.com/micz/ThunderAI/issues/353">#353</a>].</li>
|
||||
<li>Added an option when composing in plain text to remove the extra empty lines [<a href="https://github.com/micz/ThunderAI/issues/350">#350</a>].</li>
|
||||
<li><i>[Ollama API]</i> It's now possibile to define the default context length (the <i>num_ctx</i> parameter) in the options page [<a href="https://github.com/micz/ThunderAI/issues/351">#351</a>].</li>
|
||||
<li><i>[ChatGPT Web]</i> Updated the list of available models in the option page.</li>
|
||||
<li><i>[ChatGPT Web]</i> Opening the ChatGPT webpage from the options now enforce the selected model, if any.</li>
|
||||
<li><i>[All APIs]</i> Fix: Really correctly getting the message body even in multilevel subpart messages when processing incoming messages for tags or spam [<a href="https://github.com/micz/ThunderAI/issues/335">#335</a>].</li>
|
||||
<li>Minor improvements.</li>
|
||||
</ul>
|
||||
<h2>Version 3.3.4 - 14/04/2025</h2>
|
||||
<ul>
|
||||
<li><i>[ChatGPT Web]</i> Fixed a blocking error with the " character in the mail text [<a href="https://github.com/micz/ThunderAI/issues/344">#344</a>].</li>
|
||||
<li>...</li>
|
||||
</ul>
|
||||
<h2>Version 3.3.3 - 12/04/2025</h2>
|
||||
<ul>
|
||||
|
|
@ -491,7 +239,7 @@
|
|||
<li>Added a better error message when there is an error fetching models.</li>
|
||||
<li>When selecting a correct model in the options page, the field is no more highlighted in red [<a href="https://github.com/micz/ThunderAI/issues/100">#100</a>].</li>
|
||||
<li>When using the ChatGPT API, the double quotes at the beginning and end of the response are removed [<a href="https://github.com/micz/ThunderAI/issues/99">#99</a>].</li>
|
||||
<li><i>[ChatGPT Web]</i> "Keep formatting" option removed.</li>
|
||||
<li><i>[ChatGPT Web]</i>"Keep formatting" option removed.</li>
|
||||
</ul>
|
||||
<h2>Version 2.0.1 - 09/08/2024</h2>
|
||||
<ul>
|
||||
|
|
|
|||
65
CLAUDE.md
|
|
@ -1,65 +0,0 @@
|
|||
# ThunderAI - Claude Code Guide
|
||||
|
||||
## Project Overview
|
||||
ThunderAI is a **Thunderbird WebExtension (Manifest V2)** that integrates multiple AI providers (ChatGPT Web, OpenAI API, Google Gemini, Claude/Anthropic, Ollama, and OpenAI-compatible APIs) directly into the Thunderbird email client.
|
||||
|
||||
- **Extension ID:** `thunderai@micz.it`
|
||||
- **Min Thunderbird:** 140.0+
|
||||
- **Language:** Plain ES6+ JavaScript modules — no build tools, no transpilation, no npm
|
||||
- **License:** GPLv3
|
||||
|
||||
## Key Rules
|
||||
|
||||
1. **Localization:** Modify ONLY `_locales/en/messages.json`. All other locale files are managed via Weblate — never touch them.
|
||||
2. **No build system:** There is no bundler, compiler, or package manager. All JS files are plain ES6 modules loaded directly by the browser engine.
|
||||
3. **Module imports:** Use relative paths with `.js` extension (e.g., `import { foo } from '../js/mzta-utils.js'`).
|
||||
4. **Placeholder format:** Placeholders in prompt text use the `{%placeholder_id%}` syntax (e.g., `{%mail_text_body_or_selected%}`).
|
||||
5. **No test suite:** There is no automated test framework. Testing is done manually in Thunderbird.
|
||||
6. **Settings defaults:** All new preferences must be added to `options/mzta-options-default.js` in `prefs_default`.
|
||||
7. **Keep spec files up to date:** When making code changes that affect a subsystem described in claude-spec/, update the relevant spec file to reflect the new behavior. Read the spec before modifying, update it after.
|
||||
|
||||
## Directory Map
|
||||
|
||||
```
|
||||
/
|
||||
├── mzta-background.js # Background script (main entry point)
|
||||
├── mzta-background.html # Loads the background script
|
||||
├── manifest.json # Extension manifest
|
||||
├── js/ # Core modules
|
||||
│ ├── api/ # AI API integration modules
|
||||
│ ├── workers/ # Web Workers (one per API provider)
|
||||
│ ├── lib/ # Third-party libraries (diff.js)
|
||||
│ └── mzta-*.js # Core utilities, menus, prompts, placeholders
|
||||
├── options/ # Settings UI
|
||||
│ ├── mzta-options.html/.js/.css
|
||||
│ ├── mzta-options-default.js # ALL default preference values
|
||||
│ └── mzta-release-notes.html
|
||||
├── pages/ # Feature-specific settings pages
|
||||
│ ├── addtags/
|
||||
│ ├── customprompts/
|
||||
│ ├── customdataplaceholders/
|
||||
│ ├── get-calendar-event/
|
||||
│ ├── get-task/
|
||||
│ ├── spamfilter/
|
||||
│ ├── summarize/
|
||||
│ └── onboarding/
|
||||
├── popup/ # Popup menu (shown on toolbar click)
|
||||
│ └── mzta-popup.html/.js/.css
|
||||
├── _locales/ # Localization
|
||||
│ ├── en/messages.json # ← ONLY THIS FILE is edited directly
|
||||
│ └── [15 other languages managed by Weblate]
|
||||
├── images/ # Icons and graphical assets
|
||||
└── api_webchat/ # Web chat API interface
|
||||
```
|
||||
|
||||
## Spec Files
|
||||
|
||||
For detailed documentation see [`claude-spec/`](claude-spec/):
|
||||
|
||||
- [01-architecture.md](claude-spec/01-architecture.md) — Module structure and data flow
|
||||
- [02-prompts.md](claude-spec/02-prompts.md) — Prompt system (types, actions, properties)
|
||||
- [03-placeholders.md](claude-spec/03-placeholders.md) — Placeholder system
|
||||
- [04-api-integrations.md](claude-spec/04-api-integrations.md) — AI provider integrations
|
||||
- [05-options.md](claude-spec/05-options.md) — Settings and preferences system
|
||||
- [06-localization.md](claude-spec/06-localization.md) — i18n rules and workflow
|
||||
- [99-thunderbird-team-spec.md](claude-spec/99-thunderbird-team-spec.md) — Thunderbird WebExtensions development guidelines (API usage, experiments, review requirements)
|
||||
10
LANG.md
|
|
@ -1,15 +1,9 @@
|
|||
cs
|
||||
de
|
||||
el
|
||||
en
|
||||
es
|
||||
fr
|
||||
hr
|
||||
it
|
||||
ja
|
||||
pl
|
||||
pt-br
|
||||
ru
|
||||
sv
|
||||
zh_Hans
|
||||
zh_Hant
|
||||
cs
|
||||
zh_Hans
|
||||
108
README.md
|
|
@ -1,6 +1,6 @@
|
|||
#  ThunderAI
|
||||
|
||||
ThunderAI is a Thunderbird Addon that uses the capabilities of ChatGPT, Google Gemini, Claude or Ollama to enhance email management.
|
||||
ThunderAI is a Thunderbird Addon that uses the capabilities of ChatGPT, Google Gemini or Ollama to enhance email management.
|
||||
|
||||
It enables users to analyse, write, correct, assign tags, create calendar events or tasks and optimize their emails, facilitating more effective and professional communication.
|
||||
|
||||
|
|
@ -17,58 +17,40 @@ Using an API integration, you can activate some automatic features:
|
|||
<br>
|
||||
|
||||
|
||||
> [!NOTE]
|
||||
> **Available Integrations**
|
||||
> - **ChatGPT Web**
|
||||
> - There is no need for an API key!
|
||||
> - You can use a free account!
|
||||
>
|
||||
> <br>
|
||||
>
|
||||
> - **OpenAI API**
|
||||
> - Connect directly to ChatGPT using your API key.
|
||||
>
|
||||
> <br>
|
||||
> [!TIP]
|
||||
> **Using ChatGPT**
|
||||
>
|
||||
> - **Google Gemini**
|
||||
> - You can use also the _System Instructions_ and _thinkingBudget_ options if needed.
|
||||
> There is no need for an API key and is possibile to use this extension even with a free account, using the **ChatGPT web interface**!
|
||||
>
|
||||
>
|
||||
> <br>
|
||||
>
|
||||
> - **Claude API**
|
||||
> - You need to grant the permission "_Access your data for sites in the https://anthropic.com domain_" to use the Claude API.
|
||||
>
|
||||
>
|
||||
> <br>
|
||||
>
|
||||
> - **Using Ollama**
|
||||
> - Just remember to add `OLLAMA_ORIGINS = moz-extension://*` to the Ollama server environment variables.
|
||||
> - [More info about CORS](https://micz.it/thunderbird-addon-thunderai/ollama-cors-information/)
|
||||
>
|
||||
> <br>
|
||||
>
|
||||
> - **OpenAI Compatible API**
|
||||
> - You can also use a local OpenAI Compatible API server, like LM Studio or Mistral AI!
|
||||
> - There is also an option to remove the "v1" segment from the API url, if needed, and to manually set the model name if the server doesn't have a models list endpoint.
|
||||
> - You can also use one of these predefined configurations:
|
||||
> - DeepSeek API
|
||||
> - Grok API
|
||||
> - Mistral API
|
||||
> - OpenRouter API
|
||||
> - Perplexity API
|
||||
|
||||
|
||||
> If you want to connect with the OpenAI API integration, instead, you can use an **API Key**!
|
||||
|
||||
<br>
|
||||
|
||||
## Documentation
|
||||
> [!TIP]
|
||||
> **Using Google Gemini**
|
||||
>
|
||||
> Connect directly with Google Gemini API, using also the _System Instructions_ option if needed.
|
||||
|
||||
[Setup Guides](https://micz.it/thunderbird-addon-thunderai/guides/) - Step-by-step guides to connect ThunderAI to the AI backend of your choice, from ChatGPT to local models with Ollama.
|
||||
<br>
|
||||
|
||||
[Custom Prompt Tutorial](https://micz.it/thunderbird-addon-thunderai/tutorial/) - Learn how to build your first custom prompt from scratch, combining placeholders and user input to automate your email replies.
|
||||
> [!TIP]
|
||||
> **Using Ollama**
|
||||
>
|
||||
> It's also possible to use a local Ollama server!
|
||||
>
|
||||
> Just remember to add `OLLAMA_ORIGINS = moz-extension://*` to the Ollama server environment variables.
|
||||
>
|
||||
> [More info about CORS](https://micz.it/thunderbird-addon-thunderai/ollama-cors-information/)
|
||||
|
||||
<br>
|
||||
|
||||
> [!TIP]
|
||||
> **Using an OpenAI Compatible API**
|
||||
>
|
||||
> You can also use a local OpenAI Compatible API server, like LM Studio!
|
||||
>
|
||||
> There is also an option to remove the "v1" segment from the API url, if needed, and to manually set the model name if the server doesn't have a models list endpoint (like Gemini).
|
||||
|
||||
[ThunderAI Prompt Architect](https://chatgpt.com/g/g-69b6b11c89b88191a6798be6e97025f1-thunder-ai-prompt-architect) - Let ChatGPT help you crafting your custom prompts. Thanks to [Paweł](https://github.com/PawelKinczyk) for this tool!
|
||||
|
||||
<br>
|
||||
|
||||
|
|
@ -77,8 +59,6 @@ Do you want to help translate this addon?
|
|||
|
||||
[Find out how!](https://micz.it/thunderbird-addon-thunderai/translate/)
|
||||
|
||||
|
||||
|
||||
<br>
|
||||
|
||||
## Changelog
|
||||
|
|
@ -100,24 +80,14 @@ Are you using this addon in your Thunderbird?
|
|||
## Attributions
|
||||
|
||||
### Translations
|
||||
- Brazilian Portuguese - Português Brasileiro (pt-br): Bruno Pereira de Souza <img src="https://micz.it/weblate/thunderai/pt-br.svg">
|
||||
- Chinese (Simplified) - Jiǎntǐ Zhōngwén (简体中文) (zh_Hans): [jeklau](https://github.com/jeklau), [Min9X1n](https://github.com/Min9X1n) <img src="https://micz.it/weblate/thunderai/zh_Hans.svg">
|
||||
- Chinese (Traditional) - Fántǐ Zhōngwén (繁體中文) (zh_Hant): [evez](https://github.com/evez) <img src="https://micz.it/weblate/thunderai/zh_Hant.svg">
|
||||
- Croatian - Hrvatski (hr): Petar Jedvaj <img src="https://micz.it/weblate/thunderai/hr.svg">
|
||||
- Czech - Čeština (cs): [Fjuro](https://hosted.weblate.org/user/Fjuro/), [Jaroslav Staněk](https://hosted.weblate.org/user/jaroush/) <img src="https://micz.it/weblate/thunderai/cs.svg">
|
||||
- French - Français (fr): Generated automatically, [Noam](https://github.com/noam-sc) <img src="https://micz.it/weblate/thunderai/fr.svg">
|
||||
- German - Deutsch (de): Generated automatically <img src="https://micz.it/weblate/thunderai/de.svg">
|
||||
- Greek - Elliniká (Ελληνικά) (el): [ChristosK.](https://github.com/christoskaterini) <img src="https://micz.it/weblate/thunderai/el.svg">
|
||||
- Italian - Italiano (it): [Mic](https://github.com/micz) <img src="https://micz.it/weblate/thunderai/it.svg">
|
||||
- Japanese - Nihongo (日本語) (ja): [Taichi Ito](https://github.com/watya1) <img src="https://micz.it/weblate/thunderai/ja.svg">
|
||||
- Polish - Polski (pl): [neexpl](https://github.com/neexpl), [makkacprzak](https://github.com/makkacprzak) <img src="https://micz.it/weblate/thunderai/pl.svg">
|
||||
- Russian - Russkiy (русский) (ru): [Maksim](https://hosted.weblate.org/user/law820314/) <img src="https://micz.it/weblate/thunderai/ru.svg">
|
||||
- Spanish - Español (es): [Gerardo Sobarzo](https://hosted.weblate.org/user/gerardo.sobarzo/), [Andrés Rendón Hernández](https://hosted.weblate.org/user/arendon/), [Erick Limon](https://hosted.weblate.org/user/ErickLimonG/) <img src="https://micz.it/weblate/thunderai/es.svg">
|
||||
- Swedish - Svenska (sv): [Andreas Pettersson](https://hosted.weblate.org/user/Andy_tb/), [Luna Jernberg](https://hosted.weblate.org/user/bittin1ddc447d824349b2/) <img src="https://micz.it/weblate/thunderai/sv.svg">
|
||||
<br>
|
||||
|
||||
Do you want to help translate this addon? [Find out how!](https://micz.it/thunderbird-addon-thunderai/translate/) <br>
|
||||
_The language status represents the percentage of translated strings in the latest stable release._
|
||||
- Chinese (Simplified): [jeklau](https://github.com/jeklau)
|
||||
- Czech (cs): [Fjuro](https://hosted.weblate.org/user/Fjuro/), [Jaroslav Staněk](https://hosted.weblate.org/user/jaroush/)
|
||||
- English (en-US): [Mic](https://github.com/micz/)
|
||||
- French (fr): Generated automatically, [Noam](https://github.com/noam-sc)
|
||||
- German (de): Generated automatically
|
||||
- Italian (it): [Mic](https://github.com/micz/)
|
||||
- Polski (pl): [neexpl](https://github.com/neexpl), [makkacprzak](https://github.com/makkacprzak)
|
||||
- Português Brasileiro (pt-br): Bruno Pereira de Souza
|
||||
|
||||
|
||||
<br>
|
||||
|
|
@ -127,13 +97,6 @@ _The language status represents the percentage of translated strings in the late
|
|||
- <a href="https://loading.io">loading.io</a> for the loading SVGs
|
||||
- [Fluent Design System](https://www.iconfinder.com/fluent-designsystem) for the Custom Prompts table sorting icons
|
||||
- [JessiGue](https://www.flaticon.com/authors/jessigue) for the show/hide icon for api key fields
|
||||
- [Iconka.com](https://www.iconarchive.com/artist/iconka.html) for the autotag context menu icon
|
||||
- [Icojam](https://www.iconarchive.com/artist/icojam.html) for the spam filter context menu icon
|
||||
- [Roundicons](https://www.flaticon.com/authors/roundicons) for the summarize context menu icon
|
||||
- [HideMau](https://www.flaticon.com/authors/hidemaru) for the ai summarize icon
|
||||
- [Hilmy Abiyyu A.](https://www.flaticon.com/authors/hilmy-abiyyu-a) for the ai translate and context menu icons
|
||||
- [bearicons](https://www.flaticon.com/authors/bearicons) for the empty context menu icon
|
||||
- [meaicon](https://www.flaticon.com/authors/meaicon) for the add task context menu icon
|
||||
|
||||
|
||||
<br>
|
||||
|
|
@ -143,3 +106,4 @@ _The language status represents the percentage of translated strings in the late
|
|||
- <a href="https://github.com/KudoAI/chatgpt.js">chatgpt.js</a> for providing methods to interact with the ChatGPT web frontend
|
||||
- <a href="https://github.com/boxabirds">Julian Harris</a> for his project <a href="https://github.com/boxabirds/chatgpt-frontend-nobuild">chatgpt-frontend-nobuild</a>, that has been used as a starting point for the API Web Interface
|
||||
- <a href="https://hosted.weblate.org/widgets/thunderai/">Hosted Weblate</a> for managing the localization
|
||||
- <a href="https://github.com/ali-raheem/Aify">Aify</a> for inspiration
|
||||
|
|
|
|||
11
VENDORS.md
|
|
@ -1,11 +0,0 @@
|
|||
file: pages\_lib\list.js
|
||||
source: https://raw.githubusercontent.com/javve/list.js/v2.3.1/dist/list.js
|
||||
|
||||
file: pages\_lib\tom-select.base.js
|
||||
source: https://cdn.jsdelivr.net/npm/tom-select@v2.6.1/dist/js/tom-select.base.js
|
||||
|
||||
file: pages\_lib\tom-select.default.min.css
|
||||
source: https://cdn.jsdelivr.net/npm/tom-select@v2.6.1/dist/css/tom-select.default.min.css
|
||||
|
||||
file: js\lib\diff.js
|
||||
source: https://cdnjs.cloudflare.com/ajax/libs/jsdiff/7.0.0/diff.js
|
||||
|
|
@ -1,200 +0,0 @@
|
|||
{
|
||||
"extensionDescription": {
|
||||
"message": "Използвайте ChatGPT, Google Gemini, Claude или Ollama, за да подобрите вашите имейли!"
|
||||
},
|
||||
"menu_title": {
|
||||
"message": "ИИ"
|
||||
},
|
||||
"prompt_lang": {
|
||||
"message": "Отговорено за"
|
||||
},
|
||||
"prompt_reply": {
|
||||
"message": "Отговор на този имейл"
|
||||
},
|
||||
"prompt_reply_advanced": {
|
||||
"message": "Отговор към тази нишка"
|
||||
},
|
||||
"prompt_reply_custom_command": {
|
||||
"message": "Отговаряне с команда"
|
||||
},
|
||||
"prompt_rewrite_polite": {
|
||||
"message": "Пренаписване по-учтиво"
|
||||
},
|
||||
"prompt_rewrite_formal": {
|
||||
"message": "Пренаписване по-формално"
|
||||
},
|
||||
"prompt_classify": {
|
||||
"message": "Класифициране"
|
||||
},
|
||||
"prompt_translate_this": {
|
||||
"message": "Превеждане"
|
||||
},
|
||||
"prompt_this": {
|
||||
"message": "Поискайте това"
|
||||
},
|
||||
"prompt_selection_needed": {
|
||||
"message": "Селектирайте текст, за да продължите!"
|
||||
},
|
||||
"customPrompts_managePrompts": {
|
||||
"message": "Управление на подкани"
|
||||
},
|
||||
"more_info_string": {
|
||||
"message": "Повече информация"
|
||||
},
|
||||
"customPrompts_managePrompts_info_default": {
|
||||
"message": "Подканите по подразбиране не са редактируеми. Можете да ги изключите, да копирате текста им и да го поставите в нова, модифицирана версия."
|
||||
},
|
||||
"customPrompts_managePrompts_info_default_2": {
|
||||
"message": "Можете да внесете и изнесете подканите. Съществуващите подкани със същото ID ще бъдат презаписани. Подканите с нови ID-та ще бъдат добавени."
|
||||
},
|
||||
"customPrompts_managePrompts_info_default_3": {
|
||||
"message": "Ако всичко е правилно, след внасянето натиснете бутона \"Запази всичко\"."
|
||||
},
|
||||
"customPrompts_start_saving": {
|
||||
"message": "Запазване на подканите..."
|
||||
},
|
||||
"customPrompts_reindexing_list": {
|
||||
"message": "Преиндексиране на списъка..."
|
||||
},
|
||||
"customPrompts_filtering_prompts": {
|
||||
"message": "Филтриране на подканите..."
|
||||
},
|
||||
"customPrompts_saving_default_prompts": {
|
||||
"message": "Запазване на подкани по подразбиране..."
|
||||
},
|
||||
"customPrompts_saving_custom_prompts": {
|
||||
"message": "Запазване на персонализирани подкани..."
|
||||
},
|
||||
"customPrompts_reloading_menus": {
|
||||
"message": "Презареждане на менюта..."
|
||||
},
|
||||
"customPrompts_saved": {
|
||||
"message": "Подканите са запазени!"
|
||||
},
|
||||
"customPrompts_form_label_ID": {
|
||||
"message": "ID"
|
||||
},
|
||||
"customPrompts_form_label_ID_rules": {
|
||||
"message": "Трябва да е уникално, с малки букви и без разстояния"
|
||||
},
|
||||
"customPrompts_form_label_Name": {
|
||||
"message": "Име"
|
||||
},
|
||||
"customPrompts_form_label_Text": {
|
||||
"message": "Текст на подкана"
|
||||
},
|
||||
"customPrompts_form_label_Action": {
|
||||
"message": "Действие"
|
||||
},
|
||||
"customPrompts_form_label_need_selected": {
|
||||
"message": "Необходима е селекция на текст"
|
||||
},
|
||||
"customPrompts_form_label_need_signature": {
|
||||
"message": "Винаги добавяй подпис"
|
||||
},
|
||||
"customPrompts_form_label_need_custom_text": {
|
||||
"message": "Поискай допълнителен текст"
|
||||
},
|
||||
"customPrompts_form_label_enabled": {
|
||||
"message": "Разрешено"
|
||||
},
|
||||
"customPrompts_form_label_use_diff_viewer": {
|
||||
"message": "Активиране на инструмента за сравнение на текст"
|
||||
},
|
||||
"customPrompts_form_label_use_diff_viewer_title": {
|
||||
"message": "Инструментът за сравнение на текст може да бъде избран, когато действието е зададено на „Замести текст“."
|
||||
},
|
||||
"customPrompts_form_required_fields": {
|
||||
"message": "Задължително поле"
|
||||
},
|
||||
"customPrompts_btnEdit": {
|
||||
"message": "Редакция"
|
||||
},
|
||||
"customPrompts_btnCancel": {
|
||||
"message": "Откажи"
|
||||
},
|
||||
"customPrompts_btnOK": {
|
||||
"message": "ОК"
|
||||
},
|
||||
"customPrompts_btnDelete": {
|
||||
"message": "Изтрий"
|
||||
},
|
||||
"customPrompts_btnDelete_confirmText": {
|
||||
"message": "Сигурни ли сте, че искате да изтриете този елемент?"
|
||||
},
|
||||
"customPrompts_unsaved_changes": {
|
||||
"message": "Има незапазени промени!"
|
||||
},
|
||||
"btnSaveAll_string": {
|
||||
"message": "Запази всичко"
|
||||
},
|
||||
"btnNew_string": {
|
||||
"message": "Добави нов"
|
||||
},
|
||||
"customPrompts_btnAddNewCommit": {
|
||||
"message": "Добави подканата"
|
||||
},
|
||||
"customPrompts_add_to_menu": {
|
||||
"message": "Добави към меню"
|
||||
},
|
||||
"customPrompts_add_to_menu_always": {
|
||||
"message": "Винаги"
|
||||
},
|
||||
"customPrompts_add_to_menu_reading": {
|
||||
"message": "Четене на писмо"
|
||||
},
|
||||
"customPrompts_add_to_menu_composing": {
|
||||
"message": "Писане на писмо"
|
||||
},
|
||||
"customPrompts_close_button": {
|
||||
"message": "Бутон за изключване"
|
||||
},
|
||||
"customPrompts_do_reply": {
|
||||
"message": "Отговори"
|
||||
},
|
||||
"customPrompts_substitute_text": {
|
||||
"message": "Замени тескт"
|
||||
},
|
||||
"chatgpt_win_working": {
|
||||
"message": "В процес на работа..."
|
||||
},
|
||||
"chatgpt_win_job_completed": {
|
||||
"message": "Завършено!"
|
||||
},
|
||||
"chatgpt_win_job_completed_select": {
|
||||
"message": "Изберете текста, който искате да използвате, и щракнете върху бутона."
|
||||
},
|
||||
"chatgpt_win_get_answer": {
|
||||
"message": "Използвайте избрания отговор"
|
||||
},
|
||||
"chatgpt_win_close": {
|
||||
"message": "Затвори"
|
||||
},
|
||||
"chatgpt_textarea_not_found_error": {
|
||||
"message": "Изглежда, че страницата на ChatGPT се зарежда твърде бавно. Ако зареждането приключи, щракнете върху бутона вдясно. Ако проблемът продължава, моля, проверете състоянието на услугата."
|
||||
},
|
||||
"chatgpt_btn_retry": {
|
||||
"message": "Повторeн опит"
|
||||
},
|
||||
"chatgpt_sendbutton_not_found_error": {
|
||||
"message": "Кликнете върху бутона за изпращане, за да изпратите подканата."
|
||||
},
|
||||
"chatgpt_user_not_logged_in": {
|
||||
"message": "Не сте влезли в ChatGPT. Моля, влезте с вашите идентификационни данни, затворете прозореца на ChatGPT и след това повторете действието, което сте опитали. След това ще останете влезли."
|
||||
},
|
||||
"chatgpt_win_model_warning": {
|
||||
"message": "По някаква причина не е възможно да се провери дали е зареден правилният модел. Засега можете да натиснете синия бутон, за да продължите."
|
||||
},
|
||||
"chatgpt_win_custom_text": {
|
||||
"message": "Въведете тук допълнителния текст за подканата."
|
||||
},
|
||||
"chatgpt_win_send": {
|
||||
"message": "Изпрати"
|
||||
},
|
||||
"warn_API_needed": {
|
||||
"message": "За да използвате тази функция, ви е необходима API интеграция, а не ChatGPT Web интеграция. Можете да дефинирате конкретен API на страницата с настройки на функцията, като първо поставите отметка в квадратчето по-горе и след това щракнете върху бутона отляво."
|
||||
},
|
||||
"prefs_google_gemini_thinking_budget": {
|
||||
"message": "Бюджет за мислене"
|
||||
}
|
||||
}
|
||||
|
|
@ -1,14 +1,17 @@
|
|||
{
|
||||
"customPrompts_save_button": {
|
||||
"message": "Konservi"
|
||||
},
|
||||
"prompt_lang": {
|
||||
"message": "Respondi per"
|
||||
},
|
||||
"extensionDescription": {
|
||||
"message": "Uzu ChatGPT, Google Gemini, Claude aŭ Ollama por poluri viajn retpoŝtajn mesaĝojn!"
|
||||
"message": "Uzu ChatGPT, Google Gemini aŭ Ollama por poluri viajn retpoŝtajn mesaĝojn!"
|
||||
},
|
||||
"prompt_rewrite_formal": {
|
||||
"message": "Reverki formale"
|
||||
},
|
||||
"more_info_string": {
|
||||
"customPrompts_managePrompts_help": {
|
||||
"message": "Pliaj informoj"
|
||||
},
|
||||
"customPrompts_managePrompts": {
|
||||
|
|
@ -20,10 +23,10 @@
|
|||
"customPrompts_form_required_fields": {
|
||||
"message": "Postulataj kampoj"
|
||||
},
|
||||
"btnSaveAll_string": {
|
||||
"customPrompts_btnSaveAll": {
|
||||
"message": "Konservi ĉion"
|
||||
},
|
||||
"btnNew_string": {
|
||||
"customPrompts_btnNew": {
|
||||
"message": "Aldoni novan"
|
||||
},
|
||||
"chatgpt_btn_retry": {
|
||||
|
|
@ -38,7 +41,7 @@
|
|||
"From": {
|
||||
"message": "De"
|
||||
},
|
||||
"no_string": {
|
||||
"spamfilter_not_moved": {
|
||||
"message": "Ne"
|
||||
},
|
||||
"apiwebchat_stopping": {
|
||||
|
|
@ -65,6 +68,9 @@
|
|||
"prompt_reply_advanced": {
|
||||
"message": "Respondu al ĉi tiu fadeno"
|
||||
},
|
||||
"prompt_summarize_this": {
|
||||
"message": "Resumu ĉi tion"
|
||||
},
|
||||
"prompt_translate_this": {
|
||||
"message": "Traduku ĉi tion"
|
||||
},
|
||||
|
|
@ -119,7 +125,7 @@
|
|||
"Explanation": {
|
||||
"message": "Klarigo"
|
||||
},
|
||||
"yes_string": {
|
||||
"spamfilter_moved": {
|
||||
"message": "Jes"
|
||||
},
|
||||
"apiwebchat_you": {
|
||||
|
|
@ -148,26 +154,5 @@
|
|||
},
|
||||
"Ollama_Models_Fetch": {
|
||||
"message": "Ĝisdatigi liston de modeloj Ollama"
|
||||
},
|
||||
"prefs_OptionText_reply_all": {
|
||||
"message": "Respondi al ĉiuj"
|
||||
},
|
||||
"prefs_OptionText_reply_sender": {
|
||||
"message": "Respondi al sendinto"
|
||||
},
|
||||
"prefs_OptionText_reply_type": {
|
||||
"message": "Speco de respondo"
|
||||
},
|
||||
"SelectAll": {
|
||||
"message": "Elekti ĉion"
|
||||
},
|
||||
"DeselectAll": {
|
||||
"message": "Malelekti ĉion"
|
||||
},
|
||||
"prompt_reply_custom_command": {
|
||||
"message": "Respondi per komando..."
|
||||
},
|
||||
"customPrompts_substitute_text": {
|
||||
"message": "Anstataŭigi tekston"
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
{
|
||||
"extensionDescription": {
|
||||
"message": "Koristite ChatGPT, Google Gemini, Claude ili Ollama kako bi poboljšali vaše e-poruke!",
|
||||
"message": "Koristite ChatGPT, Google Gemini ili Ollama kako bi poboljšali vaše e-poruke!",
|
||||
"description": "Description of the extension."
|
||||
},
|
||||
"menu_title": {
|
||||
|
|
@ -24,6 +24,9 @@
|
|||
"prompt_classify": {
|
||||
"message": "Klasificiraj"
|
||||
},
|
||||
"prompt_summarize_this": {
|
||||
"message": "Sažmi ovo"
|
||||
},
|
||||
"prompt_translate_this": {
|
||||
"message": "Prevedi ovo"
|
||||
},
|
||||
|
|
@ -36,7 +39,7 @@
|
|||
"customPrompts_managePrompts": {
|
||||
"message": "Upravljaj upitima"
|
||||
},
|
||||
"more_info_string": {
|
||||
"customPrompts_managePrompts_help": {
|
||||
"message": "Više informacija"
|
||||
},
|
||||
"customPrompts_managePrompts_info_default": {
|
||||
|
|
@ -117,10 +120,13 @@
|
|||
"customPrompts_unsaved_changes": {
|
||||
"message": "Ima nespremljenih promjena!"
|
||||
},
|
||||
"btnSaveAll_string": {
|
||||
"customPrompts_btnSaveAll": {
|
||||
"message": "Spremi sve"
|
||||
},
|
||||
"btnNew_string": {
|
||||
"customPrompts_save_button": {
|
||||
"message": "Spremi"
|
||||
},
|
||||
"customPrompts_btnNew": {
|
||||
"message": "Dodaj novo"
|
||||
},
|
||||
"customPrompts_btnAddNewCommit": {
|
||||
|
|
@ -175,7 +181,7 @@
|
|||
"message": "Niste prijavljeni na ChatGPT. Prijavite se sa svojim vjerodajnicama, zatvorite prozor ChatGPT, a zatim ponovite radnju koju ste pokušali. Nakon toga ćete ostati prijavljeni."
|
||||
},
|
||||
"chatgpt_win_model_warning": {
|
||||
"message": "Iz nekog razloga nije moguće provjeriti je li učitan ispravan model. Za sada možeš pritisnuti plavi gumb za nastavak."
|
||||
"message": "Postavili ste opciju korištenja određenog modela, ali čini se da se ne učitava ispravno. Provjerite vrijednost i pokušajte ponovno. Za sada možete pritisnuti plavi gumb za nastavak."
|
||||
},
|
||||
"chatgpt_win_custom_text": {
|
||||
"message": "Ovdje umetnite dodatni tekst za upit."
|
||||
|
|
@ -183,6 +189,9 @@
|
|||
"chatgpt_win_send": {
|
||||
"message": "Pošalji"
|
||||
},
|
||||
"chatgpt_use_gpt35": {
|
||||
"message": "Koristi GPT3.5"
|
||||
},
|
||||
"chatgpt_force_completion": {
|
||||
"message": "prisilno dovrši"
|
||||
},
|
||||
|
|
@ -199,7 +208,7 @@
|
|||
"message": "status usluge"
|
||||
},
|
||||
"prefs_OptionText_chatgpt_win_text": {
|
||||
"message": "Dimenzije prozora za AI razgovor"
|
||||
"message": "Dimenzije prozora ChatGPT"
|
||||
},
|
||||
"prefs_OptionText_chatgpt_win_height": {
|
||||
"message": "Visina"
|
||||
|
|
@ -324,6 +333,9 @@
|
|||
"chagpt_api_send_button": {
|
||||
"message": "Korištenje modela"
|
||||
},
|
||||
"chagpt_api_connecting": {
|
||||
"message": "Pokušaj povezivanja na OpenAI ChatGPT pomoću dostavljenog API ključa"
|
||||
},
|
||||
"Debug": {
|
||||
"message": "Otklanjanje pogrešaka"
|
||||
},
|
||||
|
|
@ -357,6 +369,12 @@
|
|||
"ollama_empty_model": {
|
||||
"message": "Niste odabrali model za Ollama API. Odaberite jedan na stranici s mogućnostima."
|
||||
},
|
||||
"ollama_api_connecting": {
|
||||
"message": "Pokušaj povezivanja na Ollama lokalni poslužitelj pomoću glavnog računala"
|
||||
},
|
||||
"andModel": {
|
||||
"message": "i model"
|
||||
},
|
||||
"error_connection_interrupted": {
|
||||
"message": "Veza s poslužiteljem je neočekivano prekinuta"
|
||||
},
|
||||
|
|
@ -366,7 +384,7 @@
|
|||
"chatgpt_api_request_failed": {
|
||||
"message": "OpenAI ChatGPT API zahtjev nije uspio"
|
||||
},
|
||||
"WaitingServerResponse": {
|
||||
"WaitingServerReponse": {
|
||||
"message": "Čeka se odgovor poslužitelja"
|
||||
},
|
||||
"prefs_API_Host_Info": {
|
||||
|
|
@ -387,6 +405,9 @@
|
|||
"OpenAIComp_empty_model": {
|
||||
"message": "Niste odabrali model za OpenAI kompatibilan API. Odaberite jedan na stranici s opcijama."
|
||||
},
|
||||
"OpenAIComp_api_connecting": {
|
||||
"message": "Pokušaj povezivanja s OpenAI kompatibilnim API lokalnim poslužiteljem pomoću glavnog računala"
|
||||
},
|
||||
"OpenAIComp_api_request_failed": {
|
||||
"message": "OpenAI Comp API zahtjev nije uspio"
|
||||
},
|
||||
|
|
@ -408,6 +429,12 @@
|
|||
"prefs_OptionText_dynamic_menu_force_enter_info": {
|
||||
"message": "Ako je označeno, korištenje tipkovničkog prečaca CTRL+ALT+A automatski će poslati istaknuti upit iz izbornika. U protivnom će korisniku biti prikazan naziv upita, koji će zahtijevati još jedan pritisak tipke Enter za slanje."
|
||||
},
|
||||
"prefs_OptionText_dynamic_menu_order_alphabet": {
|
||||
"message": "Izbornik: redoslijed po abecedi"
|
||||
},
|
||||
"prefs_OptionText_dynamic_menu_order_alphabet_info": {
|
||||
"message": "Ako je označeno, upiti u izborniku bit će poredani abecednim redom."
|
||||
},
|
||||
"prefs_OptionText_chatgpt_win_dims_info": {
|
||||
"message": "Postavite na 0 ako ne želite odrediti veličinu prozora."
|
||||
},
|
||||
|
|
@ -460,13 +487,13 @@
|
|||
"message": "Maksimalna duljina upita"
|
||||
},
|
||||
"prefs_OptionText_max_prompt_length_Info": {
|
||||
"message": "Ovo je najveći broj znakova koji se mogu koristiti u upitu. U protivnom će se prikazati poruka o pogrešci. Vrijednost nije moguće uređivati za ChatGPT web sučelje. Postavite na nulu kako biste onemogućili provjeru."
|
||||
"message": "Ovo je najveći broj znakova koji se mogu koristiti u upitu. U protivnom će se prikazati poruka o pogrešci. Vrijednost nije moguće uređivati za ChatGPT web sučelje."
|
||||
},
|
||||
"prefs_OptionText_chatgpt_web_model": {
|
||||
"message": "ChatGPT Web Model"
|
||||
"message": "ChatGPT web model"
|
||||
},
|
||||
"prefs_OptionText_chatgpt_web_model_info": {
|
||||
"message": "Ovo je Model koji će se provoditi za ChatGPT web sučelje. Ako nijedan nije naveden ili je naveden netočan, zadani model postavit će ChatGPT na web stranici. Ova postavka neće raditi s besplatnim ChatGPT računom."
|
||||
"message": "Ovo je model koji će se provoditi za ChatGPT web sučelje. Ako nijedan nije naveden ili je naveden netočan, zadani model postavit će ChatGPT na web stranici."
|
||||
},
|
||||
"prefs_OptionText_chatgpt_web_tempchat": {
|
||||
"message": "ChatGPT Web privremeno čavrljanje"
|
||||
|
|
@ -477,12 +504,18 @@
|
|||
"chatgpt_btn_model": {
|
||||
"message": "Koristi trenutni model"
|
||||
},
|
||||
"SendingPrompt": {
|
||||
"message": "Slanje upita..."
|
||||
},
|
||||
"AllowedValues": {
|
||||
"message": "Dopuštene vrijednosti"
|
||||
},
|
||||
"prefs_OptionText_btnManagePrompts_infoline": {
|
||||
"message": "Možete koristiti dodatna rezervirana mjesta za podatke."
|
||||
},
|
||||
"prefs_OptionText_btnManagePrompts_infoline2": {
|
||||
"message": "Možete promijeniti upit kako želite, ali odgovor primljen od AI mora biti popis oznaka odvojenih zarezima!"
|
||||
},
|
||||
"prefs_OptionText_openai_comp_use_v1": {
|
||||
"message": "Zadrži \"v1\" kompatibilnost"
|
||||
},
|
||||
|
|
@ -492,6 +525,9 @@
|
|||
"prefs_OptionText_owl_warning": {
|
||||
"message": "Čini se da barem jedan od vaših računa koristi dodatak Owl for Exchange. Postoji poznati problem između Thunderbirda i Owl, koji se trenutno rješava. Trenutačno možete koristiti ThunderAI dok sastavljate e-poruke, ali ne i dok ih čitate."
|
||||
},
|
||||
"prefs_OptionText_chatgpt_web_model_tooltip": {
|
||||
"message": "Pritisnite vrijednost da biste je postavili."
|
||||
},
|
||||
"prompt_reply_full_text": {
|
||||
"message": "Odgovori na sljedeću e-poruku. Odgovorit samo s potrebnim tekstom i bez dodatnih komentara ili drugog teksta."
|
||||
},
|
||||
|
|
@ -516,8 +552,11 @@
|
|||
"prompt_classify_full_text": {
|
||||
"message": "Klasificiraj sljedeći tekst u smislu ljubaznosti, topline, formalnosti, asertivnosti, uvredljivosti dajući postotak za svaku kategoriju. Odgovori samo kategorijom i ocijeni bez dodatnih komentara ili drugog teksta."
|
||||
},
|
||||
"prompt_summarize_this_full_text": {
|
||||
"message": "Sažmi sljedeću e-poruku u popis s točkama."
|
||||
},
|
||||
"prompt_translate_this_full_text": {
|
||||
"message": "Prevedite donju e-poštu na {%thunderai_translate_lang%}.\n\nPravila:\n- Prevedite i predmet i tijelo e-pošte.\n- Vratite rezultat kao JSON objekt s tri polja: \"subject\", \"body\" i \"status\".\n- Ako je prijevod izvršen, status je jedan 1.\n- Ako je e-pošta napisana na jednom od ovih jezika \"{%thunderai_translate_exclude_lang%}\" ili na jeziku {%thunderai_translate_lang%}, vratite prazan niz za tijelo i predmet i postavite status na -1.\n- Nemojte dodavati objašnjenja, bilješke ili bilo kakav tekst izvan JSON-a.\n\nPredmet e-pošte: {%mail_subject%}\n\nTijelo e-pošte: {%mail_html_body%}\n\nGenerirajte odgovor isključivo u JSON formatu. Izlaz treba biti samo JSON objekt. Evo primjera JSON formata koji treba koristiti:\n{\n\"subject\": \"subject translation\",\n\"body\": \"body translation\",\n\"status\": \"status result\"\n}"
|
||||
"message": "Prevedi sljedeću e-poruku na"
|
||||
},
|
||||
"prompt_this_full_text": {
|
||||
"message": "Odgovori samo s potrebnim tekstom i bez dodatnih komentara ili drugog teksta."
|
||||
|
|
@ -532,7 +571,7 @@
|
|||
"message": "Dodaj oznake ovoj e-poruci"
|
||||
},
|
||||
"prompt_add_tags_full_text": {
|
||||
"message": "Analiziraj sljedeći tekst e-pošte i generiraj JSON niz oznaka koje sažimaju njezin sadržaj. Koristi teme, ključne pojmove i relevantne opise kao oznake. Pobrini se da oznake budu sažete i relevantne za sadržaj poruke.\nTekst e-poruke: {%mail_text_body%}\nUzmi u obzir sljedeće pojedinosti za kontekst:\n- Šalje: {%author%}\n- Primatelji: {%recipients%}\n- CC popis: {%cc_list%}\n- Naslov: {%mail_subject%}\nTemelji oznake na tekstu poruke i kontekstu, zanemarujući nebitne informacije ili trivijalne detalje.\nGeneriraj odgovor isključivo u JSON formatu. Izlaz mora biti samo JSON niz oznaka, bez ikakvih dodatnih komentara ili teksta. Evo primjera formata koji treba koristiti:\n{\n\"tags\": [\"tag1\", \"tag2\", \"tag3\", \"tag4\", \"tag5\"]\n}"
|
||||
"message": "Analiziraj sljedeći tekst e-poruke i generiraj popis oznaka odvojenih zarezima koji sažimaju njegov sadržaj. Koristi teme, ključne teme i relevantne deskriptore kao oznake. Provjeri jesu li oznake sažete i relevantne za sadržaj e-poruke.\nTekst e-poruke: {%mail_text_body%}\nUzmi u obzir sljedeće pojedinosti za kontekst:\n- Šalje: {%author%}\n- Primatelji: {%recipients%}\n- CC popis: {%cc_list%}\n- Naslov: {%mail_subject%}\nTemelji svoje oznake na tekstu i kontekstu e-poruke, zanemarujući nepotrebne informacije ili trivijalne detalje. Izlaz bi trebao biti samo popis oznaka odvojenih zarezom bez dodatnih komentara ili teksta."
|
||||
},
|
||||
"placeholder_tags_current_email": {
|
||||
"message": "Oznake e-pošte"
|
||||
|
|
@ -651,6 +690,9 @@
|
|||
"google_gemini_api_request_failed": {
|
||||
"message": "Google Gemini API zahtjev nije uspio"
|
||||
},
|
||||
"google_gemini_api_connecting": {
|
||||
"message": "Pokušaj povezivanja na Google Gemini pomoću dostavljenog API ključa"
|
||||
},
|
||||
"google_gemini_empty_apikey": {
|
||||
"message": "Niste dodali API ključ za Google Gemini API. Unesite jedan na stranicu s mogućnostima."
|
||||
},
|
||||
|
|
@ -679,7 +721,7 @@
|
|||
"message": "Dodaj novi kalendarski događaj"
|
||||
},
|
||||
"prompt_get_calendar_event_full_text": {
|
||||
"message": "Izdvoji sve relevantne detalje potrebne za generiranje kalendarskog događaja iz sljedećeg teksta. Izdvojene informacije trebaju uključivati:\n- Naslov događaja\n- Datum i vrijeme početka (uključujući vremensku zonu, ako je navedeno)\n- Datum i vrijeme završetka (uključujući vremensku zonu, ako je navedeno)\n- Cijeli dan (ako je navedeno)\n- Sudionici\nOsiguraj da su podaci oblikovani jasno i dosljedno kako bi se mogli izravno koristiti za stvaranje kalendarskog događaja.\nAko postoje relativne vremenske napomene, smatraj da su datum i vrijeme e-poruke \"{%mail_datetime%}\". Izračunajte datum i vrijeme početka na temelju ove napomene. Ako su izračunati početni datum i vrijeme raniji od \"{%current_datetime%}\", ponovno izračunaj početni datum i vrijeme koristeći \"{%current_datetime%}\" kao osnovu.\nAko trajanje nije navedeno, postavi ga na jedan sat.\nOvo su sudionici: {%author%}, {%recipients%}, {%cc_list%}. Ako je prisutna, isključi moju adresu: {%account_email_address%}.\nAko je događaj cjelodnevni, **endDate** mora biti jedan dan nakon **startDate** s vremenom postavljenim na **\"T000000\"**.\nAko ne možeš dobiti jednu ili više potrebnih informacija, odgovori praznim nizom.\nGeneriraj odgovor samo u JSON formatu. Nemoj uključivati nikakav dodatni tekst ili objašnjenja; pruži samo JSON. Ovo je format koji će se koristiti:\n{\n\"startDate\": \"YYYYMMDDTHHMMSS\",\n\"endDate\": \"YYYYMMDDTHHMMSS\",\n\"summary\": \"Sažetak kalendarskih događaja ovdje\",\n\"forceAllDay\": false\n\"attendees\": [attendee1@example.com,attendee2@example.com,attendee3@example.com]\n}\nOvo je tekst:\"{%mail_text_body_or_selected%}\""
|
||||
"message": "Izdvoji sve relevantne detalje potrebne za generiranje kalendarskog događaja iz sljedećeg teksta. Izdvojene informacije trebaju uključivati:\n- Naslov događaja\n- Datum i vrijeme početka (uključujući vremensku zonu, ako je navedeno)\n- Datum i vrijeme završetka (uključujući vremensku zonu, ako je navedeno)\n- Cijeli dan (ako je navedeno)\n- Sudionici\nOsiguraj da su podaci oblikovani jasno i dosljedno kako bi se mogli izravno koristiti za stvaranje kalendarskog događaja.\nAko postoje relativne vremenske napomene, smatraj da su datum i vrijeme e-poruke \"{%mail_datetime%}\". Izračunajte datum i vrijeme početka na temelju ove napomene. Ako su izračunati početni datum i vrijeme raniji od \"{%current_datetime%}\", ponovno izračunaj početni datum i vrijeme koristeći \"{%current_datetime%}\" kao osnovu.\nAko trajanje nije navedeno, postavi ga na jedan sat.\nOvo su sudionici: {%author%}, {%recipients%}, {%cc_list%}. Ako je prisutna, isključi moju adresu: {%account_email_address%}.\nAko ne možeš dobiti jednu ili više potrebnih informacija, odgovori praznim nizom.\nGeneriraj odgovor samo u JSON formatu. Nemoj uključivati nikakav dodatni tekst ili objašnjenja; pruži samo JSON. Ovo je format koji će se koristiti:\n{\n\"startDate\": \"YYYYMMDDTHHMMSS\",\n\"endDate\": \"YYYYMMDDTHHMMSS\",\n\"summary\": \"Sažetak kalendarskih događaja ovdje\",\n\"forceAllDay\": false\n\"attendees\": [attendee1@example.com,attendee2@example.com,attendee3@example.com]\n}\nOvo je tekst:\"{%selected_text%}\""
|
||||
},
|
||||
"prefs_OptionText_get_calendar_event": {
|
||||
"message": "Dodaj novi kalendarski događaj iz odabranog teksta"
|
||||
|
|
@ -699,11 +741,11 @@
|
|||
"GetCalendarEvent_prompt_text_title": {
|
||||
"message": "Trenutačni tekst upita"
|
||||
},
|
||||
"prefs_OptionText_AdvancedPromptResponse_infoline2": {
|
||||
"prefs_OptionText_GetCalendarEvent_infoline2": {
|
||||
"message": "Možete promijeniti upit kako želite, ali odgovor primljen od AI mora biti u JSON formatu kako je navedeno u zadanom upitu!"
|
||||
},
|
||||
"prefs_OptionText_get_calendar_event_Sparks_not_present": {
|
||||
"message": "Za korištenje značajki kalendarskih događaja i zadataka, molimo instalirajte dodatak ThunderAI Sparks."
|
||||
"message": "Za korištenje značajke kalendarskih događaja instalirajte dodatak ThunderAI Sparks."
|
||||
},
|
||||
"prefs_OptionText_download_now": {
|
||||
"message": "Preuzmite ThunderAI Sparks sada!"
|
||||
|
|
@ -720,6 +762,9 @@
|
|||
"calendar_opening_dialog_error": {
|
||||
"message": "Pogreška pri otvaranju dijaloškog okvira kalendarskog događaja"
|
||||
},
|
||||
"sparks_not_installed": {
|
||||
"message": "ThunderAI Sparks nije instaliran!"
|
||||
},
|
||||
"prefs_OptionText_add_tags_auto": {
|
||||
"message": "Dodajte oznake automatski"
|
||||
},
|
||||
|
|
@ -727,7 +772,7 @@
|
|||
"message": "Ako je označeno, AI će automatski dodati oznake novoprimljenim e-porukama."
|
||||
},
|
||||
"prefs_OptionText_add_tags_auto_force_existing": {
|
||||
"message": "Prisilno koristi postojeće oznake pri automatskom označavanju ili korištenju kontekstnog izbornika"
|
||||
"message": "Nametni postojeće oznake prilikom automatskog označavanja"
|
||||
},
|
||||
"prefs_OptionText_add_tags_auto_force_existing_Info": {
|
||||
"message": "Ako je označeno, AI će novoprimljenim e-porukama dodati samo postojeće oznake i neće stvarati nove oznake."
|
||||
|
|
@ -741,7 +786,7 @@
|
|||
"placeholder_thunderai_def_sign": {
|
||||
"message": "Zadani potpis kako je određeno u mogućnostima ThunderAI."
|
||||
},
|
||||
"placeholder_thunderai_def_lang": {
|
||||
"thunderai_def_lang": {
|
||||
"message": "Zadani jezik kako je određeno u mogućnostima ThunderAI."
|
||||
},
|
||||
"prefs_OptionText_spamfilter": {
|
||||
|
|
@ -762,11 +807,14 @@
|
|||
"SpamFilter_prompt_text_title": {
|
||||
"message": "Trenutačni tekst upita"
|
||||
},
|
||||
"prefs_OptionText_spamfilter_infoline": {
|
||||
"message": "Možete promijeniti upit kako želite, ali odgovor primljen od AI mora biti u JSON formatu kako je navedeno u zadanom upitu!"
|
||||
},
|
||||
"prompt_spamfilter": {
|
||||
"message": "Prepoznaj neželjenu poštu"
|
||||
},
|
||||
"prompt_spamfilter_full_text": {
|
||||
"message": "Analiziraj sljedeću e-poruku i utvrdi je li neželjena ili ne. Razmotri čimbenike kao što su sumnjive ključne riječi, pretjerani promotivni jezik, zavaravajuće linije predmeta, zahtjevi za osobnim podacima i neobične adrese pošiljatelja.\nNavedi vrijednost od 0 (nije spam) do 100 (neželjena pošta) i objašnjenje od najviše 10 riječi.\nU slučaju nedostatka podataka poruke, postavite vrijednost na 0 (nije spam) i navedite razlog.\nGeneriraj odgovor samo u JSON formatu. Nemoj uključivati nikakav dodatni tekst ili objašnjenje; pruži samo JSON. Ovdje je format koji treba koristiti:\n{\n\"explanation\": \"Kratko objašnjenje vašeg obrazloženja\",\n\"spamValue\": <cijeli broj od 0 do 100>\n}\nOvdje su informacije o e-poruci:\nŠalje: \"{%author%}\"\nNaslov: \"{%mail_subject%}\"\nHtml tijelo: \"{%mail_html_body%}\""
|
||||
"message": "Analiziraj sljedeću e-poruku i utvrdi je li neželjena ili ne. Razmotri čimbenike kao što su sumnjive ključne riječi, pretjerani promotivni jezik, zavaravajuće linije predmeta, zahtjevi za osobnim podacima i neobične adrese pošiljatelja.\nNavedi vrijednost od 0 (nije spam) do 100 (neželjena pošta) i objašnjenje od najviše 10 riječi.\nGeneriraj odgovor samo u JSON formatu. Nemoj uključivati nikakav dodatni tekst ili objašnjenje; pruži samo JSON. Ovdje je format koji treba koristiti:\n{\n\"spamValue\": <cijeli broj od 0 do 100>,\n\"explanation\": \"Kratko objašnjenje vašeg obrazloženja\",\n}\nOvdje su informacije o e-poruci:\nŠalje: \"{%author%}\"\nNaslov: \"{%mail_subject%}\"\nHtml tijelo: \"{%mail_html_body%}\""
|
||||
},
|
||||
"SpamFilter_prompt_prefs_title": {
|
||||
"message": "Mogućnosti filtera neželjene pošte"
|
||||
|
|
@ -810,10 +858,10 @@
|
|||
"Report_Date": {
|
||||
"message": "Datum izvješća"
|
||||
},
|
||||
"yes_string": {
|
||||
"spamfilter_moved": {
|
||||
"message": "Da"
|
||||
},
|
||||
"no_string": {
|
||||
"spamfilter_not_moved": {
|
||||
"message": "Ne"
|
||||
},
|
||||
"prefs_OptionText_openai_comp_info_remote": {
|
||||
|
|
|
|||
|
|
@ -1,17 +0,0 @@
|
|||
{
|
||||
"extensionDescription": {
|
||||
"message": "Használja a ChatGPT, Google Gemini, Claude vagy Ollama modelleket, hogy még jobbá tegye emailjeit!"
|
||||
},
|
||||
"menu_title": {
|
||||
"message": "MI"
|
||||
},
|
||||
"prompt_reply": {
|
||||
"message": "Válasz erre az emailre"
|
||||
},
|
||||
"prompt_rewrite_polite": {
|
||||
"message": "Újraírás udvariasabban"
|
||||
},
|
||||
"prompt_rewrite_formal": {
|
||||
"message": "Újraírás formálisan"
|
||||
}
|
||||
}
|
||||
|
|
@ -1 +0,0 @@
|
|||
{}
|
||||
|
|
@ -1,5 +0,0 @@
|
|||
{
|
||||
"extensionDescription": {
|
||||
"message": "Gebruik ChatGPT, Google Gemini, Claude of Ollama om uw emails te verbeteren."
|
||||
}
|
||||
}
|
||||
|
|
@ -1,6 +1,6 @@
|
|||
{
|
||||
"extensionDescription": {
|
||||
"message": "Używaj ChatGPT, Goolge Gemini, Claude lub Ollama do ulepszania swoich e-maili!",
|
||||
"message": "Używaj ChatGPT, Goolge Gemini lub Ollama do ulepszania swoich e-maili!",
|
||||
"description": "Description of the extension."
|
||||
},
|
||||
"menu_title": {
|
||||
|
|
@ -24,6 +24,9 @@
|
|||
"prompt_classify": {
|
||||
"message": "Klasyfikuj"
|
||||
},
|
||||
"prompt_summarize_this": {
|
||||
"message": "Podsumuj to"
|
||||
},
|
||||
"prompt_translate_this": {
|
||||
"message": "Przetłumacz to"
|
||||
},
|
||||
|
|
@ -36,7 +39,7 @@
|
|||
"customPrompts_managePrompts": {
|
||||
"message": "Zarządzaj poleceniami"
|
||||
},
|
||||
"more_info_string": {
|
||||
"customPrompts_managePrompts_help": {
|
||||
"message": "Więcej informacji"
|
||||
},
|
||||
"customPrompts_managePrompts_info_default": {
|
||||
|
|
@ -117,10 +120,13 @@
|
|||
"customPrompts_unsaved_changes": {
|
||||
"message": "Istnieją niezapisane zmiany!"
|
||||
},
|
||||
"btnSaveAll_string": {
|
||||
"customPrompts_btnSaveAll": {
|
||||
"message": "Zapisz wszystko"
|
||||
},
|
||||
"btnNew_string": {
|
||||
"customPrompts_save_button": {
|
||||
"message": "Zapisz"
|
||||
},
|
||||
"customPrompts_btnNew": {
|
||||
"message": "Dodaj nowe"
|
||||
},
|
||||
"customPrompts_btnAddNewCommit": {
|
||||
|
|
@ -175,7 +181,7 @@
|
|||
"message": "Nie jesteś zalogowany do ChatGPT. Zaloguj się swoimi danymi, zamknij okno ChatGPT, a następnie powtórz próbę. Po autoryzacji pozostaniesz zalogowany."
|
||||
},
|
||||
"chatgpt_win_model_warning": {
|
||||
"message": "Z jakiegoś powodu nie można zweryfikować, czy załadowano właściwy model. Na razie możesz nacisnąć niebieski przycisk, aby kontynuować."
|
||||
"message": "Ustawiłeś opcję używania określonego modelu, ale wygląda na to, że nie ładuje się on poprawnie. Sprawdź wartość i spróbuj ponownie. Na razie możesz nacisnąć niebieski przycisk, aby kontynuować."
|
||||
},
|
||||
"chatgpt_win_custom_text": {
|
||||
"message": "Wstaw tutaj dodatkowy tekst dla zapytania."
|
||||
|
|
@ -183,6 +189,9 @@
|
|||
"chatgpt_win_send": {
|
||||
"message": "Wyślij"
|
||||
},
|
||||
"chatgpt_use_gpt35": {
|
||||
"message": "Użyj GPT3.5"
|
||||
},
|
||||
"chatgpt_force_completion": {
|
||||
"message": "wymuś zakończenie"
|
||||
},
|
||||
|
|
@ -199,7 +208,7 @@
|
|||
"message": "status usługi"
|
||||
},
|
||||
"prefs_OptionText_chatgpt_win_text": {
|
||||
"message": "Wymiary okna czatu AI"
|
||||
"message": "Wymiary okna ChatGPT"
|
||||
},
|
||||
"prefs_OptionText_chatgpt_win_height": {
|
||||
"message": "Wysokość"
|
||||
|
|
@ -324,6 +333,9 @@
|
|||
"chagpt_api_send_button": {
|
||||
"message": "Używając modelu"
|
||||
},
|
||||
"chagpt_api_connecting": {
|
||||
"message": "Próba połączenia z OpenAI ChatGPT przy użyciu podanego klucza API"
|
||||
},
|
||||
"Debug": {
|
||||
"message": "Debugowanie"
|
||||
},
|
||||
|
|
@ -357,6 +369,12 @@
|
|||
"ollama_empty_model": {
|
||||
"message": "Nie wybrałeś modelu dla API Ollama. Proszę wybierz jeden na stronie opcji."
|
||||
},
|
||||
"ollama_api_connecting": {
|
||||
"message": "Próba połączenia z lokalnym serwerem Ollama używając hosta"
|
||||
},
|
||||
"andModel": {
|
||||
"message": "i modelu"
|
||||
},
|
||||
"error_connection_interrupted": {
|
||||
"message": "Połączenie z serwerem zostało nieoczekiwanie przerwane"
|
||||
},
|
||||
|
|
@ -366,7 +384,7 @@
|
|||
"chatgpt_api_request_failed": {
|
||||
"message": "Zapytanie do API OpenAI ChatGPT nie powiodło się"
|
||||
},
|
||||
"WaitingServerResponse": {
|
||||
"WaitingServerReponse": {
|
||||
"message": "Oczekiwanie na odpowiedź serwera"
|
||||
},
|
||||
"prefs_API_Host_Info": {
|
||||
|
|
@ -387,6 +405,9 @@
|
|||
"OpenAIComp_empty_model": {
|
||||
"message": "Nie wybrałeś modelu dla API kompatybilnego z OpenAI. Proszę wybierz jeden na stronie opcji."
|
||||
},
|
||||
"OpenAIComp_api_connecting": {
|
||||
"message": "Próba połączenia z lokalnym serwerem API kompatybilnym z OpenAI używając hosta"
|
||||
},
|
||||
"OpenAIComp_api_request_failed": {
|
||||
"message": "Zapytanie do API kompatybilnego z OpenAI nie powiodło się"
|
||||
},
|
||||
|
|
@ -408,6 +429,12 @@
|
|||
"prefs_OptionText_dynamic_menu_force_enter_info": {
|
||||
"message": "Jeśli zaznaczone, użycie skrótu klawiszowego CTRL+ALT+A automatycznie wyśle zaznaczone polecenie z menu. W przeciwnym razie nazwa polecenia zostanie wyświetlona użytkownikowi, wymagając kolejnego naciśnięcia klawisza Enter, aby je wysłać."
|
||||
},
|
||||
"prefs_OptionText_dynamic_menu_order_alphabet": {
|
||||
"message": "Menu: sortuj alfabetycznie"
|
||||
},
|
||||
"prefs_OptionText_dynamic_menu_order_alphabet_info": {
|
||||
"message": "Jeśli zaznaczone, polecenia w menu będą uporządkowane alfabetycznie."
|
||||
},
|
||||
"prefs_OptionText_chatgpt_win_dims_info": {
|
||||
"message": "Ustaw na 0, jeśli nie chcesz określać rozmiaru okna."
|
||||
},
|
||||
|
|
@ -460,13 +487,13 @@
|
|||
"message": "Maksymalna długość polecenia"
|
||||
},
|
||||
"prefs_OptionText_max_prompt_length_Info": {
|
||||
"message": "To jest maksymalna liczba znaków, która może być użyta w poleceniu. W przeciwnym razie zostanie wyświetlony komunikat o błędzie. Wartość nie jest edytowalna dla interfejsu webowego ChatGPT. Ustaw na zero, aby wyłączyć sprawdzanie."
|
||||
"message": "To jest maksymalna liczba znaków, która może być użyta w poleceniu. W przeciwnym razie zostanie wyświetlony komunikat o błędzie. Wartość nie jest edytowalna dla interfejsu webowego ChatGPT."
|
||||
},
|
||||
"prefs_OptionText_chatgpt_web_model": {
|
||||
"message": "Model interfejsu webowego ChatGPT"
|
||||
},
|
||||
"prefs_OptionText_chatgpt_web_model_info": {
|
||||
"message": "To jest model, który będzie wymuszony dla interfejsu webowego ChatGPT. Jeśli nie zostanie określony lub podano nieprawidłowy, domyślny model zostanie ustawiony przez ChatGPT na stronie. To ustawienie nie będzie działać z darmowym kontem ChatGPT."
|
||||
"message": "To jest model, który będzie wymuszony dla interfejsu webowego ChatGPT. Jeśli nie zostanie określony lub podano nieprawidłowy, domyślny model zostanie ustawiony przez ChatGPT na stronie."
|
||||
},
|
||||
"prefs_OptionText_chatgpt_web_tempchat": {
|
||||
"message": "Tymczasowy czat w interfejsie webowym ChatGPT"
|
||||
|
|
@ -477,12 +504,18 @@
|
|||
"chatgpt_btn_model": {
|
||||
"message": "Użyj bieżącego modelu"
|
||||
},
|
||||
"SendingPrompt": {
|
||||
"message": "Wysyłanie polecenia..."
|
||||
},
|
||||
"AllowedValues": {
|
||||
"message": "Dozwolone wartości"
|
||||
},
|
||||
"prefs_OptionText_btnManagePrompts_infoline": {
|
||||
"message": "Możesz użyć dodatkowych symboli zastępczych danych."
|
||||
},
|
||||
"prefs_OptionText_btnManagePrompts_infoline2": {
|
||||
"message": "Możesz zmienić polecenie według własnego uznania, ale odpowiedź otrzymana od AI musi być listą tagów oddzielonych przecinkami!"
|
||||
},
|
||||
"prefs_OptionText_openai_comp_use_v1": {
|
||||
"message": "Zachowaj kompatybilność z \"v1\""
|
||||
},
|
||||
|
|
@ -492,6 +525,9 @@
|
|||
"prefs_OptionText_owl_warning": {
|
||||
"message": "Wygląda na to, że przynajmniej jedno z Twoich kont używa dodatku Owl for Exchange. Istnieje znany problem między Thunderbirdem a Owl, który jest obecnie rozwiązywany. Na ten moment możesz używać ThunderAI podczas pisania e-maili, ale nie podczas ich czytania."
|
||||
},
|
||||
"prefs_OptionText_chatgpt_web_model_tooltip": {
|
||||
"message": "Kliknij na wartość, aby ją ustawić."
|
||||
},
|
||||
"prompt_reply_full_text": {
|
||||
"message": "Odpowiedz na poniższy e-mail. Odpowiedz wyłącznie wymaganym tekstem, bez dodatkowych komentarzy ani innego tekstu."
|
||||
},
|
||||
|
|
@ -516,8 +552,11 @@
|
|||
"prompt_classify_full_text": {
|
||||
"message": "Sklasyfikuj poniższy tekst pod względem uprzejmości, serdeczności, formalności, stanowczości, obraźliwości, podając procent dla każdej kategorii. Odpowiedz wyłącznie kategorią i wynikiem, bez dodatkowych komentarzy ani innego tekstu."
|
||||
},
|
||||
"prompt_summarize_this_full_text": {
|
||||
"message": "Podsumuj poniższy e-mail w formie listy punktowanej."
|
||||
},
|
||||
"prompt_translate_this_full_text": {
|
||||
"message": "Przetłumacz poniższą wiadomość e-mail na język {%thunderai_translate_lang%}.\n\nZasady:\n- Przetłumacz zarówno temat, jak i treść wiadomości.\n- Zwróć wynik jako obiekt JSON z trzema polami: \"subject\", \"body\" i \"status\".\n- Jeśli tłumaczenie zostało wykonane, status wynosi 1.\n- Jeśli wiadomość e-mail jest napisana w jednym z tych języków \"{%thunderai_translate_exclude_lang%}\" lub w języku {%thunderai_translate_lang%}, zwróć pusty ciąg znaków dla treści i tematu oraz ustaw status na -1.\n- Nie dodawaj wyjaśnień, notatek ani żadnego tekstu poza formatem JSON.\n\nTemat wiadomości: {%mail_subject%}\n\nTreść wiadomości: {%mail_html_body%}\n\nWygeneruj odpowiedź wyłącznie w formacie JSON. Wynikiem powinien być tylko obiekt JSON. Oto przykład formatu JSON, którego należy użyć:\n{\n\"subject\": \"subject translation\",\n\"body\": \"body translation\",\n\"status\": \"status result\"\n}"
|
||||
"message": "Przetłumacz poniższy e-mail na"
|
||||
},
|
||||
"prompt_this_full_text": {
|
||||
"message": "Odpowiedz wyłącznie wymaganym tekstem, bez dodatkowych komentarzy ani innego tekstu."
|
||||
|
|
@ -532,7 +571,7 @@
|
|||
"message": "Dodaj tagi do tego e-maila"
|
||||
},
|
||||
"prompt_add_tags_full_text": {
|
||||
"message": "Przeanalizuj poniższy tekst e-maila i wygeneruj tablicę JSON z tagami podsumowującymi jego treść. Użyj tematów, kluczowych zagadnień i odpowiednich określeń jako tagów. Upewnij się, że tagi są zwięzłe i adekwatne do treści wiadomości.\nTekst e-maila: {%mail_text_body%}\nWeź pod uwagę następujące szczegóły w kontekście:\n- Nadawca: {%author%}\n- Odbiorcy: {%recipients%}\n- Lista DW: {%cc_list%}\n- Temat e-maila: {%mail_subject%}\nNa podstawie treści wiadomości i kontekstu wygeneruj tagi, pomijając zbędne informacje lub nieistotne szczegóły.\nWygeneruj odpowiedź wyłącznie w formacie JSON. Wynik powinien zawierać jedynie tablicę tagów w formacie JSON, bez dodatkowych komentarzy czy tekstu. Oto przykład wymaganego formatu:\n{\n\"tags\": [\"tag1\", \"tag2\", \"tag3\", \"tag4\", \"tag5\"]\n}"
|
||||
"message": "Przeanalizuj poniższy tekst e-maila i wygeneruj listę tagów oddzielonych przecinkami, które podsumowują jego treść. Użyj tematów, kluczowych zagadnień i odpowiednich opisów jako tagów. Upewnij się, że tagi są zwięzłe i odpowiednie do treści e-maila.\nTekst e-maila: {%mail_text_body%}\nWeź pod uwagę następujące szczegóły w kontekście:\n- Nadawca: {%author%}\n- Odbiorcy: {%recipients%}\n- Lista DW: {%cc_list%}\n- Temat e-maila: {%mail_subject%}\nOprzyj swoje tagi na treści i kontekście e-maila, ignorując zbędne informacje lub nieistotne szczegóły. Wynik powinien być jedynie listą tagów oddzielonych przecinkami, bez żadnych dodatkowych komentarzy ani tekstu."
|
||||
},
|
||||
"placeholder_tags_current_email": {
|
||||
"message": "Tagi e-maila"
|
||||
|
|
@ -642,6 +681,9 @@
|
|||
"google_gemini_api_request_failed": {
|
||||
"message": "Połączenie do interfejsu API Google Gemini nie powiodło się"
|
||||
},
|
||||
"google_gemini_api_connecting": {
|
||||
"message": "Próba połączenia z Google Gemini przy użyciu dostarczonego klucza API"
|
||||
},
|
||||
"google_gemini_empty_apikey": {
|
||||
"message": "Nie dodałeś klucza API dla API Google Gemini. Wstaw go na stronie opcji."
|
||||
},
|
||||
|
|
@ -670,7 +712,7 @@
|
|||
"message": "Dodaj nowe wydarzenie w kalendarzu"
|
||||
},
|
||||
"prompt_get_calendar_event_full_text": {
|
||||
"message": "Wyodrębnij wszystkie istotne szczegóły wymagane do wygenerowania wydarzenia w kalendarzu z poniższego tekstu. Wyodrębnione informacje powinny obejmować:\n- Tytuł wydarzenia\n- Datę i godzinę rozpoczęcia (w tym strefę czasową, jeśli została określona)\n- Datę i godzinę zakończenia (w tym strefę czasową, jeśli została określona)\n- Cały dzień (jeśli jest podany)\n- Uczestnicy\nUpewnij się, że dane są sformatowane w sposób jasny i spójny, tak aby można go bezpośrednio wykorzystać do utworzenia wydarzenia w kalendarzu.\nJeśli istnieją odniesienia do czasu względnego, pamiętaj, że data i godzina wysłania wiadomości e-mail to „{%mail_datetime%}”. Oblicz datę i godzinę rozpoczęcia na podstawie tego odniesienia. Jeśli obliczona data i godzina rozpoczęcia są wcześniejsze niż „{%current_datetime%}”, oblicz ponownie datę i godzinę rozpoczęcia, stosując jako podstawę „{%current_datetime%}”.\nJeśli wydarzenie jest całodniowe, data zakończenia (endDate) musi przypadać na dzień po dacie rozpoczęcia (startDate), a godzina musi być ustawiona na \"T000000\".\nJeśli czas trwania nie jest określony, ustaw go na jedną godzinę.\nOto uczestnicy: {%author%}, {%recipients%}, {%cc_list%}. Jeśli jest obecny, wyklucz mój adres: {%account_email_address%}.\nJeśli nie możesz uzyskać co najmniej jednej z wymaganych informacji, w odpowiedzi wpisz pusty ciąg znaków.\nWygeneruj odpowiedź tylko w formacie JSON. Nie dołączaj żadnego dodatkowego tekstu ani wyjaśnień; podaj tylko JSON. Oto format, którego należy użyć:\n{\n\"startDate\": \"YYYYMMDDTHHMMSS\",\n\"endDate\": \"YYYYMMDDTHHMMSS\",\n\"summary\": \"Tutaj podsumowanie wydarzenia w kalendarzu\",\n\"forceAllDay\": false\n\"attendees\": [attendee1@example.com,attendee2@example.com,attendee3@example.com]\n}\nOto tekst:\"{%mail_text_body_or_selected%}\""
|
||||
"message": "Wyodrębnij wszystkie istotne szczegóły wymagane do wygenerowania wydarzenia w kalendarzu z poniższego tekstu. Wyodrębnione informacje powinny obejmować:\n- Tytuł wydarzenia\n- Datę i godzinę rozpoczęcia (w tym strefę czasową, jeśli została określona)\n- Datę i godzinę zakończenia (w tym strefę czasową, jeśli została określona)\n- Cały dzień (jeśli jest podany)\n- Uczestnicy\nUpewnij się, że dane są sformatowane w sposób jasny i spójny, tak aby można go bezpośrednio wykorzystać do utworzenia wydarzenia w kalendarzu.\nJeśli istnieją odniesienia do czasu względnego, pamiętaj, że data i godzina wysłania wiadomości e-mail to „{%mail_datetime%}”. Oblicz datę i godzinę rozpoczęcia na podstawie tego odniesienia. Jeśli obliczona data i godzina rozpoczęcia są wcześniejsze niż „{%current_datetime%}”, oblicz ponownie datę i godzinę rozpoczęcia, stosując jako podstawę „{%current_datetime%}”.\nJeśli czas trwania nie jest określony, ustaw go na jedną godzinę.\nOto uczestnicy: {%author%}, {%recipients%}, {%cc_list%}. Jeśli jest obecny, wyklucz mój adres: {%account_email_address%}.\nJeśli nie możesz uzyskać co najmniej jednej z wymaganych informacji, w odpowiedzi wpisz pusty ciąg znaków.\nWygeneruj odpowiedź tylko w formacie JSON. Nie dołączaj żadnego dodatkowego tekstu ani wyjaśnień; podaj tylko JSON. Oto format, którego należy użyć:\n{\n\"startDate\": \"YYYYMMDDTHHMMSS\",\n\"endDate\": \"YYYYMMDDTHHMMSS\",\n\"summary\": \"Tutaj podsumowanie wydarzenia w kalendarzu\",\n\"forceAllDay\": false\n\"attendees\": [attendee1@example.com,attendee2@example.com,attendee3@example.com]\n}\nOto tekst:\"{%selected_text %}\""
|
||||
},
|
||||
"prefs_OptionText_get_calendar_event": {
|
||||
"message": "Dodaj nowe wydarzenie w kalendarzu z zaznaczonego tekstu"
|
||||
|
|
@ -690,7 +732,7 @@
|
|||
"GetCalendarEvent_prompt_text_title": {
|
||||
"message": "Bieżący tekst zapytania"
|
||||
},
|
||||
"prefs_OptionText_AdvancedPromptResponse_infoline2": {
|
||||
"prefs_OptionText_GetCalendarEvent_infoline2": {
|
||||
"message": "Możesz zmienić monit według własnego uznania, ale odpowiedź otrzymana od AI musi być w formacie JSON, jak określono w domyślnym zapytaniu!"
|
||||
},
|
||||
"prefs_OptionText_get_calendar_event_Sparks_not_present": {
|
||||
|
|
@ -723,6 +765,9 @@
|
|||
"message": "Klucz API Google Gemini",
|
||||
"description": "Klucz API dla interfejsu API Google Gemini"
|
||||
},
|
||||
"prefs_OptionText_spamfilter_infoline": {
|
||||
"message": "Możesz zmienić prompt według własnego uznania, ale odpowiedź AI musi być w formacie JSON zgodnie z domyślnym promptem!"
|
||||
},
|
||||
"Subject": {
|
||||
"message": "Temat"
|
||||
},
|
||||
|
|
@ -738,14 +783,14 @@
|
|||
"Spam_Value": {
|
||||
"message": "Wartość spamu"
|
||||
},
|
||||
"placeholder_thunderai_def_lang": {
|
||||
"thunderai_def_lang": {
|
||||
"message": "Domyślny język zgodnie z opcjami ThunderAI."
|
||||
},
|
||||
"prompt_spamfilter": {
|
||||
"message": "Wykrywanie e-maili spamowych"
|
||||
},
|
||||
"prefs_OptionText_add_tags_auto_force_existing": {
|
||||
"message": "Wymuś użycie istniejących tagów podczas automatycznego tagowania lub korzystania z menu kontekstowego"
|
||||
"message": "Wymuszaj istniejące tagi podczas automatycznego tagowania"
|
||||
},
|
||||
"prefs_OptionText_add_tags_auto": {
|
||||
"message": "Automatycznie dodawaj tagi"
|
||||
|
|
@ -759,12 +804,15 @@
|
|||
"Moved_to_Spam": {
|
||||
"message": "Przeniesiono do spamu"
|
||||
},
|
||||
"no_string": {
|
||||
"spamfilter_not_moved": {
|
||||
"message": "Nie"
|
||||
},
|
||||
"prefs_OptionText_add_tags_auto_force_existing_Info": {
|
||||
"message": "Jeśli zaznaczone, AI doda tylko istniejące tagi do nowo odebranych e-maili i nie utworzy nowych."
|
||||
},
|
||||
"sparks_not_installed": {
|
||||
"message": "ThunderAI Sparks nie zainstalowany!"
|
||||
},
|
||||
"SpamFilter_info_default": {
|
||||
"message": "Na tej stronie możesz edytować domyślny prompt używany do wykrywania e-maili spamowych."
|
||||
},
|
||||
|
|
@ -777,7 +825,7 @@
|
|||
"prefs_OptionText_btnManageSpamFilterInfo": {
|
||||
"message": "Zarządzaj ustawieniami filtra spamu"
|
||||
},
|
||||
"yes_string": {
|
||||
"spamfilter_moved": {
|
||||
"message": "Tak"
|
||||
},
|
||||
"prefs_OptionText_spamfilter": {
|
||||
|
|
@ -811,7 +859,7 @@
|
|||
"message": "Zarządzaj ustawieniami filtra spamu"
|
||||
},
|
||||
"prompt_spamfilter_full_text": {
|
||||
"message": "Przeanalizuj poniższy e-mail i określ, czy jest to spam, czy nie. Weź pod uwagę takie czynniki jak podejrzane słowa kluczowe, nadmierny język promocyjny, wprowadzające w błąd tematy, prośby o podanie danych osobowych i nietypowe adresy nadawców.\nPodaj wartość od 0 (nie spam) do 100 (spam) oraz wyjaśnienie nie dłuższe niż 10 słów.\nW przypadku braku danych wiadomości ustaw wartość na 0 (nie spam) i podaj powód.\nWygeneruj odpowiedź tylko w formacie JSON. Nie dodawaj żadnego dodatkowego tekstu ani wyjaśnień; podaj tylko JSON. Oto format, który należy użyć:\n{\n\"explanation\": \"Krótkie wyjaśnienie twojego rozumowania\",\n\"spamValue\": <liczba całkowita od 0 do 100>\n}\nOto informacje o e-mailu:\nNadawca: \"{%author%}\"\nTemat: \"{%mail_subject%}\"\nTreść HTML: \"{%mail_html_body%}\""
|
||||
"message": "Przeanalizuj poniższy e-mail i określ, czy jest to spam, czy nie. Weź pod uwagę takie czynniki jak podejrzane słowa kluczowe, nadmierny język promocyjny, wprowadzające w błąd tematy, prośby o podanie danych osobowych i nietypowe adresy nadawców.\nPodaj wartość od 0 (nie spam) do 100 (spam) oraz wyjaśnienie nie dłuższe niż 10 słów.\nWygeneruj odpowiedź tylko w formacie JSON. Nie dodawaj żadnego dodatkowego tekstu ani wyjaśnień; podaj tylko JSON. Oto format, który należy użyć:\n{\n\"spamValue\": <liczba całkowita od 0 do 100>,\n\"explanation\": \"Krótkie wyjaśnienie twojego rozumowania\",\n}\nOto informacje o e-mailu:\nNadawca: \"{%author%}\"\nTemat: \"{%mail_subject%}\"\nTreść HTML: \"{%mail_html_body%}\""
|
||||
},
|
||||
"prefs_OptionText_spamfilter_threshold_Info": {
|
||||
"message": "Jeśli wartość zwrócona przez AI przekroczy ten próg, e-mail zostanie przeniesiony do folderu spamu."
|
||||
|
|
@ -822,6 +870,9 @@
|
|||
"spamfilter_no_reports": {
|
||||
"message": "Nie przeskanowano jeszcze żadnych wiadomości pod kątem spamu. Tutaj znajdziesz listę ostatnich 100 raportów o spamie tylko dla bieżącej sesji."
|
||||
},
|
||||
"context_menu_mzta-add-tags": {
|
||||
"message": "Dodaj tagi"
|
||||
},
|
||||
"customPrompts_form_label_use_diff_viewer_title": {
|
||||
"message": "Widok zmian może zostać wybrany, kiedy wybrana akcja to \"Tekst zastępczy\"."
|
||||
},
|
||||
|
|
@ -835,7 +886,7 @@
|
|||
"message": "By używać wydarzeń w kalendarzu prosimy zainstalować zaktualizowaną wersję dodatku ThunderAI Sparks."
|
||||
},
|
||||
"customPrompts_form_label_use_diff_viewer": {
|
||||
"message": "Włącz podgląd zmian"
|
||||
"message": "Włącz widok zmian"
|
||||
},
|
||||
"placeholder_folder_name": {
|
||||
"message": "Nazwa folderu"
|
||||
|
|
@ -852,6 +903,9 @@
|
|||
"prefs_OptionText_calendar_enforce_timezone": {
|
||||
"message": "Wymuś konkretną strefę czasową"
|
||||
},
|
||||
"context_menu_mzta-spamfilter": {
|
||||
"message": "Analizuj pod kątem spamu"
|
||||
},
|
||||
"placeholder_account_email_address": {
|
||||
"message": "Adres email konta"
|
||||
},
|
||||
|
|
@ -863,8 +917,5 @@
|
|||
},
|
||||
"placeholder_mail_quoted_text": {
|
||||
"message": "Zacytowany tekst w treści maila"
|
||||
},
|
||||
"placeholder_selected_html": {
|
||||
"message": "Zaznaczony HTML"
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
{
|
||||
"extensionDescription": {
|
||||
"message": "Use o ChatGPT, Goolge Gemini, Claude ou o Ollama para aprimorar seus e-mails!",
|
||||
"message": "Use o ChatGPT, Goolge Gemini ou o Ollama para aprimorar seus e-mails!",
|
||||
"description": "Description of the extension."
|
||||
},
|
||||
"menu_title": {
|
||||
|
|
@ -24,6 +24,9 @@
|
|||
"prompt_classify": {
|
||||
"message": "Classificar"
|
||||
},
|
||||
"prompt_summarize_this": {
|
||||
"message": "Resuma isso"
|
||||
},
|
||||
"prompt_translate_this": {
|
||||
"message": "Traduza isso"
|
||||
},
|
||||
|
|
@ -36,7 +39,7 @@
|
|||
"customPrompts_managePrompts": {
|
||||
"message": "Gerenciar Prompts"
|
||||
},
|
||||
"more_info_string": {
|
||||
"customPrompts_managePrompts_help": {
|
||||
"message": "Mais informações"
|
||||
},
|
||||
"customPrompts_managePrompts_info_default": {
|
||||
|
|
@ -117,10 +120,13 @@
|
|||
"customPrompts_unsaved_changes": {
|
||||
"message": "Existem alterações não salvas!"
|
||||
},
|
||||
"btnSaveAll_string": {
|
||||
"customPrompts_btnSaveAll": {
|
||||
"message": "Salvar Tudo"
|
||||
},
|
||||
"btnNew_string": {
|
||||
"customPrompts_save_button": {
|
||||
"message": "Salvar"
|
||||
},
|
||||
"customPrompts_btnNew": {
|
||||
"message": "Adicionar Novo"
|
||||
},
|
||||
"customPrompts_btnAddNewCommit": {
|
||||
|
|
@ -175,7 +181,7 @@
|
|||
"message": "Você não está logado no ChatGPT. Por favor, faça login com suas credenciais, feche a janela do ChatGPT e, em seguida, repita a ação que você tentou. Você permanecerá logado depois."
|
||||
},
|
||||
"chatgpt_win_model_warning": {
|
||||
"message": "Por algum motivo, não é possível verificar se o modelo correto foi carregado. Por enquanto, você pode pressionar o botão azul para continuar."
|
||||
"message": "Você configurou a opção para usar um modelo específico, mas parece que ele não está carregando corretamente. Por favor, verifique o valor e tente novamente. Por enquanto, você pode pressionar o botão azul para continuar."
|
||||
},
|
||||
"chatgpt_win_custom_text": {
|
||||
"message": "Insira aqui o texto adicional para o prompt."
|
||||
|
|
@ -183,6 +189,9 @@
|
|||
"chatgpt_win_send": {
|
||||
"message": "Enviar"
|
||||
},
|
||||
"chatgpt_use_gpt35": {
|
||||
"message": "Usar GPT-3.5"
|
||||
},
|
||||
"chatgpt_force_completion": {
|
||||
"message": "forçar conclusão"
|
||||
},
|
||||
|
|
@ -199,7 +208,7 @@
|
|||
"message": "status do serviço"
|
||||
},
|
||||
"prefs_OptionText_chatgpt_win_text": {
|
||||
"message": "Dimensões da janela de chat de IA"
|
||||
"message": "Dimensões da janela do ChatGPT"
|
||||
},
|
||||
"prefs_OptionText_chatgpt_win_height": {
|
||||
"message": "Altura"
|
||||
|
|
@ -324,6 +333,9 @@
|
|||
"chagpt_api_send_button": {
|
||||
"message": "Usando modelo"
|
||||
},
|
||||
"chagpt_api_connecting": {
|
||||
"message": "Tentando conectar ao OpenAI ChatGPT usando a chave de API fornecida"
|
||||
},
|
||||
"Debug": {
|
||||
"message": "Depurar"
|
||||
},
|
||||
|
|
@ -357,6 +369,12 @@
|
|||
"ollama_empty_model": {
|
||||
"message": "Você não escolheu um modelo para a API do Ollama. Por favor, escolha um na página de opções."
|
||||
},
|
||||
"ollama_api_connecting": {
|
||||
"message": "Tentando conectar ao servidor local do Ollama usando o host"
|
||||
},
|
||||
"andModel": {
|
||||
"message": "e modelo"
|
||||
},
|
||||
"error_connection_interrupted": {
|
||||
"message": "A conexão com o servidor foi interrompida inesperadamente"
|
||||
},
|
||||
|
|
@ -366,7 +384,7 @@
|
|||
"chatgpt_api_request_failed": {
|
||||
"message": "A solicitação da API do OpenAI ChatGPT falhou"
|
||||
},
|
||||
"WaitingServerResponse": {
|
||||
"WaitingServerReponse": {
|
||||
"message": "Aguardando a resposta do servidor"
|
||||
},
|
||||
"prefs_API_Host_Info": {
|
||||
|
|
@ -387,6 +405,9 @@
|
|||
"OpenAIComp_empty_model": {
|
||||
"message": "Você não escolheu um modelo para a API Compatível com OpenAI. Por favor, escolha um na página de opções."
|
||||
},
|
||||
"OpenAIComp_api_connecting": {
|
||||
"message": "Tentando conectar ao Servidor Local da API Compatível com OpenAI usando o host"
|
||||
},
|
||||
"OpenAIComp_api_request_failed": {
|
||||
"message": "Solicitação da API Comp do OpenAI falhou"
|
||||
},
|
||||
|
|
@ -408,6 +429,12 @@
|
|||
"prefs_OptionText_dynamic_menu_force_enter_info": {
|
||||
"message": "Se marcado, usar o atalho de teclado CTRL+ALT+A enviará automaticamente o prompt destacado do menu. Caso contrário, o nome do prompt será exibido para o usuário, exigindo outra pressão da tecla Enter para enviá-lo."
|
||||
},
|
||||
"prefs_OptionText_dynamic_menu_order_alphabet": {
|
||||
"message": "Menu: ordenar alfabeticamente"
|
||||
},
|
||||
"prefs_OptionText_dynamic_menu_order_alphabet_info": {
|
||||
"message": "Se marcado, os prompts no menu serão ordenados alfabeticamente."
|
||||
},
|
||||
"prefs_OptionText_chatgpt_win_dims_info": {
|
||||
"message": "Defina como 0 se você não quiser especificar o tamanho da janela."
|
||||
},
|
||||
|
|
@ -460,13 +487,13 @@
|
|||
"message": "Comprimento máximo do prompt"
|
||||
},
|
||||
"prefs_OptionText_max_prompt_length_Info": {
|
||||
"message": "Este é o número máximo de caracteres que podem ser usados em um prompt. Caso contrário, uma mensagem de erro será exibida. O valor não é editável para a Interface Web do ChatGPT. Defina como zero para desativar a verificação."
|
||||
"message": "Este é o número máximo de caracteres que podem ser usados em um prompt. Caso contrário, uma mensagem de erro será exibida. O valor não é editável para a Interface Web do ChatGPT."
|
||||
},
|
||||
"prefs_OptionText_chatgpt_web_model": {
|
||||
"message": "Modelo Web do ChatGPT"
|
||||
},
|
||||
"prefs_OptionText_chatgpt_web_model_info": {
|
||||
"message": "Este é o modelo que será aplicado para a Interface Web do ChatGPT. Se nenhum for especificado ou um incorreto for fornecido, o modelo padrão será definido pelo ChatGPT na página web. Essa configuração não funcionará com uma conta gratuita do ChatGPT."
|
||||
"message": "Este é o modelo que será aplicado para a Interface Web do ChatGPT. Se nenhum for especificado ou um incorreto for fornecido, o modelo padrão será definido pelo ChatGPT na página web."
|
||||
},
|
||||
"prefs_OptionText_chatgpt_web_tempchat": {
|
||||
"message": "Chat temporário Web do ChatGPT"
|
||||
|
|
@ -477,12 +504,18 @@
|
|||
"chatgpt_btn_model": {
|
||||
"message": "Usar o modelo atual"
|
||||
},
|
||||
"SendingPrompt": {
|
||||
"message": "Enviando prompt..."
|
||||
},
|
||||
"AllowedValues": {
|
||||
"message": "Valores permitidos"
|
||||
},
|
||||
"prefs_OptionText_btnManagePrompts_infoline": {
|
||||
"message": "Você pode usar espaços reservados para dados adicionais."
|
||||
},
|
||||
"prefs_OptionText_btnManagePrompts_infoline2": {
|
||||
"message": "Você pode alterar o prompt como quiser, mas a resposta recebida da IA deve ser uma lista de tags separadas por vírgulas!"
|
||||
},
|
||||
"prefs_OptionText_openai_comp_use_v1": {
|
||||
"message": "Manter a compatibilidade com \"v1\""
|
||||
},
|
||||
|
|
@ -492,6 +525,9 @@
|
|||
"prefs_OptionText_owl_warning": {
|
||||
"message": "Parece que pelo menos uma das suas contas está usando o complemento Coruja para Exchange. Existe um problema conhecido entre Thunderbird e Coruja, que está sendo resolvido no momento. Por enquanto, você pode usar o ThunderAI ao redigir e-mails, mas não ao lê-los."
|
||||
},
|
||||
"prefs_OptionText_chatgpt_web_model_tooltip": {
|
||||
"message": "Clique em um valor para defini-lo."
|
||||
},
|
||||
"prompt_reply_full_text": {
|
||||
"message": "Responda ao e-mail a seguir. Responda apenas com o texto necessário e sem comentários adicionais ou outros textos."
|
||||
},
|
||||
|
|
@ -516,8 +552,11 @@
|
|||
"prompt_classify_full_text": {
|
||||
"message": "Classifique o texto a seguir em termos de Educação, Calor, Formalidade, Assertividade e Ofensividade, atribuindo uma porcentagem para cada categoria. Responda apenas com as categorias e as pontuações, sem comentários adicionais ou outros textos."
|
||||
},
|
||||
"prompt_summarize_this_full_text": {
|
||||
"message": "Resuma o e-mail a seguir em uma lista de tópicos."
|
||||
},
|
||||
"prompt_translate_this_full_text": {
|
||||
"message": "Traduza o e-mail abaixo para o idioma {%thunderai_translate_lang%}.\n\nRegras:\n- Traduza tanto o assunto quanto o corpo do e-mail.\n- Retorne o resultado como um objeto JSON com três campos: \"subject\", \"body\" e \"status\".\n- Se a tradução for realizada, o status é igual a 1.\n- Se o e-mail estiver escrito em um destes idiomas \"{%thunderai_translate_exclude_lang%}\" ou no idioma {%thunderai_translate_lang%}, retorne uma string vazia para o corpo e o assunto e defina o status como -1.\n- Não adicione explicações, notas ou qualquer texto fora do JSON.\n\nAssunto do e-mail: {%mail_subject%}\n\nCorpo do e-mail: {%mail_html_body%}\n\nGere uma resposta apenas em formato JSON. A saída deve ser apenas um objeto JSON. Aqui está um exemplo do formato JSON a ser usado:\n{\n\"subject\": \"subject translation\",\n\"body\": \"body translation\",\n\"status\": \"status result\"\n}"
|
||||
"message": "Traduza o e-mail a seguir para"
|
||||
},
|
||||
"prompt_this_full_text": {
|
||||
"message": "Responda apenas com o texto necessário e sem comentários adicionais ou outros textos."
|
||||
|
|
@ -532,7 +571,7 @@
|
|||
"message": "Adicione tags a este e-mail"
|
||||
},
|
||||
"prompt_add_tags_full_text": {
|
||||
"message": "Analise o seguinte texto de e-mail e gere um array JSON de tags que resumam seu conteúdo. Use temas, tópicos principais e descritores relevantes como tags. Certifique-se de que as tags sejam concisas e pertinentes ao conteúdo do e-mail.\nTexto do e-mail: {%mail_text_body%}\nConsidere os seguintes detalhes como contexto:\n- Remetente: {%author%}\n- Destinatários: {%recipients%}\n- Lista CC: {%cc_list%}\n- Assunto do e-mail: {%mail_subject%}\nBaseie-se no texto do e-mail e no contexto para gerar as tags, ignorando informações desnecessárias ou detalhes triviais.\nGere uma resposta somente no formato JSON. A saída deve ser apenas um array JSON de tags, sem nenhum comentário ou texto adicional. Aqui está um exemplo do formato JSON a ser usado:\n{\n\"tags\": [\"tag1\", \"tag2\", \"tag3\", \"tag4\", \"tag5\"]\n}"
|
||||
"message": "Analise o seguinte texto do e-mail e gere uma lista de tags separadas por vírgulas que resumam seu conteúdo. Use temas, tópicos principais e descritores relevantes como tags. Certifique-se de que as tags sejam concisas e relevantes para o conteúdo do e-mail.\nTexto do e-mail: {%mail_text_body%}\nConsidere os seguintes detalhes como contexto:\n- Remetente: {%author%}\n- Destinatários: {%recipients%}\n- Lista CC: {%cc_list%}\n- Assunto do e-mail: {%mail_subject%}\nBaseie suas tags no texto e contexto do e-mail, ignorando informações desnecessárias ou triviais. A saída deve ser apenas uma lista de tags separadas por vírgulas, sem nenhum comentário ou texto adicional."
|
||||
},
|
||||
"placeholder_tags_current_email": {
|
||||
"message": "Tags do e-mail"
|
||||
|
|
@ -651,6 +690,9 @@
|
|||
"google_gemini_api_request_failed": {
|
||||
"message": "A solicitação para a API Google Gemini falhou"
|
||||
},
|
||||
"google_gemini_api_connecting": {
|
||||
"message": "Tentando conectar ao Google Gemini usando a chave API fornecida"
|
||||
},
|
||||
"google_gemini_empty_apikey": {
|
||||
"message": "Você não adicionou uma chave API para a API Google Gemini. Por favor, insira uma na página de opções."
|
||||
},
|
||||
|
|
@ -679,7 +721,7 @@
|
|||
"message": "Adicionar um novo evento ao calendário"
|
||||
},
|
||||
"prompt_get_calendar_event_full_text": {
|
||||
"message": "Extraia todos os detalhes relevantes necessários para gerar um evento de calendário a partir do texto a seguir. As informações extraídas devem incluir:\n- Título do evento\n- Data e hora de início (incluindo fuso horário, se especificado)\n- Data e hora de término (incluindo fuso horário, se especificado)\n- Dia inteiro (se mencionado)\n- Participantes\nCertifique-se de que os dados estejam formatados de maneira clara e consistente para que possam ser usados diretamente para criar um evento de calendário.\nSe houver referências de tempo relativas, considere que a data e a hora do e-mail são \"{%mail_datetime%}\". Calcule a data e a hora de início com base nessa referência. Se a data e a hora de início calculadas forem anteriores a \"{%current_datetime%}\", recalcule a data e a hora de início usando \"{%current_datetime%}\" como base.\nSe a duração não for especificada, defina-a como uma hora.\nEstes são os participantes: {%author%}, {%recipients%}, {%cc_list%}. Se estiver presente, exclua meu endereço: {%account_email_address%}.\nSe o evento for de dia inteiro, o campo endDate deve ser o dia seguinte ao startDate com o horário definido como \"T000000\".\nSe você não conseguir obter uma ou mais informações necessárias, responda com uma string vazia.\nGere uma resposta apenas no formato JSON. Não inclua texto ou explicações adicionais; forneça apenas o JSON. Aqui está o formato a ser usado:\n{\n\"startDate\": \"YYYYMMDDTHHMMSS\",\n\"endDate\": \"YYYYMMDDTHHMMSS\",\n\"summary\": \"Resumo do evento do calendário aqui\",\n\"forceAllDay\": false\n\"attendees\": [attendee1@example.com,attendee2@example.com,attendee3@example.com]\n}\nAqui está o texto: \"{%mail_text_body_or_selected%}\""
|
||||
"message": "Extraia todos os detalhes relevantes necessários para gerar um evento de calendário a partir do texto a seguir. As informações extraídas devem incluir:\n- Título do evento\n- Data e hora de início (incluindo fuso horário, se especificado)\n- Data e hora de término (incluindo fuso horário, se especificado)\n- Dia inteiro (se mencionado)\n- Participantes\nCertifique-se de que os dados estejam formatados de maneira clara e consistente para que possam ser usados diretamente para criar um evento de calendário.\nSe houver referências de tempo relativas, considere que a data e a hora do e-mail são \"{%mail_datetime%}\". Calcule a data e a hora de início com base nessa referência. Se a data e a hora de início calculadas forem anteriores a \"{%current_datetime%}\", recalcule a data e a hora de início usando \"{%current_datetime%}\" como base.\nSe a duração não for especificada, defina-a como uma hora.\nEstes são os participantes: {%author%}, {%recipients%}, {%cc_list%}. Se estiver presente, exclua meu endereço: {%account_email_address%}.\nSe você não conseguir obter uma ou mais informações necessárias, responda com uma string vazia.\nGere uma resposta apenas no formato JSON. Não inclua texto ou explicações adicionais; forneça apenas o JSON. Aqui está o formato a ser usado:\n{\n\"startDate\": \"YYYYMMDDTHHMMSS\",\n\"endDate\": \"YYYYMMDDTHHMMSS\",\n\"summary\": \"Resumo do evento do calendário aqui\",\n\"forceAllDay\": false\n\"attendees\": [attendee1@example.com,attendee2@example.com,attendee3@example.com]\n}\nAqui está o texto: \"{%selected_text%}\""
|
||||
},
|
||||
"prefs_OptionText_get_calendar_event": {
|
||||
"message": "Adicionar um novo evento ao calendário a partir do texto selecionado"
|
||||
|
|
@ -699,7 +741,7 @@
|
|||
"GetCalendarEvent_prompt_text_title": {
|
||||
"message": "Texto do prompt atual"
|
||||
},
|
||||
"prefs_OptionText_AdvancedPromptResponse_infoline2": {
|
||||
"prefs_OptionText_GetCalendarEvent_infoline2": {
|
||||
"message": "Você pode alterar o prompt como desejar, mas a resposta recebida da IA deve estar no formato JSON conforme especificado no prompt padrão!"
|
||||
},
|
||||
"prefs_OptionText_get_calendar_event_Sparks_not_present": {
|
||||
|
|
@ -720,6 +762,9 @@
|
|||
"calendar_opening_dialog_error": {
|
||||
"message": "Erro ao abrir a caixa de diálogo do evento de calendário"
|
||||
},
|
||||
"prefs_OptionText_spamfilter_infoline": {
|
||||
"message": "Você pode alterar o prompt como quiser, mas a resposta recebida da IA deve estar no formato JSON, conforme especificado no prompt padrão!"
|
||||
},
|
||||
"spamfilter_threshold_zero": {
|
||||
"message": "O limite de spam está em zero! Você marcará todos os e-mails como spam!"
|
||||
},
|
||||
|
|
@ -748,7 +793,7 @@
|
|||
"message": "Valor de spam"
|
||||
},
|
||||
"prefs_OptionText_add_tags_auto_force_existing": {
|
||||
"message": "Forçar tags existentes ao usar a marcação automática ou o menu de contexto"
|
||||
"message": "Forçar tags existentes ao adicionar automaticamente"
|
||||
},
|
||||
"prefs_OptionText_add_tags_auto": {
|
||||
"message": "Adicionar tags automaticamente"
|
||||
|
|
@ -762,9 +807,12 @@
|
|||
"Moved_to_Spam": {
|
||||
"message": "Movido para spam"
|
||||
},
|
||||
"no_string": {
|
||||
"spamfilter_not_moved": {
|
||||
"message": "Não"
|
||||
},
|
||||
"sparks_not_installed": {
|
||||
"message": "ThunderAI Sparks não instalado!"
|
||||
},
|
||||
"prefs_OptionText_btnManageSpamFilterInfo": {
|
||||
"message": "Gerenciar configurações do filtro de spam"
|
||||
},
|
||||
|
|
@ -774,7 +822,7 @@
|
|||
"prefs_OptionText_spamfilter_Info": {
|
||||
"message": "Se marcado, o ThunderAI moverá automaticamente e-mails de spam para a pasta de spam."
|
||||
},
|
||||
"yes_string": {
|
||||
"spamfilter_moved": {
|
||||
"message": "Sim"
|
||||
},
|
||||
"SpamReport_Title": {
|
||||
|
|
@ -796,7 +844,7 @@
|
|||
"message": "Filtro de spam automático"
|
||||
},
|
||||
"prompt_spamfilter_full_text": {
|
||||
"message": "Analise o seguinte e-mail e determine se é spam ou não. Considere fatores como palavras-chave suspeitas, linguagem promocional excessiva, linhas de assunto enganosas, solicitações de informações pessoais e endereços de remetentes incomuns.\nForneça um valor de 0 (não é spam) a 100 (spam) e uma explicação de no máximo 10 palavras.\nEm caso de ausência de dados da mensagem, defina o valor como 0 (não é spam) e informe o motivo.\nGere uma resposta apenas no formato JSON. Não inclua nenhum texto ou explicação adicional; forneça apenas o JSON. Aqui está o formato a ser usado:\n{\n\"explanation\": \"Breve explicação do seu raciocínio\",\n\"spamValue\": <inteiro de 0 a 100>\n}\nAqui estão as informações do e-mail:\nRemetente: \"{%author%}\"\nAssunto: \"{%mail_subject%}\"\nCorpo HTML: \"{%mail_html_body%}\""
|
||||
"message": "Analise o seguinte e-mail e determine se é spam ou não. Considere fatores como palavras-chave suspeitas, linguagem promocional excessiva, linhas de assunto enganosas, solicitações de informações pessoais e endereços de remetentes incomuns.\nForneça um valor de 0 (não é spam) a 100 (spam) e uma explicação de no máximo 10 palavras.\nGere uma resposta apenas no formato JSON. Não inclua nenhum texto ou explicação adicional; forneça apenas o JSON. Aqui está o formato a ser usado:\n{\n\"spamValue\": <inteiro de 0 a 100>,\n\"explanation\": \"Breve explicação do seu raciocínio\",\n}\nAqui estão as informações do e-mail:\nRemetente: \"{%author%}\"\nAssunto: \"{%mail_subject%}\"\nCorpo HTML: \"{%mail_html_body%}\""
|
||||
},
|
||||
"prefs_OptionText_spamfilter_threshold_Info": {
|
||||
"message": "Se o valor retornado pela IA estiver acima deste limite, o e-mail será movido para a pasta de spam."
|
||||
|
|
@ -804,7 +852,7 @@
|
|||
"prefs_OptionText_add_tags_auto_only_inbox_Info": {
|
||||
"message": "Se marcado, a IA adicionará tags apenas aos e-mails recebidos na pasta da caixa de entrada."
|
||||
},
|
||||
"placeholder_thunderai_def_lang": {
|
||||
"thunderai_def_lang": {
|
||||
"message": "Idioma padrão conforme definido nas opções do ThunderAI."
|
||||
},
|
||||
"placeholder_thunderai_def_sign": {
|
||||
|
|
|
|||
|
|
@ -1,173 +0,0 @@
|
|||
{
|
||||
"prompt_lang": {
|
||||
"message": "Responder em"
|
||||
},
|
||||
"prompt_selection_needed": {
|
||||
"message": "Para avançar tens que selecionar texto!"
|
||||
},
|
||||
"customPrompts_form_label_need_custom_text": {
|
||||
"message": "Pedir texto adicional"
|
||||
},
|
||||
"customPrompts_form_label_use_diff_viewer_title": {
|
||||
"message": "O visualizador de comparação de texto pode ser selecionado quando a ação escolhida é \"Substituir texto\"."
|
||||
},
|
||||
"chatgpt_win_job_completed": {
|
||||
"message": "Concluído!"
|
||||
},
|
||||
"chatgpt_win_job_completed_select": {
|
||||
"message": "Seleciona o texto que queres usar e carrega no botão."
|
||||
},
|
||||
"prompt_reply_advanced": {
|
||||
"message": "Responder a esta sequência"
|
||||
},
|
||||
"more_info_string": {
|
||||
"message": "Mais informação"
|
||||
},
|
||||
"menu_title": {
|
||||
"message": "IA"
|
||||
},
|
||||
"prompt_classify": {
|
||||
"message": "Classificar"
|
||||
},
|
||||
"customPrompts_add_to_menu_always": {
|
||||
"message": "Sempre"
|
||||
},
|
||||
"prompt_reply": {
|
||||
"message": "Responder a este email"
|
||||
},
|
||||
"prompt_rewrite_polite": {
|
||||
"message": "Rescrever educadamente"
|
||||
},
|
||||
"prompt_rewrite_formal": {
|
||||
"message": "Rescrever formalmente"
|
||||
},
|
||||
"prompt_translate_this": {
|
||||
"message": "Traduzir isto"
|
||||
},
|
||||
"customPrompts_reindexing_list": {
|
||||
"message": "A redefinir os índices da lista..."
|
||||
},
|
||||
"customPrompts_reloading_menus": {
|
||||
"message": "A recarregar menus..."
|
||||
},
|
||||
"customPrompts_form_label_ID": {
|
||||
"message": "ID"
|
||||
},
|
||||
"customPrompts_form_label_ID_rules": {
|
||||
"message": "Deve ser único, letra minúscula e sem espaços"
|
||||
},
|
||||
"customPrompts_form_label_Action": {
|
||||
"message": "Ação"
|
||||
},
|
||||
"customPrompts_form_label_need_selected": {
|
||||
"message": "Necessário selecionar texto"
|
||||
},
|
||||
"customPrompts_form_label_need_signature": {
|
||||
"message": "Adicionar sempre assinatura"
|
||||
},
|
||||
"customPrompts_form_label_enabled": {
|
||||
"message": "Ativo"
|
||||
},
|
||||
"customPrompts_form_required_fields": {
|
||||
"message": "Campos obrigatórios"
|
||||
},
|
||||
"customPrompts_btnEdit": {
|
||||
"message": "Editar"
|
||||
},
|
||||
"customPrompts_btnCancel": {
|
||||
"message": "Cancelar"
|
||||
},
|
||||
"customPrompts_btnOK": {
|
||||
"message": "OK"
|
||||
},
|
||||
"customPrompts_btnDelete": {
|
||||
"message": "Apagar"
|
||||
},
|
||||
"customPrompts_btnDelete_confirmText": {
|
||||
"message": "Tens a certeza que queres apagar este item?"
|
||||
},
|
||||
"customPrompts_unsaved_changes": {
|
||||
"message": "Existem alterações por guardar!"
|
||||
},
|
||||
"btnSaveAll_string": {
|
||||
"message": "Guardar Tudo"
|
||||
},
|
||||
"btnNew_string": {
|
||||
"message": "Adicionar Novo"
|
||||
},
|
||||
"customPrompts_add_to_menu": {
|
||||
"message": "Adicionar ao menu"
|
||||
},
|
||||
"customPrompts_add_to_menu_reading": {
|
||||
"message": "A ler um email"
|
||||
},
|
||||
"customPrompts_substitute_text": {
|
||||
"message": "Substituir texto"
|
||||
},
|
||||
"customPrompts_managePrompts_info_default_3": {
|
||||
"message": "Se estiver tudo certo, depois de importar carrega no botão \"Guardar Tudo\"."
|
||||
},
|
||||
"extensionDescription": {
|
||||
"message": "Usa o ChatGPT, Google Gemini, Claude ou Ollama para melhorar os teus emails!"
|
||||
},
|
||||
"customPrompts_form_label_Name": {
|
||||
"message": "Nome"
|
||||
},
|
||||
"customPrompts_add_to_menu_composing": {
|
||||
"message": "A compor um email"
|
||||
},
|
||||
"customPrompts_form_label_use_diff_viewer": {
|
||||
"message": "Ativar o visualizador de comparação de texto"
|
||||
},
|
||||
"chatgpt_win_working": {
|
||||
"message": "Trabalho em curso..."
|
||||
},
|
||||
"customPrompts_managePrompts": {
|
||||
"message": "Gerir prompts"
|
||||
},
|
||||
"customPrompts_managePrompts_info_default": {
|
||||
"message": "Os prompts padrão não são editáveis. Podes desativá-los, copiar o texto do prompt e colá-lo num novo, para criar uma versão modificada."
|
||||
},
|
||||
"customPrompts_managePrompts_info_default_2": {
|
||||
"message": "Podes importar e exportar prompts. Prompts existentes com o mesmo ID serão escritos por cima. Prompts com um ID novo serão adicionados."
|
||||
},
|
||||
"customPrompts_start_saving": {
|
||||
"message": "A guardar prompts..."
|
||||
},
|
||||
"customPrompts_filtering_prompts": {
|
||||
"message": "A filtrar prompts..."
|
||||
},
|
||||
"customPrompts_saving_default_prompts": {
|
||||
"message": "A guardar prompts padrão..."
|
||||
},
|
||||
"customPrompts_saving_custom_prompts": {
|
||||
"message": "A guardar prompts personalizados..."
|
||||
},
|
||||
"customPrompts_saved": {
|
||||
"message": "Prompts guardados!"
|
||||
},
|
||||
"customPrompts_btnAddNewCommit": {
|
||||
"message": "Adicionar o prompt"
|
||||
},
|
||||
"customPrompts_do_reply": {
|
||||
"message": "Responder"
|
||||
},
|
||||
"prompt_reply_custom_command": {
|
||||
"message": "Responder com o comando..."
|
||||
},
|
||||
"chatgpt_win_close": {
|
||||
"message": "Fechar"
|
||||
},
|
||||
"chatgpt_win_get_answer": {
|
||||
"message": "Utilize a resposta escolhida"
|
||||
},
|
||||
"customPrompts_close_button": {
|
||||
"message": "Botão de Fechar"
|
||||
},
|
||||
"chatgpt_btn_retry": {
|
||||
"message": "Repetir"
|
||||
},
|
||||
"chatgpt_win_send": {
|
||||
"message": "Enviar"
|
||||
}
|
||||
}
|
||||
|
|
@ -1,230 +0,0 @@
|
|||
{
|
||||
"menu_title": {
|
||||
"message": "IA"
|
||||
},
|
||||
"more_info_string": {
|
||||
"message": "Informații"
|
||||
},
|
||||
"customPrompts_form_label_Action": {
|
||||
"message": "Acțiune"
|
||||
},
|
||||
"customPrompts_form_label_need_signature": {
|
||||
"message": "Adaugă întotdeauna semnatura"
|
||||
},
|
||||
"extensionDescription": {
|
||||
"message": "Folosește ChatGPT, Google Gemini, Claude sau Ollama pentru îmbunătățirea e-mailurilor tale!"
|
||||
},
|
||||
"prompt_lang": {
|
||||
"message": "Răspunde în"
|
||||
},
|
||||
"prompt_reply": {
|
||||
"message": "Răspunde la acest e-mail"
|
||||
},
|
||||
"prompt_reply_advanced": {
|
||||
"message": "Răspunde la această conversație"
|
||||
},
|
||||
"prompt_reply_custom_command": {
|
||||
"message": "Răspunde cu instrucțiuni suplimentare..."
|
||||
},
|
||||
"prompt_rewrite_polite": {
|
||||
"message": "Rescrie politicos"
|
||||
},
|
||||
"prompt_rewrite_formal": {
|
||||
"message": "Rescrie formal"
|
||||
},
|
||||
"prompt_classify": {
|
||||
"message": "Clasifica"
|
||||
},
|
||||
"prompt_translate_this": {
|
||||
"message": "Tradu"
|
||||
},
|
||||
"prompt_selection_needed": {
|
||||
"message": "Pentru a putea continua trebuie selectat un text!"
|
||||
},
|
||||
"customPrompts_managePrompts": {
|
||||
"message": "Administrare prompturi"
|
||||
},
|
||||
"customPrompts_managePrompts_info_default": {
|
||||
"message": "Prompturile predefinite nu pot fi modificate. Însă poți să le dezactivezi, să le copiezi și, în final, să creezi o nouă versiune a lor."
|
||||
},
|
||||
"customPrompts_form_label_ID": {
|
||||
"message": "ID"
|
||||
},
|
||||
"msg_prompt_too_long": {
|
||||
"message": "Textul pe care l-ai furnizat este pre lung. Este necesară reducerea lui."
|
||||
},
|
||||
"prefsDonation_2": {
|
||||
"message": "Consideră o donație!"
|
||||
},
|
||||
"customPrompts_managePrompts_info_default_2": {
|
||||
"message": "Poți importa și exporta prompturile. Prompturile existente cu același ID vor fi suprascrise. Prompturile cu ID-uri noi vor fi adăugate."
|
||||
},
|
||||
"customPrompts_managePrompts_info_default_3": {
|
||||
"message": "Dacă totul este corect, dupa import apasă pe butonul 'Salvează Tot'."
|
||||
},
|
||||
"customPrompts_saved": {
|
||||
"message": "Prompturile au fost salvate!"
|
||||
},
|
||||
"customPrompts_form_label_Name": {
|
||||
"message": "Nume"
|
||||
},
|
||||
"customPrompts_form_label_Text": {
|
||||
"message": "Traducere"
|
||||
},
|
||||
"customPrompts_form_label_need_selected": {
|
||||
"message": "Este necesară selecția unui text"
|
||||
},
|
||||
"customPrompts_form_label_need_custom_text": {
|
||||
"message": "Solicită text suplimentar"
|
||||
},
|
||||
"customPrompts_form_required_fields": {
|
||||
"message": "Câmpuri necesare"
|
||||
},
|
||||
"customPrompts_btnCancel": {
|
||||
"message": "Renunță"
|
||||
},
|
||||
"customPrompts_btnOK": {
|
||||
"message": "OK"
|
||||
},
|
||||
"customPrompts_btnDelete": {
|
||||
"message": "Șterge"
|
||||
},
|
||||
"customPrompts_btnDelete_confirmText": {
|
||||
"message": "Ești sigur că dorești ștergerea acestui element?"
|
||||
},
|
||||
"customPrompts_unsaved_changes": {
|
||||
"message": "Există schimbări nesalvate!"
|
||||
},
|
||||
"btnSaveAll_string": {
|
||||
"message": "Salvează Tot"
|
||||
},
|
||||
"btnNew_string": {
|
||||
"message": "Adaugă"
|
||||
},
|
||||
"customPrompts_btnAddNewCommit": {
|
||||
"message": "Adaugă Promptul"
|
||||
},
|
||||
"customPrompts_add_to_menu": {
|
||||
"message": "Adaugă la meniu"
|
||||
},
|
||||
"customPrompts_add_to_menu_always": {
|
||||
"message": "Întotdeauna"
|
||||
},
|
||||
"customPrompts_close_button": {
|
||||
"message": "Buton de închidere"
|
||||
},
|
||||
"customPrompts_substitute_text": {
|
||||
"message": "Înlocuiește textul"
|
||||
},
|
||||
"chatgpt_win_job_completed": {
|
||||
"message": "Finalizat!"
|
||||
},
|
||||
"chatgpt_win_job_completed_select": {
|
||||
"message": "Selectează textul pe care vrei să-l folosești și apasă butonul."
|
||||
},
|
||||
"chatgpt_win_get_answer": {
|
||||
"message": "Folosește răspunsul selectat"
|
||||
},
|
||||
"chatgpt_win_close": {
|
||||
"message": "Închide"
|
||||
},
|
||||
"chatgpt_btn_retry": {
|
||||
"message": "Reîncearcă"
|
||||
},
|
||||
"chatgpt_sendbutton_not_found_error": {
|
||||
"message": "Apasă butonul de trimitere pentru a transmite promptul."
|
||||
},
|
||||
"chatgpt_win_custom_text": {
|
||||
"message": "Introdu aici textul suplimentar pentru prompt."
|
||||
},
|
||||
"chatgpt_win_send": {
|
||||
"message": "Trimite"
|
||||
},
|
||||
"prefs_status_page": {
|
||||
"message": "starea serviciului"
|
||||
},
|
||||
"prefs_OptionText_chatgpt_win_text": {
|
||||
"message": "Dimensiunile ferestrei de dialog IA"
|
||||
},
|
||||
"prefs_OptionText_chatgpt_win_height": {
|
||||
"message": "Înălțime"
|
||||
},
|
||||
"prefs_OptionText_chatgpt_win_width": {
|
||||
"message": "Lățime"
|
||||
},
|
||||
"prefs_OptionText_default_sign_name": {
|
||||
"message": "Numele semnăturii implicite"
|
||||
},
|
||||
"prefs_OptionText_default_chatgpt_lang": {
|
||||
"message": "Limba implicită pentru răspunsuri"
|
||||
},
|
||||
"prefs_OptionText_reply_all": {
|
||||
"message": "Răspunde tuturor"
|
||||
},
|
||||
"prefs_OptionText_reply_sender": {
|
||||
"message": "Răspunde expeditorului"
|
||||
},
|
||||
"prefs_OptionText_reply_type": {
|
||||
"message": "Tipul răspunsului"
|
||||
},
|
||||
"prefs_OptionText_btnManagePrompts": {
|
||||
"message": "Administrează prompturile"
|
||||
},
|
||||
"prompt_this": {
|
||||
"message": "Întreabă"
|
||||
},
|
||||
"customPrompts_form_label_ID_rules": {
|
||||
"message": "Trebuie să fie unic, cu litere mici și fără spații"
|
||||
},
|
||||
"customPrompts_form_label_enabled": {
|
||||
"message": "Activat"
|
||||
},
|
||||
"customPrompts_form_label_use_diff_viewer": {
|
||||
"message": "Activează compararea de text"
|
||||
},
|
||||
"customPrompts_form_label_use_diff_viewer_title": {
|
||||
"message": "Comparatorul de text poate fi selectat când acțiunea este setată pe \"Înlocuire text\"."
|
||||
},
|
||||
"customPrompts_btnEdit": {
|
||||
"message": "Editează"
|
||||
},
|
||||
"customPrompts_add_to_menu_reading": {
|
||||
"message": "Citirea unui e-mail"
|
||||
},
|
||||
"customPrompts_add_to_menu_composing": {
|
||||
"message": "Compunerea unui e-mail"
|
||||
},
|
||||
"customPrompts_do_reply": {
|
||||
"message": "Răspunde"
|
||||
},
|
||||
"chatgpt_win_working": {
|
||||
"message": "În lucru..."
|
||||
},
|
||||
"chatgpt_textarea_not_found_error": {
|
||||
"message": "Se pare că pagina ChatGPT durează prea mult să se încarce. Dacă încărcarea se termină, faceți clic pe butonul din dreapta. Dacă problema persistă, vă rugăm să verificați starea serviciului."
|
||||
},
|
||||
"chatgpt_user_not_logged_in": {
|
||||
"message": "Nu ești conectat la ChatGPT. Te rugăm să te conectezi cu datele tale de autentificare, să închizi fereastra ChatGPT și apoi să repeți acțiunea încercată. Vei rămâne conectat ulterior."
|
||||
},
|
||||
"chatgpt_win_model_warning": {
|
||||
"message": "Din anumite motive, nu este posibil să verificăm dacă este încărcat modelul corect. Deocamdată, puteți apăsa butonul albastru pentru a continua."
|
||||
},
|
||||
"chatgpt_force_completion": {
|
||||
"message": "închidere forțată"
|
||||
},
|
||||
"chatgpt_force_completion_title": {
|
||||
"message": "Apăsați aici pentru a afișa butonul 'Utilizați ultimul răspuns' dacă ChatGPT a terminat treaba, dar butonul nu a apărut."
|
||||
},
|
||||
"prefs_OptionText_release_notes": {
|
||||
"message": "Note de Lansare"
|
||||
},
|
||||
"prefs_OptionText_reply_type_Info": {
|
||||
"message": "Acesta este tipul de răspuns implicit care va fi utilizat atunci când răspundeți la un e-mail. Puteți alege celălalt tip ulterior în fereastra de răspuns."
|
||||
},
|
||||
"prefsInfoTitle": {
|
||||
"message": "Informații Importante"
|
||||
},
|
||||
"prefsInfoDesc_1": {
|
||||
"message": "S-ar putea ca interfața web ChatGPT să se modifice într-un mod care să perturbe funcționarea addon-ului. Verificați pagina \"Starea serviciului\" accesibilă prin legătura din partea de jos a acestei pagini. De asemenea, rețineți că prima dată când utilizați ThunderAI, trebuie să vă conectați la ChatGPT."
|
||||
}
|
||||
}
|
||||
|
|
@ -1 +0,0 @@
|
|||
{}
|
||||
|
|
@ -1,20 +0,0 @@
|
|||
{
|
||||
"extensionDescription": {
|
||||
"message": "E-postalarınızı geliştirmek için ChatGPT, Google Gemini, Claude veya Ollama’yı kullanın!"
|
||||
},
|
||||
"menu_title": {
|
||||
"message": "YZ"
|
||||
},
|
||||
"customPrompts_managePrompts_info_default": {
|
||||
"message": "Varsayılan promptlar düzenlenemez. Bunları devre dışı bırakabilir, ardından prompt metnini kopyalayıp yeni bir prompta yapıştırarak düzenlenmiş bir sürüm oluşturabilirsiniz."
|
||||
},
|
||||
"customPrompts_managePrompts_info_default_2": {
|
||||
"message": "Promptları içe ve dışa aktarabilirsiniz. Aynı kimliğe (ID) sahip mevcut istemlerin üzerine yazılır. Yeni kimliklere (ID) sahip promptlar eklenir."
|
||||
},
|
||||
"customPrompts_managePrompts_info_default_3": {
|
||||
"message": "Her şey doğruysa, içe aktarma işleminden sonra ‘Tümünü Kaydet’ düğmesine tıklayın."
|
||||
},
|
||||
"customPrompts_start_saving": {
|
||||
"message": "Promptlar kaydediliyor…"
|
||||
}
|
||||
}
|
||||
|
|
@ -1,4 +1,7 @@
|
|||
{
|
||||
"prompt_summarize_this": {
|
||||
"message": "总结一下这个"
|
||||
},
|
||||
"prompt_reply": {
|
||||
"message": "回复此电子邮件"
|
||||
},
|
||||
|
|
@ -35,17 +38,17 @@
|
|||
"customPrompts_form_label_need_signature": {
|
||||
"message": "始终添加签名"
|
||||
},
|
||||
"btnNew_string": {
|
||||
"customPrompts_btnNew": {
|
||||
"message": "新增"
|
||||
},
|
||||
"chatgpt_win_job_completed": {
|
||||
"message": "完成了!"
|
||||
},
|
||||
"btnSaveAll_string": {
|
||||
"customPrompts_btnSaveAll": {
|
||||
"message": "保存所有变动"
|
||||
},
|
||||
"extensionDescription": {
|
||||
"message": "使用 ChatGPT、Google Gemini、Claude 或 Ollama 来提升你的电子邮件!"
|
||||
"message": "使用ChatGPT、Google Gemini 或Ollama 来增强你的邮件!"
|
||||
},
|
||||
"menu_title": {
|
||||
"message": "AI"
|
||||
|
|
@ -62,7 +65,7 @@
|
|||
"customPrompts_managePrompts": {
|
||||
"message": "管理提示词"
|
||||
},
|
||||
"more_info_string": {
|
||||
"customPrompts_managePrompts_help": {
|
||||
"message": "更多信息"
|
||||
},
|
||||
"customPrompts_managePrompts_info_default": {
|
||||
|
|
@ -87,13 +90,13 @@
|
|||
"message": "最大提示词长度"
|
||||
},
|
||||
"prefs_OptionText_max_prompt_length_Info": {
|
||||
"message": "这是提示中可以使用的最大字符数。否则,将显示错误消息。该值在 ChatGPT Web 界面中不可编辑。将其设置为零以禁用检查。"
|
||||
"message": "这是提示中可以使用的最大字符数。否则,将显示错误消息。该值在 ChatGPT Web 界面中不可编辑。"
|
||||
},
|
||||
"prefs_OptionText_chatgpt_web_model": {
|
||||
"message": "ChatGPT Web 模型"
|
||||
},
|
||||
"prefs_OptionText_chatgpt_web_model_info": {
|
||||
"message": "这是将为 ChatGPT Web 界面强制使用的模型。如果未指定或提供了不正确的模型,ChatGPT 将在网页上设置默认模型。此设置不适用于免费的 ChatGPT 账户。"
|
||||
"message": "这是将为 ChatGPT Web 界面强制使用的模型。如果未指定或提供了不正确的模型,ChatGPT 将在网页上设置默认模型。"
|
||||
},
|
||||
"prefs_OptionText_chatgpt_web_tempchat": {
|
||||
"message": "ChatGPT Web 临时聊天"
|
||||
|
|
@ -104,12 +107,18 @@
|
|||
"chatgpt_btn_model": {
|
||||
"message": "使用当前模型"
|
||||
},
|
||||
"SendingPrompt": {
|
||||
"message": "正在发送提示词..."
|
||||
},
|
||||
"AllowedValues": {
|
||||
"message": "允许的值"
|
||||
},
|
||||
"prefs_OptionText_btnManagePrompts_infoline": {
|
||||
"message": "您可以使用额外的数据占位符。"
|
||||
},
|
||||
"prefs_OptionText_btnManagePrompts_infoline2": {
|
||||
"message": "您可以根据需要更改提示,但从 AI 收到的响应必须是以逗号分隔的标签列表!"
|
||||
},
|
||||
"prefs_OptionText_openai_comp_use_v1": {
|
||||
"message": "保持“v1”兼容性"
|
||||
},
|
||||
|
|
@ -128,10 +137,10 @@
|
|||
"SpamReport_Title": {
|
||||
"message": "垃圾邮件过滤报告"
|
||||
},
|
||||
"no_string": {
|
||||
"spamfilter_not_moved": {
|
||||
"message": "否"
|
||||
},
|
||||
"yes_string": {
|
||||
"spamfilter_moved": {
|
||||
"message": "是"
|
||||
},
|
||||
"prompt_rewrite_polite": {
|
||||
|
|
@ -188,6 +197,9 @@
|
|||
"customPrompts_btnCancel": {
|
||||
"message": "取消"
|
||||
},
|
||||
"customPrompts_save_button": {
|
||||
"message": "保存"
|
||||
},
|
||||
"customPrompts_add_to_menu": {
|
||||
"message": "添加到菜单"
|
||||
},
|
||||
|
|
@ -230,6 +242,9 @@
|
|||
"prefs_OptionText_spamfilter_Info": {
|
||||
"message": "如果选中,ThunderAI将自动将垃圾邮件移至垃圾邮件文件夹。"
|
||||
},
|
||||
"sparks_not_installed": {
|
||||
"message": "ThunderAI Sparks 未安装!"
|
||||
},
|
||||
"chatgpt_textarea_not_found_error": {
|
||||
"message": "看起来 ChatGPT 页面加载时间太长。如果加载完成,请点击右边的按钮。如果问题仍然存在,请检查服务状态。"
|
||||
},
|
||||
|
|
@ -276,7 +291,7 @@
|
|||
"message": "您可以通过单击此页面右上角的齿轮图标并选择“管理扩展快捷方式”来更改快捷方式。"
|
||||
},
|
||||
"chatgpt_win_model_warning": {
|
||||
"message": "由于某种原因,无法验证是否加载了正确的模型。现在,你可以点击蓝色按钮继续。"
|
||||
"message": "您已设置使用特定模型的选项,但似乎无法正确加载。请检查值并重试。目前,您可以按蓝色按钮继续。"
|
||||
},
|
||||
"prefs_Connection_type_ChatGPT_API": {
|
||||
"message": "ChatGPT OpenAI API"
|
||||
|
|
@ -314,6 +329,9 @@
|
|||
"prefs_Connection_type_OpenAI_Comp_API": {
|
||||
"message": "OpenAI 兼容的 API"
|
||||
},
|
||||
"prefs_OptionText_dynamic_menu_order_alphabet_info": {
|
||||
"message": "如果勾选此项,菜单中的提示将按字母顺序排列。"
|
||||
},
|
||||
"chatgpt_win_send": {
|
||||
"message": "发送"
|
||||
},
|
||||
|
|
@ -329,6 +347,9 @@
|
|||
"prefs_ChatGPT_API_Key": {
|
||||
"message": "ChatGPT API 密钥"
|
||||
},
|
||||
"chagpt_api_connecting": {
|
||||
"message": "尝试使用提供的 API 密钥连接到 OpenAI ChatGPT"
|
||||
},
|
||||
"prefs_OptionText_release_notes": {
|
||||
"message": "发行说明"
|
||||
},
|
||||
|
|
@ -338,12 +359,15 @@
|
|||
"prefsDonation_2": {
|
||||
"message": "考虑捐款!"
|
||||
},
|
||||
"WaitingServerResponse": {
|
||||
"WaitingServerReponse": {
|
||||
"message": "正在等待服务器响应"
|
||||
},
|
||||
"OpenAIComp_Models_Error_fetching": {
|
||||
"message": "尝试获取 OpenAI 兼容 API 模型时出错"
|
||||
},
|
||||
"chatgpt_use_gpt35": {
|
||||
"message": "使用 GPT3.5"
|
||||
},
|
||||
"customPrompts_Import": {
|
||||
"message": "导入新提示词"
|
||||
},
|
||||
|
|
@ -356,6 +380,9 @@
|
|||
"OpenAIComp_empty_model": {
|
||||
"message": "您尚未选择 OpenAI Compatible API 的模型。请在选项页面中选择一个。"
|
||||
},
|
||||
"OpenAIComp_api_connecting": {
|
||||
"message": "尝试使用主机连接到 OpenAI 兼容 API 本地服务器"
|
||||
},
|
||||
"prefs_OpenAIComp_ChatName": {
|
||||
"message": "对话名称"
|
||||
},
|
||||
|
|
@ -387,7 +414,7 @@
|
|||
"message": "若要使用此集成功能,您需要搭建一台兼容OpenAI API的本地服务器(如LM Studio)。服务器启动后,请在应用程序中的指定字段填入其地址。为确保ThunderAI与本地服务器之间通信正常,请务必正确配置CORS设置。"
|
||||
},
|
||||
"prefs_OptionText_chatgpt_win_text": {
|
||||
"message": "AI 聊天窗口尺寸"
|
||||
"message": "ChatGPT 窗口尺寸"
|
||||
},
|
||||
"prefs_OptionText_chatgpt_win_width": {
|
||||
"message": "宽度"
|
||||
|
|
@ -398,9 +425,15 @@
|
|||
"importPrompts_invalidPrompts": {
|
||||
"message": "您尝试导入的文件不包含任何有效提示词。"
|
||||
},
|
||||
"andModel": {
|
||||
"message": "和模型"
|
||||
},
|
||||
"ChatGPT_Models_Error_fetching": {
|
||||
"message": "尝试获取 ChatGPT 模型时出错"
|
||||
},
|
||||
"prefs_OptionText_dynamic_menu_order_alphabet": {
|
||||
"message": "菜单:按字母顺序排列"
|
||||
},
|
||||
"prefsInfoDesc_2": {
|
||||
"message": "要使用 ChatGPT API,您需要一个 OpenAI ChatGPT API 密钥并且必须选择一个模型。"
|
||||
},
|
||||
|
|
@ -477,7 +510,7 @@
|
|||
"message": "如果在 ThunderAI 窗口中遇到登录问题,请使用右侧的按钮在新标签页中打开 ChatGPT,完成登录后关闭该标签页,然后继续使用 ThunderAI。"
|
||||
},
|
||||
"prompt_translate_this_full_text": {
|
||||
"message": "将以下电子邮件翻译成 {%thunderai_translate_lang%}。\n\n规则:\n- 翻译主题和正文。\n- 以包含三个字段(“subject”、“body”和“status”)的 JSON 对象形式返回结果。\n- 如果翻译已完成,则状态等于 1。\n- 如果电子邮件是以这些语言“{%thunderai_translate_exclude_lang%}”之一或 {%thunderai_translate_lang%} 语言编写的,请为主体和主题返回空字符串,并将状态设置为 -1。\n- 请勿在 JSON 之外添加解释、注释或任何文本。\n\n邮件主题:{%mail_subject%}\n\n邮件正文:{%mail_html_body%}\n\n仅以 JSON 格式生成响应。输出应仅为一个 JSON 对象。以下是要使用的 JSON 格式示例:\n\n{\n\n\"subject\": \"主题翻译\",\n\"body\": \"正文翻译\",\n\"status\": \"状态结果\"\n}"
|
||||
"message": "将以下电子邮件翻译成"
|
||||
},
|
||||
"prompt_add_tags": {
|
||||
"message": "为这封电子邮件添加标签"
|
||||
|
|
@ -545,6 +578,9 @@
|
|||
"placeholder_cc_list": {
|
||||
"message": "抄送列表"
|
||||
},
|
||||
"prefs_OptionText_chatgpt_web_model_tooltip": {
|
||||
"message": "单击一个值进行设置。"
|
||||
},
|
||||
"prefs_OpenAIComp_ForceModel": {
|
||||
"message": "手动填入模型"
|
||||
},
|
||||
|
|
@ -554,6 +590,9 @@
|
|||
"OpenAIComp_force_model_ask": {
|
||||
"message": "在此处填入您想要使用的模型名称。"
|
||||
},
|
||||
"ollama_api_connecting": {
|
||||
"message": "尝试使用主机连接到 Ollama 本地服务器"
|
||||
},
|
||||
"ollama_api_request_failed": {
|
||||
"message": "Ollama API 请求失败"
|
||||
},
|
||||
|
|
@ -596,7 +635,7 @@
|
|||
"prefs_OptionText_placeholders_use_default_value": {
|
||||
"message": "占位符:使用默认值"
|
||||
},
|
||||
"placeholder_thunderai_def_lang": {
|
||||
"thunderai_def_lang": {
|
||||
"message": "ThunderAI 选项中定义的默认语言。"
|
||||
},
|
||||
"prefs_OptionText_openai_comp_info_remote": {
|
||||
|
|
@ -605,11 +644,14 @@
|
|||
"thunderai_warning_title": {
|
||||
"message": "ThunderAI 警告"
|
||||
},
|
||||
"google_gemini_api_connecting": {
|
||||
"message": "尝试使用提供的 API 密钥连接到 Google Gemini"
|
||||
},
|
||||
"prefs_SurveyLinkText2": {
|
||||
"message": "单击此处,只需一分钟!"
|
||||
},
|
||||
"prompt_add_tags_full_text": {
|
||||
"message": "分析以下邮件正文,并生成一个总结其内容的 JSON 标签数组。使用主题、关键话题和相关描述符作为标签。确保标签简洁且与邮件内容相关。\n邮件正文:{%mail_text_body%}\n考虑以下背景详情:\n- 发件人:{%author%}\n- 收件人:{%recipients%}\n- 抄送列表:{%cc_list%}\n- 邮件主题:{%mail_subject%}\n请根据邮件的正文和背景信息生成标签,忽略不必要的信息或琐碎的细节。\n仅以 JSON 格式生成响应。输出应仅为标签的 JSON 数组,不含任何额外注释或文本。以下是要使用的 JSON 格式示例:\n{\n\"tags\": [\"tag1\", \"tag2\", \"tag3\", \"tag4\", \"tag5\"]\n}"
|
||||
"message": "分析以下邮件文本,并生成一个用逗号分隔的标签列表,以概括其内容。使用主题、关键话题和相关描述词作为标签。确保标签简洁且与邮件内容相关。\n邮件文本:{%mail_text_body%}\n考虑以下细节以获取上下文:\n- 发件人:{%author%}\n- 收件人:{%recipients%}\n- 抄送列表:{%cc_list%}\n- 邮件主题:{%mail_subject%}\n请根据邮件文本和上下文生成标签,忽略不必要的信息或琐碎的细节。输出应仅为逗号分隔的标签列表,不包含任何额外评论或文字。"
|
||||
},
|
||||
"placeholder_tags_full_list": {
|
||||
"message": "现有标签"
|
||||
|
|
@ -617,6 +659,9 @@
|
|||
"addtags_dialog_title": {
|
||||
"message": "为电子邮件添加标签"
|
||||
},
|
||||
"prompt_summarize_this_full_text": {
|
||||
"message": "将以下电子邮件总结为要点列表。"
|
||||
},
|
||||
"prompt_rewrite_formal_full_text": {
|
||||
"message": "重写以下文字,使其更加正式。回复时只使用重写的文字,不要添加任何额外的评论或其他文字。"
|
||||
},
|
||||
|
|
@ -747,7 +792,7 @@
|
|||
"message": "管理垃圾邮件过滤器设置"
|
||||
},
|
||||
"prompt_get_calendar_event_full_text": {
|
||||
"message": "从以下文本中提取生成日历事件所需的所有相关细节。提取的信息应包括:\n- 事件标题\n- 开始日期和时间(如果指定时区,则包括时区)\n- 结束日期和时间(如果指定时区,则包括时区)\n- 全天事件(如果提及)\n- 参与者 \n确保数据格式清晰且一致,以便可以直接用于创建日历事件。\n如果存在相对时间的引用,请注意邮件的日期和时间为“{%mail_datetime%}”。基于此参考计算开始日期和时间。如果计算出的开始日期和时间早于“{%current_datetime%}”,则使用“{%current_datetime%}”作为基准重新计算开始日期和时间。\n如果未指定持续时间,请将其设置为一小时。\n以下是参与者:{%author%}, {%recipients%}, {%cc_list%}。如有,请排除我的地址:{%account_email_address%}。\n如果该活动为全天活动,endDate 必须为 startDate 的后一天,且时间设置为 \"T000000\"。\n如果无法获取一个或多个所需信息,请以空字符串响应。\n仅以 JSON 格式生成响应。不要包含任何额外的文本或说明,仅提供 JSON。以下是使用的格式:\n{\n\"startDate\": \"YYYYMMDDTHHMMSS\",\n\"endDate\": \"YYYYMMDDTHHMMSS\",\n\"summary\": \"日历事件摘要\",\n\"forceAllDay\": false\n\"attendees\": [attendee1@example.com,attendee2@example.com,attendee3@example.com]\n}\n以下是文本:“{%mail_text_body_or_selected%}”"
|
||||
"message": "从以下文本中提取生成日历事件所需的所有相关细节。提取的信息应包括:\n- 事件标题\n- 开始日期和时间(如果指定时区,则包括时区)\n- 结束日期和时间(如果指定时区,则包括时区)\n- 全天事件(如果提及)\n- 参与者 \n确保数据格式清晰且一致,以便可以直接用于创建日历事件。\n如果存在相对时间的引用,请注意邮件的日期和时间为“{%mail_datetime%}”。基于此参考计算开始日期和时间。如果计算出的开始日期和时间早于“{%current_datetime%}”,则使用“{%current_datetime%}”作为基准重新计算开始日期和时间。\n如果未指定持续时间,请将其设置为一小时。\n以下是参与者:{%author%}, {%recipients%}, {%cc_list%}。如有,请排除我的地址:{%account_email_address%}。\n如果无法获取一个或多个所需信息,请以空字符串响应。\n仅以 JSON 格式生成响应。不要包含任何额外的文本或说明,仅提供 JSON。以下是使用的格式:\n{\n\"startDate\": \"YYYYMMDDTHHMMSS\",\n\"endDate\": \"YYYYMMDDTHHMMSS\",\n\"summary\": \"日历事件摘要\",\n\"forceAllDay\": false\n\"attendees\": [attendee1@example.com,attendee2@example.com,attendee3@example.com]\n}\n以下是文本:“{%selected_text%}”"
|
||||
},
|
||||
"prefs_OptionText_get_calendar_event": {
|
||||
"message": "从所选文本添加新日历事件"
|
||||
|
|
@ -762,7 +807,7 @@
|
|||
"message": "在此页面中,您可以修改用于从选定文本获取日历事件的默认提示。"
|
||||
},
|
||||
"prefs_OptionText_add_tags_auto_force_existing": {
|
||||
"message": "在自动标记或使用上下文菜单时强制使用现有标签"
|
||||
"message": "自动标记时强制使用现有标签"
|
||||
},
|
||||
"prefs_OptionText_add_tags_auto_only_inbox_Info": {
|
||||
"message": "如果选中,AI 将仅向收件箱文件夹中收到的电子邮件添加标签。"
|
||||
|
|
@ -785,14 +830,17 @@
|
|||
"GetCalendarEvent_prompt_text_title": {
|
||||
"message": "当前提示词文字"
|
||||
},
|
||||
"prefs_OptionText_AdvancedPromptResponse_infoline2": {
|
||||
"prefs_OptionText_GetCalendarEvent_infoline2": {
|
||||
"message": "您可以根据需要更改提示,但从 AI 收到的响应必须采用默认提示中指定的 JSON 格式!"
|
||||
},
|
||||
"calendar_getting_data_error": {
|
||||
"message": "获取日历事件数据时出错"
|
||||
},
|
||||
"prefs_OptionText_spamfilter_infoline": {
|
||||
"message": "您可以根据需要更改提示词,但从 AI 收到的响应必须采用默认提示中指定的 JSON 格式!"
|
||||
},
|
||||
"prompt_spamfilter_full_text": {
|
||||
"message": "分析以下邮件并判断它是否为垃圾邮件。考虑因素包括可疑关键词、过多的宣传语言、误导性的主题行、索取个人信息的请求以及异常的发件人地址。\n提供一个从 0(非垃圾邮件)到 100(垃圾邮件)的值,并附上不超过 10 个单词的解释。\n如果消息数据缺失,将数值设为0(非垃圾邮件),并说明原因。\n仅以 JSON 格式生成响应。不要包含任何额外文本或解释;仅提供 JSON。以下是使用的格式:\n{\n\"explanation\": \"简要说明您的判断理由\",\n\"spamValue\": <0 到 100 的整数>\n}\n以下是邮件信息:\n发件人:“{%author%}”\n主题:“{%mail_subject%}”\nHTML 正文:“{%mail_html_body%}”"
|
||||
"message": "分析以下邮件并判断它是否为垃圾邮件。考虑因素包括可疑关键词、过多的宣传语言、误导性的主题行、索取个人信息的请求以及异常的发件人地址。\n提供一个从 0(非垃圾邮件)到 100(垃圾邮件)的值,并附上不超过 10 个单词的解释。\n仅以 JSON 格式生成响应。不要包含任何额外文本或解释;仅提供 JSON。以下是使用的格式:\n{\n\"spamValue\": <0 到 100 的整数>,\n\"explanation\": \"简要说明您的判断理由\",\n}\n以下是邮件信息:\n发件人:“{%author%}”\n主题:“{%mail_subject%}”\nHTML 正文:“{%mail_html_body%}”"
|
||||
},
|
||||
"prefs_OptionText_spamfilter_threshold_Info": {
|
||||
"message": "如果 AI 返回的值高于此阈值,电子邮件将被移至垃圾邮件文件夹。"
|
||||
|
|
@ -815,11 +863,29 @@
|
|||
"Report_Date": {
|
||||
"message": "报告日期"
|
||||
},
|
||||
"context_menu_mzta-add-tags": {
|
||||
"message": "添加标签"
|
||||
},
|
||||
"prefs_OptionText_add_tags_context_menu": {
|
||||
"message": "显示“添加标签”上下文菜单项"
|
||||
},
|
||||
"prefs_OptionText_spamfilter_context_menu": {
|
||||
"message": "显示“分析垃圾邮件”上下文菜单项"
|
||||
},
|
||||
"prefs_OptionText_spamfilter_context_menu_Info": {
|
||||
"message": "如果选中,则在邮件列表中右键单击电子邮件时,将显示“分析垃圾邮件”上下文菜单项。"
|
||||
},
|
||||
"context_menu_mzta-spamfilter": {
|
||||
"message": "分析垃圾邮件"
|
||||
},
|
||||
"prefs_OptionText_add_tags_context_menu_Info": {
|
||||
"message": "如果选中,“添加标签”上下文菜单项将在消息列表中右键单击电子邮件时显示。"
|
||||
},
|
||||
"noActiveCalendar": {
|
||||
"message": "未找到可编辑的日历!"
|
||||
"message": "未找到激活的日历!"
|
||||
},
|
||||
"customPrompts_form_label_use_diff_viewer": {
|
||||
"message": "启用文本差异查看器"
|
||||
"message": "启用差异查看器"
|
||||
},
|
||||
"get_calendar_event_prompt_prefs_title": {
|
||||
"message": "日历事件选项"
|
||||
|
|
@ -862,250 +928,5 @@
|
|||
},
|
||||
"Explanation": {
|
||||
"message": "说明"
|
||||
},
|
||||
"customPrompts_form_label_use_diff_viewer_title": {
|
||||
"message": "当操作设置为“替换文本”时,可以选择文本差异查看器。"
|
||||
},
|
||||
"prompt_reply_custom_command": {
|
||||
"message": "使用附加的提示词指令..."
|
||||
},
|
||||
"prompt_string": {
|
||||
"message": "提示词"
|
||||
},
|
||||
"prefs_OptionText_reply_type_Info": {
|
||||
"message": "此即回复电子邮件时默认采用的回复类型。您可在后续的回信对话框中另行选择其他选项。"
|
||||
},
|
||||
"prefs_OptionText_btnManageCustomDataPH": {
|
||||
"message": "管理您的数据占位符"
|
||||
},
|
||||
"OpenChatGPTTab_Info2": {
|
||||
"message": "从此处打开 ChatGPT Web 版将应用已配置好的模型、项目及自定义 GPT 相关设置。"
|
||||
},
|
||||
"placeholder_mail_headers": {
|
||||
"message": "邮件头"
|
||||
},
|
||||
"placeholder_selected_html": {
|
||||
"message": "已选中的 HTML"
|
||||
},
|
||||
"webchat_save_as_summary": {
|
||||
"message": "另存为摘要"
|
||||
},
|
||||
"prefs_storage_title": {
|
||||
"message": "存储"
|
||||
},
|
||||
"prefs_storage_info": {
|
||||
"message": "该存储用于保存每条消息的垃圾邮件分数、摘要和翻译。"
|
||||
},
|
||||
"prefs_storage_size": {
|
||||
"message": "存储容量"
|
||||
},
|
||||
"prefs_storage_clear_button": {
|
||||
"message": "清除存储"
|
||||
},
|
||||
"prefs_storage_clear_confirm": {
|
||||
"message": "您确定要清除所有已存储的数据(包含:摘要、垃圾邮件报告、翻译等)吗?此操作无法撤销。"
|
||||
},
|
||||
"prefs_storage_clear_done": {
|
||||
"message": "清除存储后显示的消息",
|
||||
"placeholders": {
|
||||
"count": {
|
||||
"content": "$1"
|
||||
}
|
||||
}
|
||||
},
|
||||
"prefsInfoDesc_7": {
|
||||
"message": "要使用 Google Gemini API,您需要一个 Google Gemini API 密钥,并且必须选择一个模型。"
|
||||
},
|
||||
"prefsInfoDesc_8": {
|
||||
"message": "要使用 Claude API,您需要一个 Anthropic Claude API 密钥,并且必须选择一个模型。"
|
||||
},
|
||||
"placeholder_mail_text_body_or_selected": {
|
||||
"message": "邮件正文或选定文本"
|
||||
},
|
||||
"placeholder_mail_html_body_or_selected": {
|
||||
"message": "邮件正文或选定的 HTML"
|
||||
},
|
||||
"prefs_OptionText_chatgpt_web_load_wait_time": {
|
||||
"message": "页面加载等待时间"
|
||||
},
|
||||
"prefs_OptionText_chatgpt_web_load_wait_time_info": {
|
||||
"message": "在加载附加内容之前等待 ChatGPT 页面加载的时间(以毫秒为单位)。默认值为 1000 毫秒。如果定义了自定义 GPT 或项目,则该值将额外增加 1000 毫秒。"
|
||||
},
|
||||
"sign_msg_as": {
|
||||
"message": "使用以下身份签名"
|
||||
},
|
||||
"prompt_reply_custom_command_full_text": {
|
||||
"message": "请回复以下邮件 \"{%mail_text_body%}\"。{%additional_text%}。仅回复所需文本,不要包含额外的评论或其他文字。"
|
||||
},
|
||||
"prompt_proofread_this": {
|
||||
"message": "校对这封邮件"
|
||||
},
|
||||
"prompt_proofread_this_full_text": {
|
||||
"message": "请校对以下电子邮件,并纠正任何拼写或语法错误。仅回复更正后的文本,不要包含任何额外评论或其他文字。\n\n“{%mail_typed_text%}”"
|
||||
},
|
||||
"reset": {
|
||||
"message": "重置"
|
||||
},
|
||||
"prefs_doc_title": {
|
||||
"message": "文档"
|
||||
},
|
||||
"prefs_doc_setup_guide": {
|
||||
"message": "设置指南"
|
||||
},
|
||||
"prefs_doc_custom_prompt_tutorial": {
|
||||
"message": "自定义提示词教程"
|
||||
},
|
||||
"prefs_doc_open_welcome": {
|
||||
"message": "打开欢迎页面"
|
||||
},
|
||||
"prompt_add_tags_force_lang": {
|
||||
"message": "标签必须用以下方式编写:"
|
||||
},
|
||||
"placeholder_mail_quoted_text": {
|
||||
"message": "邮件正文中的引用文本"
|
||||
},
|
||||
"prompt_get_calendar_event_from_clipboard": {
|
||||
"message": "从剪贴板添加日历事件"
|
||||
},
|
||||
"clipboard_read_error": {
|
||||
"message": "无法读取剪贴板。请检查权限。"
|
||||
},
|
||||
"clipboard_empty_error": {
|
||||
"message": "剪贴板为空。请先复制一些文本。"
|
||||
},
|
||||
"clipboard_permission_denied": {
|
||||
"message": "剪贴板权限被拒绝。请在设置中重新启用该功能以授予权限。"
|
||||
},
|
||||
"clipboard_permission_error": {
|
||||
"message": "请求剪贴板权限时出错,请重试。"
|
||||
},
|
||||
"prefs_OptionText_get_calendar_event_from_clipboard": {
|
||||
"message": "从剪贴板获取日历事件"
|
||||
},
|
||||
"prefs_OptionText_get_calendar_event_from_clipboard_Info": {
|
||||
"message": "显示一个额外的菜单项,用于根据剪贴板文本内容创建日历事件。"
|
||||
},
|
||||
"Summarize_prompt_prefs_title": {
|
||||
"message": "摘要选项"
|
||||
},
|
||||
"prompt_summarize": {
|
||||
"message": "总结这封或这些邮件"
|
||||
},
|
||||
"prompt_summarize_full_text": {
|
||||
"message": "请提供以下电子邮件的简明摘要。摘要应不超过 3-5 句话,并概括要点。请使用纯段落格式,不要使用项目符号、列表或 Markdown 格式。\n\n"
|
||||
},
|
||||
"prompt_summarize_email_template": {
|
||||
"message": "邮件模板摘要"
|
||||
},
|
||||
"prompt_summarize_email_template_full_text": {
|
||||
"message": "发件人:{%author%} \n收件人:{%recipients%} \n抄送:{%cc_list%} \n主题:{%mail_subject%} \n日期:{%mail_datetime%} \n附件: {%mail_attachments_info%} \n\n正文:\n{%mail_text_body%}"
|
||||
},
|
||||
"prompt_summarize_email_separator": {
|
||||
"message": "电子邮件分隔符"
|
||||
},
|
||||
"prompt_summarize_email_separator_full_text": {
|
||||
"message": "\n\n----------下一封邮件----------\n\n"
|
||||
},
|
||||
"prompt_get_task": {
|
||||
"message": "添加新任务"
|
||||
},
|
||||
"prompt_get_task_full_text": {
|
||||
"message": "从以下文本中提取生成任务所需的所有相关详细信息。提取的信息应包括:\n- 截止日期和时间(如果指定,包括时区)\n- 任务摘要\n- 开始日期和时间(如果指定,包括时区)\n- 确保数据格式清晰且一致,以便直接用于创建任务。\n如果存在相对时间引用,请认为电子邮件的日期和时间为“{%mail_datetime%}”。根据此参考计算开始日期和时间。如果计算出的开始日期和时间早于“{%current_datetime%}”,请使用“{%current_datetime%}”作为基准重新计算开始日期和时间。\n如果您无法获取一项或多项所需信息,请回复空字符串。\n仅以 JSON 格式生成响应。不要包含任何额外的文本或说明;仅提供 JSON。以下是要使用的格式:\n{\n\"InitialDate\": \"YYYYMMDDTHHMMSS\",\n\"dueDate\": \"YYYYMMDDTHHMMSS\",\n\"summary\": \"在此处填写任务摘要\"\n}\n如果没有关于日期的信息,请将其删除。\n以下是文本:“{%selected_text%}”"
|
||||
},
|
||||
"prefs_OptionText_get_task": {
|
||||
"message": "从选定文本添加新任务"
|
||||
},
|
||||
"prefs_OptionText_get_task_Info": {
|
||||
"message": "如果选中,则会在菜单中添加一个项目,以便从选定的文本获取任务信息。"
|
||||
},
|
||||
"get_task_prompt_prefs_title": {
|
||||
"message": "任务选项"
|
||||
},
|
||||
"prefs_OptionText_Summarize_infoline2": {
|
||||
"message": "您可以根据需要更改提示词,第一个字段是主提示词,第二个字段是单封邮件的模板。邮件列表将附加到主提示词中。邮件将由第三个字段中指定的间隔符分隔。"
|
||||
},
|
||||
"prefs_OptionText_Summarize_main_prompt": {
|
||||
"message": "针对所有选定电子邮件,描述要执行的任务的主要提示:"
|
||||
},
|
||||
"prefs_OptionText_Summarize_email_template": {
|
||||
"message": "单封邮件的模板:"
|
||||
},
|
||||
"prefs_OptionText_Summarize_email_separator": {
|
||||
"message": "电子邮件地址之间的分隔符:"
|
||||
},
|
||||
"prefs_OptionText_get_calendar_event_Sparks_wrong_version": {
|
||||
"message": "要使用日历事件和任务功能,请安装最新版本的 ThunderAI Sparks 插件。"
|
||||
},
|
||||
"GetTask_PageTitle": {
|
||||
"message": "管理任务设置"
|
||||
},
|
||||
"GetTask_info_default": {
|
||||
"message": "在此页面中,您可以修改用于从选定文本获取任务的默认提示。"
|
||||
},
|
||||
"prefs_OptionText_btnManageTaskInfo": {
|
||||
"message": "管理任务设置"
|
||||
},
|
||||
"task_getting_data_error": {
|
||||
"message": "获取任务数据时出错"
|
||||
},
|
||||
"task_opening_dialog_error": {
|
||||
"message": "打开任务对话框时出错"
|
||||
},
|
||||
"no_valid_data_received": {
|
||||
"message": "未收到来自 AI 的有效数据。"
|
||||
},
|
||||
"prefs_OptionText_add_tags_auto_Info2": {
|
||||
"message": "请在页面底部选择要为其激活此功能的帐户。"
|
||||
},
|
||||
"prefs_OptionText_add_tags_auto_uselist": {
|
||||
"message": "仅使用这些标签"
|
||||
},
|
||||
"prefs_OptionText_add_tags_auto_uselist_Info": {
|
||||
"message": "如果选中此项,AI 将仅添加以下列表中的标签。"
|
||||
},
|
||||
"prefs_OptionText_add_tags_auto_uselist_list_Info": {
|
||||
"message": "列表中必须至少包含一个标签。每行添加一个标签,标签之间用逗号分隔。"
|
||||
},
|
||||
"prompt_add_tags_use_list": {
|
||||
"message": "仅使用此逗号分隔列表中的标签"
|
||||
},
|
||||
"prefs_OptionText_add_tags_use_specific_integration_Info": {
|
||||
"message": "如果选中此项,则无论在 ThunderAI 选项页面中选择哪个模型和 API,都将使用下面指定的模型和 API 向电子邮件添加标签。"
|
||||
},
|
||||
"SpamFilter_skip_addresses_infoline2": {
|
||||
"message": "每行添加一个电子邮件地址,或用逗号分隔。"
|
||||
},
|
||||
"spamfilter_skip_addresses_explanation": {
|
||||
"message": "发件人已在反垃圾邮件跳过列表中。"
|
||||
},
|
||||
"Valid": {
|
||||
"message": "有效的"
|
||||
},
|
||||
"hyprland_warning": {
|
||||
"message": "如果您在打开 AI 聊天窗口时遇到问题,请尝试将高度和宽度值设置为 0。此问题可能在某些 Linux 环境下出现,例如在使用 Hyprland 时。"
|
||||
},
|
||||
"remember_CORS": {
|
||||
"message": "记住,您需要在服务器上设置 CORS 设置!"
|
||||
},
|
||||
"maybe_CORS_openai_comp": {
|
||||
"message": "使用 OpenAI 兼容 API 可能需要在服务器上设置 CORS 设置。"
|
||||
},
|
||||
"CORS_alternative_1": {
|
||||
"message": "CORS设置有问题吗?"
|
||||
},
|
||||
"CORS_alternative_2_new": {
|
||||
"message": "点击下方按钮授予当前主机权限,以避免任何 CORS 问题。"
|
||||
},
|
||||
"CORS_give_host_perm": {
|
||||
"message": "授予当前主机权限"
|
||||
},
|
||||
"CORS_localhost_warn": {
|
||||
"message": "如果您使用 localhost 或 127.0.0.1,因为 AI 服务器托管在您的 PC 上,则需要 <all_urls> 权限。"
|
||||
},
|
||||
"prefs_OptionText_composing_plain_text": {
|
||||
"message": "以纯文本编写"
|
||||
},
|
||||
"prefs_OptionText_composing_plain_text_Info": {
|
||||
"message": "如果您以纯文本格式编写电子邮件,请选中此选项。"
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
/*
|
||||
* ThunderAI [https://micz.it/thunderbird-addon-thunderai/]
|
||||
* Copyright (C) 2024 - 2026 Mic (m@micz.it)
|
||||
* Copyright (C) 2024 - 2025 Mic (m@micz.it)
|
||||
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
|
|
@ -20,18 +20,13 @@
|
|||
* The original code has been released under the Apache License, Version 2.0.
|
||||
*/
|
||||
|
||||
import { prefs_default, integration_options_config } from '../options/mzta-options-default.js';
|
||||
import { placeholdersUtils } from '../js/mzta-placeholders.js';
|
||||
import { getAPIsInitMessageString, convertNewlinesToBr } from '../js/mzta-utils.js';
|
||||
import { loadPrompt } from '../js/mzta-prompts.js';
|
||||
|
||||
// Get the LLM to be used
|
||||
const urlParams = new URLSearchParams(window.location.search);
|
||||
const llm = urlParams.get('llm');
|
||||
const call_id = urlParams.get('call_id');
|
||||
const ph_def_val = urlParams.get('ph_def_val');
|
||||
const prompt_id = urlParams.get('prompt_id');
|
||||
const prompt_name = urlParams.get('prompt_name');
|
||||
|
||||
// Data received from the user
|
||||
let promptData = null;
|
||||
|
|
@ -47,192 +42,83 @@ const messagesArea = document.querySelector('messages-area');
|
|||
// The controller wires up all the components and workers together,
|
||||
// managing the dependencies. A kind of "DI" class.
|
||||
let worker = null;
|
||||
const integration = llm.replace('_api', '');
|
||||
const worker_path_map = {
|
||||
chatgpt: '../js/workers/model-worker-openai_responses.js',
|
||||
google_gemini: '../js/workers/model-worker-google_gemini.js',
|
||||
ollama: '../js/workers/model-worker-ollama.js',
|
||||
openai_comp: '../js/workers/model-worker-openai_comp.js',
|
||||
anthropic: '../js/workers/model-worker-anthropic.js',
|
||||
};
|
||||
|
||||
const worker_path = worker_path_map[integration];
|
||||
|
||||
if (worker_path) {
|
||||
worker = new Worker(worker_path, { type: 'module' });
|
||||
} else {
|
||||
console.error('[ThunderAI] API WebChat Unknown LLM type:', llm);
|
||||
switch (llm) {
|
||||
case "chatgpt_api":
|
||||
worker = new Worker('../js/workers/model-worker-openai.js', { type: 'module' });
|
||||
break;
|
||||
case "google_gemini_api":
|
||||
worker = new Worker('../js/workers/model-worker-google_gemini.js', { type: 'module' });
|
||||
break;
|
||||
case "ollama_api":
|
||||
worker = new Worker('../js/workers/model-worker-ollama.js', { type: 'module' });
|
||||
break;
|
||||
case "openai_comp_api":
|
||||
worker = new Worker('../js/workers/model-worker-openai_comp.js', { type: 'module' });
|
||||
break;
|
||||
}
|
||||
|
||||
if (worker) {
|
||||
messagesArea.init(worker);
|
||||
messageInput.init(worker);
|
||||
messageInput.setMessagesArea(messagesArea);
|
||||
messagesArea.init(worker);
|
||||
|
||||
if (integration_options_config[integration]) {
|
||||
const integration_prefix = integration;
|
||||
const options_config = integration_options_config[integration];
|
||||
|
||||
let prefsToGet = { do_debug: prefs_default.do_debug, hide_thinking: prefs_default.hide_thinking };
|
||||
for (const key in options_config) {
|
||||
prefsToGet[`${integration_prefix}_${key}`] = prefs_default[`${integration_prefix}_${key}`];
|
||||
}
|
||||
if (integration === 'openai_comp') {
|
||||
prefsToGet.openai_comp_chat_name = prefs_default.openai_comp_chat_name;
|
||||
}
|
||||
|
||||
let prefs_api = await browser.storage.sync.get(prefsToGet);
|
||||
|
||||
if (prompt_id) {
|
||||
try {
|
||||
const prompt = await loadPrompt(prompt_id);
|
||||
if (prompt && prompt.api_type === llm) {
|
||||
for (const key in options_config) {
|
||||
const prefKey = `${integration_prefix}_${key}`;
|
||||
if (prompt[prefKey] !== undefined) {
|
||||
prefs_api[prefKey] = prompt[prefKey];
|
||||
}
|
||||
}
|
||||
}
|
||||
} catch (e) {
|
||||
console.error("[ThunderAI] Error loading prompt settings:", e);
|
||||
}
|
||||
}
|
||||
// Initialize the messageInput component and pass the worker to it
|
||||
messageInput.init(worker);
|
||||
messageInput.setMessagesArea(messagesArea);
|
||||
|
||||
switch (llm) {
|
||||
case "chatgpt_api": {
|
||||
let prefs_api = await browser.storage.sync.get({chatgpt_api_key: '', chatgpt_model: '', chatgpt_developer_messages:'', do_debug: false});
|
||||
let i18nStrings = {};
|
||||
const i18n_msg_key = integration === 'openai_comp' ? 'OpenAIComp_api_request_failed' : `${integration}_api_request_failed`;
|
||||
i18nStrings[i18n_msg_key] = browser.i18n.getMessage(i18n_msg_key);
|
||||
i18nStrings["chatgpt_api_request_failed"] = browser.i18n.getMessage('chatgpt_api_request_failed');
|
||||
i18nStrings["error_connection_interrupted"] = browser.i18n.getMessage('error_connection_interrupted');
|
||||
|
||||
messageInput.setModel(prefs_api[`${integration_prefix}_model`]);
|
||||
|
||||
let llmName = "API";
|
||||
switch(integration) {
|
||||
case 'chatgpt': llmName = "ChatGPT"; break;
|
||||
case 'google_gemini': llmName = "Google Gemini"; break;
|
||||
case 'ollama': llmName = "Ollama Local"; break;
|
||||
case 'openai_comp': llmName = prefs_api.openai_comp_chat_name || "OpenAI Comp"; break;
|
||||
case 'anthropic': llmName = "Claude"; break;
|
||||
}
|
||||
messagesArea.setLLMName(llmName);
|
||||
messagesArea.setHideThinking(!!prefs_api.hide_thinking);
|
||||
|
||||
document.title += " [" + llmName + " | " + decodeURIComponent(prompt_name) + "]";
|
||||
|
||||
document.title += " [" + llmName + " | " + decodeURIComponent(prompt_name) + "]";
|
||||
|
||||
let workerInitMessage = {
|
||||
type: 'init',
|
||||
do_debug: prefs_api.do_debug,
|
||||
i18nStrings: i18nStrings,
|
||||
};
|
||||
|
||||
for (const key in options_config) {
|
||||
const prefKey = `${integration_prefix}_${key}`;
|
||||
workerInitMessage[prefKey] = prefs_api[prefKey];
|
||||
}
|
||||
|
||||
worker.postMessage(workerInitMessage);
|
||||
|
||||
const additional_messages_config = {
|
||||
chatgpt: [
|
||||
{ key: 'store', labelKey: 'ChatGPT_chatgpt_api_store', type: 'boolean' },
|
||||
{ key: 'developer_messages', labelKey: 'ChatGPT_Developer_Messages', type: 'string' },
|
||||
{ key: 'temperature', labelKey: 'prefs_api_temperature', type: 'string' }
|
||||
],
|
||||
google_gemini: [
|
||||
{ key: 'system_instruction', labelKey: 'GoogleGemini_SystemInstruction', type: 'string' },
|
||||
{ key: 'temperature', labelKey: 'prefs_api_temperature', type: 'string' },
|
||||
{ key: 'thinking_budget', labelKey: 'prefs_google_gemini_thinking_budget', type: 'string' }
|
||||
],
|
||||
ollama: [
|
||||
{ key: 'think', labelKey: 'prefs_ollama_think', type: 'boolean' },
|
||||
{ key: 'temperature', labelKey: 'prefs_api_temperature', type: 'string' },
|
||||
{ key: 'num_ctx', labelKey: 'prefs_ollama_num_ctx', type: 'number_gt_zero' }
|
||||
],
|
||||
openai_comp: [
|
||||
{ key: 'temperature', labelKey: 'prefs_api_temperature', type: 'string' }
|
||||
],
|
||||
anthropic: [
|
||||
{ key: 'system_prompt', labelKey: 'Anthropic_System_Prompt', type: 'string' },
|
||||
{ key: 'max_tokens', labelKey: 'prefs_OptionText_anthropic_max_tokens', type: 'number_gt_zero' },
|
||||
{ key: 'temperature', labelKey: 'prefs_api_temperature', type: 'string' },
|
||||
{ key: 'extended_thinking_budget', labelKey: 'prefs_OptionText_anthropic_extended_thinking_budget', type: 'number_gt_zero' }
|
||||
]
|
||||
};
|
||||
|
||||
const getAdditionalMessages = (integration, prefs) => {
|
||||
const messages = [];
|
||||
const config = additional_messages_config[integration];
|
||||
if (!config) return messages;
|
||||
|
||||
for (const item of config) {
|
||||
const prefKey = `${integration}_${item.key}`;
|
||||
const value = prefs[prefKey];
|
||||
|
||||
if (value !== undefined && value !== null && value !== '') {
|
||||
let displayValue;
|
||||
let shouldAdd = false;
|
||||
|
||||
switch (item.type) {
|
||||
case 'boolean':
|
||||
displayValue = value ? 'Yes' : 'No';
|
||||
shouldAdd = true;
|
||||
break;
|
||||
case 'string':
|
||||
if (value.length > 0) {
|
||||
displayValue = value;
|
||||
shouldAdd = true;
|
||||
}
|
||||
break;
|
||||
case 'number_gt_zero':
|
||||
if (value > 0) {
|
||||
displayValue = value;
|
||||
shouldAdd = true;
|
||||
}
|
||||
break;
|
||||
}
|
||||
if (shouldAdd) {
|
||||
messages.push({ label: browser.i18n.getMessage(item.labelKey), value: displayValue });
|
||||
}
|
||||
}
|
||||
}
|
||||
return messages;
|
||||
};
|
||||
|
||||
let additional_text_elements = [];
|
||||
additional_text_elements.push({label: browser.i18n.getMessage("prompt_string"), value: '[' + prompt_id + '] ' + decodeURIComponent(prompt_name)});
|
||||
additional_text_elements.push(...getAdditionalMessages(integration, prefs_api));
|
||||
|
||||
const api_strings = {
|
||||
chatgpt: "ChatGPT API",
|
||||
google_gemini: "Google Gemini API",
|
||||
ollama: "Ollama API",
|
||||
openai_comp: "OpenAI Compatible API",
|
||||
anthropic: "Claude API"
|
||||
};
|
||||
|
||||
messagesArea.appendUserMessage(getAPIsInitMessageString({
|
||||
api_string: api_strings[integration],
|
||||
model_string: prefs_api[`${integration_prefix}_model`],
|
||||
host_string: prefs_api[`${integration_prefix}_host`],
|
||||
version_string: prefs_api[`${integration_prefix}_version`],
|
||||
additional_messages: additional_text_elements
|
||||
}), "info");
|
||||
|
||||
//console.log(`>>>>>>>>>>>>> command: ${llm}_ready_${call_id}`,)
|
||||
|
||||
browser.runtime.sendMessage({
|
||||
command: `${llm}_ready_${call_id}`,
|
||||
window_id: (await browser.windows.getCurrent()).id
|
||||
});
|
||||
messageInput.setModel(prefs_api.chatgpt_model);
|
||||
messagesArea.setLLMName("ChatGPT");
|
||||
worker.postMessage({ type: 'init', chatgpt_api_key: prefs_api.chatgpt_api_key, chatgpt_model: prefs_api.chatgpt_model, chatgpt_developer_messages: prefs_api.chatgpt_developer_messages, do_debug: prefs_api.do_debug, i18nStrings: i18nStrings});
|
||||
messagesArea.appendUserMessage(browser.i18n.getMessage("chagpt_api_connecting") + " " +browser.i18n.getMessage("AndModel") + " \"" + prefs_api.chatgpt_model + "\"...", "info");
|
||||
browser.runtime.sendMessage({command: "openai_api_ready_" + call_id, window_id: (await browser.windows.getCurrent()).id});
|
||||
break;
|
||||
}
|
||||
case "google_gemini_api": {
|
||||
let prefs_api = await browser.storage.sync.get({google_gemini_api_key: '', google_gemini_model: '', google_gemini_system_instruction: '', do_debug: false});
|
||||
let i18nStrings = {};
|
||||
i18nStrings["google_gemini_api_request_failed"] = browser.i18n.getMessage('google_gemini_api_request_failed');
|
||||
i18nStrings["error_connection_interrupted"] = browser.i18n.getMessage('error_connection_interrupted');
|
||||
messageInput.setModel(prefs_api.google_gemini_model);
|
||||
messagesArea.setLLMName("Google Gemini");
|
||||
worker.postMessage({ type: 'init', google_gemini_api_key: prefs_api.google_gemini_api_key, google_gemini_model: prefs_api.google_gemini_model, google_gemini_system_instruction: prefs_api.google_gemini_system_instruction, do_debug: prefs_api.do_debug, i18nStrings: i18nStrings});
|
||||
messagesArea.appendUserMessage(browser.i18n.getMessage("google_gemini_api_connecting") + " " +browser.i18n.getMessage("AndModel") + " \"" + prefs_api.google_gemini_model + "\"...", "info");
|
||||
browser.runtime.sendMessage({command: "google_gemini_api_ready_" + call_id, window_id: (await browser.windows.getCurrent()).id});
|
||||
break;
|
||||
}
|
||||
case "ollama_api": {
|
||||
let prefs_api = await browser.storage.sync.get({ollama_host: '', ollama_model: '', do_debug: false});
|
||||
let i18nStrings = {};
|
||||
i18nStrings["ollama_api_request_failed"] = browser.i18n.getMessage('ollama_api_request_failed');
|
||||
i18nStrings["error_connection_interrupted"] = browser.i18n.getMessage('error_connection_interrupted');
|
||||
messageInput.setModel(prefs_api.ollama_model);
|
||||
messagesArea.setLLMName("Ollama Local");
|
||||
worker.postMessage({ type: 'init', ollama_host: prefs_api.ollama_host, ollama_model: prefs_api.ollama_model, do_debug: prefs_api.do_debug, i18nStrings: i18nStrings});
|
||||
browser.runtime.sendMessage({command: "ollama_api_ready_" + call_id, window_id: (await browser.windows.getCurrent()).id});
|
||||
messagesArea.appendUserMessage(browser.i18n.getMessage("ollama_api_connecting") + " \"" + prefs_api.ollama_host + "\" " +browser.i18n.getMessage("AndModel") + " \"" + prefs_api.ollama_model + "\"...", "info");
|
||||
break;
|
||||
}
|
||||
case "openai_comp_api": {
|
||||
let prefs_api = await browser.storage.sync.get({openai_comp_host: '', openai_comp_model: '', openai_comp_api_key: '', openai_comp_use_v1: true, openai_comp_chat_name: '', do_debug: false});
|
||||
let i18nStrings = {};
|
||||
i18nStrings["OpenAIComp_api_request_failed"] = browser.i18n.getMessage('OpenAIComp_api_request_failed');
|
||||
i18nStrings["error_connection_interrupted"] = browser.i18n.getMessage('error_connection_interrupted');
|
||||
messageInput.setModel(prefs_api.openai_comp_model);
|
||||
messagesArea.setLLMName(prefs_api.openai_comp_chat_name);
|
||||
worker.postMessage({ type: 'init', openai_comp_host: prefs_api.openai_comp_host, openai_comp_model: prefs_api.openai_comp_model, openai_comp_api_key: prefs_api.openai_comp_api_key, openai_comp_use_v1: prefs_api.openai_comp_use_v1, do_debug: prefs_api.do_debug, i18nStrings: i18nStrings});
|
||||
messagesArea.appendUserMessage(browser.i18n.getMessage("OpenAIComp_api_connecting") + " \"" + prefs_api.openai_comp_host + "\" " +browser.i18n.getMessage("AndModel") + " \"" + prefs_api.openai_comp_model + "\"...", "info");
|
||||
browser.runtime.sendMessage({command: "openai_comp_api_ready_" + call_id, window_id: (await browser.windows.getCurrent()).id});
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
//let prefs_ph = await browser.storage.sync.get({placeholders_use_default_value: false});
|
||||
|
||||
// Event listeners for worker messages
|
||||
worker.onmessage = async function(event) {
|
||||
worker.onmessage = function(event) {
|
||||
const { type, payload } = event.data;
|
||||
switch (type) {
|
||||
case 'messageSent':
|
||||
|
|
@ -242,17 +128,13 @@ worker.onmessage = async function(event) {
|
|||
messagesArea.handleNewToken(payload.token);
|
||||
messageInput.setStatusMessage(browser.i18n.getMessage("apiwebchat_receiving_data") + '...');
|
||||
break;
|
||||
case 'newThinkingToken':
|
||||
messagesArea.handleNewThinkingToken(payload.token);
|
||||
messageInput.setStatusMessage(browser.i18n.getMessage("apiwebchat_receiving_data") + '...');
|
||||
break;
|
||||
case 'tokensDone':
|
||||
await messagesArea.handleTokensDone(promptData);
|
||||
messagesArea.handleTokensDone(promptData);
|
||||
messageInput.enableInput();
|
||||
break;
|
||||
case 'error':
|
||||
messagesArea.appendBotMessage(payload,'error');
|
||||
messageInput.enableInput(false);
|
||||
messageInput.enableInput();
|
||||
break;
|
||||
default:
|
||||
console.error('[ThunderAI] Unknown event type from API worker:', type);
|
||||
|
|
@ -266,46 +148,34 @@ browser.runtime.onMessage.addListener((message, sender, sendResponse) => {
|
|||
promptData = message;
|
||||
//send the received prompt to the llm api
|
||||
if(message.do_custom_text=="1") {
|
||||
messageInput._showCustomTextField(message.prompt_info?.custom_text_array);
|
||||
messageInput._showCustomTextField();
|
||||
}else{
|
||||
sendPrompt(message);
|
||||
}
|
||||
break;
|
||||
case 'api_send_custom_text':
|
||||
let userInput = message.custom_text; // From version 4.0.0 this is an array
|
||||
let userInput = message.custom_text;
|
||||
if(userInput !== null) {
|
||||
if(!placeholdersUtils.hasPlaceholder(promptData.prompt, 'additional_text')){
|
||||
// no additional_text placeholder, do as usual
|
||||
const inputText = Array.isArray(userInput) ? userInput.map(obj => obj.custom_text).join(' ') : userInput;
|
||||
promptData.prompt += " " + inputText;
|
||||
promptData.prompt += " " + userInput;
|
||||
}else{
|
||||
// we have the additional_text placeholder, do the magic!
|
||||
let finalSubs = {};
|
||||
|
||||
if (Array.isArray(userInput)) {
|
||||
userInput.forEach(obj => {
|
||||
finalSubs[obj.placeholder.replace(/^{%|%}$/g, '').trim()] = obj.custom_text;
|
||||
});
|
||||
} else {
|
||||
finalSubs["additional_text"] = userInput;
|
||||
}
|
||||
promptData.prompt = placeholdersUtils.replacePlaceholders({
|
||||
text: promptData.prompt,
|
||||
replacements: finalSubs,
|
||||
use_default_value: ph_def_val==='1'
|
||||
})
|
||||
finalSubs["additional_text"] = userInput;
|
||||
promptData.prompt = placeholdersUtils.replacePlaceholders(promptData.prompt, finalSubs, ph_def_val==='1')
|
||||
}
|
||||
sendPrompt(promptData);
|
||||
}
|
||||
break;
|
||||
case "api_error":
|
||||
messagesArea.appendBotMessage(message.error,'error');
|
||||
messageInput.enableInput(false);
|
||||
messageInput.enableInput();
|
||||
break;
|
||||
}
|
||||
});
|
||||
|
||||
function sendPrompt(message){
|
||||
messageInput._setMessageInputValue(convertNewlinesToBr(message.prompt));
|
||||
messageInput._setMessageInputValue(message.prompt);
|
||||
messageInput._handleNewChatMessage();
|
||||
}
|
||||
}
|
||||
|
|
@ -3,6 +3,7 @@
|
|||
<head>
|
||||
<!-- Other meta tags and stylesheets -->
|
||||
<link rel="stylesheet" type="text/css" href="styles.css">
|
||||
|
||||
</head>
|
||||
<body>
|
||||
<!-- Use the custom tags directly -->
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
/*
|
||||
* ThunderAI [https://micz.it/thunderbird-addon-thunderai/]
|
||||
* Copyright (C) 2024 - 2026 Mic (m@micz.it)
|
||||
* Copyright (C) 2024 - 2025 Mic (m@micz.it)
|
||||
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
|
|
@ -49,7 +49,6 @@ messagesInputStyle.textContent = `
|
|||
height: 36px;
|
||||
cursor: pointer;
|
||||
border-radius: 10px;
|
||||
border: 1px outset buttonface;
|
||||
}
|
||||
#stopButton {
|
||||
width: 44px;
|
||||
|
|
@ -66,44 +65,15 @@ messagesInputStyle.textContent = `
|
|||
border-radius: 5px;
|
||||
padding: 5px;
|
||||
background: #F2F2F2;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 3px;
|
||||
}
|
||||
#statusLoggerImg{
|
||||
display: none;
|
||||
vertical-align: middle;
|
||||
}
|
||||
#statusLoggerText{
|
||||
font-weight: 600;
|
||||
}
|
||||
#statusLogger.status-working{
|
||||
border-color: #2196F3;
|
||||
background: #E3F2FD;
|
||||
color: #1565C0;
|
||||
}
|
||||
#statusLogger.status-done{
|
||||
border-color: #4CAF50;
|
||||
background: #E8F5E9;
|
||||
color: #2E7D32;
|
||||
}
|
||||
@keyframes statusFadeOut {
|
||||
from { opacity: 1; }
|
||||
to { opacity: 0; }
|
||||
}
|
||||
#statusLogger.status-fadeout{
|
||||
animation: statusFadeOut 0.5s ease-out forwards;
|
||||
}
|
||||
#mzta-custom_text{
|
||||
padding:10px;
|
||||
width:50%;
|
||||
min-width:300px;
|
||||
width:auto;
|
||||
max-width:80%;
|
||||
height:auto;
|
||||
max-height:80%;
|
||||
border-radius:5px;
|
||||
overflow-y:auto;
|
||||
overflow-x:hidden;
|
||||
overflow:auto;
|
||||
position:fixed;
|
||||
top:50%;
|
||||
left:50%;
|
||||
|
|
@ -113,18 +83,15 @@ messagesInputStyle.textContent = `
|
|||
background:#333;
|
||||
color:white;
|
||||
border:3px solid white;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
#mzta-custom_loading{
|
||||
height:50px;display:none;
|
||||
}
|
||||
#mzta-custom_textarea{
|
||||
color:black;
|
||||
padding:5px;
|
||||
padding:1px;
|
||||
font-size:15px;
|
||||
width:100%;
|
||||
box-sizing: border-box;
|
||||
resize: vertical;
|
||||
}
|
||||
#mzta-custom_info{
|
||||
text-align:center;
|
||||
|
|
@ -132,19 +99,6 @@ messagesInputStyle.textContent = `
|
|||
padding-bottom:10px;
|
||||
font-size:15px;
|
||||
}
|
||||
#mzta-custom_info span{
|
||||
font-size:0.8em;
|
||||
}
|
||||
#mzta-custom_step{
|
||||
position: absolute;
|
||||
bottom: 5px;
|
||||
right: 10px;
|
||||
font-size: 12px;
|
||||
color: #ccc;
|
||||
}
|
||||
#mzta-custom_btn{
|
||||
margin-top:7px;
|
||||
}
|
||||
@media (prefers-color-scheme: dark) {
|
||||
#messageInputField {
|
||||
background-color: #303030;
|
||||
|
|
@ -154,16 +108,6 @@ messagesInputStyle.textContent = `
|
|||
background: #212121;
|
||||
color: #ffffff;
|
||||
}
|
||||
#statusLogger.status-working{
|
||||
border-color: #64B5F6;
|
||||
background: #1A3A5C;
|
||||
color: #90CAF9;
|
||||
}
|
||||
#statusLogger.status-done{
|
||||
border-color: #81C784;
|
||||
background: #1B3D1E;
|
||||
color: #A5D6A7;
|
||||
}
|
||||
}
|
||||
`;
|
||||
messageInputTemplate.content.appendChild(messagesInputStyle);
|
||||
|
|
@ -220,14 +164,8 @@ messageInputTemplate.content.appendChild(stopButton);
|
|||
|
||||
const statusLogger = document.createElement('div');
|
||||
statusLogger.id = 'statusLogger';
|
||||
statusLogger.textContent = '';
|
||||
statusLogger.style.display = 'none';
|
||||
const statusLoggerImg = document.createElement('img');
|
||||
statusLoggerImg.id = 'statusLoggerImg';
|
||||
statusLoggerImg.src = browser.runtime.getURL('/images/mzta-loading.svg');
|
||||
statusLogger.appendChild(statusLoggerImg);
|
||||
const statusLoggerText = document.createElement('span');
|
||||
statusLoggerText.id = 'statusLoggerText';
|
||||
statusLogger.appendChild(statusLoggerText);
|
||||
messageInputTemplate.content.appendChild(statusLogger);
|
||||
|
||||
//div per custom text
|
||||
|
|
@ -239,7 +177,6 @@ customInfo.textContent = browser.i18n.getMessage("chatgpt_win_custom_text");
|
|||
customDiv.appendChild(customInfo);
|
||||
const customTextArea = document.createElement('textarea');
|
||||
customTextArea.id = 'mzta-custom_textarea';
|
||||
customTextArea.rows = 5;
|
||||
customDiv.appendChild(customTextArea);
|
||||
const customLoading = document.createElement('img');
|
||||
customLoading.src = browser.runtime.getURL("/images/loading.gif");
|
||||
|
|
@ -250,17 +187,11 @@ customBtn.id = 'mzta-custom_btn';
|
|||
customBtn.textContent = browser.i18n.getMessage("chatgpt_win_send");
|
||||
customBtn.classList.add('mzta-btn');
|
||||
customDiv.appendChild(customBtn);
|
||||
const customStep = document.createElement('div');
|
||||
customStep.id = 'mzta-custom_step';
|
||||
customDiv.appendChild(customStep);
|
||||
messageInputTemplate.content.appendChild(customDiv);
|
||||
|
||||
class MessageInput extends HTMLElement {
|
||||
|
||||
model = '';
|
||||
_doneTimeout = null;
|
||||
_customTextArray = [];
|
||||
_currentCustomTextIndex = 0;
|
||||
|
||||
constructor() {
|
||||
super();
|
||||
|
|
@ -271,8 +202,6 @@ class MessageInput extends HTMLElement {
|
|||
this._sendButton = shadowRoot.querySelector('#sendButton');
|
||||
this._stopButton = shadowRoot.querySelector('#stopButton');
|
||||
this._statusLogger = shadowRoot.querySelector('#statusLogger');
|
||||
this._statusLoggerImg = shadowRoot.querySelector('#statusLoggerImg');
|
||||
this._statusLoggerText = shadowRoot.querySelector('#statusLoggerText');
|
||||
|
||||
this._messageInputField.addEventListener('keydown', this._handleKeyDown.bind(this));
|
||||
this._sendButton.addEventListener('click', this._handleClick.bind(this));
|
||||
|
|
@ -282,14 +211,8 @@ class MessageInput extends HTMLElement {
|
|||
this._customTextArea = shadowRoot.querySelector('#mzta-custom_textarea');
|
||||
this._customLoading = shadowRoot.querySelector('#mzta-custom_loading');
|
||||
this._customBtn = shadowRoot.querySelector('#mzta-custom_btn');
|
||||
this._customStep = shadowRoot.querySelector('#mzta-custom_step');
|
||||
this._customBtn.addEventListener("click", () => { this._customTextBtnClick({customBtn:this._customBtn,customLoading:this._customLoading,customDiv:this._customText}) });
|
||||
this._customTextArea.addEventListener("keydown", (event) => {
|
||||
if (event.key === "Enter" && !event.shiftKey) {
|
||||
event.preventDefault();
|
||||
this._customTextBtnClick({customBtn:this._customBtn,customLoading:this._customLoading,customDiv:this._customText});
|
||||
}
|
||||
});
|
||||
this._customTextArea.addEventListener("keydown", (event) => { if(event.code == "Enter" && event.ctrlKey) this._customTextBtnClick({customBtn:this._customBtn,customLoading:this._customLoading,customDiv:this._customText}) });
|
||||
}
|
||||
|
||||
connectedCallback() {
|
||||
|
|
@ -316,7 +239,7 @@ class MessageInput extends HTMLElement {
|
|||
this._messageInputField.value = '';
|
||||
}
|
||||
|
||||
enableInput(showDone = true) {
|
||||
enableInput() {
|
||||
// console.log("[ThunderAI] enableInput");
|
||||
this._messageInputField.value = '';
|
||||
this._messageInputField.removeAttribute('disabled');
|
||||
|
|
@ -325,56 +248,20 @@ class MessageInput extends HTMLElement {
|
|||
this._stopButton.setAttribute('disabled', 'disabled');
|
||||
this._stopButton.style.display = 'none';
|
||||
this._stopButton.title = browser.i18n.getMessage("chagpt_api_send_button") + ": " + this.model;
|
||||
if (showDone) {
|
||||
this.showDoneStatus();
|
||||
} else {
|
||||
this.hideStatusMessage();
|
||||
this.setStatusMessage('');
|
||||
}
|
||||
this.hideStatusMessage();
|
||||
this.setStatusMessage('');
|
||||
}
|
||||
|
||||
setStatusMessage(message) {
|
||||
this._statusLoggerText.textContent = message;
|
||||
this._statusLogger.textContent = message;
|
||||
}
|
||||
|
||||
showStatusMessage(state = 'working') {
|
||||
if (this._doneTimeout) {
|
||||
clearTimeout(this._doneTimeout);
|
||||
this._doneTimeout = null;
|
||||
}
|
||||
this._setStatusClass('status-' + state);
|
||||
this._statusLogger.style.display = 'flex';
|
||||
showStatusMessage() {
|
||||
this._statusLogger.style.display = 'block';
|
||||
}
|
||||
|
||||
hideStatusMessage() {
|
||||
this._statusLogger.style.display = 'none';
|
||||
this._statusLoggerImg.style.display = 'none';
|
||||
this._setStatusClass(null);
|
||||
}
|
||||
|
||||
_setStatusClass(className) {
|
||||
this._statusLogger.classList.remove('status-working', 'status-done', 'status-fadeout');
|
||||
if (className) {
|
||||
this._statusLogger.classList.add(className);
|
||||
}
|
||||
}
|
||||
|
||||
showDoneStatus() {
|
||||
if (this._doneTimeout) {
|
||||
clearTimeout(this._doneTimeout);
|
||||
}
|
||||
this._statusLoggerImg.style.display = 'none';
|
||||
this.setStatusMessage(browser.i18n.getMessage('apiwebchat_done'));
|
||||
this._setStatusClass('status-done');
|
||||
this._statusLogger.style.display = 'flex';
|
||||
|
||||
this._doneTimeout = setTimeout(() => {
|
||||
this._statusLogger.classList.add('status-fadeout');
|
||||
this._doneTimeout = setTimeout(() => {
|
||||
this.hideStatusMessage();
|
||||
this.setStatusMessage('');
|
||||
}, 500);
|
||||
}, 1500);
|
||||
}
|
||||
|
||||
_handleKeyDown(event) {
|
||||
|
|
@ -409,8 +296,7 @@ class MessageInput extends HTMLElement {
|
|||
if (this.messagesAreaComponent) {
|
||||
this.messagesAreaComponent.appendUserMessage(messageContent);
|
||||
}
|
||||
this.setStatusMessage(browser.i18n.getMessage('WaitingServerResponse') + '...');
|
||||
this._statusLoggerImg.style.display = 'inline';
|
||||
this.setStatusMessage(browser.i18n.getMessage('WaitingServerReponse') + '...');
|
||||
this.showStatusMessage();
|
||||
this.worker.postMessage({ type: 'chatMessage', message: messageContent });
|
||||
}
|
||||
|
|
@ -419,64 +305,21 @@ class MessageInput extends HTMLElement {
|
|||
this._messageInputField.value = msg;
|
||||
}
|
||||
|
||||
_showCustomTextField(custom_text_array){
|
||||
this._customTextArray = custom_text_array || [];
|
||||
if (this._customTextArray.length === 0) {
|
||||
this._customTextArray.push({ placeholder: "{%additional_text%}", info: "" });
|
||||
}
|
||||
this._currentCustomTextIndex = 0;
|
||||
_showCustomTextField(){
|
||||
this._customText.style.display = 'block';
|
||||
this._renderCustomTextStep();
|
||||
}
|
||||
|
||||
_renderCustomTextStep() {
|
||||
const currentItem = this._customTextArray[this._currentCustomTextIndex];
|
||||
const infoDiv = this.shadowRoot.querySelector('#mzta-custom_info');
|
||||
|
||||
this._customTextArea.value = "";
|
||||
infoDiv.textContent = browser.i18n.getMessage("chatgpt_win_custom_text");
|
||||
|
||||
if (currentItem.info && currentItem.info.trim() !== "") {
|
||||
infoDiv.appendChild(document.createElement("br"));
|
||||
const infoSpan = document.createElement("span");
|
||||
infoSpan.textContent = "[" + browser.i18n.getMessage("customPrompts_form_label_ID") + ": " + currentItem.info + "]";
|
||||
infoDiv.appendChild(infoSpan);
|
||||
}
|
||||
|
||||
if(this._customTextArray.length > 1) {
|
||||
this._customStep.textContent = (this._currentCustomTextIndex + 1) + "/" + this._customTextArray.length;
|
||||
this._customStep.style.display = 'block';
|
||||
} else {
|
||||
this._customStep.style.display = 'none';
|
||||
}
|
||||
|
||||
this._customTextArea.focus();
|
||||
}
|
||||
|
||||
async _customTextBtnClick(args) {
|
||||
const customText = this._customTextArea.value;
|
||||
|
||||
if (this._customTextArray[this._currentCustomTextIndex]) {
|
||||
this._customTextArray[this._currentCustomTextIndex].custom_text = customText;
|
||||
}
|
||||
|
||||
this._currentCustomTextIndex++;
|
||||
|
||||
if (this._currentCustomTextIndex < this._customTextArray.length) {
|
||||
this._renderCustomTextStep();
|
||||
} else {
|
||||
args.customBtn.disabled = true;
|
||||
args.customBtn.classList.add('disabled');
|
||||
args.customLoading.style.display = 'inline-block';
|
||||
|
||||
let tab = await browser.tabs.query({ active: true, currentWindow: true });
|
||||
browser.runtime.sendMessage({ command: "api_send_custom_text", custom_text: this._customTextArray, tabId: tab[0].id });
|
||||
args.customDiv.style.display = 'none';
|
||||
|
||||
args.customBtn.disabled = false;
|
||||
args.customBtn.classList.remove('disabled');
|
||||
args.customLoading.style.display = 'none';
|
||||
}
|
||||
// console.log(">>>>>>>>>>>>>>>> customText: " + customText);
|
||||
args.customBtn.disabled = true;
|
||||
args.customBtn.classList.add('disabled');
|
||||
args.customLoading.style.display = 'inline-block';
|
||||
args.customLoading.style.display = 'none';
|
||||
let tab = await browser.tabs.query({ active: true, currentWindow: true });
|
||||
browser.runtime.sendMessage({ command: "api_send_custom_text", custom_text: customText, tabId: tab[0].id });
|
||||
args.customDiv.style.display = 'none';
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
/*
|
||||
* ThunderAI [https://micz.it/thunderbird-addon-thunderai/]
|
||||
* Copyright (C) 2024 - 2026 Mic (m@micz.it)
|
||||
* Copyright (C) 2024 - 2025 Mic (m@micz.it)
|
||||
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
|
|
@ -20,7 +20,6 @@
|
|||
* The original code has been released under the Apache License, Version 2.0.
|
||||
*/
|
||||
|
||||
import { prefs_default } from '../options/mzta-options-default.js';
|
||||
const messagesAreaTemplate = document.createElement('template');
|
||||
|
||||
const messagesAreaStyle = document.createElement('style');
|
||||
|
|
@ -58,29 +57,14 @@ messagesAreaStyle.textContent = `
|
|||
}
|
||||
.action-buttons {
|
||||
line-height: 1.3;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
text-align: center;
|
||||
}
|
||||
.action-buttons button {
|
||||
display: inline;
|
||||
margin: 0 10px;
|
||||
padding: 5px 10px;
|
||||
cursor: pointer;
|
||||
border: 1px outset buttonface;
|
||||
}
|
||||
.action-buttons button.close_btn, .action-buttons button.diffv_btn {
|
||||
border-radius: 5px;
|
||||
}
|
||||
.action-buttons button.action_btn {
|
||||
margin-right: 0;
|
||||
border-top-left-radius: 5px;
|
||||
border-bottom-left-radius: 5px;
|
||||
}
|
||||
.action_btn_info {
|
||||
font-size: 0.6rem;
|
||||
color: gray;
|
||||
display: inline-block;
|
||||
cursor: pointer;
|
||||
}
|
||||
@keyframes fadeIn {
|
||||
to {
|
||||
|
|
@ -104,20 +88,7 @@ messagesAreaStyle.textContent = `
|
|||
background: lightblue;
|
||||
color: navy;
|
||||
margin-bottom: var(--margin);
|
||||
font-size: 0.8em;
|
||||
}
|
||||
.info_obj{
|
||||
color:rgb(0, 71, 36);
|
||||
}
|
||||
.sel_info{
|
||||
font-size: 0.7rem;
|
||||
color: gray;
|
||||
margin-top: 5px;
|
||||
display: none;
|
||||
width: 100%;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
/* diff viewer */
|
||||
.added {
|
||||
background-color: #d4fcdc;
|
||||
|
|
@ -128,92 +99,6 @@ messagesAreaStyle.textContent = `
|
|||
display: inline;
|
||||
text-decoration: line-through;
|
||||
}
|
||||
|
||||
/* Split button styles */
|
||||
.split-button {
|
||||
display: inline-flex;
|
||||
position: relative;
|
||||
font-family: sans-serif;
|
||||
}
|
||||
|
||||
.split-button button {
|
||||
padding: 5px 0px 5px 10px;
|
||||
cursor: pointer;
|
||||
font-size: 14px;
|
||||
}
|
||||
|
||||
.split-button .dropdown-toggle {
|
||||
border-left: none;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
width: 38px;
|
||||
margin-left:-1px;
|
||||
border-top-right-radius: 5px;
|
||||
border-bottom-right-radius: 5px;
|
||||
padding:0;
|
||||
}
|
||||
|
||||
.dropdown-toggle svg {
|
||||
fill: #555;
|
||||
margin-left: -4px;
|
||||
}
|
||||
|
||||
.dropdown-menu {
|
||||
position: absolute;
|
||||
top: 2.55rem;
|
||||
right:0;
|
||||
display: none;
|
||||
flex-direction: column;
|
||||
background-color: white;
|
||||
border: 1px solid #ccc;
|
||||
min-width: 160px;
|
||||
z-index: 1000;
|
||||
margin-top: 2px;
|
||||
box-shadow: 0 4px 6px rgba(0,0,0,0.1);
|
||||
border-radius: 5px;
|
||||
text-align: right;
|
||||
width: -moz-available;
|
||||
}
|
||||
|
||||
.dropdown-menu button {
|
||||
padding: 10px 14px;
|
||||
border: none;
|
||||
background-color: white;
|
||||
text-align: right;
|
||||
cursor: pointer;
|
||||
font-size: 0.6rem;
|
||||
color: gray;
|
||||
}
|
||||
|
||||
.dropdown-menu button:hover {
|
||||
background-color: #f0f0f0;
|
||||
}
|
||||
|
||||
.dropdown-menu.show {
|
||||
display: flex;
|
||||
}
|
||||
|
||||
/* Thinking block styles */
|
||||
details.thinking-block {
|
||||
border-left: 3px solid #bbb;
|
||||
background: #f7f7f7;
|
||||
padding: 0.3em 0.6em;
|
||||
margin: 0 0 0.6em 0;
|
||||
font-size: 0.9em;
|
||||
color: #555;
|
||||
border-radius: 4px;
|
||||
}
|
||||
details.thinking-block > summary {
|
||||
cursor: pointer;
|
||||
font-weight: 600;
|
||||
}
|
||||
details.thinking-block .thinking-content {
|
||||
white-space: pre-wrap;
|
||||
margin-top: 0.3em;
|
||||
}
|
||||
|
||||
/* Dark mode styles */
|
||||
@media (prefers-color-scheme: dark) {
|
||||
.added {
|
||||
background-color:rgb(0, 94, 0);
|
||||
|
|
@ -221,11 +106,6 @@ messagesAreaStyle.textContent = `
|
|||
.removed {
|
||||
background-color:rgb(90, 0, 0);
|
||||
}
|
||||
details.thinking-block {
|
||||
background: #2a2a2a;
|
||||
color: #bbb;
|
||||
border-left-color: #555;
|
||||
}
|
||||
}
|
||||
`;
|
||||
messagesAreaTemplate.content.appendChild(messagesAreaStyle);
|
||||
|
|
@ -242,8 +122,6 @@ class MessagesArea extends HTMLElement {
|
|||
constructor() {
|
||||
super();
|
||||
this.accumulatingMessageEl = null;
|
||||
this.thinkingAccumulator = '';
|
||||
this.hideThinking = false;
|
||||
|
||||
const shadowRoot = this.attachShadow({ mode: 'open' });
|
||||
shadowRoot.appendChild(messagesAreaTemplate.content.cloneNode(true));
|
||||
|
|
@ -274,17 +152,9 @@ class MessagesArea extends HTMLElement {
|
|||
this.llmName = llmName;
|
||||
}
|
||||
|
||||
setHideThinking(val) {
|
||||
this.hideThinking = !!val;
|
||||
}
|
||||
|
||||
handleNewThinkingToken(token) {
|
||||
this.thinkingAccumulator += token;
|
||||
}
|
||||
|
||||
async handleTokensDone(promptData = null) {
|
||||
handleTokensDone(promptData = null) {
|
||||
this.flushAccumulatingMessage();
|
||||
await this.addActionButtons(promptData);
|
||||
this.addActionButtons(promptData);
|
||||
this.addDivider();
|
||||
}
|
||||
|
||||
|
|
@ -295,7 +165,7 @@ class MessagesArea extends HTMLElement {
|
|||
let source = browser.i18n.getMessage("apiwebchat_you");
|
||||
switch (type) {
|
||||
case "user":
|
||||
source = browser.i18n.getMessage("apiwebchat_you");
|
||||
source = browser.i18n.getMessage("apiwebchat_you");;
|
||||
break;
|
||||
case "info":
|
||||
source = browser.i18n.getMessage("apiwebchat_info");
|
||||
|
|
@ -306,21 +176,7 @@ class MessagesArea extends HTMLElement {
|
|||
|
||||
const messageElement = document.createElement('div');
|
||||
messageElement.classList.add('message', type);
|
||||
// Replace \n with <br> for correct HTML display
|
||||
if (type === "info") {
|
||||
messageElement.appendChild(htmlStringToFragment(messageText));
|
||||
} else {
|
||||
messageElement.appendChild(textWithBrToFragment(messageText));
|
||||
}
|
||||
// messageElement.textContent = messageText;
|
||||
// // Replace \n with <br> elements for correct HTML display
|
||||
// messageElement.innerHTML = '';
|
||||
// messageText.split('\n').forEach((line, idx, arr) => {
|
||||
// messageElement.appendChild(document.createTextNode(line));
|
||||
// if (idx < arr.length - 1) {
|
||||
// messageElement.appendChild(document.createElement('br'));
|
||||
// }
|
||||
// });
|
||||
messageElement.textContent = messageText;
|
||||
this.messages.appendChild(messageElement);
|
||||
this.scrollToBottom();
|
||||
}
|
||||
|
|
@ -367,159 +223,40 @@ class MessagesArea extends HTMLElement {
|
|||
this.messages.scrollTop = this.messages.scrollHeight;
|
||||
}
|
||||
|
||||
// Helper to create dropdown options
|
||||
createOption(label, callback) {
|
||||
const btn = document.createElement('button');
|
||||
btn.textContent = label;
|
||||
btn.onclick = callback;
|
||||
return btn;
|
||||
}
|
||||
|
||||
// click callcback for the "use this answer" button
|
||||
handleUseThisAnswerButtonClick(promptData, replyType, fullTextHTMLAtAssignment){
|
||||
return async () => {
|
||||
if(promptData.mailMessageId == -1) { // we are using the reply from the compose window!
|
||||
promptData.action = "2"; // replace text
|
||||
}
|
||||
let finalText = removeAloneBRs(fullTextHTMLAtAssignment);
|
||||
const selectedHTML = this.getCurrentSelectionHTML();
|
||||
if(selectedHTML != "") {
|
||||
finalText = removeAloneBRs(selectedHTML);
|
||||
}
|
||||
|
||||
switch(promptData.action) {
|
||||
case "1": // do reply
|
||||
// console.log("[ThunderAI] (do reply) fullTextHTMLAtAssignment: " + fullTextHTMLAtAssignment);
|
||||
await browser.runtime.sendMessage({command: "chatgpt_replyMessage", text: finalText, tabId: promptData.tabId, mailMessageId: promptData.mailMessageId, replyType: replyType});
|
||||
browser.runtime.sendMessage({command: "chatgpt_close", window_id: (await browser.windows.getCurrent()).id});
|
||||
break;
|
||||
case "2": // replace text
|
||||
// console.log("[ThunderAI] (replace text) fullTextHTMLAtAssignment: " + fullTextHTMLAtAssignment);
|
||||
await browser.runtime.sendMessage({command: "chatgpt_replaceSelectedText", text: finalText, tabId: promptData.tabId, mailMessageId: promptData.mailMessageId});
|
||||
browser.runtime.sendMessage({command: "chatgpt_close", window_id: (await browser.windows.getCurrent()).id});
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
async addActionButtons(promptData = null) {
|
||||
addActionButtons(promptData = null) {
|
||||
if(promptData == null) { return; }
|
||||
const actionButtons = document.createElement('div');
|
||||
actionButtons.classList.add('action-buttons');
|
||||
// Create the main container for the "use this answer" button when replying
|
||||
const splitButton = document.createElement('div');
|
||||
splitButton.className = 'split-button';
|
||||
// selection info
|
||||
const selectionInfo = document.createElement('p');
|
||||
selectionInfo.textContent = browser.i18n.getMessage("apiwebchat_selection_info");
|
||||
selectionInfo.classList.add('sel_info');
|
||||
// main button
|
||||
const actionButton = document.createElement('button');
|
||||
actionButton.className = 'action_btn';
|
||||
const actionButton_line1 = document.createElement('span');
|
||||
actionButton_line1.textContent = browser.i18n.getMessage("apiwebchat_use_this_answer");
|
||||
actionButton.appendChild(actionButton_line1);
|
||||
splitButton.appendChild(actionButton);
|
||||
actionButton.textContent = browser.i18n.getMessage("apiwebchat_use_this_answer");
|
||||
const fullTextHTMLAtAssignment = this.fullTextHTML.trim().replace(/^"|"$/g, '').replace(/^<p>"/, '<p>').replace(/"<\/p>$/, '</p>'); // strip quotation marks
|
||||
//console.log(">>>>>>>>>>>> fullTextHTMLAtAssignment: " + fullTextHTMLAtAssignment);
|
||||
let reply_type_pref = await browser.storage.sync.get({ reply_type: prefs_default.reply_type });
|
||||
if((promptData.action == "1") && (promptData.mailMessageId != -1)) {
|
||||
const actionButton_line2 = document.createElement('span');
|
||||
actionButton_line2.classList.add('action_btn_info');
|
||||
actionButton_line2.textContent = reply_type_pref.reply_type == 'reply_all' ? browser.i18n.getMessage("prefs_OptionText_reply_all") : browser.i18n.getMessage("prefs_OptionText_reply_sender");
|
||||
actionButton.appendChild(document.createElement('br'));
|
||||
actionButton.appendChild(actionButton_line2);
|
||||
// Dropdown toggle button
|
||||
const toggleBtn = document.createElement('button');
|
||||
toggleBtn.className = 'dropdown-toggle';
|
||||
toggleBtn.setAttribute('aria-label', 'Show options');
|
||||
// SVG icon
|
||||
const svg = document.createElementNS('http://www.w3.org/2000/svg', 'svg');
|
||||
svg.setAttribute('viewBox', '0 0 20 20');
|
||||
svg.setAttribute('width', '16');
|
||||
svg.setAttribute('height', '16');
|
||||
svg.setAttribute('fill', 'currentColor');
|
||||
svg.setAttribute('stroke-width', '2');
|
||||
const path = document.createElementNS('http://www.w3.org/2000/svg', 'path');
|
||||
path.setAttribute('d', 'M19 9l-7 7-7-7');
|
||||
svg.appendChild(path);
|
||||
toggleBtn.appendChild(svg);
|
||||
splitButton.appendChild(toggleBtn);
|
||||
// Dropdown menu
|
||||
const dropdown = document.createElement('div');
|
||||
dropdown.className = 'dropdown-menu';
|
||||
dropdown.id = 'dropdown';
|
||||
// Add options
|
||||
dropdown.appendChild(this.createOption(
|
||||
reply_type_pref.reply_type == 'reply_all' ? browser.i18n.getMessage("prefs_OptionText_reply_sender") : browser.i18n.getMessage("prefs_OptionText_reply_all"),
|
||||
this.handleUseThisAnswerButtonClick(promptData, reply_type_pref.reply_type == 'reply_all' ? 'reply_sender' : 'reply_all', fullTextHTMLAtAssignment))
|
||||
);
|
||||
splitButton.appendChild(dropdown);
|
||||
let dropdownJustOpened = false;
|
||||
// Toggle function
|
||||
toggleBtn.onclick = () => {
|
||||
dropdownJustOpened = true;
|
||||
dropdown.classList.toggle('show');
|
||||
};
|
||||
// Close on outside click
|
||||
window.addEventListener('click', (e) => {
|
||||
// Delay the execution to allow other handlers (like toggle) to run first
|
||||
if (dropdownJustOpened) {
|
||||
dropdownJustOpened = false;
|
||||
return; // Skip this click because it's the one that opened the menu
|
||||
}
|
||||
setTimeout(() => {
|
||||
if (!splitButton.contains(e.target)) {
|
||||
dropdown.classList.remove('show');
|
||||
}
|
||||
}, 0);
|
||||
});
|
||||
}else{
|
||||
actionButton.style.paddingRight = "10px";
|
||||
actionButton.style.borderTopRightRadius = "5px";
|
||||
actionButton.style.borderBottomRightRadius = "5px";
|
||||
actionButton.style.marginRight = "10px";
|
||||
}
|
||||
actionButton.addEventListener('click', this.handleUseThisAnswerButtonClick(promptData,reply_type_pref.reply_type, fullTextHTMLAtAssignment));
|
||||
actionButton.addEventListener('click', async () => {
|
||||
switch(promptData.action) {
|
||||
case "1": // do reply
|
||||
// console.log("[ThunderAI] (do reply) fullTextHTMLAtAssignment: " + fullTextHTMLAtAssignment);
|
||||
await browser.runtime.sendMessage({command: "chatgpt_replyMessage", text: fullTextHTMLAtAssignment, tabId: promptData.tabId, mailMessageId: promptData.mailMessageId});
|
||||
browser.runtime.sendMessage({command: "chatgpt_close", window_id: (await browser.windows.getCurrent()).id});
|
||||
break;
|
||||
case "2": // replace text
|
||||
// console.log("[ThunderAI] (replace text) fullTextHTMLAtAssignment: " + fullTextHTMLAtAssignment);
|
||||
await browser.runtime.sendMessage({command: "chatgpt_replaceSelectedText", text: fullTextHTMLAtAssignment, tabId: promptData.tabId, mailMessageId: promptData.mailMessageId});
|
||||
browser.runtime.sendMessage({command: "chatgpt_close", window_id: (await browser.windows.getCurrent()).id});
|
||||
break;
|
||||
}
|
||||
});
|
||||
const closeButton = document.createElement('button');
|
||||
closeButton.textContent = browser.i18n.getMessage("chatgpt_win_close");
|
||||
closeButton.classList.add('close_btn');
|
||||
closeButton.addEventListener('click', async () => {
|
||||
// console.log("[ThunderAI] (close) fullTextHTMLAtAssignment: " + fullTextHTMLAtAssignment);
|
||||
browser.runtime.sendMessage({command: "chatgpt_close", window_id: (await browser.windows.getCurrent()).id}); // close window
|
||||
});
|
||||
if(promptData.action != 0) {
|
||||
actionButtons.appendChild(splitButton);
|
||||
selectionInfo.style.display = "block"; // show selection info
|
||||
}
|
||||
|
||||
// Save as Summary button (only shown for summary webchat sessions)
|
||||
if(promptData.prompt_info?.headerMessageId && promptData.prompt_info?.summaryTabId) {
|
||||
const saveSummaryButton = document.createElement('button');
|
||||
saveSummaryButton.textContent = browser.i18n.getMessage("webchat_save_as_summary");
|
||||
saveSummaryButton.classList.add('action_btn');
|
||||
saveSummaryButton.addEventListener('click', async () => {
|
||||
let finalText = removeAloneBRs(fullTextHTMLAtAssignment);
|
||||
const selectedHTML = this.getCurrentSelectionHTML();
|
||||
if(selectedHTML != "") {
|
||||
finalText = removeAloneBRs(selectedHTML);
|
||||
}
|
||||
await browser.runtime.sendMessage({
|
||||
command: "chatgpt_saveSummary",
|
||||
text: finalText,
|
||||
headerMessageId: promptData.prompt_info.headerMessageId,
|
||||
tabId: promptData.prompt_info.summaryTabId || promptData.tabId,
|
||||
});
|
||||
browser.runtime.sendMessage({command: "chatgpt_close", window_id: (await browser.windows.getCurrent()).id});
|
||||
});
|
||||
actionButtons.appendChild(saveSummaryButton);
|
||||
selectionInfo.style.display = "block";
|
||||
}
|
||||
if(promptData.action != 0) { actionButtons.appendChild(actionButton); }
|
||||
|
||||
// diff viewer button
|
||||
if(promptData.prompt_info?.use_diff_viewer == "1") {
|
||||
const diffvButton = document.createElement('button');
|
||||
diffvButton.textContent = browser.i18n.getMessage("btn_show_differences");
|
||||
diffvButton.classList.add('diffv_btn');
|
||||
diffvButton.addEventListener('click', async () => {
|
||||
let strippedText = fullTextHTMLAtAssignment.replace(/<\/?[^>]+(>|$)/g, "");
|
||||
let originalText = promptData.prompt_info?.selection_text;
|
||||
|
|
@ -534,7 +271,6 @@ class MessagesArea extends HTMLElement {
|
|||
|
||||
actionButtons.appendChild(closeButton);
|
||||
this.messages.appendChild(actionButtons);
|
||||
this.messages.appendChild(selectionInfo);
|
||||
this.scrollToBottom();
|
||||
}
|
||||
|
||||
|
|
@ -552,29 +288,22 @@ class MessagesArea extends HTMLElement {
|
|||
|
||||
// Iterate over each part of the diff to create the HTML output
|
||||
wordDiff.forEach(part => {
|
||||
// Split part.value by <br> (handling <br>, <br/>, <br />)
|
||||
const brRegex = /(<br\s*\/?>)/gi;
|
||||
const segments = part.value.split(brRegex);
|
||||
|
||||
segments.forEach(segment => {
|
||||
if (segment.match(brRegex)) {
|
||||
// It's a <br>, add a real <br> element
|
||||
messageElement.appendChild(document.createElement("br"));
|
||||
} else if (segment.length > 0) {
|
||||
const diffElement = document.createElement("span");
|
||||
if (part.added) {
|
||||
diffElement.className = "added";
|
||||
diffElement.textContent = segment;
|
||||
} else if (part.removed) {
|
||||
diffElement.className = "removed";
|
||||
diffElement.textContent = segment;
|
||||
} else {
|
||||
diffElement.textContent = segment;
|
||||
}
|
||||
messageElement.appendChild(diffElement);
|
||||
const diffElement = document.createElement("span");
|
||||
|
||||
// Apply a different class depending on whether the word is added, removed, or unchanged
|
||||
if (part.added) {
|
||||
diffElement.className = "added";
|
||||
diffElement.textContent = part.value;
|
||||
} else if (part.removed) {
|
||||
diffElement.className = "removed";
|
||||
diffElement.textContent = part.value;
|
||||
} else {
|
||||
diffElement.textContent = part.value;
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
// Add the element to the container
|
||||
messageElement.appendChild(diffElement);
|
||||
});
|
||||
|
||||
const header = document.createElement('h2');
|
||||
header.textContent = browser.i18n.getMessage("chatgpt_win_diff_title");
|
||||
|
|
@ -592,67 +321,21 @@ class MessagesArea extends HTMLElement {
|
|||
this.accumulatingMessageEl.querySelectorAll('.token').forEach(tokenEl => {
|
||||
fullText += tokenEl.textContent;
|
||||
});
|
||||
|
||||
// If an unterminated <think> block is present (mid-stream), defer the
|
||||
// markdown render until the closing tag arrives — tokens stay in the DOM
|
||||
// as raw fading spans, but the partial <think> content is never sent
|
||||
// through markdown-it or promoted to the final thinking block.
|
||||
const openThink = fullText.match(/<think>/i);
|
||||
const closeThink = fullText.match(/<\/think>/i);
|
||||
if (openThink && !closeThink) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Extract inline <think>...</think> blocks (Ollama / OpenAI Comp) and strip them from fullText.
|
||||
let inlineThinking = '';
|
||||
const thinkRegex = /<think>([\s\S]*?)<\/think>/gi;
|
||||
let match;
|
||||
while ((match = thinkRegex.exec(fullText)) !== null) {
|
||||
inlineThinking += (inlineThinking ? '\n' : '') + match[1];
|
||||
}
|
||||
fullText = fullText.replace(thinkRegex, '').replace(/^\s+/, '');
|
||||
|
||||
// Combined thinking content: worker-side (Anthropic) + inline (<think> tags)
|
||||
let combinedThinking = this.thinkingAccumulator;
|
||||
if (inlineThinking) {
|
||||
combinedThinking += (combinedThinking ? '\n' : '') + inlineThinking;
|
||||
}
|
||||
this.thinkingAccumulator = '';
|
||||
|
||||
|
||||
// Convert Markdown to DOM nodes using the markdown-it library
|
||||
const md = window.markdownit();
|
||||
const html = md.render(fullText);
|
||||
|
||||
this.fullTextHTML += html;
|
||||
|
||||
// console.log(">>>>>>>>>>>>>>>> flushAccumulatingMessage this.fullTextHTML: " + this.fullTextHTML);
|
||||
|
||||
|
||||
// Create a new DOM parser
|
||||
const parser = new DOMParser();
|
||||
const doc = parser.parseFromString(html, 'text/html');
|
||||
convertTextNodeNewlinesToBr(doc.body);
|
||||
|
||||
|
||||
// Remove existing tokens
|
||||
while (this.accumulatingMessageEl.firstChild) {
|
||||
this.accumulatingMessageEl.removeChild(this.accumulatingMessageEl.firstChild);
|
||||
}
|
||||
|
||||
// Prepend thinking block (if any). hide_thinking controls the initial
|
||||
// open/collapsed state: true -> collapsed, false -> open. Users can always
|
||||
// toggle with a click.
|
||||
if (combinedThinking) {
|
||||
const details = document.createElement('details');
|
||||
details.classList.add('thinking-block');
|
||||
if (!this.hideThinking) details.open = true;
|
||||
const summary = document.createElement('summary');
|
||||
summary.textContent = browser.i18n.getMessage('prefs_OptionText_thinking_summary') || 'Thinking';
|
||||
const content = document.createElement('div');
|
||||
content.classList.add('thinking-content');
|
||||
content.textContent = combinedThinking;
|
||||
details.appendChild(summary);
|
||||
details.appendChild(content);
|
||||
this.accumulatingMessageEl.appendChild(details);
|
||||
}
|
||||
|
||||
// Append new nodes
|
||||
Array.from(doc.body.childNodes).forEach(node => {
|
||||
|
|
@ -663,89 +346,6 @@ class MessagesArea extends HTMLElement {
|
|||
}
|
||||
}
|
||||
|
||||
getCurrentSelectionHTML() {
|
||||
const selection = window.getSelection();
|
||||
// console.log(">>>>>>>>>>>>>>>> getCurrentSelectionHTML: " + JSON.stringify(selection.toString()));
|
||||
if (selection.rangeCount > 0) {
|
||||
const range = selection.getRangeAt(0);
|
||||
const container = document.createElement('div');
|
||||
container.appendChild(range.cloneContents());
|
||||
return container.innerHTML;
|
||||
}
|
||||
return '';
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
customElements.define('messages-area', MessagesArea);
|
||||
|
||||
|
||||
function textWithBrToFragment(text) {
|
||||
const fragment = document.createDocumentFragment();
|
||||
const segments = text.split(/<br\s*\/?>/gi);
|
||||
segments.forEach((segment, idx) => {
|
||||
if (segment.length > 0) {
|
||||
fragment.appendChild(document.createTextNode(segment));
|
||||
}
|
||||
if (idx < segments.length - 1) {
|
||||
fragment.appendChild(document.createElement('br'));
|
||||
}
|
||||
});
|
||||
return fragment;
|
||||
}
|
||||
|
||||
function htmlStringToFragment(htmlString) {
|
||||
// console.log(">>>>>>>>>>>>>>>> htmlStringToFragment htmlString: " + htmlString);
|
||||
const normalizedHtml = htmlString.replace(/\n/g, '<br>');
|
||||
// console.log(">>>>>>>>>>>>>>>> htmlStringToFragment normalizedHtml: " + normalizedHtml);
|
||||
const parser = new DOMParser();
|
||||
const doc = parser.parseFromString(normalizedHtml, 'text/html');
|
||||
const fragment = document.createDocumentFragment();
|
||||
Array.from(doc.body.childNodes).forEach(node => fragment.appendChild(node));
|
||||
return fragment;
|
||||
}
|
||||
|
||||
function convertTextNodeNewlinesToBr(element) {
|
||||
element.childNodes.forEach(node => {
|
||||
if (node.nodeType === Node.TEXT_NODE) {
|
||||
if (node.textContent.includes('\n') && node.textContent.trim() !== '') {
|
||||
const fragment = document.createDocumentFragment();
|
||||
node.textContent.split('\n').forEach((part, idx, arr) => {
|
||||
fragment.appendChild(document.createTextNode(part));
|
||||
if (idx < arr.length - 1) {
|
||||
fragment.appendChild(document.createElement('br'));
|
||||
}
|
||||
});
|
||||
node.parentNode.replaceChild(fragment, node);
|
||||
}
|
||||
} else if (node.nodeType === Node.ELEMENT_NODE) {
|
||||
convertTextNodeNewlinesToBr(node);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
function removeAloneBRs(htmlString) {
|
||||
const parser = new DOMParser();
|
||||
const doc = parser.parseFromString(htmlString, 'text/html');
|
||||
|
||||
const brElements = Array.from(doc.querySelectorAll('br'));
|
||||
|
||||
brElements.forEach(br => {
|
||||
let current = br;
|
||||
let isInsideP = false;
|
||||
|
||||
while (current.parentElement) {
|
||||
if (current.parentElement.tagName.toLowerCase() === 'p') {
|
||||
isInsideP = true;
|
||||
break;
|
||||
}
|
||||
current = current.parentElement;
|
||||
}
|
||||
|
||||
if (!isInsideP) {
|
||||
br.remove();
|
||||
}
|
||||
});
|
||||
|
||||
return doc.body.innerHTML;
|
||||
}
|
||||
customElements.define('messages-area', MessagesArea);
|
||||
|
|
@ -1,230 +0,0 @@
|
|||
# Architecture
|
||||
|
||||
## Extension Structure (Manifest V2)
|
||||
|
||||
ThunderAI runs as a standard Thunderbird WebExtension with three main execution contexts:
|
||||
|
||||
```
|
||||
Background Page → mzta-background.html / mzta-background.js
|
||||
Popup → popup/mzta-popup.html / popup/mzta-popup.js
|
||||
Options Page → options/mzta-options.html / options/mzta-options.js
|
||||
Feature Pages → pages/*/
|
||||
Content Script → js/lib/diff.js (injected into chatgpt.com)
|
||||
Web Workers → js/workers/model-worker-*.js (one per API provider)
|
||||
```
|
||||
|
||||
## Data Flow: User Action → AI Response
|
||||
|
||||
```
|
||||
User clicks popup or presses Ctrl+Alt+A
|
||||
↓
|
||||
popup/mzta-popup.js (renders prompt list, handles selection)
|
||||
↓ (sendMessage to background)
|
||||
mzta-background.js (orchestrates everything)
|
||||
↓
|
||||
js/mzta-placeholders.js (resolves {%placeholder%} values from email data)
|
||||
↓
|
||||
js/mzta-prompts.js (builds final prompt string)
|
||||
↓
|
||||
┌─────────────────────────────────────────┐
|
||||
│ Based on connection_type: │
|
||||
│ chatgpt_web → js/mzta-chatgpt.js │ (opens ChatGPT window)
|
||||
│ chatgpt_api → Web Worker (openai) │
|
||||
│ ollama_api → Web Worker (ollama) │
|
||||
│ google_gemini → Web Worker (gemini) │
|
||||
│ anthropic → Web Worker (anthropic)│
|
||||
│ openai_comp → Web Worker (comp) │
|
||||
└─────────────────────────────────────────┘
|
||||
↓
|
||||
Result returned to background
|
||||
↓
|
||||
js/mzta-compose-script.js (inserts text into Thunderbird compose window and display window)
|
||||
```
|
||||
|
||||
### Data Flow: Inline Summary on Message Display
|
||||
|
||||
The `summarize_display_mode` preference (`'inline'` or `'webchat'`) controls where
|
||||
the summary is displayed. The `summarize_auto` preference controls when it is triggered.
|
||||
|
||||
- `summarize_auto = 2` (automatic) always generates inline, regardless of `summarize_display_mode`.
|
||||
- `summarize_auto = 3` (on receive) pre-caches the summary silently when the email arrives via `onNewMailReceived`. When the user later opens the message, the cache hit triggers an instant display.
|
||||
- `summarize_auto = 1` (manual button) respects `summarize_display_mode`:
|
||||
- `'inline'` → button click triggers inline generation
|
||||
- `'webchat'` → button click opens the AI chat window via `_openSummaryWebchat()`
|
||||
- Context menu summarize also respects `summarize_display_mode`:
|
||||
- `'inline'` with a single message → generates inline via `_generateSummaryForMessage()`
|
||||
- `'webchat'` or multiple messages → opens the AI chat window via `openChatGPT()`
|
||||
|
||||
```
|
||||
User opens/selects a message in Thunderbird
|
||||
↓
|
||||
mzta-compose-script.js (sends "initSummary" to background)
|
||||
↓
|
||||
mzta-background.js (checks summarize_auto + summarize_display_mode prefs)
|
||||
↓
|
||||
┌──────────────────────────────────────────────────────────┐
|
||||
│ summarize_auto = 0 → do nothing │
|
||||
│ summarize_auto = 1 → show "click to generate" button │
|
||||
│ display_mode = inline → click triggers inline gen │
|
||||
│ display_mode = webchat → click opens chat window │
|
||||
│ summarize_auto = 2 → generate immediately (always inline)│
|
||||
│ summarize_auto = 3 → cache hit (pre-cached on receive) │
|
||||
└──────────────────────────────────────────────────────────┘
|
||||
↓ (if generating inline)
|
||||
taSummaryStore (check cache / set processing)
|
||||
↓ (cache miss)
|
||||
mzta-special-commands (via Web Worker, NOT chatgpt_web)
|
||||
↓
|
||||
taSummaryStore (save result via taStorage)
|
||||
↓
|
||||
mzta-compose-script.js (render summary banner in message body)
|
||||
```
|
||||
|
||||
### Data Flow: Inline Translation on Message Display
|
||||
|
||||
The `translate_auto` preference controls when translation is triggered.
|
||||
Translation always renders inline (webchat mode has been removed).
|
||||
|
||||
The target language is determined by `translate_lang` (fallback on `default_chatgpt_lang`).
|
||||
|
||||
```
|
||||
User opens/selects a message in Thunderbird
|
||||
↓
|
||||
mzta-compose-script.js (sends "initTranslation" to background)
|
||||
↓
|
||||
mzta-background.js (checks translate + translate_auto prefs)
|
||||
↓
|
||||
┌──────────────────────────────────────────────────────────┐
|
||||
│ translate_auto = 0 → do nothing │
|
||||
│ translate_auto = 1 → show "click to translate" button │
|
||||
│ translate_auto = 2 → generate immediately │
|
||||
└──────────────────────────────────────────────────────────┘
|
||||
↓
|
||||
taTranslationStore (check cache / set processing)
|
||||
↓ (cache miss)
|
||||
mzta-special-commands (via Web Worker, NOT chatgpt_web)
|
||||
↓
|
||||
taTranslationStore (save result via taStorage)
|
||||
↓
|
||||
mzta-compose-script.js (render translation banner in message body)
|
||||
```
|
||||
### Data Flow: Background Summary on Email Receive (summarize_auto = 3)
|
||||
|
||||
When `summarize_auto = 3`, a summary is generated silently when a new email arrives. The flow mirrors `add_tags_auto`:
|
||||
|
||||
```
|
||||
New email arrives
|
||||
↓
|
||||
browser.messages.onNewMailReceived
|
||||
↓
|
||||
newEmailListener (checks _process_incoming, which includes summarize_auto === 3)
|
||||
↓
|
||||
processEmails({ summarizeOnReceive: true })
|
||||
↓ (single loop — shared with addTagsAuto / spamFilter / translateOnReceive)
|
||||
_generateSummaryForMessage(headerMessageId, null, { messageData })
|
||||
← tabId is null → no UI messages sent, silent pre-cache
|
||||
↓
|
||||
taSummaryStore.saveSummary()
|
||||
↓
|
||||
[later] user opens the message → initSummary → cache hit → showSummary instantly
|
||||
```
|
||||
|
||||
### Data Flow: Background Translation on Email Receive (translate_auto = 3)
|
||||
|
||||
When `translate_auto = 3`, a translation is generated silently when a new email arrives. Mirrors the summarize on-receive flow:
|
||||
|
||||
```
|
||||
New email arrives
|
||||
↓
|
||||
browser.messages.onNewMailReceived
|
||||
↓
|
||||
newEmailListener (checks _process_incoming, which includes translate_auto === 3)
|
||||
↓
|
||||
processEmails({ translateOnReceive: true })
|
||||
↓ (single loop — shared with addTagsAuto / spamFilter / summarizeOnReceive)
|
||||
_generateTranslationForMessage(headerMessageId, null, { messageData })
|
||||
← tabId is null → no UI messages sent, silent pre-cache
|
||||
↓
|
||||
taTranslationStore.saveTranslation()
|
||||
↓
|
||||
[later] user opens the message → initTranslation → cache hit → showTranslation instantly
|
||||
```
|
||||
|
||||
## Key Modules
|
||||
|
||||
| File | Role |
|
||||
|------|------|
|
||||
| `mzta-background.js` | Main orchestrator: listens for messages, coordinates all features |
|
||||
| `js/mzta-menus.js` | Context menu creation and management |
|
||||
| `js/mzta-prompts.js` | Prompt definitions (built-in) and custom prompt loading |
|
||||
| `js/mzta-placeholders.js` | Placeholder definitions and resolution logic |
|
||||
| `js/mzta-utils.js` | General utilities (email parsing, storage helpers, etc.) |
|
||||
| `js/mzta-utils-prompt.js` | Prompt-specific utilities (text truncation, lang injection, `buildSummaryPrompt()` for unified summary prompt assembly, `buildTranslationPrompt()` for translation prompt assembly) |
|
||||
| `js/mzta-compose-script.js` | Content script for compose and message display: injects AI response into compose window, renders unified toolbar (spam badge, summary/translation trigger buttons) and content panels (generic error, spam explanation, summary, translation) in message display via `#mzta-container` |
|
||||
| `js/mzta-chatgpt.js` | ChatGPT Web integration (opens browser window, reads DOM) |
|
||||
| `js/mzta-special-commands.js` | Handles special prompt actions (add_tags, calendar, task) |
|
||||
| `js/mzta-spamreport.js` | Spam filter logic |
|
||||
| `js/mzta-i18n.js` | i18n helper (wraps `browser.i18n.getMessage`) |
|
||||
| `js/mzta-logger.js` | Debug logging (gated by `do_debug` pref) |
|
||||
| `js/mzta-store.js` | Storage abstraction helpers |
|
||||
| `js/mzta-storage.js` | Unified per-message storage layer (`taStorage` class) for summary, spam, and translation data |
|
||||
| `js/mzta-summarystore.js` | Summary-specific storage wrapper (`taSummaryStore` class) with caching, truncation, and processing-state tracking |
|
||||
| `js/mzta-translationstore.js` | Translation-specific storage wrapper (`taTranslationStore` class) with caching, truncation, and processing-state tracking |
|
||||
| `js/mzta-working-status.js` | Visual status indicator during AI processing |
|
||||
| `js/mzta-addtags-exclusion-list.js` | Tag exclusion list management |
|
||||
| `js/mzta-placeholders-autocomplete.js` | Autocomplete for placeholders in prompt editor |
|
||||
|
||||
## API Modules (`js/api/`)
|
||||
|
||||
Each file handles HTTP communication for one provider:
|
||||
|
||||
| File | Provider |
|
||||
|------|----------|
|
||||
| `anthropic.js` | Claude (Anthropic) API |
|
||||
| `google_gemini.js` | Google Gemini API |
|
||||
| `ollama.js` | Ollama (self-hosted) |
|
||||
| `openai_comp.js` | OpenAI-compatible APIs |
|
||||
| `openai_comp_configs.js` | Pre-configured providers (DeepSeek, Grok, Mistral, OpenRouter, Perplexity) |
|
||||
| `openai_responses.js` | OpenAI Responses API |
|
||||
|
||||
## Web Workers (`js/workers/`)
|
||||
|
||||
Each API provider has a dedicated Web Worker so API calls don't block the UI:
|
||||
|
||||
- `model-worker-anthropic.js`
|
||||
- `model-worker-google_gemini.js`
|
||||
- `model-worker-ollama.js`
|
||||
- `model-worker-openai_comp.js`
|
||||
- `model-worker-openai_responses.js`
|
||||
|
||||
Workers receive a message with the prompt and settings, make the API call, and post back the result.
|
||||
|
||||
## Feature Pages (`pages/`)
|
||||
|
||||
Each subdirectory is a self-contained settings/UI page for a specific feature:
|
||||
|
||||
| Directory | Feature |
|
||||
|-----------|---------|
|
||||
| `addtags/` | Auto-tagging configuration |
|
||||
| `customprompts/` | Custom prompt editor |
|
||||
| `customdataplaceholders/` | Custom placeholder editor |
|
||||
| `get-calendar-event/` | Calendar event extraction settings |
|
||||
| `get-task/` | Task creation settings |
|
||||
| `menu_order/` | Drag-and-drop reordering and visibility control for popup and context menus |
|
||||
| `spamfilter/` | Spam filter settings |
|
||||
| `summarize/` | Email summarization settings |
|
||||
| `translate/` | Email translation settings |
|
||||
| `onboarding/` | First-run welcome page |
|
||||
| `_lib/` | Shared libraries used by pages |
|
||||
|
||||
## Storage
|
||||
|
||||
All preferences are stored via `browser.storage.local`. The keys and default values are defined in `options/mzta-options-default.js` (`prefs_default` export). Custom prompts and custom placeholders are stored separately in storage under their own keys.
|
||||
|
||||
### Per-Message Data Storage
|
||||
|
||||
Per-message data (summaries, spam reports, translations) is stored via `js/mzta-storage.js` (`taStorage` class). Each record is keyed by `msg:<headerMessageId>` in `messenger.storage.local` and follows schema version 1. Records contain optional fields: `summary`, `spam`, `translation`, plus metadata (`v`, `ts`). The `taStorage` class provides typed read/write/delete methods per field, automatic record cleanup when all fields are removed, and age-based cleanup.
|
||||
|
||||
`js/mzta-summarystore.js` (`taSummaryStore` class) wraps `taStorage` for summary-specific operations: load/save/remove summaries, track in-flight generation state via `browser.storage.session`, enforce a 100-entry cache limit with oldest-first truncation, and store error states.
|
||||
|
||||
`js/mzta-translationstore.js` (`taTranslationStore` class) wraps `taStorage` for translation-specific operations: load/save/remove translations, track in-flight generation state via `browser.storage.session`, enforce a 100-entry cache limit with oldest-first truncation, and store error states. Each translation record stores `translated_text`, `lang`, and optional error information.
|
||||
|
|
@ -1,203 +0,0 @@
|
|||
# Prompts System
|
||||
|
||||
## Overview
|
||||
|
||||
Prompts are the core user-facing feature of ThunderAI. Each prompt defines an AI instruction and how it behaves. There are two kinds:
|
||||
|
||||
- **Built-in prompts** — defined in `js/mzta-prompts.js`
|
||||
- **Custom prompts** — created by the user and stored in `browser.storage.local`
|
||||
|
||||
## Prompt Properties
|
||||
|
||||
### Base Properties (built-in only)
|
||||
|
||||
| Property | Type | Description |
|
||||
|----------|------|-------------|
|
||||
| `id` | string | Unique identifier |
|
||||
| `name` | string | `__MSG_key__` i18n reference or plain text |
|
||||
| `prompt` | string | The prompt template text (may contain `{%placeholder%}` tokens) |
|
||||
| `type` | number | `0` = always visible, `1` = reading email only, `2` = composing only |
|
||||
| `action` | number | `0` = close, `1` = reply (open compose), `2` = substitute text in-place |
|
||||
| `need_selected` | number | `0` = use full message body, `1` = requires text selection |
|
||||
| `need_signature` | number | `0` = no signature, `1` = include signature |
|
||||
| `need_custom_text` | number | `0` = no custom input, `1` = show custom text input field |
|
||||
| `define_response_lang` | number | `0` = no language hint, `1` = append response language instruction |
|
||||
| `use_diff_viewer` | number | `0` = normal output, `1` = show diff viewer (ChatGPT Web only) |
|
||||
|
||||
### User Properties (stored per-prompt in storage)
|
||||
|
||||
| Property | Type | Description |
|
||||
|----------|------|-------------|
|
||||
| `enabled` | number | `0` = hidden, `1` = shown in menus |
|
||||
| `position_display` | number | Sort order for the popup menu in reading view |
|
||||
| `position_compose` | number | Sort order for the popup menu in compose view |
|
||||
| `position_context` | number | Sort order for the context menu |
|
||||
| `show_in` | string | `"popup"` = popup only, `"context"` = context menu only, `"both"` = both, `"none"` = hidden from all menus. Default: `"popup"` for default/custom prompts, `"both"` for special prompts |
|
||||
| `custom_icon` | string | Filename (with extension) of an icon in `images/context_menu/custom/` used as the context-menu icon. Empty string = no icon. Only used for non-special prompts (special prompts use their hard-coded icons in `specialPromptToContextMenuID`). Selectable from a dropdown on the Menu Order page, context-menu tab. |
|
||||
|
||||
### Per-Prompt API Override Properties
|
||||
|
||||
Each prompt can override the global API connection. These mirror the keys in `integration_options_config` and `prefs_default`:
|
||||
|
||||
| Property | Description |
|
||||
|----------|-------------|
|
||||
| `connection_type` | Override API type for this prompt |
|
||||
| `chatgpt_web_model` | Override ChatGPT Web model |
|
||||
| `chatgpt_web_project` | Override ChatGPT Web project |
|
||||
| `chatgpt_web_custom_gpt` | Override custom GPT |
|
||||
| All `chatgpt_*`, `ollama_*`, `openai_comp_*`, `google_gemini_*`, `anthropic_*` keys | Override specific API settings |
|
||||
|
||||
## Special Prompts
|
||||
|
||||
Some prompts trigger additional Thunderbird actions beyond just sending text to the AI. They are identified by their `id`:
|
||||
|
||||
| ID | Feature |
|
||||
|----|---------|
|
||||
| `add_tags` | Auto-tag the email after AI response |
|
||||
| `spamfilter` | Classify as spam and optionally move email |
|
||||
| `summarize` | Summarize email content |
|
||||
| `get_calendar_event` | Extract and create a calendar event |
|
||||
| `get_task` | Extract and create a task |
|
||||
| `translate` | Translate email content into a target language |
|
||||
|
||||
These special prompts can have their own dedicated API integration settings (configured in the Options page). The list of these special prompts is in `options/mzta-options-default.js` as `special_prompts_with_integration`.
|
||||
|
||||
## Menu System
|
||||
|
||||
### Popup Menu
|
||||
- Displays prompts filtered by `show_in` (`"popup"` or `"both"`) and by tab context (`type` property: reading view shows types `0`+`1`, compose view shows types `0`+`2`)
|
||||
- Ordering: always position-based using `position_display` (reading view) or `position_compose` (compose view). Alphabetical ordering has been removed
|
||||
- Special prompts retain their colored background (CSS class `special_prompt`) in the popup based on `is_special == "1"`
|
||||
|
||||
### Context Menu
|
||||
- Dynamically built from all prompts with `show_in` set to `"context"` or `"both"`, filtered to reading types only (`type` 0 or 1)
|
||||
- Appears as a "ThunderAI" submenu in the `message_list` context
|
||||
- Ordering: position-based using `position_context` (fallback to alphabetical only when positions are equal)
|
||||
- Special prompts (add_tags, spamfilter, summarize, translate) route through `processEmails()` for batch processing; regular prompts execute via `menus.executeMenuAction()`
|
||||
- Icons: special prompts use dedicated icons (defined in `contextMenuIconsPath`); all other prompts use the addon icon (`images/icon-32.png`)
|
||||
- Add Tags in context menu assigns tags automatically (`addTagsAuto: true`), while in the popup it shows the interactive tag selection form
|
||||
|
||||
### Menu Order Page (`pages/menu_order/`)
|
||||
|
||||
Dedicated page for reordering, enabling, and disabling menu items across both the popup and the context menu. Opened from the options page via the "Menu Order" button.
|
||||
|
||||
**UI layout** — two side-by-side panels:
|
||||
- **Popup Menu panel**: sub-tabs for "Reading" / "Composing" switch the list between `position_display` / `position_compose` ordering and between the allowed types (`0`+`1` vs `0`+`2`)
|
||||
- **Context Menu panel**: single list ordered by `position_context`. Items with `type: "2"` (composing-only) are never shown here
|
||||
|
||||
Each list has two sections:
|
||||
- **Visible items**: active for the menu (`show_in` includes the menu), draggable to reorder
|
||||
- **Hidden items**: inactive for the menu (`show_in` excludes the menu), sorted alphabetically, not draggable
|
||||
|
||||
**Toggle coordination** — flipping the checkbox updates the prompt's `show_in` with four-state logic:
|
||||
- Popup ON: `"none"` → `"popup"`, `"context"` → `"both"`
|
||||
- Popup OFF: `"popup"` → `"none"`, `"both"` → `"context"`
|
||||
- Context ON: `"none"` → `"context"`, `"popup"` → `"both"`
|
||||
- Context OFF: `"context"` → `"none"`, `"both"` → `"popup"`
|
||||
|
||||
**Drag and drop** — native HTML5 DnD assigns sequential position numbers (1, 2, 3, ...) to `position_display`, `position_compose`, or `position_context` depending on which list is being sorted.
|
||||
|
||||
**Exclusions from the UI** (preserved on save so data is not lost):
|
||||
- Prompts with `enabled === 0` (disabled)
|
||||
- Special prompts whose base definition has `show_in: "none"` (internal prompts like `prompt_summarize_email_template` and `prompt_summarize_email_separator`) — retrieved via `getHiddenSpecialPromptIds()`
|
||||
- Special prompts whose feature is not active — retrieved from background via `get_active_special_ids` message, which calls `getActiveSpecialPromptsIDs()` with current prefs and `_sparks_presence`
|
||||
|
||||
**Cross-tab reload** — the page listens on `browser.storage.onChanged` for changes to `_default_prompts_properties`, `_custom_prompt`, or `_special_prompts`. When one of those keys changes (e.g. user saves from the Custom Prompts page in another tab), the page reloads its data with a 200ms debounce. Any unsaved local changes are discarded to avoid overwriting the other page's work.
|
||||
|
||||
**Save flow**:
|
||||
1. Re-concat preserved prompts (disabled + hidden-specials + inactive-feature specials) with the UI-visible prompts
|
||||
2. Split by `is_default` / `is_special` and call `setDefaultPromptsProperties()`, `setCustomPrompts()`, `setSpecialPrompts()`
|
||||
3. Send `reload_menus` to the background to rebuild both menus
|
||||
|
||||
### Alphabetic-to-Position Migration
|
||||
|
||||
The `dynamic_menu_order_alphabet` preference (previously a user-facing option) has been retired and removed from the UI, but the key still exists in storage as a one-shot migration flag. At every background startup, `migrateMenuOrderAlphabetic()` in `js/mzta-prompts.js` runs:
|
||||
|
||||
1. Reads `dynamic_menu_order_alphabet` (defaults to `true` if unset)
|
||||
2. If `true`: sorts all visible prompts with special prompts first (alphabetically), then the rest (alphabetically), and assigns sequential `position_display` = `position_compose` = `position_context` numbers. Hidden special prompts are preserved untouched.
|
||||
3. Persists the new positions via `setDefaultPromptsProperties` / `setCustomPrompts` / `setSpecialPrompts`
|
||||
4. Sets `dynamic_menu_order_alphabet = false` in sync storage so the migration does not run again
|
||||
|
||||
This ensures existing users upgrading from the previous alphabetical-default behaviour get the same visible ordering on first run, while subsequent launches keep whatever custom ordering the user has set.
|
||||
|
||||
### Special Prompt Visibility Dependencies
|
||||
|
||||
`getActiveSpecialPromptsIDs()` in `js/mzta-utils.js` maps feature prefs to active special prompt IDs. Notable dependency:
|
||||
|
||||
- `prompt_get_calendar_event_from_clipboard` is emitted only if **both** `get_calendar_event` and `get_calendar_event_from_clipboard` are active. If `get_calendar_event` is off, neither calendar prompt is shown regardless of the clipboard pref.
|
||||
|
||||
### Summarize: Dual-Mode Prompt System
|
||||
|
||||
The summarize feature uses two distinct prompt pathways:
|
||||
|
||||
**Context Menu Summarize** (right-click on messages in message list):
|
||||
- Activated via the `summarize` context menu item, controlled by the `summarize` feature flag
|
||||
- Uses 3 special prompts stored in `specialPrompts`:
|
||||
- `prompt_summarize` — the main instruction prompt for the LLM
|
||||
- `prompt_summarize_email_template` — template for formatting each email's content
|
||||
- `prompt_summarize_email_separator` — separator text between multiple emails
|
||||
- Supports multi-email summarization: each selected message is formatted with the email template, joined by the separator, then prepended with the instruction prompt
|
||||
- All 3 prompts support placeholder autocomplete (`{%placeholder%}` syntax)
|
||||
- Result is displayed via `openChatGPT()` in the standard chat output window (not inline)
|
||||
- Default prompt texts are stored as i18n keys: `prompt_summarize_full_text`, `prompt_summarize_email_template_full_text`, `prompt_summarize_email_separator_full_text`
|
||||
|
||||
**Inline Summary on Message Display** (automatic or manual per `summarize_auto` pref):
|
||||
- Uses the same 3 special prompts as webchat mode, via `taPromptUtils.buildSummaryPrompt()` in `js/mzta-utils-prompt.js`
|
||||
- Does **not** support `chatgpt_web` connection type (shows error if configured)
|
||||
- Result is rendered as a styled banner at the top of the message body via `mzta-compose-script.js`
|
||||
- Banner includes a refresh button (↻) to regenerate the summary
|
||||
- Cached per-message via `taSummaryStore` / `taStorage` (max 100 entries)
|
||||
|
||||
**Unified Prompt Building** — `taPromptUtils.buildSummaryPrompt(messageDataArray)`:
|
||||
- All summary paths (inline, webchat single, webchat multi) use this single method
|
||||
- Accepts an array of `{ message, fullMessage }` entries
|
||||
- Returns `{ promptText, promptInfo }` where `promptInfo` is the `prompt_summarize` prompt object
|
||||
|
||||
### Translate: Inline-Only Prompt System
|
||||
|
||||
The translate feature uses a single special prompt (`prompt_translate_this`) for translating emails. Translation always renders inline (no webchat mode).
|
||||
|
||||
**Inline Translation on Message Display** (controlled by `translate_auto` pref):
|
||||
- Uses a single special prompt: `prompt_translate_this`
|
||||
- The prompt uses placeholders (`{%mail_subject%}`, `{%mail_html_body%}`, `{%thunderai_translate_lang%}`, `{%thunderai_translate_exclude_lang%}`) resolved via the standard placeholder system
|
||||
- The AI response is a JSON object: `{ "subject": "...", "body": "...", "status": "1"|"-1" }`
|
||||
- `status = "1"`: translation completed, subject and body are displayed
|
||||
- `status = "-1"`: translation skipped (excluded/target language), a "skipped" message is shown
|
||||
- Target language is determined by `translate_lang` pref, falling back to `default_chatgpt_lang`
|
||||
- Does **not** support `chatgpt_web` connection type (shows error if configured)
|
||||
- Result is rendered as a styled banner (green/teal theme) in the message body via `mzta-compose-script.js`
|
||||
- Banner includes refresh (↻) and delete (×) buttons
|
||||
- Cached per-message via `taTranslationStore` / `taStorage` (max 100 entries)
|
||||
- The prompt was originally a regular prompt (`defaultPrompts`) and was moved to `specialPrompts` with `is_special: "1"` and `type: "1"` (reading email only)
|
||||
|
||||
**Prompt Building** — `taPromptUtils.buildTranslationPrompt(fullMessage)`:
|
||||
- Retrieves the `prompt_translate_this` special prompt text
|
||||
- Resolves placeholders via `placeholdersUtils.getPlaceholdersValues()` + `replacePlaceholders()`
|
||||
- Returns `{ promptText, promptInfo }`
|
||||
|
||||
## Prompt Types Reference
|
||||
|
||||
```
|
||||
type 0 → shown when reading AND composing
|
||||
type 1 → shown only when reading an email (message display)
|
||||
type 2 → shown only when composing an email
|
||||
```
|
||||
|
||||
## Action Types Reference
|
||||
|
||||
```
|
||||
action 0 → no output, just close (e.g. for tag/spam actions handled in background)
|
||||
action 1 → open a reply compose window with AI response
|
||||
action 2 → replace selected text (or insert) in compose window
|
||||
```
|
||||
|
||||
## Adding a New Built-in Prompt
|
||||
|
||||
1. Add the prompt object to the `defaultPrompts` array in `js/mzta-prompts.js`
|
||||
2. Add the `name` string key to `_locales/en/messages.json`
|
||||
3. If the prompt text needs a localized string, add it to `_locales/en/messages.json` as well
|
||||
4. Reference any needed placeholders using `{%placeholder_id%}` syntax in the `prompt` field
|
||||
|
||||
## Custom Prompts
|
||||
|
||||
Custom prompts are stored in `browser.storage.local` and managed via `pages/customprompts/`. They follow the same property structure as built-in prompts but are created/edited/deleted by the user through the UI. Custom placeholders can also be referenced in custom prompt text.
|
||||
|
|
@ -1,79 +0,0 @@
|
|||
# Placeholders System
|
||||
|
||||
## Overview
|
||||
|
||||
Placeholders are dynamic tokens embedded in prompt text that get replaced with real data at runtime (email content, headers, user input, etc.).
|
||||
|
||||
**Format:** `{%placeholder_id%}`
|
||||
|
||||
Example in a prompt: `"Summarize this email: {%mail_text_body_or_selected%}"`
|
||||
|
||||
## Placeholder Properties
|
||||
|
||||
| Property | Type | Description |
|
||||
|----------|------|-------------|
|
||||
| `id` | string | Unique identifier used in `{%id%}` tokens |
|
||||
| `name` | string | Display name (i18n `__MSG_key__` or plain text) |
|
||||
| `default_value` | string | Value used if placeholder cannot be resolved |
|
||||
| `type` | number | `0` = always, `1` = reading only, `2` = composing only |
|
||||
| `is_default` | string | `"1"` = built-in (not editable/deletable), `"0"` = custom |
|
||||
| `is_dynamic` | string | `"0"` = fixed value, `"1"` = dynamic (takes a parameter after `:`) |
|
||||
| `enabled` | number | `0` = disabled, `1` = enabled |
|
||||
| `text` | string | Content for custom placeholders only |
|
||||
|
||||
## Built-in Placeholders (defined in `js/mzta-placeholders.js`)
|
||||
|
||||
| ID | Description | Type |
|
||||
|----|-------------|------|
|
||||
| `mail_text_body` | Full plain text of the email | 0 |
|
||||
| `mail_html_body` | Full HTML of the email | 0 |
|
||||
| `mail_typed_text` | Text typed so far in compose window | 2 |
|
||||
| `mail_text_body_or_selected` | Plain text body, or selected text if any | 1 |
|
||||
| `mail_html_body_or_selected` | HTML body, or selected HTML if any | 1 |
|
||||
| `mail_selected_text` | Only the selected text | 1 |
|
||||
| `mail_selected_html` | Only the selected HTML | 1 |
|
||||
| `mail_subject` | Email subject line | 0 |
|
||||
| `mail_date` | Email date | 1 |
|
||||
| `mail_author` | Email sender | 0 |
|
||||
| `mail_recipients` | Email recipients | 0 |
|
||||
| `mail_tags` | Current tags on the email | 1 |
|
||||
| `mail_available_tags` | All available tags in Thunderbird | 1 |
|
||||
| `identity_name` | Current identity display name | 0 |
|
||||
| `identity_email` | Current identity email address | 0 |
|
||||
| `identity_signature` | Current identity signature | 0 |
|
||||
| `additional_text[id]` | User input field (dynamic, shows input in popup) | 0 |
|
||||
| `mail_header:name` | Any email header by name (dynamic) | 1 |
|
||||
| `mail_full_headers` | All mail headers (key: value format, newline-separated) | 1 |
|
||||
|
||||
## Dynamic Placeholders
|
||||
|
||||
Dynamic placeholders use a colon separator to pass a parameter:
|
||||
|
||||
```
|
||||
{%additional_text:my_field_id%} → shows an input field labelled "my_field_id" in the popup
|
||||
{%mail_header:x-spam-score%} → fetches the X-Spam-Score header value
|
||||
```
|
||||
|
||||
The `is_dynamic: "1"` property signals this behavior in the placeholder definition.
|
||||
|
||||
## Custom Placeholders
|
||||
|
||||
Users can define their own placeholders via `pages/customdataplaceholders/`. Custom placeholders:
|
||||
- Have `is_default: "0"`
|
||||
- Have a `text` property containing the replacement value
|
||||
- Are stored in `browser.storage.local`
|
||||
- Are merged with default placeholders at runtime before prompt processing
|
||||
|
||||
## Placeholder Resolution Order
|
||||
|
||||
1. Built-in placeholders are defined in `js/mzta-placeholders.js`
|
||||
2. Custom placeholders are loaded from storage
|
||||
3. At runtime, `mzta-background.js` gathers email data (via Thunderbird APIs)
|
||||
4. Each `{%id%}` token in the prompt string is replaced with the resolved value
|
||||
5. If a value cannot be resolved, `default_value` is used as fallback
|
||||
|
||||
## Adding a New Built-in Placeholder
|
||||
|
||||
1. Add the object to the `defaultPlaceholders` array in `js/mzta-placeholders.js`
|
||||
2. Add the `name` i18n key to `_locales/en/messages.json` as `placeholder_<id>` (or choose a descriptive key)
|
||||
3. Implement the resolution logic in the relevant section of `mzta-background.js`
|
||||
|
|
@ -1,119 +0,0 @@
|
|||
# API Integrations
|
||||
|
||||
## Connection Types
|
||||
|
||||
The active AI provider is controlled by the `connection_type` preference. Possible values:
|
||||
|
||||
| `connection_type` value | Provider |
|
||||
|------------------------|----------|
|
||||
| `chatgpt_web` | ChatGPT Web (no API key, opens browser window) |
|
||||
| `chatgpt_api` | OpenAI API (ChatGPT via API key) |
|
||||
| `ollama_api` | Ollama (self-hosted LLM) |
|
||||
| `openai_comp_api` | OpenAI-compatible API |
|
||||
| `google_gemini_api` | Google Gemini API |
|
||||
| `anthropic_api` | Claude (Anthropic) API |
|
||||
|
||||
The global default is `chatgpt_web`. Each special prompt (`add_tags`, `spamfilter`, etc.) can independently override this via its own `{prefix}_connection_type` pref.
|
||||
|
||||
## Provider Configuration
|
||||
|
||||
Each provider has its own settings block in `integration_options_config` (`options/mzta-options-default.js`):
|
||||
|
||||
### ChatGPT Web
|
||||
Controlled via `js/mzta-chatgpt.js`. Opens a browser window to `chatgpt.com`, injects the prompt via DOM automation, and reads back the response. Settings: `chatgpt_web_model`, `chatgpt_web_tempchat`, `chatgpt_web_project`, `chatgpt_web_custom_gpt`, `chatgpt_web_load_wait_time`.
|
||||
|
||||
Content script `js/lib/diff.js` is injected into ChatGPT pages for diff-view support.
|
||||
|
||||
### OpenAI API (`chatgpt_api`)
|
||||
- Module: `js/api/openai_responses.js`
|
||||
- Worker: `js/workers/model-worker-openai_responses.js`
|
||||
- Settings keys: `chatgpt_api_key`, `chatgpt_model`, `chatgpt_developer_messages`, `chatgpt_temperature`, `chatgpt_store`
|
||||
|
||||
### Ollama (`ollama_api`)
|
||||
- Module: `js/api/ollama.js`
|
||||
- Worker: `js/workers/model-worker-ollama.js`
|
||||
- Settings keys: `ollama_host`, `ollama_model`, `ollama_num_ctx`, `ollama_temperature`, `ollama_think`, `ollama_format_json`
|
||||
- Requires CORS to be configured on the Ollama server
|
||||
|
||||
### OpenAI-Compatible (`openai_comp_api`)
|
||||
- Module: `js/api/openai_comp.js`
|
||||
- Worker: `js/workers/model-worker-openai_comp.js`
|
||||
- Settings keys: `openai_comp_host`, `openai_comp_model`, `openai_comp_api_key`, `openai_comp_use_v1`, `openai_comp_chat_name`, `openai_comp_temperature`
|
||||
- Pre-configured providers: `js/api/openai_comp_configs.js` (DeepSeek, Grok, Mistral, OpenRouter, Perplexity)
|
||||
|
||||
### Google Gemini (`google_gemini_api`)
|
||||
- Module: `js/api/google_gemini.js`
|
||||
- Worker: `js/workers/model-worker-google_gemini.js`
|
||||
- Settings keys: `google_gemini_api_key`, `google_gemini_model`, `google_gemini_system_instruction`, `google_gemini_thinking_budget`, `google_gemini_temperature`
|
||||
|
||||
### Anthropic / Claude (`anthropic_api`)
|
||||
- Module: `js/api/anthropic.js`
|
||||
- Worker: `js/workers/model-worker-anthropic.js`
|
||||
- Settings keys: `anthropic_api_key`, `anthropic_model`, `anthropic_version`, `anthropic_max_tokens`, `anthropic_system_prompt`, `anthropic_temperature`, `anthropic_extended_thinking_budget`
|
||||
- **Extended thinking**: when `anthropic_extended_thinking_budget > 0`, the request body adds `thinking: { type: 'enabled', budget_tokens: N }` and **omits** `temperature` (the Claude API forbids setting temperature with extended thinking). Thinking output arrives in the SSE stream as `content_block_delta` events with `delta.type === 'thinking_delta'` and is forwarded to the webchat UI as `newThinkingToken` messages, captured into a `thinkingAccumulator` in the worker and passed on `tokensDone`.
|
||||
|
||||
## Thinking output in the webchat UI
|
||||
|
||||
Two provider categories emit reasoning/thinking content:
|
||||
|
||||
- **Ollama / OpenAI Compatible**: thinking arrives inline in the normal token stream wrapped in `<think>…</think>` tags. `MessagesArea.flushAccumulatingMessage()` strips these blocks from the rendered text and renders them as a `<details class="thinking-block">` prepended to the answer. If an unterminated `<think>` is detected mid-stream, the flush is deferred until the closing tag arrives.
|
||||
- **Anthropic**: thinking is captured in the worker and posted to the controller as `newThinkingToken`. `MessagesArea` accumulates it and renders the same `<details>` block on final flush.
|
||||
|
||||
The global `hide_thinking` pref (default `true`) controls the **initial open/collapsed state** of the thinking block: `true` → collapsed, `false` → open. The user can always toggle by clicking. Thinking content is never discarded. Other providers (Google Gemini, OpenAI Responses, ChatGPT Web) are not affected by this UI logic.
|
||||
|
||||
## Configuration Validation
|
||||
|
||||
For special prompts (`mzta_specialCommand`), required fields are validated in `initWorker()` (`js/mzta-special-commands.js`) **before** the worker is created. If a required field is empty, an `Error` with `isConfigError = true` is thrown. Validation covers:
|
||||
|
||||
| Provider | Required fields |
|
||||
|----------|----------------|
|
||||
| `chatgpt_api` | `chatgpt_api_key`, `chatgpt_model` |
|
||||
| `google_gemini_api` | `google_gemini_api_key`, `google_gemini_model` |
|
||||
| `ollama_api` | `ollama_host`, `ollama_model` |
|
||||
| `openai_comp_api` | `openai_comp_host`, `openai_comp_model` |
|
||||
| `anthropic_api` | `anthropic_api_key`, `anthropic_model`, `anthropic_version` |
|
||||
|
||||
Validation is skipped when `use_specific_api = true` (i.e., the prompt's own `api_type` overrides the global setting — credentials come from the prompt config, not global prefs).
|
||||
|
||||
The `isConfigError` flag on the thrown error tells callers in `mzta-background.js` to display the error in the panel **without saving it to storage** — so the user can fix settings and retry cleanly.
|
||||
|
||||
Feature-specific routing of `isConfigError`:
|
||||
|
||||
- `summarize` / `translate` / `spamfilter`: the error is shown in their dedicated panel (summary / translation / spam panel) and **not** persisted to storage.
|
||||
- `add_tags`: it has **no dedicated panel**, so the error is routed to the **generic error panel** via `showGenericError(errMsg, source)` in `mzta-background.js`, which broadcasts a `showGenericError` message to all tabs. The content script `js/mzta-compose-script.js` renders it as `#mzta-generic-error` inside `#mzta-container`. The panel is dismissible and reusable by any future feature without its own UI.
|
||||
|
||||
For regular prompts (`openChatGPT()`), validation still happens inside the listener callback after the API webchat window is created (unchanged behavior).
|
||||
|
||||
## Web Worker Pattern
|
||||
|
||||
For all API-based providers (everything except ChatGPT Web), the call goes through a Web Worker:
|
||||
|
||||
```
|
||||
mzta-background.js
|
||||
→ creates new Worker('js/workers/model-worker-<provider>.js')
|
||||
→ postMessage({ prompt, settings })
|
||||
→ worker makes HTTP fetch to provider API
|
||||
→ worker postMessage({ result }) back
|
||||
→ background handles result
|
||||
```
|
||||
|
||||
This keeps API calls off the main thread and avoids blocking the Thunderbird UI.
|
||||
|
||||
## Optional Permissions
|
||||
|
||||
API calls require host permissions. These are declared as `optional_permissions` in `manifest.json` and requested at runtime:
|
||||
|
||||
- `https://*.chatgpt.com/*` and `https://*.openai.com/*` for ChatGPT
|
||||
- `https://*.anthropic.com/*` for Claude
|
||||
- `https://*/*` and `http://*/*` for Ollama and OpenAI-compatible endpoints
|
||||
|
||||
## Adding a New Provider
|
||||
|
||||
1. Create `js/api/<provider>.js` with the API call logic
|
||||
2. Create `js/workers/model-worker-<provider>.js` that imports and calls the API module
|
||||
3. Add a new `connection_type` value constant
|
||||
4. Add settings keys to `integration_options_config` in `options/mzta-options-default.js`
|
||||
5. Add UI controls to `options/mzta-options.html` and `options/mzta-options.js`
|
||||
6. Add the new `connection_type` case to the dispatch logic in `mzta-background.js`
|
||||
7. Add required host permissions to `manifest.json` optional_permissions
|
||||
8. Add i18n strings to `_locales/en/messages.json`
|
||||
|
|
@ -1,161 +0,0 @@
|
|||
# Options & Settings System
|
||||
|
||||
## Overview
|
||||
|
||||
All extension preferences are stored in `browser.storage.local`. Defaults and the full list of valid keys are defined in `options/mzta-options-default.js`.
|
||||
|
||||
## Key Exports from `mzta-options-default.js`
|
||||
|
||||
| Export | Description |
|
||||
|--------|-------------|
|
||||
| `prefs_default` | All preference keys with their default values |
|
||||
| `integration_options_config` | Per-provider API settings structure |
|
||||
| `getDynamicSettingsDefaults(keysFilter)` | Returns per-special-prompt integration defaults |
|
||||
| `getDynamicSettingValue(prefs, prefix, settingName)` | Reads a prefixed setting for a special prompt |
|
||||
|
||||
## Settings Structure
|
||||
|
||||
### Global Integration Settings
|
||||
|
||||
Stored flat in `prefs_default` with `{provider}_{key}` naming:
|
||||
|
||||
```
|
||||
chatgpt_api_key, chatgpt_model, chatgpt_developer_messages, chatgpt_temperature, chatgpt_store
|
||||
ollama_host, ollama_model, ollama_num_ctx, ollama_temperature, ollama_think
|
||||
openai_comp_host, openai_comp_model, openai_comp_api_key, openai_comp_use_v1, openai_comp_chat_name, openai_comp_temperature
|
||||
google_gemini_api_key, google_gemini_model, google_gemini_system_instruction, google_gemini_thinking_budget, google_gemini_temperature
|
||||
anthropic_api_key, anthropic_model, anthropic_version, anthropic_max_tokens, anthropic_system_prompt, anthropic_temperature, anthropic_extended_thinking_budget
|
||||
```
|
||||
|
||||
Plus the global connection selector:
|
||||
```
|
||||
connection_type (default: 'chatgpt_web')
|
||||
use_specific_integration (default: false)
|
||||
```
|
||||
|
||||
### Special Prompt Integration Overrides
|
||||
|
||||
The 6 special prompts (`add_tags`, `spamfilter`, `summarize`, `get_calendar_event`, `get_task`, `translate`) each get their own `use_specific_integration` and `connection_type` keys:
|
||||
|
||||
```
|
||||
{prefix}_use_specific_integration (default: false)
|
||||
{prefix}_connection_type (default: 'chatgpt_api')
|
||||
```
|
||||
|
||||
These are generated programmatically at the bottom of `mzta-options-default.js` using `special_prompts_with_integration` array.
|
||||
|
||||
### UI & Feature Preferences
|
||||
|
||||
| Key | Default | Description |
|
||||
|-----|---------|-------------|
|
||||
| `do_debug` | `false` | Enable debug logging |
|
||||
| `chatgpt_win_height` | `800` | ChatGPT window height |
|
||||
| `chatgpt_win_width` | `700` | ChatGPT window width |
|
||||
| `chatgpt_win_top` | `''` | Window top position |
|
||||
| `chatgpt_win_left` | `''` | Window left position |
|
||||
| `chatgpt_win_save_position` | `false` | Remember window position |
|
||||
| `default_chatgpt_lang` | `''` | Force response language |
|
||||
| `default_sign_name` | `''` | Default signature name |
|
||||
| `reply_type` | `'reply_all'` | Default reply type |
|
||||
| `composing_plain_text` | `false` | Use plain text in compose |
|
||||
| `chatgpt_web_model` | `''` | ChatGPT Web model override |
|
||||
| `chatgpt_web_tempchat` | `false` | Use temporary chat |
|
||||
| `chatgpt_web_project` | `''` | ChatGPT Web project |
|
||||
| `chatgpt_web_custom_gpt` | `''` | Custom GPT URL |
|
||||
| `chatgpt_web_load_wait_time` | `1000` | Wait time (ms) for ChatGPT page |
|
||||
| `dynamic_menu_force_enter` | `false` | Force Enter to submit in popup |
|
||||
| `dynamic_menu_order_alphabet` | `true` | Internal migration flag only; no UI. Set to `false` by `migrateMenuOrderAlphabetic()` on first boot after upgrade to bootstrap position-based ordering. See `claude-spec/02-prompts.md` for details. |
|
||||
| `placeholders_use_default_value` | `false` | Use placeholder defaults when empty |
|
||||
| `hide_thinking` | `true` | Controls the initial state of the thinking `<details>` block prepended above the answer: `true` = collapsed by default, `false` = open by default. The user can always toggle with a click; thinking content is never discarded. |
|
||||
| `max_prompt_length` | `30000` | Max prompt string length |
|
||||
|
||||
### Feature Flags
|
||||
|
||||
| Key | Default | Description |
|
||||
|-----|---------|-------------|
|
||||
| `add_tags` | `false` | Enable auto-tagging feature |
|
||||
| `add_tags_maxnum` | `3` | Max tags to apply |
|
||||
| `add_tags_hide_exclusions` | `false` | Hide excluded tags from menu |
|
||||
| `add_tags_exclusions_exact_match` | `false` | Exact match for exclusions |
|
||||
| `add_tags_first_uppercase` | `true` | Capitalize first letter of tags |
|
||||
| `add_tags_force_lang` | `true` | Force language for tags |
|
||||
| `add_tags_auto` | `false` | Auto-tag on message open |
|
||||
| `add_tags_auto_force_existing` | `false` | Only use existing tags |
|
||||
| `add_tags_auto_only_inbox` | `true` | Auto-tag only inbox messages |
|
||||
| `add_tags_auto_uselist` | `false` | Use tag allow-list |
|
||||
| `add_tags_auto_uselist_list` | `''` | Tag allow-list content |
|
||||
| `add_tags_enabled_accounts` | `[]` | Accounts where auto-tag is active |
|
||||
| `get_calendar_event` | `true` | Enable calendar event extraction |
|
||||
| `get_calendar_event_from_clipboard` | `false` | Enable calendar from clipboard |
|
||||
| `get_task` | `true` | Enable task creation |
|
||||
| `calendar_enforce_timezone` | `false` | Force specific timezone |
|
||||
| `calendar_timezone` | `''` | Timezone to enforce |
|
||||
| `calendar_no_selection` | `false` | Skip selection prompt |
|
||||
| `spamfilter` | `false` | Enable spam filter |
|
||||
| `spamfilter_threshold` | `70` | Spam confidence threshold (%) |
|
||||
| `spamfilter_enabled_accounts` | `[]` | Accounts where spam filter is active |
|
||||
| `spamfilter_show_msg_panel` | `true` | Show info panel on spam detection |
|
||||
| `summarize` | `false` | Enable email summarization |
|
||||
| `summarize_auto` | `1` | Auto-summarize mode: `0` = disabled, `1` = manual (show "click to generate" button), `2` = automatic (generate on message open), `3` = generate on email receive (background pre-cache via `onNewMailReceived`, no UI during generation) |
|
||||
| `summarize_display_mode` | `'inline'` | Where to display summaries: `'inline'` = message pane banner, `'webchat'` = AI chat window. Note: `summarize_auto = 2` and `summarize_auto = 3` always use inline regardless of this setting. |
|
||||
| `summarize_max_display_length` | `0` | Maximum characters shown in inline summary before truncation. `0` = no limit (show full text). When set, text is truncated at a word boundary and a "See more"/"See less" toggle link is shown. |
|
||||
| `summarize_strip_formatting` | `false` | Strip HTML and Markdown formatting from AI-generated summaries, showing plain text only. |
|
||||
| `translate` | `true` | Enable email translation |
|
||||
| `translate_auto` | `0` | Auto-translate mode: `0` = disabled, `1` = manual (show button), `2` = automatic (translate on message open), `3` = generate on email receive (background pre-cache via `onNewMailReceived`, no UI during generation) |
|
||||
| `translate_max_display_length` | `0` | Maximum characters shown in inline translation before truncation. `0` = no limit (show full text). When set, text is truncated at a word boundary and a "See more"/"See less" toggle link is shown. |
|
||||
| `translate_lang` | `''` | Target language for translation. Falls back to `default_chatgpt_lang` if empty. |
|
||||
|
||||
### Summarize Settings Page (`pages/summarize/`)
|
||||
|
||||
The summarize settings page provides:
|
||||
|
||||
1. **Specific integration checkbox** — enables per-feature API override (like other special prompts)
|
||||
2. **Auto-summarize dropdown** (`summarize_auto`) — three modes:
|
||||
- `0` (Disabled) — no inline summaries
|
||||
- `1` (Manual) — shows a "Click to generate summary" button in message display
|
||||
- `2` (Automatic) — generates summary immediately when message is opened
|
||||
3. **Display mode dropdown** (`summarize_display_mode`) — controls where summaries are shown:
|
||||
- `'inline'` — summary banner in the message pane (default)
|
||||
- `'webchat'` — opens the AI chat window
|
||||
- Note: `summarize_auto = 2` always generates inline regardless of this setting. Context menu summarize with multiple messages always falls back to webchat.
|
||||
4. **Max display length** (`summarize_max_display_length`) — number input, limits inline summary text to N characters. `0` = no limit. When truncated, a "See more"/"See less" toggle link is appended.
|
||||
5. **Strip formatting** (`summarize_strip_formatting`) — checkbox, removes HTML/Markdown formatting from AI summary responses, displaying plain text only. Default: off.
|
||||
6. **Three editable prompts** (used by context menu summarize and webchat mode):
|
||||
- Summarize instruction prompt (`prompt_summarize`)
|
||||
- Email template prompt (`prompt_summarize_email_template`)
|
||||
- Email separator prompt (`prompt_summarize_email_separator`)
|
||||
- Each has Save/Reset buttons and placeholder autocomplete
|
||||
- Default text comes from i18n strings (`prompt_summarize_full_text`, etc.)
|
||||
|
||||
### Menu Order Page (`pages/menu_order/`)
|
||||
|
||||
Entry point from the options page via the "Menu Order" button (next to "Manage your prompts"). Provides drag-and-drop reordering and toggle-based visibility control for both the popup and the context menu. See `claude-spec/02-prompts.md` ("Menu Order Page") for the full behaviour, data flow, and exclusion rules.
|
||||
|
||||
### Translate Settings Page (`pages/translate/`)
|
||||
|
||||
The translate settings page provides:
|
||||
|
||||
1. **Specific integration checkbox** — enables per-feature API override (like other special prompts)
|
||||
2. **Auto-translate dropdown** (`translate_auto`) — three modes:
|
||||
- `0` (Disabled) — no inline translations
|
||||
- `1` (Manual) — shows a "Get AI Translation" button in message display
|
||||
- `2` (Automatic) — generates translation immediately when message is opened
|
||||
3. **Max display length** (`translate_max_display_length`) — number input, limits inline translation text to N characters. `0` = no limit. When truncated, a "See more"/"See less" toggle link is appended.
|
||||
4. **Target language** (`translate_lang`) — text input for the destination language. If empty, falls back to `default_chatgpt_lang`.
|
||||
5. **One editable prompt** — the translation instruction prompt (`prompt_translate_this`) with Save/Reset buttons and placeholder autocomplete. Default text comes from i18n string `prompt_translate_this_full_text`.
|
||||
|
||||
## Adding a New Preference
|
||||
|
||||
1. Add the key and default value to `prefs_default` in `options/mzta-options-default.js`
|
||||
2. Add UI control to `options/mzta-options.html`
|
||||
3. Add load/save logic to `options/mzta-options.js`
|
||||
4. Add i18n label to `_locales/en/messages.json`
|
||||
5. Read the pref in the relevant module via `browser.storage.local.get()`
|
||||
|
||||
## Reading Preferences at Runtime
|
||||
|
||||
```javascript
|
||||
const prefs = await browser.storage.local.get(prefs_default);
|
||||
// prefs now contains all keys with defaults for any unset values
|
||||
const myPref = prefs.my_new_pref;
|
||||
```
|
||||
|
|
@ -1,91 +0,0 @@
|
|||
# Localization
|
||||
|
||||
## Golden Rule
|
||||
|
||||
**Only ever modify `_locales/en/messages.json`.**
|
||||
|
||||
All other locale files (`de`, `fr`, `it`, `es`, `zh_Hans`, `zh_Hant`, `pl`, `ru`, `pt-br`, `sv`, `el`, `cs`, `hr`, `ja`, `nb_NO`) are managed by translators through [Weblate](https://hosted.weblate.org/). Never edit them manually.
|
||||
|
||||
## Message File Format
|
||||
|
||||
Each entry in `_locales/en/messages.json` follows the standard WebExtension i18n format:
|
||||
|
||||
```json
|
||||
"key_name": {
|
||||
"message": "The English text",
|
||||
"description": "Context for translators explaining where/how this string is used"
|
||||
}
|
||||
```
|
||||
|
||||
The `description` field is important — it helps Weblate translators understand the context.
|
||||
|
||||
## Using Strings in Code
|
||||
|
||||
### In JavaScript
|
||||
```javascript
|
||||
import { i18n } from './mzta-i18n.js';
|
||||
const text = i18n('key_name');
|
||||
// or directly:
|
||||
const text = browser.i18n.getMessage('key_name');
|
||||
```
|
||||
|
||||
### In HTML
|
||||
```html
|
||||
<span data-i18n="key_name"></span>
|
||||
<!-- or via manifest/attribute references: -->
|
||||
__MSG_key_name__
|
||||
```
|
||||
|
||||
### In manifest.json
|
||||
```json
|
||||
"description": "__MSG_extensionDescription__"
|
||||
```
|
||||
|
||||
## Naming Conventions
|
||||
|
||||
| Prefix | Usage |
|
||||
|--------|-------|
|
||||
| `menu_*` | Context menu and popup menu labels |
|
||||
| `prompt_*` | Built-in prompt names |
|
||||
| `placeholder_*` | Placeholder display names |
|
||||
| `options_*` | Settings page labels |
|
||||
| `pages_*` | Feature page labels |
|
||||
| `error_*` | Error messages |
|
||||
| `info_*` | Informational messages |
|
||||
| `btn_*` | Button labels |
|
||||
|
||||
## Adding a New String
|
||||
|
||||
1. Open `_locales/en/messages.json`
|
||||
2. Add the new key in alphabetical order within the file (or near related keys)
|
||||
3. Include both `message` and `description` fields
|
||||
4. Use the string in code via `browser.i18n.getMessage('key_name')` or `__MSG_key_name__`
|
||||
|
||||
Example:
|
||||
```json
|
||||
"my_new_feature_label": {
|
||||
"message": "My New Feature",
|
||||
"description": "Label for the new feature button in the options page"
|
||||
}
|
||||
```
|
||||
|
||||
## Supported Languages (16)
|
||||
|
||||
| Code | Language |
|
||||
|------|----------|
|
||||
| `en` | English (source) |
|
||||
| `de` | German |
|
||||
| `es` | Spanish |
|
||||
| `fr` | French |
|
||||
| `it` | Italian |
|
||||
| `pl` | Polish |
|
||||
| `ru` | Russian |
|
||||
| `pt-br` | Brazilian Portuguese |
|
||||
| `sv` | Swedish |
|
||||
| `el` | Greek |
|
||||
| `cs` | Czech |
|
||||
| `hr` | Croatian |
|
||||
| `ja` | Japanese |
|
||||
| `nb_NO` | Norwegian Bokmål |
|
||||
| `zh_Hans` | Chinese Simplified |
|
||||
| `zh_Hant` | Chinese Traditional |
|
||||
|
|
@ -1,398 +0,0 @@
|
|||
# Thunderbird WebExtensions Development Guidelines
|
||||
|
||||
> **Purpose:** This file provides operative guidelines for Claude when helping develop or modify ThunderAI. These rules override general AI assistant behavior and must be followed strictly.
|
||||
|
||||
## ThunderAI-Specific Context
|
||||
|
||||
- ThunderAI uses **Manifest Version 2** — do not suggest or apply any MV3 migration.
|
||||
- No build tools, no transpilation, no npm — plain ES6 modules loaded directly.
|
||||
- The manifest already uses `browser_specific_settings` (not `applications`).
|
||||
- All module imports use relative paths with `.js` extension.
|
||||
- The `mzta-` prefix is used for all core module filenames.
|
||||
|
||||
---
|
||||
|
||||
## Important Guidelines for AI Assistants
|
||||
|
||||
### 1. Always use `browser_specific_settings` in manifest.json
|
||||
|
||||
The `applications` manifest entry is deprecated. Always use `browser_specific_settings`:
|
||||
|
||||
```json
|
||||
{
|
||||
"manifest_version": 2,
|
||||
"name": "ThunderAI",
|
||||
"browser_specific_settings": {
|
||||
"gecko": {
|
||||
"id": "thunderai@micz.it",
|
||||
"strict_min_version": "140.0"
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### 2. Do not guess APIs by using Try-Catch
|
||||
|
||||
A widespread antipattern in AI-generated Thunderbird extensions:
|
||||
|
||||
```javascript
|
||||
// WRONG - Never do this!
|
||||
try {
|
||||
await browser.someApi.method({ guessedParam: value });
|
||||
} catch (e) {
|
||||
try {
|
||||
await browser.someApi.method({ differentGuess: value });
|
||||
} catch (e2) {
|
||||
// Giving up silently — this makes debugging impossible
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
**Why this is harmful:**
|
||||
- Makes code unmaintainable
|
||||
- Hides real errors from developers
|
||||
- Makes debugging extremely difficult
|
||||
|
||||
**The correct approach:**
|
||||
1. Read the API documentation FIRST
|
||||
2. Use the exact parameter names and types specified
|
||||
3. Only use try-catch for expected error conditions with proper handling
|
||||
4. Never suppress errors without logging or handling them
|
||||
|
||||
### 3. Do not use Experiments unnecessarily
|
||||
|
||||
```javascript
|
||||
// WRONG - Using Experiment when standard API exists
|
||||
// Don't use Experiment just because you found example code using it
|
||||
|
||||
// RIGHT - Check if standard API can do it first
|
||||
const folders = await browser.folders.query({ name: "Inbox" });
|
||||
```
|
||||
|
||||
### 4. Handle file storage correctly
|
||||
|
||||
```javascript
|
||||
// WRONG - Trying to use raw filesystem APIs
|
||||
const fs = require('fs'); // Not available!
|
||||
|
||||
// RIGHT - Use storage.local with File objects
|
||||
const file = new File([content], "data.txt", { type: "text/plain" });
|
||||
await browser.storage.local.set({ file });
|
||||
```
|
||||
|
||||
### 5. Do not use async listeners for the runtime.onMessage listener
|
||||
|
||||
See https://developer.mozilla.org/en-US/docs/Mozilla/Add-ons/WebExtensions/API/runtime/onMessage
|
||||
|
||||
### 6. Parse vCard, vTodo, vEvent and iCal strings using a 3rd party library
|
||||
|
||||
Follow https://webextension-api.thunderbird.net/en/mv2/guides/vcard.html to parse vCard, vEvent and vTodo strings.
|
||||
|
||||
### 7. Parse Mailbox Strings using messengerUtilities
|
||||
|
||||
Extract email addresses from mailbox strings like "John Doe <john@example.com>":
|
||||
|
||||
```javascript
|
||||
const parsed = await browser.messengerUtilities.parseMailboxString(
|
||||
"John Doe <john@example.com>, Jane <jane@example.com>"
|
||||
);
|
||||
|
||||
// Result:
|
||||
// [
|
||||
// { name: "John Doe", email: "john@example.com" },
|
||||
// { name: "Jane", email: "jane@example.com" }
|
||||
// ]
|
||||
|
||||
// Extract just emails:
|
||||
const emails = parsed.map(p => p.email);
|
||||
```
|
||||
|
||||
**Documentation:** https://webextension-api.thunderbird.net/en/mv2/messengerUtilities.html
|
||||
|
||||
**Options:**
|
||||
- `preserveGroups`: Keep grouped hierarchies
|
||||
- `expandMailingLists`: Expand Thunderbird mailing lists (requires `addressBook` permission)
|
||||
|
||||
### 8. Set correct `strict_min_version` entry
|
||||
|
||||
Make sure `manifest.json` has a `strict_min_version` entry matching the used functions. If a function added in Thunderbird 137 is used, it must be set to `137.0` or higher.
|
||||
|
||||
### 9. Always use background type "module"
|
||||
|
||||
Always use `type: "module"` for background scripts. This allows use of the `import` directive for ES6 modules, and non-ES6 libraries can still be loaded via the `scripts` array:
|
||||
|
||||
```json
|
||||
// RIGHT - Always use type: "module"
|
||||
"background": {
|
||||
"scripts": ["lib/some-non-ES6-lib.js", "background.js"],
|
||||
"type": "module"
|
||||
}
|
||||
```
|
||||
|
||||
Then in `background.js`, import libraries explicitly:
|
||||
```javascript
|
||||
// Import ES6 module with default export
|
||||
import ICAL from "./lib/ical.js";
|
||||
|
||||
// Import ES6 module with named exports
|
||||
import { someFunction, someConstant } from "./lib/somemodule.js";
|
||||
```
|
||||
|
||||
### 10. Verify API return types — do not assume array access
|
||||
|
||||
Many Thunderbird APIs return wrapped objects, not direct arrays. Always verify the return type in the documentation before accessing the data.
|
||||
|
||||
**Common pitfall — MessageList:**
|
||||
```javascript
|
||||
// WRONG - getDisplayedMessages() returns MessageList, not an array
|
||||
const [message] = await browser.messageDisplay.getDisplayedMessages(tabId);
|
||||
|
||||
// RIGHT - MessageList has a .messages array property
|
||||
const { messages: [message] } = await browser.messageDisplay.getDisplayedMessages(tabId);
|
||||
```
|
||||
|
||||
**Common pitfall — HeadersDictionary:**
|
||||
```javascript
|
||||
// WRONG - headers might not exist or might not be an array
|
||||
let returnPath = headers["Return-Path"];
|
||||
|
||||
// RIGHT - keys are lowercase, values are always arrays
|
||||
const returnPathArray = headers["return-path"];
|
||||
const returnPath = returnPathArray?.[0] ?? null;
|
||||
```
|
||||
|
||||
**APIs that return wrapped objects (NOT direct arrays):**
|
||||
|
||||
| API | Returns | Access Pattern |
|
||||
|-----|---------|----------------|
|
||||
| `messageDisplay.getDisplayedMessages()` | `MessageList` | `result.messages[0]` |
|
||||
| `messages.list()` | `MessageList` | `result.messages[0]` |
|
||||
| `messages.query()` | `MessageList` | `result.messages[0]` |
|
||||
| `messages.getHeaders()` | `HeadersDictionary` | `result["header-name"][0]` |
|
||||
| `messages.getFull()` | `MessagePart` | `result.headers["header-name"][0]` |
|
||||
|
||||
**APIs that return direct arrays:**
|
||||
|
||||
| API | Returns | Access Pattern |
|
||||
|-----|---------|----------------|
|
||||
| `tabs.query()` | array of Tab | `result[0]` |
|
||||
| `mailTabs.query()` | array of MailTab | `result[0]` |
|
||||
| `addressBooks.list()` | array of AddressBookNode | `result[0]` |
|
||||
| `contacts.list()` | array of ContactNode | `result[0]` |
|
||||
| `folders.query()` | array of MailFolder | `result[0]` |
|
||||
|
||||
---
|
||||
|
||||
## Official API Documentation
|
||||
|
||||
**Primary resource:** https://webextension-api.thunderbird.net/en/mv2/
|
||||
|
||||
Documentation exists for different channels:
|
||||
- **Release (mv2):** https://webextension-api.thunderbird.net/en/mv2/
|
||||
- **ESR (esr-mv2):** https://webextension-api.thunderbird.net/en/esr-mv2/
|
||||
|
||||
**Key feature:** Search functionality and cross-references between types and functions.
|
||||
|
||||
---
|
||||
|
||||
## Understanding Thunderbird Release Channels
|
||||
|
||||
### Standard Release Channel (Monthly)
|
||||
- Update cadence: ~4 weeks
|
||||
- A new major version with each release (`147.0`, `148.0`, ...)
|
||||
- Gets new features and APIs (and bug fixes & security fixes)
|
||||
|
||||
### ESR Channel (Extended Support Release)
|
||||
- Update cadence: 1 major update per year, with lots of versions "missing" in between (`115.*`, `128.*`, `140.*`, `153.*`, ...)
|
||||
- Receives bug fixes & security fixes on regular basis alongside the major monthly releases, but as minor releases (`140.1`, `140.2`, ...)
|
||||
- No new features or APIs
|
||||
|
||||
### For ThunderAI
|
||||
- Target the Release channel for standard API usage.
|
||||
- Target the ESR channel for add-ons relying on Experiment APIs. Targeting the Release channel with Experiments is acceptable *only* when the developer can guarantee required monthly updates.
|
||||
|
||||
---
|
||||
|
||||
## Experiment APIs
|
||||
|
||||
### What Are Experiment APIs?
|
||||
Experiment APIs allow add-ons to access Thunderbird's core internals directly. They have full access to modify everything in Thunderbird.
|
||||
|
||||
### Critical Rules for Experiments
|
||||
|
||||
**1. Avoid Experiments Unless Absolutely Necessary**
|
||||
- Standard WebExtension APIs should always be your first choice
|
||||
- Only use Experiments when standard APIs genuinely cannot accomplish your goal
|
||||
|
||||
**2. Experiments Require Updates for Each Major Version**
|
||||
- This was manageable with ESR (1x/year)
|
||||
- Became unsustainable with monthly Release channel (12x/year)
|
||||
|
||||
**3. When Suggesting Experiments**
|
||||
- Only if standard APIs cannot accomplish the core goal
|
||||
- Developer must explicitly understand the maintenance burden
|
||||
- **Target ESR channel specifically**
|
||||
- Reference `esr-mv2` documentation
|
||||
|
||||
### Available semi-official Experiment APIs
|
||||
|
||||
#### Calendar Experiment API
|
||||
|
||||
Use this instead of creating a custom Experiment for calendar interactions.
|
||||
|
||||
**Use cases:**
|
||||
- Reading existing event/task items from Thunderbird's calendar
|
||||
- Listening for item updates
|
||||
- Creating/updating/deleting items
|
||||
|
||||
**Setup requirements:**
|
||||
1. Temporarily clone the [webext-experiments](https://github.com/thunderbird/webext-experiments/) repository.
|
||||
2. Add all `experiment_apis` entries found in `calendar/manifest.json` to the project's `manifest.json`.
|
||||
3. Copy `calendar/experiments/calendar/` into the project as `experiments/calendar/`. Do not modify these files.
|
||||
|
||||
**Note:** Always request iCal format:
|
||||
|
||||
```javascript
|
||||
// Always consult schema first, if this example is still correct
|
||||
browser.calendar.items.onCreated.addListener(
|
||||
async (calendarItem) => {
|
||||
if (calendarItem.type === "task") {
|
||||
console.log("Task in iCal format:", calendarItem.item);
|
||||
}
|
||||
},
|
||||
{ returnFormat: "ical" }
|
||||
);
|
||||
```
|
||||
|
||||
### Other Experiment Repositories
|
||||
|
||||
- https://github.com/thunderbird/webext-support — Helper APIs and modules
|
||||
- https://github.com/thunderbird/webext-examples — Example extensions (includes some Experiments)
|
||||
|
||||
---
|
||||
|
||||
## Native File System Access
|
||||
|
||||
### Current Limitations
|
||||
Native filesystem access is NOT available in Thunderbird WebExtensions.
|
||||
|
||||
### Recommended Approach
|
||||
|
||||
**For data persistence:**
|
||||
```javascript
|
||||
await browser.storage.local.set({ myData: someValue });
|
||||
const data = await browser.storage.local.get("myData");
|
||||
```
|
||||
|
||||
**For user file input:**
|
||||
```javascript
|
||||
const file = new File([content], "filename.txt", { type: "text/plain" });
|
||||
await browser.storage.local.set({ file });
|
||||
|
||||
// Retrieve later
|
||||
const data = await browser.storage.local.get("file");
|
||||
console.log(data.file.name);
|
||||
```
|
||||
|
||||
**Important:** File objects can be stored directly in `browser.storage.local` without serialization.
|
||||
|
||||
---
|
||||
|
||||
## Add-on Review Requirements
|
||||
|
||||
**Review policy:** https://thunderbird.github.io/atn-review-policy/
|
||||
|
||||
### Key Requirements
|
||||
|
||||
**1. No Build Tools**
|
||||
- Include 3rd party libraries directly (don't use webpack, rollup, etc.)
|
||||
- Include a `VENDOR.md` file documenting all 3rd party libraries with links to exact versions (not "latest"). Example: https://webextension-api.thunderbird.net/en/mv2/guides/vcard.html
|
||||
|
||||
**2. Permissions**
|
||||
- Only request permissions you actually need
|
||||
- The `tabs` and `activeTab` permissions are almost never needed in Thunderbird
|
||||
- Unnecessary permissions may cause rejection during ATN review
|
||||
|
||||
---
|
||||
|
||||
## Example Repositories
|
||||
|
||||
- https://github.com/thunderbird/webext-examples — Official example extensions
|
||||
- https://github.com/thunderbird/webext-support — Support libraries and helpers
|
||||
|
||||
Use these to see proper code structure, learn common patterns, and understand best practices.
|
||||
|
||||
---
|
||||
|
||||
## Mandatory Checklist Before Providing Code
|
||||
|
||||
Before providing any code, verify ALL of these:
|
||||
|
||||
- [ ] Consulted official API documentation — do NOT guess methods or parameters
|
||||
- [ ] NO try-catch blocks for guessing API parameters
|
||||
- [ ] Used 3rd party libraries or API methods for parsing — minimize manual string parsing or regex
|
||||
- [ ] Used 3rd party libraries are the most recent stable version
|
||||
- [ ] Event listeners registered at file scope (NOT inside init function)
|
||||
- [ ] VENDOR.md includes ALL dependencies with exact version URLs
|
||||
- [ ] Used `browser_specific_settings` (NOT deprecated `applications`)
|
||||
- [ ] Included proper error handling
|
||||
- [ ] Code has comments explaining the approach
|
||||
- [ ] No hardcoded user-facing strings — use the i18n API (`_locales/en/messages.json` only)
|
||||
- [ ] Add-on fulfills all requirements in the "Add-on Review Requirements" section
|
||||
- [ ] All guidelines in "Important Guidelines for AI Assistants" are followed
|
||||
- [ ] Manifest uses correct `strict_min_version`
|
||||
- [ ] If using Experiments: manifest has `strict_max_version` targeting current ESR (fetch https://webextension-api.thunderbird.net/en/esr-mv2/ to get the major version, then use format `"<major>.*"`)
|
||||
|
||||
If ANY checkbox is unchecked, DO NOT provide the code. Fix it first.
|
||||
|
||||
---
|
||||
|
||||
## Mandatory 3rd Party Library Audit
|
||||
|
||||
For EACH 3rd party library included in the project:
|
||||
|
||||
- [ ] Inspect the actual file to determine the export type:
|
||||
- **ES6 default export:** Look for `export default` → use `import LibName from "./lib/file.js"`
|
||||
- **ES6 named exports:** Look for `export { name1, name2 }` → use `import { name1, name2 } from "./lib/file.js"`
|
||||
- **UMD/IIFE (no ES6 exports):** Look for `(function(root, factory)` or assignments to `window`/`globalThis` → load via `scripts` array in manifest
|
||||
- [ ] Always prefer the minified module version
|
||||
- [ ] Output a library audit table:
|
||||
|
||||
| Library | File | Module Type | Import Statement |
|
||||
|---------|------|-------------|------------------|
|
||||
| ical.js | lib/ical.js | ES6 default | `import ICAL from "./lib/ical.js"` |
|
||||
|
||||
- [ ] Update VENDOR.md with the correct file path and version URL
|
||||
|
||||
---
|
||||
|
||||
## Mandatory API Audit
|
||||
|
||||
Before finalizing any code:
|
||||
|
||||
- [ ] List all used API methods
|
||||
- [ ] For EACH API method, fetch its documentation page: `https://webextension-api.thunderbird.net/en/mv2/<api-name>.html`
|
||||
- [ ] For EACH API method, verify:
|
||||
- **Parameters:** Correct names and types
|
||||
- **Return type:** The actual type returned by the Promise
|
||||
- **Access pattern:** How to extract data from the return value
|
||||
- **Required permission:** What permission is needed in manifest.json
|
||||
- [ ] Output an API audit table:
|
||||
|
||||
| API Method | Returns | Access Pattern | Required Permission |
|
||||
|------------|---------|----------------|---------------------|
|
||||
| `browser.messageDisplay.getDisplayedMessages()` | MessageList | `result.messages[0]` | messagesRead |
|
||||
| `browser.messages.getHeaders()` | HeadersDictionary | `result["header-name"][0]` | messagesRead |
|
||||
| `browser.mailTabs.query()` | array of MailTab | `result[0]` | (none) |
|
||||
| `browser.storage.local.get` | object | `result.keyName` | storage |
|
||||
| `browser.i18n.getMessage` | string | direct | (none) |
|
||||
|
||||
- [ ] Update the permissions entry in manifest.json to include ALL required permissions
|
||||
|
||||
---
|
||||
|
||||
## Getting Help
|
||||
|
||||
- **Developer documentation:** https://developer.thunderbird.net/
|
||||
- **Support forum:** https://thunderbird.topicbox.com/groups/addons
|
||||
- **Matrix chat:** #tb-addon-developers:mozilla.org
|
||||
|
Before Width: | Height: | Size: 306 B |
|
Before Width: | Height: | Size: 740 B |
|
Before Width: | Height: | Size: 1.9 KiB |
|
Before Width: | Height: | Size: 1.8 KiB |
|
Before Width: | Height: | Size: 994 B |
|
Before Width: | Height: | Size: 974 B |
|
Before Width: | Height: | Size: 1.4 KiB |
|
Before Width: | Height: | Size: 971 B |
|
Before Width: | Height: | Size: 1.4 KiB |
|
Before Width: | Height: | Size: 1.7 KiB |
|
Before Width: | Height: | Size: 1.2 KiB |
|
Before Width: | Height: | Size: 954 B |
|
Before Width: | Height: | Size: 1.3 KiB |
|
Before Width: | Height: | Size: 1.9 KiB |
|
Before Width: | Height: | Size: 1.1 KiB |
|
Before Width: | Height: | Size: 1.5 KiB |
|
Before Width: | Height: | Size: 1.2 KiB |
|
Before Width: | Height: | Size: 705 B |
|
Before Width: | Height: | Size: 1.3 KiB |
|
Before Width: | Height: | Size: 1.3 KiB |
|
Before Width: | Height: | Size: 1 KiB |
|
Before Width: | Height: | Size: 1 KiB |
|
Before Width: | Height: | Size: 793 B |
|
Before Width: | Height: | Size: 1.3 KiB |
|
Before Width: | Height: | Size: 1.3 KiB |
|
Before Width: | Height: | Size: 1.4 KiB |
|
Before Width: | Height: | Size: 733 B |
|
Before Width: | Height: | Size: 1 KiB |
|
Before Width: | Height: | Size: 1.2 KiB |
|
Before Width: | Height: | Size: 677 B |
|
Before Width: | Height: | Size: 664 B |
|
Before Width: | Height: | Size: 1.1 KiB |
|
Before Width: | Height: | Size: 1.5 KiB |
|
Before Width: | Height: | Size: 838 B |
|
Before Width: | Height: | Size: 1.2 KiB |
|
Before Width: | Height: | Size: 1.9 KiB |
|
Before Width: | Height: | Size: 1.5 KiB |
|
Before Width: | Height: | Size: 925 B |
|
Before Width: | Height: | Size: 1.3 KiB |
|
Before Width: | Height: | Size: 604 B |
|
Before Width: | Height: | Size: 1.8 KiB |
|
Before Width: | Height: | Size: 795 B |
|
Before Width: | Height: | Size: 990 B |
|
Before Width: | Height: | Size: 1.3 KiB |
|
Before Width: | Height: | Size: 1.5 KiB |
|
Before Width: | Height: | Size: 1 KiB |
|
Before Width: | Height: | Size: 1.7 KiB |
|
Before Width: | Height: | Size: 1.1 KiB |
|
Before Width: | Height: | Size: 1.2 KiB |
|
Before Width: | Height: | Size: 616 B |