listen feature refactor done
This commit is contained in:
parent
80a3c01656
commit
aa9252880b
@ -1,4 +1,6 @@
|
|||||||
import { html, css, LitElement } from '../../assets/lit-core-2.7.4.min.js';
|
import { html, css, LitElement } from '../../assets/lit-core-2.7.4.min.js';
|
||||||
|
import './stt/SttView.js';
|
||||||
|
import './summary/SummaryView.js';
|
||||||
|
|
||||||
export class AssistantView extends LitElement {
|
export class AssistantView extends LitElement {
|
||||||
static styles = css`
|
static styles = css`
|
||||||
@ -82,73 +84,6 @@ export class AssistantView extends LitElement {
|
|||||||
user-select: none;
|
user-select: none;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* highlight.js 스타일 추가 */
|
|
||||||
.insights-container pre {
|
|
||||||
background: rgba(0, 0, 0, 0.4) !important;
|
|
||||||
border-radius: 8px !important;
|
|
||||||
padding: 12px !important;
|
|
||||||
margin: 8px 0 !important;
|
|
||||||
overflow-x: auto !important;
|
|
||||||
border: 1px solid rgba(255, 255, 255, 0.1) !important;
|
|
||||||
white-space: pre !important;
|
|
||||||
word-wrap: normal !important;
|
|
||||||
word-break: normal !important;
|
|
||||||
}
|
|
||||||
|
|
||||||
.insights-container code {
|
|
||||||
font-family: 'Monaco', 'Menlo', 'Consolas', monospace !important;
|
|
||||||
font-size: 11px !important;
|
|
||||||
background: transparent !important;
|
|
||||||
white-space: pre !important;
|
|
||||||
word-wrap: normal !important;
|
|
||||||
word-break: normal !important;
|
|
||||||
}
|
|
||||||
|
|
||||||
.insights-container pre code {
|
|
||||||
white-space: pre !important;
|
|
||||||
word-wrap: normal !important;
|
|
||||||
word-break: normal !important;
|
|
||||||
display: block !important;
|
|
||||||
}
|
|
||||||
|
|
||||||
.insights-container p code {
|
|
||||||
background: rgba(255, 255, 255, 0.1) !important;
|
|
||||||
padding: 2px 4px !important;
|
|
||||||
border-radius: 3px !important;
|
|
||||||
color: #ffd700 !important;
|
|
||||||
}
|
|
||||||
|
|
||||||
.hljs-keyword {
|
|
||||||
color: #ff79c6 !important;
|
|
||||||
}
|
|
||||||
.hljs-string {
|
|
||||||
color: #f1fa8c !important;
|
|
||||||
}
|
|
||||||
.hljs-comment {
|
|
||||||
color: #6272a4 !important;
|
|
||||||
}
|
|
||||||
.hljs-number {
|
|
||||||
color: #bd93f9 !important;
|
|
||||||
}
|
|
||||||
.hljs-function {
|
|
||||||
color: #50fa7b !important;
|
|
||||||
}
|
|
||||||
.hljs-variable {
|
|
||||||
color: #8be9fd !important;
|
|
||||||
}
|
|
||||||
.hljs-built_in {
|
|
||||||
color: #ffb86c !important;
|
|
||||||
}
|
|
||||||
.hljs-title {
|
|
||||||
color: #50fa7b !important;
|
|
||||||
}
|
|
||||||
.hljs-attr {
|
|
||||||
color: #50fa7b !important;
|
|
||||||
}
|
|
||||||
.hljs-tag {
|
|
||||||
color: #ff79c6 !important;
|
|
||||||
}
|
|
||||||
|
|
||||||
.assistant-container {
|
.assistant-container {
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
@ -158,8 +93,6 @@ export class AssistantView extends LitElement {
|
|||||||
background: rgba(0, 0, 0, 0.6);
|
background: rgba(0, 0, 0, 0.6);
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
border-radius: 12px;
|
border-radius: 12px;
|
||||||
/* outline: 0.5px rgba(255, 255, 255, 0.5) solid; */
|
|
||||||
/* outline-offset: -1px; */
|
|
||||||
width: 100%;
|
width: 100%;
|
||||||
min-height: 200px;
|
min-height: 200px;
|
||||||
}
|
}
|
||||||
@ -171,7 +104,7 @@ export class AssistantView extends LitElement {
|
|||||||
left: 0;
|
left: 0;
|
||||||
right: 0;
|
right: 0;
|
||||||
bottom: 0;
|
bottom: 0;
|
||||||
border-radius: 12px; /* Match parent */
|
border-radius: 12px;
|
||||||
padding: 1px;
|
padding: 1px;
|
||||||
background: linear-gradient(169deg, rgba(255, 255, 255, 0.17) 0%, rgba(255, 255, 255, 0.08) 50%, rgba(255, 255, 255, 0.17) 100%);
|
background: linear-gradient(169deg, rgba(255, 255, 255, 0.17) 0%, rgba(255, 255, 255, 0.08) 50%, rgba(255, 255, 255, 0.17) 100%);
|
||||||
-webkit-mask: linear-gradient(#fff 0 0) content-box, linear-gradient(#fff 0 0);
|
-webkit-mask: linear-gradient(#fff 0 0) content-box, linear-gradient(#fff 0 0);
|
||||||
@ -299,8 +232,8 @@ export class AssistantView extends LitElement {
|
|||||||
height: 24px;
|
height: 24px;
|
||||||
flex-shrink: 0;
|
flex-shrink: 0;
|
||||||
transition: background-color 0.15s ease;
|
transition: background-color 0.15s ease;
|
||||||
position: relative; /* For icon positioning */
|
position: relative;
|
||||||
overflow: hidden; /* Hide overflowing parts of icons during animation */
|
overflow: hidden;
|
||||||
}
|
}
|
||||||
|
|
||||||
.copy-button:hover {
|
.copy-button:hover {
|
||||||
@ -330,213 +263,6 @@ export class AssistantView extends LitElement {
|
|||||||
transform: translate(-50%, -50%) scale(1);
|
transform: translate(-50%, -50%) scale(1);
|
||||||
}
|
}
|
||||||
|
|
||||||
.transcription-container {
|
|
||||||
overflow-y: auto;
|
|
||||||
padding: 12px 12px 16px 12px;
|
|
||||||
display: flex;
|
|
||||||
flex-direction: column;
|
|
||||||
gap: 8px;
|
|
||||||
min-height: 150px;
|
|
||||||
max-height: 600px;
|
|
||||||
position: relative;
|
|
||||||
z-index: 1;
|
|
||||||
flex: 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
.transcription-container.hidden {
|
|
||||||
display: none;
|
|
||||||
}
|
|
||||||
|
|
||||||
.transcription-container::-webkit-scrollbar {
|
|
||||||
width: 8px;
|
|
||||||
}
|
|
||||||
.transcription-container::-webkit-scrollbar-track {
|
|
||||||
background: rgba(0, 0, 0, 0.1);
|
|
||||||
border-radius: 4px;
|
|
||||||
}
|
|
||||||
.transcription-container::-webkit-scrollbar-thumb {
|
|
||||||
background: rgba(255, 255, 255, 0.3);
|
|
||||||
border-radius: 4px;
|
|
||||||
}
|
|
||||||
.transcription-container::-webkit-scrollbar-thumb:hover {
|
|
||||||
background: rgba(255, 255, 255, 0.5);
|
|
||||||
}
|
|
||||||
|
|
||||||
.stt-message {
|
|
||||||
padding: 8px 12px;
|
|
||||||
border-radius: 12px;
|
|
||||||
max-width: 80%;
|
|
||||||
word-wrap: break-word;
|
|
||||||
word-break: break-word;
|
|
||||||
line-height: 1.5;
|
|
||||||
font-size: 13px;
|
|
||||||
margin-bottom: 4px;
|
|
||||||
box-sizing: border-box;
|
|
||||||
}
|
|
||||||
|
|
||||||
.stt-message.them {
|
|
||||||
background: rgba(255, 255, 255, 0.1);
|
|
||||||
color: rgba(255, 255, 255, 0.9);
|
|
||||||
align-self: flex-start;
|
|
||||||
border-bottom-left-radius: 4px;
|
|
||||||
margin-right: auto;
|
|
||||||
}
|
|
||||||
|
|
||||||
.stt-message.me {
|
|
||||||
background: rgba(0, 122, 255, 0.8);
|
|
||||||
color: white;
|
|
||||||
align-self: flex-end;
|
|
||||||
border-bottom-right-radius: 4px;
|
|
||||||
margin-left: auto;
|
|
||||||
}
|
|
||||||
|
|
||||||
.insights-container {
|
|
||||||
overflow-y: auto;
|
|
||||||
padding: 12px 16px 16px 16px;
|
|
||||||
position: relative;
|
|
||||||
z-index: 1;
|
|
||||||
min-height: 150px;
|
|
||||||
max-height: 600px;
|
|
||||||
flex: 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
insights-title {
|
|
||||||
color: rgba(255, 255, 255, 0.8);
|
|
||||||
font-size: 15px;
|
|
||||||
font-weight: 500;
|
|
||||||
font-family: 'Helvetica Neue', sans-serif;
|
|
||||||
margin: 12px 0 8px 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
.insights-container.hidden {
|
|
||||||
display: none;
|
|
||||||
}
|
|
||||||
|
|
||||||
.insights-container::-webkit-scrollbar {
|
|
||||||
width: 8px;
|
|
||||||
}
|
|
||||||
.insights-container::-webkit-scrollbar-track {
|
|
||||||
background: rgba(0, 0, 0, 0.1);
|
|
||||||
border-radius: 4px;
|
|
||||||
}
|
|
||||||
.insights-container::-webkit-scrollbar-thumb {
|
|
||||||
background: rgba(255, 255, 255, 0.3);
|
|
||||||
border-radius: 4px;
|
|
||||||
}
|
|
||||||
.insights-container::-webkit-scrollbar-thumb:hover {
|
|
||||||
background: rgba(255, 255, 255, 0.5);
|
|
||||||
}
|
|
||||||
|
|
||||||
.insights-container h4 {
|
|
||||||
color: #ffffff;
|
|
||||||
font-size: 12px;
|
|
||||||
font-weight: 600;
|
|
||||||
margin: 12px 0 8px 0;
|
|
||||||
padding: 4px 8px;
|
|
||||||
border-radius: 4px;
|
|
||||||
background: transparent;
|
|
||||||
cursor: default;
|
|
||||||
}
|
|
||||||
|
|
||||||
.insights-container h4:hover {
|
|
||||||
background: transparent;
|
|
||||||
}
|
|
||||||
|
|
||||||
.insights-container h4:first-child {
|
|
||||||
margin-top: 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
.outline-item {
|
|
||||||
color: #ffffff;
|
|
||||||
font-size: 11px;
|
|
||||||
line-height: 1.4;
|
|
||||||
margin: 4px 0;
|
|
||||||
padding: 6px 8px;
|
|
||||||
border-radius: 4px;
|
|
||||||
background: transparent;
|
|
||||||
transition: background-color 0.15s ease;
|
|
||||||
cursor: pointer;
|
|
||||||
word-wrap: break-word;
|
|
||||||
}
|
|
||||||
|
|
||||||
.outline-item:hover {
|
|
||||||
background: rgba(255, 255, 255, 0.1);
|
|
||||||
}
|
|
||||||
|
|
||||||
.request-item {
|
|
||||||
color: #ffffff;
|
|
||||||
font-size: 12px;
|
|
||||||
line-height: 1.2;
|
|
||||||
margin: 4px 0;
|
|
||||||
padding: 6px 8px;
|
|
||||||
border-radius: 4px;
|
|
||||||
background: transparent;
|
|
||||||
cursor: default;
|
|
||||||
word-wrap: break-word;
|
|
||||||
transition: background-color 0.15s ease;
|
|
||||||
}
|
|
||||||
|
|
||||||
.request-item.clickable {
|
|
||||||
cursor: pointer;
|
|
||||||
transition: all 0.15s ease;
|
|
||||||
}
|
|
||||||
.request-item.clickable:hover {
|
|
||||||
background: rgba(255, 255, 255, 0.1);
|
|
||||||
transform: translateX(2px);
|
|
||||||
}
|
|
||||||
|
|
||||||
/* 마크다운 렌더링된 콘텐츠 스타일 */
|
|
||||||
.markdown-content {
|
|
||||||
color: #ffffff;
|
|
||||||
font-size: 11px;
|
|
||||||
line-height: 1.4;
|
|
||||||
margin: 4px 0;
|
|
||||||
padding: 6px 8px;
|
|
||||||
border-radius: 4px;
|
|
||||||
background: transparent;
|
|
||||||
cursor: pointer;
|
|
||||||
word-wrap: break-word;
|
|
||||||
transition: all 0.15s ease;
|
|
||||||
}
|
|
||||||
|
|
||||||
.markdown-content:hover {
|
|
||||||
background: rgba(255, 255, 255, 0.1);
|
|
||||||
transform: translateX(2px);
|
|
||||||
}
|
|
||||||
|
|
||||||
.markdown-content p {
|
|
||||||
margin: 4px 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
.markdown-content ul,
|
|
||||||
.markdown-content ol {
|
|
||||||
margin: 4px 0;
|
|
||||||
padding-left: 16px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.markdown-content li {
|
|
||||||
margin: 2px 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
.markdown-content a {
|
|
||||||
color: #8be9fd;
|
|
||||||
text-decoration: none;
|
|
||||||
}
|
|
||||||
|
|
||||||
.markdown-content a:hover {
|
|
||||||
text-decoration: underline;
|
|
||||||
}
|
|
||||||
|
|
||||||
.markdown-content strong {
|
|
||||||
font-weight: 600;
|
|
||||||
color: #f8f8f2;
|
|
||||||
}
|
|
||||||
|
|
||||||
.markdown-content em {
|
|
||||||
font-style: italic;
|
|
||||||
color: #f1fa8c;
|
|
||||||
}
|
|
||||||
|
|
||||||
.timer {
|
.timer {
|
||||||
font-family: 'Monaco', 'Menlo', monospace;
|
font-family: 'Monaco', 'Menlo', monospace;
|
||||||
font-size: 10px;
|
font-size: 10px;
|
||||||
@ -545,10 +271,6 @@ export class AssistantView extends LitElement {
|
|||||||
`;
|
`;
|
||||||
|
|
||||||
static properties = {
|
static properties = {
|
||||||
structuredData: { type: Object },
|
|
||||||
// outlines: { type: Array },
|
|
||||||
// analysisRequests: { type: Array },
|
|
||||||
sttMessages: { type: Array },
|
|
||||||
viewMode: { type: String },
|
viewMode: { type: String },
|
||||||
isHovering: { type: Boolean },
|
isHovering: { type: Boolean },
|
||||||
isAnimating: { type: Boolean },
|
isAnimating: { type: Boolean },
|
||||||
@ -561,183 +283,66 @@ export class AssistantView extends LitElement {
|
|||||||
|
|
||||||
constructor() {
|
constructor() {
|
||||||
super();
|
super();
|
||||||
// this.outlines = [];
|
|
||||||
// this.analysisRequests = [];
|
|
||||||
this.structuredData = {
|
|
||||||
summary: [],
|
|
||||||
topic: { header: '', bullets: [] },
|
|
||||||
actions: [],
|
|
||||||
followUps: [],
|
|
||||||
};
|
|
||||||
this.isSessionActive = false;
|
this.isSessionActive = false;
|
||||||
this.hasCompletedRecording = false;
|
this.hasCompletedRecording = false;
|
||||||
this.sttMessages = [];
|
|
||||||
this.viewMode = 'insights';
|
this.viewMode = 'insights';
|
||||||
this.isHovering = false;
|
this.isHovering = false;
|
||||||
this.isAnimating = false;
|
this.isAnimating = false;
|
||||||
this.elapsedTime = '00:00';
|
this.elapsedTime = '00:00';
|
||||||
this.captureStartTime = null;
|
this.captureStartTime = null;
|
||||||
this.timerInterval = null;
|
this.timerInterval = null;
|
||||||
this.resizeObserver = null;
|
|
||||||
this.adjustHeightThrottle = null;
|
this.adjustHeightThrottle = null;
|
||||||
this.isThrottled = false;
|
this.isThrottled = false;
|
||||||
this._shouldScrollAfterUpdate = false;
|
|
||||||
this.messageIdCounter = 0;
|
|
||||||
this.copyState = 'idle';
|
this.copyState = 'idle';
|
||||||
this.copyTimeout = null;
|
this.copyTimeout = null;
|
||||||
|
|
||||||
// 마크다운 라이브러리 초기화
|
|
||||||
this.marked = null;
|
|
||||||
this.hljs = null;
|
|
||||||
this.isLibrariesLoaded = false;
|
|
||||||
this.DOMPurify = null;
|
|
||||||
this.isDOMPurifyLoaded = false;
|
|
||||||
|
|
||||||
// --- Debug Utilities ---
|
|
||||||
this._debug = {
|
|
||||||
enabled: false, // Set to false to disable debug messages
|
|
||||||
interval: null,
|
|
||||||
counter: 1,
|
|
||||||
};
|
|
||||||
this.handleSttUpdate = this.handleSttUpdate.bind(this);
|
|
||||||
this.adjustWindowHeight = this.adjustWindowHeight.bind(this);
|
this.adjustWindowHeight = this.adjustWindowHeight.bind(this);
|
||||||
|
|
||||||
this.loadLibraries();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// --- Debug Utilities ---
|
connectedCallback() {
|
||||||
_startDebugStream() {
|
super.connectedCallback();
|
||||||
if (!this._debug.enabled) return;
|
// Only start timer if session is active
|
||||||
|
if (this.isSessionActive) {
|
||||||
this._debug.interval = setInterval(() => {
|
this.startTimer();
|
||||||
const speaker = this._debug.counter % 2 === 0 ? 'You' : 'Other Person';
|
|
||||||
const text = `이것은 ${this._debug.counter}번째 자동 생성 메시지입니다. UI가 자동으로 조절되는지 확인합니다.`;
|
|
||||||
|
|
||||||
this._debug.counter++;
|
|
||||||
|
|
||||||
this.handleSttUpdate(null, { speaker, text, isFinal: true });
|
|
||||||
}, 1000);
|
|
||||||
}
|
}
|
||||||
|
if (window.require) {
|
||||||
|
const { ipcRenderer } = window.require('electron');
|
||||||
|
ipcRenderer.on('session-state-changed', (event, { isActive }) => {
|
||||||
|
const wasActive = this.isSessionActive;
|
||||||
|
this.isSessionActive = isActive;
|
||||||
|
|
||||||
_stopDebugStream() {
|
if (!wasActive && isActive) {
|
||||||
if (this._debug.interval) {
|
this.hasCompletedRecording = false;
|
||||||
clearInterval(this._debug.interval);
|
this.startTimer();
|
||||||
}
|
// Reset child components
|
||||||
}
|
this.updateComplete.then(() => {
|
||||||
|
const sttView = this.shadowRoot.querySelector('stt-view');
|
||||||
async loadLibraries() {
|
const summaryView = this.shadowRoot.querySelector('summary-view');
|
||||||
try {
|
if (sttView) sttView.resetTranscript();
|
||||||
if (!window.marked) {
|
if (summaryView) summaryView.resetAnalysis();
|
||||||
await this.loadScript('../../assets/marked-4.3.0.min.js');
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!window.hljs) {
|
|
||||||
await this.loadScript('../../assets/highlight-11.9.0.min.js');
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!window.DOMPurify) {
|
|
||||||
await this.loadScript('../../assets/dompurify-3.0.7.min.js');
|
|
||||||
}
|
|
||||||
|
|
||||||
this.marked = window.marked;
|
|
||||||
this.hljs = window.hljs;
|
|
||||||
this.DOMPurify = window.DOMPurify;
|
|
||||||
|
|
||||||
if (this.marked && this.hljs) {
|
|
||||||
this.marked.setOptions({
|
|
||||||
highlight: (code, lang) => {
|
|
||||||
if (lang && this.hljs.getLanguage(lang)) {
|
|
||||||
try {
|
|
||||||
return this.hljs.highlight(code, { language: lang }).value;
|
|
||||||
} catch (err) {
|
|
||||||
console.warn('Highlight error:', err);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
try {
|
|
||||||
return this.hljs.highlightAuto(code).value;
|
|
||||||
} catch (err) {
|
|
||||||
console.warn('Auto highlight error:', err);
|
|
||||||
}
|
|
||||||
return code;
|
|
||||||
},
|
|
||||||
breaks: true,
|
|
||||||
gfm: true,
|
|
||||||
pedantic: false,
|
|
||||||
smartypants: false,
|
|
||||||
xhtml: false,
|
|
||||||
});
|
});
|
||||||
|
this.requestUpdate();
|
||||||
this.isLibrariesLoaded = true;
|
|
||||||
console.log('Markdown libraries loaded successfully');
|
|
||||||
}
|
}
|
||||||
|
if (wasActive && !isActive) {
|
||||||
if (this.DOMPurify) {
|
this.hasCompletedRecording = true;
|
||||||
this.isDOMPurifyLoaded = true;
|
this.stopTimer();
|
||||||
console.log('DOMPurify loaded successfully in AssistantView');
|
this.requestUpdate();
|
||||||
}
|
}
|
||||||
} catch (error) {
|
|
||||||
console.error('Failed to load libraries:', error);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
loadScript(src) {
|
|
||||||
return new Promise((resolve, reject) => {
|
|
||||||
const script = document.createElement('script');
|
|
||||||
script.src = src;
|
|
||||||
script.onload = resolve;
|
|
||||||
script.onerror = reject;
|
|
||||||
document.head.appendChild(script);
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
parseMarkdown(text) {
|
|
||||||
if (!text) return '';
|
|
||||||
|
|
||||||
if (!this.isLibrariesLoaded || !this.marked) {
|
|
||||||
return text;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
try {
|
disconnectedCallback() {
|
||||||
return this.marked(text);
|
super.disconnectedCallback();
|
||||||
} catch (error) {
|
this.stopTimer();
|
||||||
console.error('Markdown parsing error:', error);
|
|
||||||
return text;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
handleMarkdownClick(originalText) {
|
if (this.adjustHeightThrottle) {
|
||||||
this.handleRequestClick(originalText);
|
clearTimeout(this.adjustHeightThrottle);
|
||||||
|
this.adjustHeightThrottle = null;
|
||||||
}
|
}
|
||||||
|
if (this.copyTimeout) {
|
||||||
renderMarkdownContent() {
|
clearTimeout(this.copyTimeout);
|
||||||
if (!this.isLibrariesLoaded || !this.marked) {
|
|
||||||
return;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const markdownElements = this.shadowRoot.querySelectorAll('[data-markdown-id]');
|
|
||||||
markdownElements.forEach(element => {
|
|
||||||
const originalText = element.getAttribute('data-original-text');
|
|
||||||
if (originalText) {
|
|
||||||
try {
|
|
||||||
let parsedHTML = this.parseMarkdown(originalText);
|
|
||||||
|
|
||||||
if (this.isDOMPurifyLoaded && this.DOMPurify) {
|
|
||||||
parsedHTML = this.DOMPurify.sanitize(parsedHTML);
|
|
||||||
|
|
||||||
if (this.DOMPurify.removed && this.DOMPurify.removed.length > 0) {
|
|
||||||
console.warn('Unsafe content detected in insights, showing plain text');
|
|
||||||
element.textContent = '⚠️ ' + originalText;
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
element.innerHTML = parsedHTML;
|
|
||||||
} catch (error) {
|
|
||||||
console.error('Error rendering markdown for element:', error);
|
|
||||||
element.textContent = originalText;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
startTimer() {
|
startTimer() {
|
||||||
@ -766,19 +371,15 @@ export class AssistantView extends LitElement {
|
|||||||
this.updateComplete
|
this.updateComplete
|
||||||
.then(() => {
|
.then(() => {
|
||||||
const topBar = this.shadowRoot.querySelector('.top-bar');
|
const topBar = this.shadowRoot.querySelector('.top-bar');
|
||||||
const activeContent =
|
const activeContent = this.viewMode === 'transcript'
|
||||||
this.viewMode === 'transcript'
|
? this.shadowRoot.querySelector('stt-view')
|
||||||
? this.shadowRoot.querySelector('.transcription-container')
|
: this.shadowRoot.querySelector('summary-view');
|
||||||
: this.shadowRoot.querySelector('.insights-container');
|
|
||||||
|
|
||||||
if (!topBar || !activeContent) return;
|
if (!topBar || !activeContent) return;
|
||||||
|
|
||||||
const topBarHeight = topBar.offsetHeight;
|
const topBarHeight = topBar.offsetHeight;
|
||||||
|
const contentHeight = activeContent.offsetHeight;
|
||||||
const contentHeight = activeContent.scrollHeight;
|
|
||||||
|
|
||||||
const idealHeight = topBarHeight + contentHeight + 20;
|
const idealHeight = topBarHeight + contentHeight + 20;
|
||||||
|
|
||||||
const targetHeight = Math.min(700, Math.max(200, idealHeight));
|
const targetHeight = Math.min(700, Math.max(200, idealHeight));
|
||||||
|
|
||||||
console.log(
|
console.log(
|
||||||
@ -808,62 +409,17 @@ export class AssistantView extends LitElement {
|
|||||||
this.requestUpdate();
|
this.requestUpdate();
|
||||||
}
|
}
|
||||||
|
|
||||||
parseOutlineData() {
|
|
||||||
const result = {
|
|
||||||
currentSummary: [],
|
|
||||||
mainTopicHeading: '',
|
|
||||||
mainTopicBullets: [],
|
|
||||||
};
|
|
||||||
|
|
||||||
if (!this.outlines || this.outlines.length === 0) {
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
|
|
||||||
const allBullets = this.outlines.filter(item => item.startsWith('BULLET::'));
|
|
||||||
if (allBullets.length > 0) {
|
|
||||||
result.currentSummary.push(allBullets[0].replace('BULLET::', '').trim());
|
|
||||||
}
|
|
||||||
|
|
||||||
const heading = this.outlines.find(item => item.startsWith('HEADING::'));
|
|
||||||
if (heading) {
|
|
||||||
result.mainTopicHeading = heading.replace('HEADING::', '').trim();
|
|
||||||
}
|
|
||||||
|
|
||||||
if (allBullets.length > 1) {
|
|
||||||
result.mainTopicBullets = allBullets.slice(1).map(item => item.replace('BULLET::', '').trim());
|
|
||||||
}
|
|
||||||
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
|
|
||||||
async handleCopy() {
|
async handleCopy() {
|
||||||
if (this.copyState === 'copied') return;
|
if (this.copyState === 'copied') return;
|
||||||
|
|
||||||
let textToCopy = '';
|
let textToCopy = '';
|
||||||
|
|
||||||
if (this.viewMode === 'transcript') {
|
if (this.viewMode === 'transcript') {
|
||||||
textToCopy = this.sttMessages.map(msg => `${msg.speaker}: ${msg.text}`).join('\n');
|
const sttView = this.shadowRoot.querySelector('stt-view');
|
||||||
|
textToCopy = sttView ? sttView.getTranscriptText() : '';
|
||||||
} else {
|
} else {
|
||||||
const data = this.structuredData || { summary: [], topic: { header: '', bullets: [] }, actions: [] };
|
const summaryView = this.shadowRoot.querySelector('summary-view');
|
||||||
let sections = [];
|
textToCopy = summaryView ? summaryView.getSummaryText() : '';
|
||||||
|
|
||||||
if (data.summary && data.summary.length > 0) {
|
|
||||||
sections.push(`Current Summary:\n${data.summary.map(s => `• ${s}`).join('\n')}`);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (data.topic && data.topic.header && data.topic.bullets.length > 0) {
|
|
||||||
sections.push(`\n${data.topic.header}:\n${data.topic.bullets.map(b => `• ${b}`).join('\n')}`);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (data.actions && data.actions.length > 0) {
|
|
||||||
sections.push(`\nActions:\n${data.actions.map(a => `▸ ${a}`).join('\n')}`);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (data.followUps && data.followUps.length > 0) {
|
|
||||||
sections.push(`\nFollow-Ups:\n${data.followUps.map(f => `▸ ${f}`).join('\n')}`);
|
|
||||||
}
|
|
||||||
|
|
||||||
textToCopy = sections.join('\n\n').trim();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
@ -900,177 +456,24 @@ export class AssistantView extends LitElement {
|
|||||||
}, 16);
|
}, 16);
|
||||||
}
|
}
|
||||||
|
|
||||||
handleSttUpdate(event, { speaker, text, isFinal, isPartial }) {
|
updated(changedProperties) {
|
||||||
if (text === undefined) return;
|
super.updated(changedProperties);
|
||||||
|
|
||||||
const container = this.shadowRoot.querySelector('.transcription-container');
|
if (changedProperties.has('viewMode')) {
|
||||||
this._shouldScrollAfterUpdate = container ? container.scrollTop + container.clientHeight >= container.scrollHeight - 10 : false;
|
this.adjustWindowHeight();
|
||||||
|
|
||||||
const findLastPartialIdx = spk => {
|
|
||||||
for (let i = this.sttMessages.length - 1; i >= 0; i--) {
|
|
||||||
const m = this.sttMessages[i];
|
|
||||||
if (m.speaker === spk && m.isPartial) return i;
|
|
||||||
}
|
|
||||||
return -1;
|
|
||||||
};
|
|
||||||
|
|
||||||
const newMessages = [...this.sttMessages];
|
|
||||||
const targetIdx = findLastPartialIdx(speaker);
|
|
||||||
|
|
||||||
if (isPartial) {
|
|
||||||
if (targetIdx !== -1) {
|
|
||||||
newMessages[targetIdx] = {
|
|
||||||
...newMessages[targetIdx],
|
|
||||||
text,
|
|
||||||
isPartial: true,
|
|
||||||
isFinal: false,
|
|
||||||
};
|
|
||||||
} else {
|
|
||||||
newMessages.push({
|
|
||||||
id: this.messageIdCounter++,
|
|
||||||
speaker,
|
|
||||||
text,
|
|
||||||
isPartial: true,
|
|
||||||
isFinal: false,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
} else if (isFinal) {
|
|
||||||
if (targetIdx !== -1) {
|
|
||||||
newMessages[targetIdx] = {
|
|
||||||
...newMessages[targetIdx],
|
|
||||||
text,
|
|
||||||
isPartial: false,
|
|
||||||
isFinal: true,
|
|
||||||
};
|
|
||||||
} else {
|
|
||||||
newMessages.push({
|
|
||||||
id: this.messageIdCounter++,
|
|
||||||
speaker,
|
|
||||||
text,
|
|
||||||
isPartial: false,
|
|
||||||
isFinal: true,
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
this.sttMessages = newMessages;
|
handleSttMessagesUpdated(event) {
|
||||||
}
|
// Handle messages update from SttView if needed
|
||||||
|
this.adjustWindowHeightThrottled();
|
||||||
scrollToTranscriptionBottom() {
|
|
||||||
setTimeout(() => {
|
|
||||||
const container = this.shadowRoot.querySelector('.transcription-container');
|
|
||||||
if (container) {
|
|
||||||
container.scrollTop = container.scrollHeight;
|
|
||||||
}
|
|
||||||
}, 0);
|
|
||||||
}
|
|
||||||
|
|
||||||
async handleRequestClick(requestText) {
|
|
||||||
console.log('🔥 Analysis request clicked:', requestText);
|
|
||||||
|
|
||||||
if (window.require) {
|
|
||||||
const { ipcRenderer } = window.require('electron');
|
|
||||||
|
|
||||||
try {
|
|
||||||
const isAskViewVisible = await ipcRenderer.invoke('is-window-visible', 'ask');
|
|
||||||
|
|
||||||
if (!isAskViewVisible) {
|
|
||||||
await ipcRenderer.invoke('toggle-feature', 'ask');
|
|
||||||
await new Promise(resolve => setTimeout(resolve, 100));
|
|
||||||
}
|
|
||||||
|
|
||||||
const result = await ipcRenderer.invoke('send-question-to-ask', requestText);
|
|
||||||
|
|
||||||
if (result.success) {
|
|
||||||
console.log('✅ Question sent to AskView successfully');
|
|
||||||
} else {
|
|
||||||
console.error('❌ Failed to send question to AskView:', result.error);
|
|
||||||
}
|
|
||||||
} catch (error) {
|
|
||||||
console.error('❌ Error in handleRequestClick:', error);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
connectedCallback() {
|
|
||||||
super.connectedCallback();
|
|
||||||
this.startTimer();
|
|
||||||
if (window.require) {
|
|
||||||
const { ipcRenderer } = window.require('electron');
|
|
||||||
ipcRenderer.on('stt-update', this.handleSttUpdate);
|
|
||||||
ipcRenderer.on('session-state-changed', (event, { isActive }) => {
|
|
||||||
const wasActive = this.isSessionActive;
|
|
||||||
this.isSessionActive = isActive;
|
|
||||||
|
|
||||||
if (!wasActive && isActive) {
|
|
||||||
this.hasCompletedRecording = false;
|
|
||||||
|
|
||||||
// 🔄 Reset transcript & analysis when a fresh session starts
|
|
||||||
this.sttMessages = [];
|
|
||||||
this.structuredData = {
|
|
||||||
summary: [],
|
|
||||||
topic: { header: '', bullets: [] },
|
|
||||||
actions: [],
|
|
||||||
followUps: [],
|
|
||||||
};
|
|
||||||
this.requestUpdate();
|
|
||||||
}
|
|
||||||
if (wasActive && !isActive) {
|
|
||||||
this.hasCompletedRecording = true;
|
|
||||||
|
|
||||||
this.requestUpdate();
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
this._startDebugStream();
|
|
||||||
}
|
|
||||||
|
|
||||||
disconnectedCallback() {
|
|
||||||
super.disconnectedCallback();
|
|
||||||
this.stopTimer();
|
|
||||||
|
|
||||||
if (this.adjustHeightThrottle) {
|
|
||||||
clearTimeout(this.adjustHeightThrottle);
|
|
||||||
this.adjustHeightThrottle = null;
|
|
||||||
}
|
|
||||||
if (this.copyTimeout) {
|
|
||||||
clearTimeout(this.copyTimeout);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (window.require) {
|
|
||||||
const { ipcRenderer } = window.require('electron');
|
|
||||||
ipcRenderer.removeListener('stt-update', this.handleSttUpdate);
|
|
||||||
}
|
|
||||||
|
|
||||||
this._stopDebugStream();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
firstUpdated() {
|
firstUpdated() {
|
||||||
super.firstUpdated();
|
super.firstUpdated();
|
||||||
|
|
||||||
setTimeout(() => this.adjustWindowHeight(), 200);
|
setTimeout(() => this.adjustWindowHeight(), 200);
|
||||||
}
|
}
|
||||||
|
|
||||||
updated(changedProperties) {
|
|
||||||
super.updated(changedProperties);
|
|
||||||
|
|
||||||
this.renderMarkdownContent();
|
|
||||||
|
|
||||||
if (changedProperties.has('sttMessages')) {
|
|
||||||
if (this._shouldScrollAfterUpdate) {
|
|
||||||
this.scrollToTranscriptionBottom();
|
|
||||||
this._shouldScrollAfterUpdate = false;
|
|
||||||
}
|
|
||||||
this.adjustWindowHeightThrottled();
|
|
||||||
}
|
|
||||||
|
|
||||||
if (changedProperties.has('viewMode')) {
|
|
||||||
this.adjustWindowHeight();
|
|
||||||
} else if (changedProperties.has('outlines') || changedProperties.has('analysisRequests') || changedProperties.has('structuredData')) {
|
|
||||||
this.adjustWindowHeightThrottled();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
render() {
|
render() {
|
||||||
const displayText = this.isHovering
|
const displayText = this.isHovering
|
||||||
? this.viewMode === 'transcript'
|
? this.viewMode === 'transcript'
|
||||||
@ -1080,16 +483,6 @@ export class AssistantView extends LitElement {
|
|||||||
? `Live insights`
|
? `Live insights`
|
||||||
: `Glass is Listening ${this.elapsedTime}`;
|
: `Glass is Listening ${this.elapsedTime}`;
|
||||||
|
|
||||||
const data = this.structuredData || {
|
|
||||||
summary: [],
|
|
||||||
topic: { header: '', bullets: [] },
|
|
||||||
actions: [],
|
|
||||||
};
|
|
||||||
|
|
||||||
const getSpeakerClass = speaker => {
|
|
||||||
return speaker.toLowerCase() === 'me' ? 'me' : 'them';
|
|
||||||
};
|
|
||||||
|
|
||||||
return html`
|
return html`
|
||||||
<div class="assistant-container">
|
<div class="assistant-container">
|
||||||
<div class="top-bar">
|
<div class="top-bar">
|
||||||
@ -1131,84 +524,15 @@ export class AssistantView extends LitElement {
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="transcription-container ${this.viewMode !== 'transcript' ? 'hidden' : ''}">
|
<stt-view
|
||||||
${this.sttMessages.map(msg => html` <div class="stt-message ${getSpeakerClass(msg.speaker)}">${msg.text}</div> `)}
|
.isVisible=${this.viewMode === 'transcript'}
|
||||||
</div>
|
@stt-messages-updated=${this.handleSttMessagesUpdated}
|
||||||
|
></stt-view>
|
||||||
|
|
||||||
<div class="insights-container ${this.viewMode !== 'insights' ? 'hidden' : ''}">
|
<summary-view
|
||||||
<insights-title>Current Summary</insights-title>
|
.isVisible=${this.viewMode === 'insights'}
|
||||||
${data.summary.length > 0
|
.hasCompletedRecording=${this.hasCompletedRecording}
|
||||||
? data.summary
|
></summary-view>
|
||||||
.slice(0, 5)
|
|
||||||
.map(
|
|
||||||
(bullet, index) => html`
|
|
||||||
<div
|
|
||||||
class="markdown-content"
|
|
||||||
data-markdown-id="summary-${index}"
|
|
||||||
data-original-text="${bullet}"
|
|
||||||
@click=${() => this.handleMarkdownClick(bullet)}
|
|
||||||
>
|
|
||||||
${bullet}
|
|
||||||
</div>
|
|
||||||
`
|
|
||||||
)
|
|
||||||
: html` <div class="request-item">No content yet...</div> `}
|
|
||||||
${data.topic.header
|
|
||||||
? html`
|
|
||||||
<insights-title>${data.topic.header}</insights-title>
|
|
||||||
${data.topic.bullets
|
|
||||||
.slice(0, 3)
|
|
||||||
.map(
|
|
||||||
(bullet, index) => html`
|
|
||||||
<div
|
|
||||||
class="markdown-content"
|
|
||||||
data-markdown-id="topic-${index}"
|
|
||||||
data-original-text="${bullet}"
|
|
||||||
@click=${() => this.handleMarkdownClick(bullet)}
|
|
||||||
>
|
|
||||||
${bullet}
|
|
||||||
</div>
|
|
||||||
`
|
|
||||||
)}
|
|
||||||
`
|
|
||||||
: ''}
|
|
||||||
${data.actions.length > 0
|
|
||||||
? html`
|
|
||||||
<insights-title>Actions</insights-title>
|
|
||||||
${data.actions
|
|
||||||
.slice(0, 5)
|
|
||||||
.map(
|
|
||||||
(action, index) => html`
|
|
||||||
<div
|
|
||||||
class="markdown-content"
|
|
||||||
data-markdown-id="action-${index}"
|
|
||||||
data-original-text="${action}"
|
|
||||||
@click=${() => this.handleMarkdownClick(action)}
|
|
||||||
>
|
|
||||||
${action}
|
|
||||||
</div>
|
|
||||||
`
|
|
||||||
)}
|
|
||||||
`
|
|
||||||
: ''}
|
|
||||||
${this.hasCompletedRecording && data.followUps && data.followUps.length > 0
|
|
||||||
? html`
|
|
||||||
<insights-title>Follow-Ups</insights-title>
|
|
||||||
${data.followUps.map(
|
|
||||||
(followUp, index) => html`
|
|
||||||
<div
|
|
||||||
class="markdown-content"
|
|
||||||
data-markdown-id="followup-${index}"
|
|
||||||
data-original-text="${followUp}"
|
|
||||||
@click=${() => this.handleMarkdownClick(followUp)}
|
|
||||||
>
|
|
||||||
${followUp}
|
|
||||||
</div>
|
|
||||||
`
|
|
||||||
)}
|
|
||||||
`
|
|
||||||
: ''}
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
`;
|
`;
|
||||||
}
|
}
|
||||||
|
@ -1,20 +0,0 @@
|
|||||||
const sqliteRepository = require('./sqlite.repository');
|
|
||||||
// const firebaseRepository = require('./firebase.repository'); // Future implementation
|
|
||||||
const authService = require('../../../common/services/authService');
|
|
||||||
|
|
||||||
function getRepository() {
|
|
||||||
// In the future, we can check the user's login status from authService
|
|
||||||
// const user = authService.getCurrentUser();
|
|
||||||
// if (user.isLoggedIn) {
|
|
||||||
// return firebaseRepository;
|
|
||||||
// }
|
|
||||||
return sqliteRepository;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Directly export functions for ease of use, decided by the strategy
|
|
||||||
module.exports = {
|
|
||||||
addTranscript: (...args) => getRepository().addTranscript(...args),
|
|
||||||
saveSummary: (...args) => getRepository().saveSummary(...args),
|
|
||||||
getAllTranscriptsBySessionId: (...args) => getRepository().getAllTranscriptsBySessionId(...args),
|
|
||||||
getSummaryBySessionId: (...args) => getRepository().getSummaryBySessionId(...args),
|
|
||||||
};
|
|
@ -1,66 +0,0 @@
|
|||||||
const sqliteClient = require('../../../common/services/sqliteClient');
|
|
||||||
|
|
||||||
function addTranscript({ sessionId, speaker, text }) {
|
|
||||||
const db = sqliteClient.getDb();
|
|
||||||
return new Promise((resolve, reject) => {
|
|
||||||
const transcriptId = require('crypto').randomUUID();
|
|
||||||
const now = Math.floor(Date.now() / 1000);
|
|
||||||
const query = `INSERT INTO transcripts (id, session_id, start_at, speaker, text, created_at) VALUES (?, ?, ?, ?, ?, ?)`;
|
|
||||||
db.run(query, [transcriptId, sessionId, now, speaker, text, now], function(err) {
|
|
||||||
if (err) reject(err);
|
|
||||||
else resolve({ id: transcriptId });
|
|
||||||
});
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
function saveSummary({ sessionId, tldr, text, bullet_json, action_json, model = 'gpt-4.1' }) {
|
|
||||||
const db = sqliteClient.getDb();
|
|
||||||
return new Promise((resolve, reject) => {
|
|
||||||
const now = Math.floor(Date.now() / 1000);
|
|
||||||
const query = `
|
|
||||||
INSERT INTO summaries (session_id, generated_at, model, text, tldr, bullet_json, action_json, updated_at)
|
|
||||||
VALUES (?, ?, ?, ?, ?, ?, ?, ?)
|
|
||||||
ON CONFLICT(session_id) DO UPDATE SET
|
|
||||||
generated_at=excluded.generated_at,
|
|
||||||
model=excluded.model,
|
|
||||||
text=excluded.text,
|
|
||||||
tldr=excluded.tldr,
|
|
||||||
bullet_json=excluded.bullet_json,
|
|
||||||
action_json=excluded.action_json,
|
|
||||||
updated_at=excluded.updated_at
|
|
||||||
`;
|
|
||||||
db.run(query, [sessionId, now, model, text, tldr, bullet_json, action_json, now], function(err) {
|
|
||||||
if (err) reject(err);
|
|
||||||
else resolve({ changes: this.changes });
|
|
||||||
});
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
function getAllTranscriptsBySessionId(sessionId) {
|
|
||||||
const db = sqliteClient.getDb();
|
|
||||||
return new Promise((resolve, reject) => {
|
|
||||||
const query = "SELECT * FROM transcripts WHERE session_id = ? ORDER BY start_at ASC";
|
|
||||||
db.all(query, [sessionId], (err, rows) => {
|
|
||||||
if (err) reject(err);
|
|
||||||
else resolve(rows);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
function getSummaryBySessionId(sessionId) {
|
|
||||||
const db = sqliteClient.getDb();
|
|
||||||
return new Promise((resolve, reject) => {
|
|
||||||
const query = "SELECT * FROM summaries WHERE session_id = ?";
|
|
||||||
db.get(query, [sessionId], (err, row) => {
|
|
||||||
if (err) reject(err);
|
|
||||||
else resolve(row || null);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
module.exports = {
|
|
||||||
addTranscript,
|
|
||||||
saveSummary,
|
|
||||||
getAllTranscriptsBySessionId,
|
|
||||||
getSummaryBySessionId
|
|
||||||
};
|
|
228
src/features/listen/stt/SttView.js
Normal file
228
src/features/listen/stt/SttView.js
Normal file
@ -0,0 +1,228 @@
|
|||||||
|
import { html, css, LitElement } from '../../../assets/lit-core-2.7.4.min.js';
|
||||||
|
|
||||||
|
export class SttView extends LitElement {
|
||||||
|
static styles = css`
|
||||||
|
:host {
|
||||||
|
display: block;
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Inherit font styles from parent */
|
||||||
|
|
||||||
|
.transcription-container {
|
||||||
|
overflow-y: auto;
|
||||||
|
padding: 12px 12px 16px 12px;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
gap: 8px;
|
||||||
|
min-height: 150px;
|
||||||
|
max-height: 600px;
|
||||||
|
position: relative;
|
||||||
|
z-index: 1;
|
||||||
|
flex: 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Visibility handled by parent component */
|
||||||
|
|
||||||
|
.transcription-container::-webkit-scrollbar {
|
||||||
|
width: 8px;
|
||||||
|
}
|
||||||
|
.transcription-container::-webkit-scrollbar-track {
|
||||||
|
background: rgba(0, 0, 0, 0.1);
|
||||||
|
border-radius: 4px;
|
||||||
|
}
|
||||||
|
.transcription-container::-webkit-scrollbar-thumb {
|
||||||
|
background: rgba(255, 255, 255, 0.3);
|
||||||
|
border-radius: 4px;
|
||||||
|
}
|
||||||
|
.transcription-container::-webkit-scrollbar-thumb:hover {
|
||||||
|
background: rgba(255, 255, 255, 0.5);
|
||||||
|
}
|
||||||
|
|
||||||
|
.stt-message {
|
||||||
|
padding: 8px 12px;
|
||||||
|
border-radius: 12px;
|
||||||
|
max-width: 80%;
|
||||||
|
word-wrap: break-word;
|
||||||
|
word-break: break-word;
|
||||||
|
line-height: 1.5;
|
||||||
|
font-size: 13px;
|
||||||
|
margin-bottom: 4px;
|
||||||
|
box-sizing: border-box;
|
||||||
|
}
|
||||||
|
|
||||||
|
.stt-message.them {
|
||||||
|
background: rgba(255, 255, 255, 0.1);
|
||||||
|
color: rgba(255, 255, 255, 0.9);
|
||||||
|
align-self: flex-start;
|
||||||
|
border-bottom-left-radius: 4px;
|
||||||
|
margin-right: auto;
|
||||||
|
}
|
||||||
|
|
||||||
|
.stt-message.me {
|
||||||
|
background: rgba(0, 122, 255, 0.8);
|
||||||
|
color: white;
|
||||||
|
align-self: flex-end;
|
||||||
|
border-bottom-right-radius: 4px;
|
||||||
|
margin-left: auto;
|
||||||
|
}
|
||||||
|
|
||||||
|
.empty-state {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
height: 100px;
|
||||||
|
color: rgba(255, 255, 255, 0.6);
|
||||||
|
font-size: 12px;
|
||||||
|
font-style: italic;
|
||||||
|
}
|
||||||
|
`;
|
||||||
|
|
||||||
|
static properties = {
|
||||||
|
sttMessages: { type: Array },
|
||||||
|
isVisible: { type: Boolean },
|
||||||
|
};
|
||||||
|
|
||||||
|
constructor() {
|
||||||
|
super();
|
||||||
|
this.sttMessages = [];
|
||||||
|
this.isVisible = true;
|
||||||
|
this.messageIdCounter = 0;
|
||||||
|
this._shouldScrollAfterUpdate = false;
|
||||||
|
|
||||||
|
this.handleSttUpdate = this.handleSttUpdate.bind(this);
|
||||||
|
}
|
||||||
|
|
||||||
|
connectedCallback() {
|
||||||
|
super.connectedCallback();
|
||||||
|
if (window.require) {
|
||||||
|
const { ipcRenderer } = window.require('electron');
|
||||||
|
ipcRenderer.on('stt-update', this.handleSttUpdate);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
disconnectedCallback() {
|
||||||
|
super.disconnectedCallback();
|
||||||
|
if (window.require) {
|
||||||
|
const { ipcRenderer } = window.require('electron');
|
||||||
|
ipcRenderer.removeListener('stt-update', this.handleSttUpdate);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Handle session reset from parent
|
||||||
|
resetTranscript() {
|
||||||
|
this.sttMessages = [];
|
||||||
|
this.requestUpdate();
|
||||||
|
}
|
||||||
|
|
||||||
|
handleSttUpdate(event, { speaker, text, isFinal, isPartial }) {
|
||||||
|
if (text === undefined) return;
|
||||||
|
|
||||||
|
const container = this.shadowRoot.querySelector('.transcription-container');
|
||||||
|
this._shouldScrollAfterUpdate = container ? container.scrollTop + container.clientHeight >= container.scrollHeight - 10 : false;
|
||||||
|
|
||||||
|
const findLastPartialIdx = spk => {
|
||||||
|
for (let i = this.sttMessages.length - 1; i >= 0; i--) {
|
||||||
|
const m = this.sttMessages[i];
|
||||||
|
if (m.speaker === spk && m.isPartial) return i;
|
||||||
|
}
|
||||||
|
return -1;
|
||||||
|
};
|
||||||
|
|
||||||
|
const newMessages = [...this.sttMessages];
|
||||||
|
const targetIdx = findLastPartialIdx(speaker);
|
||||||
|
|
||||||
|
if (isPartial) {
|
||||||
|
if (targetIdx !== -1) {
|
||||||
|
newMessages[targetIdx] = {
|
||||||
|
...newMessages[targetIdx],
|
||||||
|
text,
|
||||||
|
isPartial: true,
|
||||||
|
isFinal: false,
|
||||||
|
};
|
||||||
|
} else {
|
||||||
|
newMessages.push({
|
||||||
|
id: this.messageIdCounter++,
|
||||||
|
speaker,
|
||||||
|
text,
|
||||||
|
isPartial: true,
|
||||||
|
isFinal: false,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
} else if (isFinal) {
|
||||||
|
if (targetIdx !== -1) {
|
||||||
|
newMessages[targetIdx] = {
|
||||||
|
...newMessages[targetIdx],
|
||||||
|
text,
|
||||||
|
isPartial: false,
|
||||||
|
isFinal: true,
|
||||||
|
};
|
||||||
|
} else {
|
||||||
|
newMessages.push({
|
||||||
|
id: this.messageIdCounter++,
|
||||||
|
speaker,
|
||||||
|
text,
|
||||||
|
isPartial: false,
|
||||||
|
isFinal: true,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
this.sttMessages = newMessages;
|
||||||
|
|
||||||
|
// Notify parent component about message updates
|
||||||
|
this.dispatchEvent(new CustomEvent('stt-messages-updated', {
|
||||||
|
detail: { messages: this.sttMessages },
|
||||||
|
bubbles: true
|
||||||
|
}));
|
||||||
|
}
|
||||||
|
|
||||||
|
scrollToBottom() {
|
||||||
|
setTimeout(() => {
|
||||||
|
const container = this.shadowRoot.querySelector('.transcription-container');
|
||||||
|
if (container) {
|
||||||
|
container.scrollTop = container.scrollHeight;
|
||||||
|
}
|
||||||
|
}, 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
getSpeakerClass(speaker) {
|
||||||
|
return speaker.toLowerCase() === 'me' ? 'me' : 'them';
|
||||||
|
}
|
||||||
|
|
||||||
|
getTranscriptText() {
|
||||||
|
return this.sttMessages.map(msg => `${msg.speaker}: ${msg.text}`).join('\n');
|
||||||
|
}
|
||||||
|
|
||||||
|
updated(changedProperties) {
|
||||||
|
super.updated(changedProperties);
|
||||||
|
|
||||||
|
if (changedProperties.has('sttMessages')) {
|
||||||
|
if (this._shouldScrollAfterUpdate) {
|
||||||
|
this.scrollToBottom();
|
||||||
|
this._shouldScrollAfterUpdate = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
render() {
|
||||||
|
if (!this.isVisible) {
|
||||||
|
return html`<div style="display: none;"></div>`;
|
||||||
|
}
|
||||||
|
|
||||||
|
return html`
|
||||||
|
<div class="transcription-container">
|
||||||
|
${this.sttMessages.length === 0
|
||||||
|
? html`<div class="empty-state">Waiting for speech...</div>`
|
||||||
|
: this.sttMessages.map(msg => html`
|
||||||
|
<div class="stt-message ${this.getSpeakerClass(msg.speaker)}">
|
||||||
|
${msg.text}
|
||||||
|
</div>
|
||||||
|
`)
|
||||||
|
}
|
||||||
|
</div>
|
||||||
|
`;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
customElements.define('stt-view', SttView);
|
559
src/features/listen/summary/SummaryView.js
Normal file
559
src/features/listen/summary/SummaryView.js
Normal file
@ -0,0 +1,559 @@
|
|||||||
|
import { html, css, LitElement } from '../../../assets/lit-core-2.7.4.min.js';
|
||||||
|
|
||||||
|
export class SummaryView extends LitElement {
|
||||||
|
static styles = css`
|
||||||
|
:host {
|
||||||
|
display: block;
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Inherit font styles from parent */
|
||||||
|
|
||||||
|
/* highlight.js 스타일 추가 */
|
||||||
|
.insights-container pre {
|
||||||
|
background: rgba(0, 0, 0, 0.4) !important;
|
||||||
|
border-radius: 8px !important;
|
||||||
|
padding: 12px !important;
|
||||||
|
margin: 8px 0 !important;
|
||||||
|
overflow-x: auto !important;
|
||||||
|
border: 1px solid rgba(255, 255, 255, 0.1) !important;
|
||||||
|
white-space: pre !important;
|
||||||
|
word-wrap: normal !important;
|
||||||
|
word-break: normal !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.insights-container code {
|
||||||
|
font-family: 'Monaco', 'Menlo', 'Consolas', monospace !important;
|
||||||
|
font-size: 11px !important;
|
||||||
|
background: transparent !important;
|
||||||
|
white-space: pre !important;
|
||||||
|
word-wrap: normal !important;
|
||||||
|
word-break: normal !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.insights-container pre code {
|
||||||
|
white-space: pre !important;
|
||||||
|
word-wrap: normal !important;
|
||||||
|
word-break: normal !important;
|
||||||
|
display: block !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.insights-container p code {
|
||||||
|
background: rgba(255, 255, 255, 0.1) !important;
|
||||||
|
padding: 2px 4px !important;
|
||||||
|
border-radius: 3px !important;
|
||||||
|
color: #ffd700 !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.hljs-keyword {
|
||||||
|
color: #ff79c6 !important;
|
||||||
|
}
|
||||||
|
.hljs-string {
|
||||||
|
color: #f1fa8c !important;
|
||||||
|
}
|
||||||
|
.hljs-comment {
|
||||||
|
color: #6272a4 !important;
|
||||||
|
}
|
||||||
|
.hljs-number {
|
||||||
|
color: #bd93f9 !important;
|
||||||
|
}
|
||||||
|
.hljs-function {
|
||||||
|
color: #50fa7b !important;
|
||||||
|
}
|
||||||
|
.hljs-variable {
|
||||||
|
color: #8be9fd !important;
|
||||||
|
}
|
||||||
|
.hljs-built_in {
|
||||||
|
color: #ffb86c !important;
|
||||||
|
}
|
||||||
|
.hljs-title {
|
||||||
|
color: #50fa7b !important;
|
||||||
|
}
|
||||||
|
.hljs-attr {
|
||||||
|
color: #50fa7b !important;
|
||||||
|
}
|
||||||
|
.hljs-tag {
|
||||||
|
color: #ff79c6 !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.insights-container {
|
||||||
|
overflow-y: auto;
|
||||||
|
padding: 12px 16px 16px 16px;
|
||||||
|
position: relative;
|
||||||
|
z-index: 1;
|
||||||
|
min-height: 150px;
|
||||||
|
max-height: 600px;
|
||||||
|
flex: 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Visibility handled by parent component */
|
||||||
|
|
||||||
|
.insights-container::-webkit-scrollbar {
|
||||||
|
width: 8px;
|
||||||
|
}
|
||||||
|
.insights-container::-webkit-scrollbar-track {
|
||||||
|
background: rgba(0, 0, 0, 0.1);
|
||||||
|
border-radius: 4px;
|
||||||
|
}
|
||||||
|
.insights-container::-webkit-scrollbar-thumb {
|
||||||
|
background: rgba(255, 255, 255, 0.3);
|
||||||
|
border-radius: 4px;
|
||||||
|
}
|
||||||
|
.insights-container::-webkit-scrollbar-thumb:hover {
|
||||||
|
background: rgba(255, 255, 255, 0.5);
|
||||||
|
}
|
||||||
|
|
||||||
|
insights-title {
|
||||||
|
color: rgba(255, 255, 255, 0.8);
|
||||||
|
font-size: 15px;
|
||||||
|
font-weight: 500;
|
||||||
|
font-family: 'Helvetica Neue', sans-serif;
|
||||||
|
margin: 12px 0 8px 0;
|
||||||
|
display: block;
|
||||||
|
}
|
||||||
|
|
||||||
|
.insights-container h4 {
|
||||||
|
color: #ffffff;
|
||||||
|
font-size: 12px;
|
||||||
|
font-weight: 600;
|
||||||
|
margin: 12px 0 8px 0;
|
||||||
|
padding: 4px 8px;
|
||||||
|
border-radius: 4px;
|
||||||
|
background: transparent;
|
||||||
|
cursor: default;
|
||||||
|
}
|
||||||
|
|
||||||
|
.insights-container h4:hover {
|
||||||
|
background: transparent;
|
||||||
|
}
|
||||||
|
|
||||||
|
.insights-container h4:first-child {
|
||||||
|
margin-top: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.outline-item {
|
||||||
|
color: #ffffff;
|
||||||
|
font-size: 11px;
|
||||||
|
line-height: 1.4;
|
||||||
|
margin: 4px 0;
|
||||||
|
padding: 6px 8px;
|
||||||
|
border-radius: 4px;
|
||||||
|
background: transparent;
|
||||||
|
transition: background-color 0.15s ease;
|
||||||
|
cursor: pointer;
|
||||||
|
word-wrap: break-word;
|
||||||
|
}
|
||||||
|
|
||||||
|
.outline-item:hover {
|
||||||
|
background: rgba(255, 255, 255, 0.1);
|
||||||
|
}
|
||||||
|
|
||||||
|
.request-item {
|
||||||
|
color: #ffffff;
|
||||||
|
font-size: 12px;
|
||||||
|
line-height: 1.2;
|
||||||
|
margin: 4px 0;
|
||||||
|
padding: 6px 8px;
|
||||||
|
border-radius: 4px;
|
||||||
|
background: transparent;
|
||||||
|
cursor: default;
|
||||||
|
word-wrap: break-word;
|
||||||
|
transition: background-color 0.15s ease;
|
||||||
|
}
|
||||||
|
|
||||||
|
.request-item.clickable {
|
||||||
|
cursor: pointer;
|
||||||
|
transition: all 0.15s ease;
|
||||||
|
}
|
||||||
|
.request-item.clickable:hover {
|
||||||
|
background: rgba(255, 255, 255, 0.1);
|
||||||
|
transform: translateX(2px);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 마크다운 렌더링된 콘텐츠 스타일 */
|
||||||
|
.markdown-content {
|
||||||
|
color: #ffffff;
|
||||||
|
font-size: 11px;
|
||||||
|
line-height: 1.4;
|
||||||
|
margin: 4px 0;
|
||||||
|
padding: 6px 8px;
|
||||||
|
border-radius: 4px;
|
||||||
|
background: transparent;
|
||||||
|
cursor: pointer;
|
||||||
|
word-wrap: break-word;
|
||||||
|
transition: all 0.15s ease;
|
||||||
|
}
|
||||||
|
|
||||||
|
.markdown-content:hover {
|
||||||
|
background: rgba(255, 255, 255, 0.1);
|
||||||
|
transform: translateX(2px);
|
||||||
|
}
|
||||||
|
|
||||||
|
.markdown-content p {
|
||||||
|
margin: 4px 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.markdown-content ul,
|
||||||
|
.markdown-content ol {
|
||||||
|
margin: 4px 0;
|
||||||
|
padding-left: 16px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.markdown-content li {
|
||||||
|
margin: 2px 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.markdown-content a {
|
||||||
|
color: #8be9fd;
|
||||||
|
text-decoration: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.markdown-content a:hover {
|
||||||
|
text-decoration: underline;
|
||||||
|
}
|
||||||
|
|
||||||
|
.markdown-content strong {
|
||||||
|
font-weight: 600;
|
||||||
|
color: #f8f8f2;
|
||||||
|
}
|
||||||
|
|
||||||
|
.markdown-content em {
|
||||||
|
font-style: italic;
|
||||||
|
color: #f1fa8c;
|
||||||
|
}
|
||||||
|
|
||||||
|
.empty-state {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
height: 100px;
|
||||||
|
color: rgba(255, 255, 255, 0.6);
|
||||||
|
font-size: 12px;
|
||||||
|
font-style: italic;
|
||||||
|
}
|
||||||
|
`;
|
||||||
|
|
||||||
|
static properties = {
|
||||||
|
structuredData: { type: Object },
|
||||||
|
isVisible: { type: Boolean },
|
||||||
|
hasCompletedRecording: { type: Boolean },
|
||||||
|
};
|
||||||
|
|
||||||
|
constructor() {
|
||||||
|
super();
|
||||||
|
this.structuredData = {
|
||||||
|
summary: [],
|
||||||
|
topic: { header: '', bullets: [] },
|
||||||
|
actions: [],
|
||||||
|
followUps: [],
|
||||||
|
};
|
||||||
|
this.isVisible = true;
|
||||||
|
this.hasCompletedRecording = false;
|
||||||
|
|
||||||
|
// 마크다운 라이브러리 초기화
|
||||||
|
this.marked = null;
|
||||||
|
this.hljs = null;
|
||||||
|
this.isLibrariesLoaded = false;
|
||||||
|
this.DOMPurify = null;
|
||||||
|
this.isDOMPurifyLoaded = false;
|
||||||
|
|
||||||
|
this.loadLibraries();
|
||||||
|
}
|
||||||
|
|
||||||
|
connectedCallback() {
|
||||||
|
super.connectedCallback();
|
||||||
|
if (window.require) {
|
||||||
|
const { ipcRenderer } = window.require('electron');
|
||||||
|
ipcRenderer.on('update-structured-data', (event, data) => {
|
||||||
|
this.structuredData = data;
|
||||||
|
this.requestUpdate();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
disconnectedCallback() {
|
||||||
|
super.disconnectedCallback();
|
||||||
|
if (window.require) {
|
||||||
|
const { ipcRenderer } = window.require('electron');
|
||||||
|
ipcRenderer.removeAllListeners('update-structured-data');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Handle session reset from parent
|
||||||
|
resetAnalysis() {
|
||||||
|
this.structuredData = {
|
||||||
|
summary: [],
|
||||||
|
topic: { header: '', bullets: [] },
|
||||||
|
actions: [],
|
||||||
|
followUps: [],
|
||||||
|
};
|
||||||
|
this.requestUpdate();
|
||||||
|
}
|
||||||
|
|
||||||
|
async loadLibraries() {
|
||||||
|
try {
|
||||||
|
if (!window.marked) {
|
||||||
|
await this.loadScript('../../../assets/marked-4.3.0.min.js');
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!window.hljs) {
|
||||||
|
await this.loadScript('../../../assets/highlight-11.9.0.min.js');
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!window.DOMPurify) {
|
||||||
|
await this.loadScript('../../../assets/dompurify-3.0.7.min.js');
|
||||||
|
}
|
||||||
|
|
||||||
|
this.marked = window.marked;
|
||||||
|
this.hljs = window.hljs;
|
||||||
|
this.DOMPurify = window.DOMPurify;
|
||||||
|
|
||||||
|
if (this.marked && this.hljs) {
|
||||||
|
this.marked.setOptions({
|
||||||
|
highlight: (code, lang) => {
|
||||||
|
if (lang && this.hljs.getLanguage(lang)) {
|
||||||
|
try {
|
||||||
|
return this.hljs.highlight(code, { language: lang }).value;
|
||||||
|
} catch (err) {
|
||||||
|
console.warn('Highlight error:', err);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
return this.hljs.highlightAuto(code).value;
|
||||||
|
} catch (err) {
|
||||||
|
console.warn('Auto highlight error:', err);
|
||||||
|
}
|
||||||
|
return code;
|
||||||
|
},
|
||||||
|
breaks: true,
|
||||||
|
gfm: true,
|
||||||
|
pedantic: false,
|
||||||
|
smartypants: false,
|
||||||
|
xhtml: false,
|
||||||
|
});
|
||||||
|
|
||||||
|
this.isLibrariesLoaded = true;
|
||||||
|
console.log('Markdown libraries loaded successfully');
|
||||||
|
}
|
||||||
|
|
||||||
|
if (this.DOMPurify) {
|
||||||
|
this.isDOMPurifyLoaded = true;
|
||||||
|
console.log('DOMPurify loaded successfully in SummaryView');
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Failed to load libraries:', error);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
loadScript(src) {
|
||||||
|
return new Promise((resolve, reject) => {
|
||||||
|
const script = document.createElement('script');
|
||||||
|
script.src = src;
|
||||||
|
script.onload = resolve;
|
||||||
|
script.onerror = reject;
|
||||||
|
document.head.appendChild(script);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
parseMarkdown(text) {
|
||||||
|
if (!text) return '';
|
||||||
|
|
||||||
|
if (!this.isLibrariesLoaded || !this.marked) {
|
||||||
|
return text;
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
return this.marked(text);
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Markdown parsing error:', error);
|
||||||
|
return text;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
handleMarkdownClick(originalText) {
|
||||||
|
this.handleRequestClick(originalText);
|
||||||
|
}
|
||||||
|
|
||||||
|
renderMarkdownContent() {
|
||||||
|
if (!this.isLibrariesLoaded || !this.marked) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const markdownElements = this.shadowRoot.querySelectorAll('[data-markdown-id]');
|
||||||
|
markdownElements.forEach(element => {
|
||||||
|
const originalText = element.getAttribute('data-original-text');
|
||||||
|
if (originalText) {
|
||||||
|
try {
|
||||||
|
let parsedHTML = this.parseMarkdown(originalText);
|
||||||
|
|
||||||
|
if (this.isDOMPurifyLoaded && this.DOMPurify) {
|
||||||
|
parsedHTML = this.DOMPurify.sanitize(parsedHTML);
|
||||||
|
|
||||||
|
if (this.DOMPurify.removed && this.DOMPurify.removed.length > 0) {
|
||||||
|
console.warn('Unsafe content detected in insights, showing plain text');
|
||||||
|
element.textContent = '⚠️ ' + originalText;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
element.innerHTML = parsedHTML;
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Error rendering markdown for element:', error);
|
||||||
|
element.textContent = originalText;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
async handleRequestClick(requestText) {
|
||||||
|
console.log('🔥 Analysis request clicked:', requestText);
|
||||||
|
|
||||||
|
if (window.require) {
|
||||||
|
const { ipcRenderer } = window.require('electron');
|
||||||
|
|
||||||
|
try {
|
||||||
|
const isAskViewVisible = await ipcRenderer.invoke('is-window-visible', 'ask');
|
||||||
|
|
||||||
|
if (!isAskViewVisible) {
|
||||||
|
await ipcRenderer.invoke('toggle-feature', 'ask');
|
||||||
|
await new Promise(resolve => setTimeout(resolve, 100));
|
||||||
|
}
|
||||||
|
|
||||||
|
const result = await ipcRenderer.invoke('send-question-to-ask', requestText);
|
||||||
|
|
||||||
|
if (result.success) {
|
||||||
|
console.log('✅ Question sent to AskView successfully');
|
||||||
|
} else {
|
||||||
|
console.error('❌ Failed to send question to AskView:', result.error);
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
console.error('❌ Error in handleRequestClick:', error);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
getSummaryText() {
|
||||||
|
const data = this.structuredData || { summary: [], topic: { header: '', bullets: [] }, actions: [] };
|
||||||
|
let sections = [];
|
||||||
|
|
||||||
|
if (data.summary && data.summary.length > 0) {
|
||||||
|
sections.push(`Current Summary:\n${data.summary.map(s => `• ${s}`).join('\n')}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (data.topic && data.topic.header && data.topic.bullets.length > 0) {
|
||||||
|
sections.push(`\n${data.topic.header}:\n${data.topic.bullets.map(b => `• ${b}`).join('\n')}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (data.actions && data.actions.length > 0) {
|
||||||
|
sections.push(`\nActions:\n${data.actions.map(a => `▸ ${a}`).join('\n')}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (data.followUps && data.followUps.length > 0) {
|
||||||
|
sections.push(`\nFollow-Ups:\n${data.followUps.map(f => `▸ ${f}`).join('\n')}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
return sections.join('\n\n').trim();
|
||||||
|
}
|
||||||
|
|
||||||
|
updated(changedProperties) {
|
||||||
|
super.updated(changedProperties);
|
||||||
|
this.renderMarkdownContent();
|
||||||
|
}
|
||||||
|
|
||||||
|
render() {
|
||||||
|
if (!this.isVisible) {
|
||||||
|
return html`<div style="display: none;"></div>`;
|
||||||
|
}
|
||||||
|
|
||||||
|
const data = this.structuredData || {
|
||||||
|
summary: [],
|
||||||
|
topic: { header: '', bullets: [] },
|
||||||
|
actions: [],
|
||||||
|
};
|
||||||
|
|
||||||
|
const hasAnyContent = data.summary.length > 0 || data.topic.bullets.length > 0 || data.actions.length > 0;
|
||||||
|
|
||||||
|
return html`
|
||||||
|
<div class="insights-container">
|
||||||
|
${!hasAnyContent
|
||||||
|
? html`<div class="empty-state">No insights yet...</div>`
|
||||||
|
: html`
|
||||||
|
<insights-title>Current Summary</insights-title>
|
||||||
|
${data.summary.length > 0
|
||||||
|
? data.summary
|
||||||
|
.slice(0, 5)
|
||||||
|
.map(
|
||||||
|
(bullet, index) => html`
|
||||||
|
<div
|
||||||
|
class="markdown-content"
|
||||||
|
data-markdown-id="summary-${index}"
|
||||||
|
data-original-text="${bullet}"
|
||||||
|
@click=${() => this.handleMarkdownClick(bullet)}
|
||||||
|
>
|
||||||
|
${bullet}
|
||||||
|
</div>
|
||||||
|
`
|
||||||
|
)
|
||||||
|
: html` <div class="request-item">No content yet...</div> `}
|
||||||
|
${data.topic.header
|
||||||
|
? html`
|
||||||
|
<insights-title>${data.topic.header}</insights-title>
|
||||||
|
${data.topic.bullets
|
||||||
|
.slice(0, 3)
|
||||||
|
.map(
|
||||||
|
(bullet, index) => html`
|
||||||
|
<div
|
||||||
|
class="markdown-content"
|
||||||
|
data-markdown-id="topic-${index}"
|
||||||
|
data-original-text="${bullet}"
|
||||||
|
@click=${() => this.handleMarkdownClick(bullet)}
|
||||||
|
>
|
||||||
|
${bullet}
|
||||||
|
</div>
|
||||||
|
`
|
||||||
|
)}
|
||||||
|
`
|
||||||
|
: ''}
|
||||||
|
${data.actions.length > 0
|
||||||
|
? html`
|
||||||
|
<insights-title>Actions</insights-title>
|
||||||
|
${data.actions
|
||||||
|
.slice(0, 5)
|
||||||
|
.map(
|
||||||
|
(action, index) => html`
|
||||||
|
<div
|
||||||
|
class="markdown-content"
|
||||||
|
data-markdown-id="action-${index}"
|
||||||
|
data-original-text="${action}"
|
||||||
|
@click=${() => this.handleMarkdownClick(action)}
|
||||||
|
>
|
||||||
|
${action}
|
||||||
|
</div>
|
||||||
|
`
|
||||||
|
)}
|
||||||
|
`
|
||||||
|
: ''}
|
||||||
|
${this.hasCompletedRecording && data.followUps && data.followUps.length > 0
|
||||||
|
? html`
|
||||||
|
<insights-title>Follow-Ups</insights-title>
|
||||||
|
${data.followUps.map(
|
||||||
|
(followUp, index) => html`
|
||||||
|
<div
|
||||||
|
class="markdown-content"
|
||||||
|
data-markdown-id="followup-${index}"
|
||||||
|
data-original-text="${followUp}"
|
||||||
|
@click=${() => this.handleMarkdownClick(followUp)}
|
||||||
|
>
|
||||||
|
${followUp}
|
||||||
|
</div>
|
||||||
|
`
|
||||||
|
)}
|
||||||
|
`
|
||||||
|
: ''}
|
||||||
|
`}
|
||||||
|
</div>
|
||||||
|
`;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
customElements.define('summary-view', SummaryView);
|
Loading…
x
Reference in New Issue
Block a user