@yallaling/web-ai-components
Version:
Universal Web AI Web Components built with Lit and secure practices
451 lines (432 loc) • 14.8 kB
JavaScript
"use strict";
var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) {
var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
return c > 3 && r && Object.defineProperty(target, key, r), r;
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.AITranslatorElement = void 0;
const lit_1 = require("lit");
const decorators_js_1 = require("lit/decorators.js");
const web_ai_core_1 = require("@yallaling/web-ai-core");
/**
* Universal AI Translator Web Component
* Built with Lit for maximum framework compatibility
*/
let AITranslatorElement = class AITranslatorElement extends lit_1.LitElement {
constructor() {
super(...arguments);
// Public properties
this.sourceLanguage = 'auto';
this.targetLanguage = 'en';
this.autoTranslate = false;
this.showInput = true;
this.showControls = true;
this.showOutput = true;
this.controlsOnly = false;
this.streaming = false;
this.resizable = true;
this.allowCopy = true;
this.allowDownload = true;
this.placeholder = 'Enter text to translate...';
this.maxLength = 5000;
this.downloadFileName = 'translation.md';
this.text = '';
this.externalText = '';
// Internal state
this.inputText = '';
this.translatedText = '';
this.isLoading = false;
this.error = '';
this.isSupported = false;
this.translator = null;
this.handleExternalTranslation = () => {
this.handleTranslate();
};
}
connectedCallback() {
super.connectedCallback();
this.initializeTranslator();
this.checkSupport();
// Listen for external events
this.addEventListener('trigger-translation', this.handleExternalTranslation);
}
disconnectedCallback() {
super.disconnectedCallback();
this.cleanup();
}
async checkSupport() {
if (!this.translator)
return;
this.isSupported = await this.translator.isSupported();
}
initializeTranslator() {
this.translator = new web_ai_core_1.WebAITranslator(this.sourceLanguage, this.targetLanguage);
// Set up event listeners
this.translator.addEventListener('translation-start', () => {
this.isLoading = true;
this.error = '';
});
this.translator.addEventListener('translation-complete', (event) => {
this.translatedText = event.detail.translatedText;
this.isLoading = false;
// Dispatch React-compatible event
this.dispatchEvent(new CustomEvent('translation-complete', {
detail: {
translatedText: event.detail.translatedText,
originalText: event.detail.originalText,
sourceLanguage: event.detail.sourceLanguage,
targetLanguage: event.detail.targetLanguage
},
bubbles: true
}));
});
this.translator.addEventListener('error', (event) => {
this.error = event.detail.error;
this.isLoading = false;
// Dispatch React-compatible event
this.dispatchEvent(new CustomEvent('translation-error', {
detail: { error: event.detail.error },
bubbles: true
}));
});
this.translator.addEventListener('progress', (event) => {
// Dispatch React-compatible event
this.dispatchEvent(new CustomEvent('translation-progress', {
detail: {
loaded: event.detail.loaded,
total: event.detail.total
},
bubbles: true
}));
});
}
cleanup() {
if (this.translator) {
this.translator.destroy();
this.translator = null;
}
}
async handleTranslate() {
if (!this.translator)
return;
const textToTranslate = this.externalText || this.inputText || this.text;
if (!textToTranslate.trim()) {
this.error = 'Please enter text to translate';
return;
}
try {
await this.translator.translate(textToTranslate);
}
catch (error) {
console.error('Translation failed:', error);
}
}
handleInputChange(event) {
const target = event.target;
this.inputText = target.value;
if (this.autoTranslate && this.inputText.trim()) {
// Debounce auto-translation
clearTimeout(this.autoTranslateTimeout);
this.autoTranslateTimeout = setTimeout(() => {
this.handleTranslate();
}, 500);
}
}
handleLanguageChange() {
if (this.translator) {
this.translator.setSourceLanguage(this.sourceLanguage);
this.translator.setTargetLanguage(this.targetLanguage);
}
}
updated(changedProperties) {
if (changedProperties.has('sourceLanguage') || changedProperties.has('targetLanguage')) {
this.handleLanguageChange();
}
if (changedProperties.has('externalText') && this.externalText && this.autoTranslate) {
this.handleTranslate();
}
}
render() {
if (this.controlsOnly) {
return (0, lit_1.html) `
<div class="translator-container">
${this.showControls ? this.renderControls() : ''}
${this.error ? (0, lit_1.html) `<div class="error">${this.error}</div>` : ''}
${this.isLoading ? (0, lit_1.html) `<div class="loading">Translating...</div>` : ''}
</div>
`;
}
return (0, lit_1.html) `
<div class="translator-container">
${this.showInput ? this.renderInput() : ''}
${this.showControls ? this.renderControls() : ''}
${this.showOutput ? this.renderOutput() : ''}
${this.error ? (0, lit_1.html) `<div class="error">${this.error}</div>` : ''}
</div>
`;
}
renderInput() {
return (0, lit_1.html) `
<div class="input-section">
<textarea
.value=${this.inputText}
=${this.handleInputChange}
placeholder=${this.placeholder}
maxlength=${this.maxLength}
></textarea>
</div>
`;
}
renderControls() {
return (0, lit_1.html) `
<div class="controls-section">
<select
.value=${this.sourceLanguage}
=${(e) => {
this.sourceLanguage = e.target.value;
}}
>
<option value="auto">Auto-detect</option>
<option value="en">English</option>
<option value="es">Spanish</option>
<option value="fr">French</option>
<option value="de">German</option>
<option value="it">Italian</option>
<option value="pt">Portuguese</option>
<option value="ru">Russian</option>
<option value="ja">Japanese</option>
<option value="ko">Korean</option>
<option value="zh">Chinese</option>
</select>
<span>→</span>
<select
.value=${this.targetLanguage}
=${(e) => {
this.targetLanguage = e.target.value;
}}
>
<option value="en">English</option>
<option value="es">Spanish</option>
<option value="fr">French</option>
<option value="de">German</option>
<option value="it">Italian</option>
<option value="pt">Portuguese</option>
<option value="ru">Russian</option>
<option value="ja">Japanese</option>
<option value="ko">Korean</option>
<option value="zh">Chinese</option>
</select>
<button
=${this.handleTranslate}
?disabled=${this.isLoading || !this.isSupported}
>
${this.isLoading ? 'Translating...' : 'Translate'}
</button>
</div>
`;
}
renderOutput() {
return (0, lit_1.html) `
<div class="output-section">
<div class="output-text">
${this.translatedText || (this.isLoading ? 'Translating...' : 'Translation will appear here')}
</div>
${this.translatedText && this.allowCopy ? (0, lit_1.html) `
<button
class="secondary-button"
=${this.handleCopy}
>
Copy Translation
</button>
` : ''}
</div>
`;
}
async handleCopy() {
if (this.translatedText) {
try {
await navigator.clipboard.writeText(this.translatedText);
// Could add a toast notification here
}
catch (error) {
console.error('Failed to copy text:', error);
}
}
}
};
exports.AITranslatorElement = AITranslatorElement;
AITranslatorElement.styles = (0, lit_1.css) `
:host {
display: block;
font-family: system-ui, -apple-system, sans-serif;
border: 1px solid #e0e0e0;
border-radius: 8px;
padding: 16px;
background: white;
--primary-color: #007bff;
--error-color: #dc3545;
--success-color: #28a745;
}
.translator-container {
display: flex;
flex-direction: column;
gap: 16px;
}
.input-section, .output-section {
display: flex;
flex-direction: column;
gap: 8px;
}
.controls-section {
display: flex;
gap: 8px;
align-items: center;
flex-wrap: wrap;
}
textarea {
width: 100%;
min-height: 100px;
padding: 12px;
border: 1px solid #ddd;
border-radius: 4px;
font-family: inherit;
font-size: 14px;
resize: vertical;
}
textarea:focus {
outline: none;
border-color: var(--primary-color);
box-shadow: 0 0 0 2px rgba(0, 123, 255, 0.25);
}
.output-text {
min-height: 100px;
padding: 12px;
border: 1px solid #ddd;
border-radius: 4px;
background: #f8f9fa;
white-space: pre-wrap;
font-size: 14px;
}
button {
padding: 8px 16px;
border: 1px solid var(--primary-color);
border-radius: 4px;
background: var(--primary-color);
color: white;
cursor: pointer;
font-size: 14px;
transition: all 0.2s;
}
button:hover:not(:disabled) {
background: #0056b3;
border-color: #0056b3;
}
button:disabled {
opacity: 0.5;
cursor: not-allowed;
}
.secondary-button {
background: transparent;
color: var(--primary-color);
}
.secondary-button:hover:not(:disabled) {
background: var(--primary-color);
color: white;
}
select {
padding: 8px;
border: 1px solid #ddd;
border-radius: 4px;
font-size: 14px;
}
.error {
color: var(--error-color);
font-size: 14px;
padding: 8px;
background: #f8d7da;
border-radius: 4px;
}
.loading {
color: var(--primary-color);
font-size: 14px;
padding: 8px;
}
.hidden {
display: none;
}
(max-width: 768px) {
.controls-section {
flex-direction: column;
align-items: stretch;
}
}
`;
__decorate([
(0, decorators_js_1.property)({ attribute: 'source-language' })
], AITranslatorElement.prototype, "sourceLanguage", void 0);
__decorate([
(0, decorators_js_1.property)({ attribute: 'target-language' })
], AITranslatorElement.prototype, "targetLanguage", void 0);
__decorate([
(0, decorators_js_1.property)({ type: Boolean, attribute: 'auto-translate' })
], AITranslatorElement.prototype, "autoTranslate", void 0);
__decorate([
(0, decorators_js_1.property)({ type: Boolean, attribute: 'show-input' })
], AITranslatorElement.prototype, "showInput", void 0);
__decorate([
(0, decorators_js_1.property)({ type: Boolean, attribute: 'show-controls' })
], AITranslatorElement.prototype, "showControls", void 0);
__decorate([
(0, decorators_js_1.property)({ type: Boolean, attribute: 'show-output' })
], AITranslatorElement.prototype, "showOutput", void 0);
__decorate([
(0, decorators_js_1.property)({ type: Boolean, attribute: 'controls-only' })
], AITranslatorElement.prototype, "controlsOnly", void 0);
__decorate([
(0, decorators_js_1.property)({ type: Boolean })
], AITranslatorElement.prototype, "streaming", void 0);
__decorate([
(0, decorators_js_1.property)({ type: Boolean })
], AITranslatorElement.prototype, "resizable", void 0);
__decorate([
(0, decorators_js_1.property)({ type: Boolean, attribute: 'allow-copy' })
], AITranslatorElement.prototype, "allowCopy", void 0);
__decorate([
(0, decorators_js_1.property)({ type: Boolean, attribute: 'allow-download' })
], AITranslatorElement.prototype, "allowDownload", void 0);
__decorate([
(0, decorators_js_1.property)()
], AITranslatorElement.prototype, "placeholder", void 0);
__decorate([
(0, decorators_js_1.property)({ type: Number, attribute: 'max-length' })
], AITranslatorElement.prototype, "maxLength", void 0);
__decorate([
(0, decorators_js_1.property)({ attribute: 'download-filename' })
], AITranslatorElement.prototype, "downloadFileName", void 0);
__decorate([
(0, decorators_js_1.property)()
], AITranslatorElement.prototype, "text", void 0);
__decorate([
(0, decorators_js_1.property)({ attribute: 'external-text' })
], AITranslatorElement.prototype, "externalText", void 0);
__decorate([
(0, decorators_js_1.state)()
], AITranslatorElement.prototype, "inputText", void 0);
__decorate([
(0, decorators_js_1.state)()
], AITranslatorElement.prototype, "translatedText", void 0);
__decorate([
(0, decorators_js_1.state)()
], AITranslatorElement.prototype, "isLoading", void 0);
__decorate([
(0, decorators_js_1.state)()
], AITranslatorElement.prototype, "error", void 0);
__decorate([
(0, decorators_js_1.state)()
], AITranslatorElement.prototype, "isSupported", void 0);
exports.AITranslatorElement = AITranslatorElement = __decorate([
(0, decorators_js_1.customElement)('ai-translator-element')
], AITranslatorElement);
//# sourceMappingURL=ai-translator.js.map