Skip to content

Commit 0649252

Browse files
authored
Merge pull request #56 from kc3hack/frontend
Frontend
2 parents 3e96614 + 0e64045 commit 0649252

8 files changed

Lines changed: 194 additions & 175 deletions

File tree

Frontend/index.html

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@
44
<meta charset="UTF-8" />
55
<link rel="icon" type="image/svg+xml" href="/vite.svg" />
66
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
7-
<title>LexiFlow</title>
7+
<title>TalkScope</title>
88
</head>
99
<body>
1010
<div id="root"></div>

Frontend/src/app/App.tsx

Lines changed: 13 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,7 @@ import {
2828
makeLeftRightLayout,
2929
removeLeaf,
3030
} from './layout/layoutUtils';
31-
import { VectorApiCheckButton } from './components/VectorApiCheckButton';
31+
3232

3333

3434

@@ -42,7 +42,7 @@ const PRESETS = [
4242
] as const;
4343

4444
const App: React.FC = () => {
45-
if (import.meta.env.DEV) console.log('[LexiFlow] App.tsx 読み込み(主題入力あり)');
45+
if (import.meta.env.DEV) console.log('[TalkScope] App.tsx 読み込み(主題入力あり)');
4646
const { transcript, setTranscript, isListening, startListening, stopListening, error } = useSpeechRecognition();
4747

4848
const [activeTerms, setActiveTerms] = useState<Term[]>([]);
@@ -53,7 +53,7 @@ const App: React.FC = () => {
5353
const [isDictionaryManagerOpen, setIsDictionaryManagerOpen] = useState(false);
5454
const [isLayoutMenuOpen, setIsLayoutMenuOpen] = useState(false);
5555
const [layout, setLayout] = useState<LayoutNode>(makeDefaultLayout);
56-
const [settings, setSettings] = useState({ darkMode: true, themeColor: 'indigo' });
56+
const [settings, setSettings] = useState({ darkMode: false, themeColor: 'indigo' });
5757
const [isPinned, setIsPinned] = useState<Set<string>>(new Set());
5858
/** ピン留めした用語一覧(IndexedDB と同期・ピン中タブで表示) */
5959
const [pinnedTermsList, setPinnedTermsList] = useState<Term[]>([]);
@@ -67,7 +67,7 @@ const App: React.FC = () => {
6767
/** API 用語の意味ベクトル (termId → vector)。バブルサイズ計算用 */
6868
const [termVectors, setTermVectors] = useState<Record<string, number[]>>({});
6969
/** フィルタ基準語(現状固定)との類似度フィルタ有効化 */
70-
const [isSimilarityFilterEnabled, setIsSimilarityFilterEnabled] = useState(true);
70+
const [isSimilarityFilterEnabled, setIsSimilarityFilterEnabled] = useState(false);
7171
/** ベクトルフィルタの強さ(0〜100) */
7272
const [similarityFilterStrength, setSimilarityFilterStrength] = useState(8);
7373
/** "it" の基準ベクトル(初期はフォールバックで即時利用可能にする) */
@@ -107,7 +107,6 @@ const App: React.FC = () => {
107107
// ── デモ機能(コア機能から独立) ──────────────────────────────
108108
const demoStream = useDemoStream({
109109
onAppend: (text) => setTranscript(text),
110-
intervalMs: 220,
111110
});
112111
// ──────────────────────────────────────────────────────────────
113112

@@ -489,13 +488,6 @@ const App: React.FC = () => {
489488
termVectors={termVectors}
490489
categoryFilter={categoryFilter}
491490
onCategoryFilterChange={setCategoryFilter}
492-
similarityFilterEnabled={isSimilarityFilterEnabled}
493-
onSimilarityFilterEnabledChange={setIsSimilarityFilterEnabled}
494-
similarityFilterStrength={similarityFilterStrength}
495-
onSimilarityFilterStrengthChange={setSimilarityFilterStrength}
496-
similarityThreshold={similarityThreshold}
497-
similarityReferenceWord="it"
498-
similarityReady={isItReferenceReady}
499491
/>
500492
),
501493
detail: (
@@ -517,7 +509,7 @@ const App: React.FC = () => {
517509
/>
518510
),
519511
// eslint-disable-next-line react-hooks/exhaustive-deps
520-
}), [transcript, isListening, filteredTerms, termWeights, termFrequencies, selectedTerm, searchHistory, dk, categoryFilter, handleTermClick, isPinned, handleTogglePin, themeVector, themeText, termVectors, apiTerms, isSimilarityFilterEnabled, similarityFilterStrength, similarityThreshold, isItReferenceReady]);
512+
}), [transcript, isListening, filteredTerms, termWeights, termFrequencies, selectedTerm, searchHistory, dk, categoryFilter, handleTermClick, isPinned, handleTogglePin, themeVector, themeText, termVectors, apiTerms]);
521513

522514
return (
523515
<div
@@ -541,7 +533,7 @@ const App: React.FC = () => {
541533
<div className="bg-indigo-600 p-1.5 rounded-xl text-white shadow-lg shadow-indigo-600/30">
542534
<Book size={18} />
543535
</div>
544-
<span className="text-lg font-black tracking-tight">LexiFlow</span>
536+
<span className="text-lg font-black tracking-tight">TalkScope</span>
545537
<span className="text-[9px] font-bold text-indigo-400 uppercase tracking-[0.2em] hidden sm:inline">Pro</span>
546538
</div>
547539

@@ -606,7 +598,7 @@ const App: React.FC = () => {
606598
単語管理
607599
</button>
608600

609-
<VectorApiCheckButton darkMode={dk} />
601+
610602
<button onClick={() => setIsSettingsOpen(true)} className={`p-1.5 rounded-lg transition-colors ${dk ? 'hover:bg-slate-800 text-slate-500 hover:text-slate-300' : 'hover:bg-slate-100 text-slate-400'}`}>
611603
<Settings size={18} />
612604
</button>
@@ -638,6 +630,12 @@ const App: React.FC = () => {
638630
onClose={() => setIsSettingsOpen(false)}
639631
settings={settings}
640632
updateSettings={s => setSettings(prev => ({ ...prev, ...s }))}
633+
similarityFilterEnabled={isSimilarityFilterEnabled}
634+
onSimilarityFilterEnabledChange={setIsSimilarityFilterEnabled}
635+
similarityFilterStrength={similarityFilterStrength}
636+
onSimilarityFilterStrengthChange={setSimilarityFilterStrength}
637+
similarityReferenceWord="IT"
638+
similarityReady={isItReferenceReady}
641639
/>
642640

643641
<DictionaryManagerModal

Frontend/src/app/components/BubbleCloud.tsx

Lines changed: 33 additions & 91 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@ import React, { useEffect, useRef, useState, useMemo } from 'react';
22
import { motion, AnimatePresence } from 'motion/react';
33
import { Term } from '../data/terms';
44
import { TermBubble } from './TermBubble';
5-
import { Hexagon, Shuffle, Pause, ChevronUp, ChevronDown, Loader2 } from 'lucide-react';
5+
import { Hexagon, Shuffle, Pause, ChevronUp, ChevronDown } from 'lucide-react';
66
import {
77
getMockTermVector,
88
getMockThemeVector,
@@ -49,20 +49,6 @@ interface BubbleCloudProps {
4949
categoryFilter?: string;
5050
/** カテゴリフィルター変更 */
5151
onCategoryFilterChange?: (category: string) => void;
52-
/** 類似度フィルタの有効状態 */
53-
similarityFilterEnabled?: boolean;
54-
/** 類似度フィルタ有効状態の変更 */
55-
onSimilarityFilterEnabledChange?: (enabled: boolean) => void;
56-
/** ベクトルフィルタの強さ(0〜100) */
57-
similarityFilterStrength?: number;
58-
/** ベクトルフィルタ強さ変更 */
59-
onSimilarityFilterStrengthChange?: (value: number) => void;
60-
/** 現在しきい値(表示用) */
61-
similarityThreshold?: number;
62-
/** 類似度基準語(現状は "it") */
63-
similarityReferenceWord?: string;
64-
/** 基準ベクトルが利用可能か */
65-
similarityReady?: boolean;
6652
}
6753

6854
export const BubbleCloud: React.FC<BubbleCloudProps> = ({
@@ -79,13 +65,6 @@ export const BubbleCloud: React.FC<BubbleCloudProps> = ({
7965
termVectors = {},
8066
categoryFilter = 'ALL',
8167
onCategoryFilterChange,
82-
similarityFilterEnabled = false,
83-
onSimilarityFilterEnabledChange,
84-
similarityFilterStrength = 50,
85-
onSimilarityFilterStrengthChange,
86-
similarityThreshold = 0.33,
87-
similarityReferenceWord = 'it',
88-
similarityReady = false,
8968
}) => {
9069
const dk = darkMode;
9170
const categories = ['ALL', 'ピン中', ...Object.keys(CATEGORY_COLORS)];
@@ -312,78 +291,41 @@ export const BubbleCloud: React.FC<BubbleCloudProps> = ({
312291

313292
return (
314293
<div className={`flex flex-col h-full transition-colors ${dk ? 'bg-[#0d0e1a]' : 'bg-white'}`}>
315-
{/* Header */}
316-
<div className={`flex items-center justify-between px-4 py-2.5 border-b shrink-0 ${dk ? 'border-slate-800/60 bg-slate-900/30' : 'border-slate-100 bg-slate-50/80'}`}>
317-
<div className="flex items-center gap-2 min-w-0">
318-
<Hexagon size={13} className={dk ? 'text-slate-600' : 'text-slate-300'} />
319-
<span className={`text-xs font-bold ${dk ? 'text-slate-300' : 'text-slate-600'}`}>用語マップ</span>
320-
{onCategoryFilterChange && (
321-
<select
322-
value={categoryFilter}
323-
onChange={(e) => onCategoryFilterChange(e.target.value)}
324-
className={`text-[10px] py-1 px-2 rounded border ${dk ? 'bg-slate-800/50 border-slate-700/50 text-slate-300' : 'bg-white border-slate-200 text-slate-700'}`}
325-
>
326-
{categories.map((c) => (
327-
<option key={c} value={c}>{c}</option>
328-
))}
329-
</select>
330-
)}
331-
{onSimilarityFilterEnabledChange && (
332-
<label className={`flex items-center gap-1.5 text-[10px] font-semibold px-2 py-1 rounded border whitespace-nowrap ${dk ? 'border-slate-700/60 bg-slate-800/40 text-slate-300' : 'border-slate-200 bg-white text-slate-700'}`}>
333-
<input
334-
type="checkbox"
335-
checked={similarityFilterEnabled}
336-
onChange={(e) => onSimilarityFilterEnabledChange(e.target.checked)}
337-
/>
338-
<span>{`"${similarityReferenceWord}" 類似`}</span>
339-
{!similarityReady && (
340-
<span className="inline-flex items-center gap-1 text-[9px] opacity-80">
341-
<Loader2 size={10} className="animate-spin" />
342-
準備中
343-
</span>
344-
)}
345-
</label>
346-
)}
347-
{onSimilarityFilterStrengthChange && similarityFilterEnabled && (
348-
<div className={`flex items-center gap-1.5 px-2 py-1 rounded border ${dk ? 'border-slate-700/60 bg-slate-800/40 text-slate-300' : 'border-slate-200 bg-white text-slate-700'}`}>
349-
<input
350-
type="range"
351-
min={0}
352-
max={100}
353-
step={1}
354-
value={similarityFilterStrength}
355-
onChange={(e) => onSimilarityFilterStrengthChange(Number(e.target.value))}
356-
className="w-20"
357-
/>
358-
<span className="text-[10px] font-mono w-10 text-right">{similarityFilterStrength}</span>
359-
<span className="text-[10px] opacity-70 whitespace-nowrap">{`th:${similarityThreshold.toFixed(2)}`}</span>
360-
</div>
361-
)}
362-
</div>
363-
<span className={`text-[10px] font-mono border px-1.5 py-0.5 rounded ${dk ? 'bg-slate-800/50 border-slate-700/50 text-slate-500' : 'bg-slate-100 border-slate-200 text-slate-400'}`}>
294+
{/* Header: filter + scale slider + term count — 1 row */}
295+
<div className={`flex items-center gap-2 px-4 py-2 border-b shrink-0 ${dk ? 'border-slate-800/60 bg-slate-900/30' : 'border-slate-100 bg-slate-50/80'}`}>
296+
<Hexagon size={13} className={dk ? 'text-slate-600' : 'text-slate-300'} />
297+
{onCategoryFilterChange && (
298+
<select
299+
value={categoryFilter}
300+
onChange={(e) => onCategoryFilterChange(e.target.value)}
301+
className={`text-[10px] py-1 px-2 rounded border shrink-0 ${dk ? 'bg-slate-800/50 border-slate-700/50 text-slate-300' : 'bg-white border-slate-200 text-slate-700'}`}
302+
>
303+
{categories.map((c) => (
304+
<option key={c} value={c}>{c}</option>
305+
))}
306+
</select>
307+
)}
308+
{categoryFilter !== 'ピン中' && (
309+
<>
310+
<span className={`text-[10px] font-bold shrink-0 ${dk ? 'text-slate-500' : 'text-slate-500'}`}>倍率</span>
311+
<input
312+
type="range"
313+
min={0.5}
314+
max={2}
315+
step={0.1}
316+
value={bubbleScale}
317+
onChange={(e) => setBubbleScale(Number(e.target.value))}
318+
className={`flex-1 h-1.5 rounded-full appearance-none cursor-pointer accent-indigo-500 min-w-0 ${dk ? 'bg-slate-700' : 'bg-slate-200'}`}
319+
/>
320+
<span className={`text-[10px] font-mono font-bold tabular-nums shrink-0 ${dk ? 'text-slate-400' : 'text-slate-600'}`}>
321+
{Math.round(bubbleScale * 100)}%
322+
</span>
323+
</>
324+
)}
325+
<span className={`ml-auto text-[10px] font-mono border px-1.5 py-0.5 rounded shrink-0 ${dk ? 'bg-slate-800/50 border-slate-700/50 text-slate-500' : 'bg-slate-100 border-slate-200 text-slate-400'}`}>
364326
{categoryFilter === 'ピン中' ? `${activeTerms.length} ピン` : `${activeTerms.length} terms`}
365327
</span>
366328
</div>
367-
368-
{/* バブル倍率スライダー(バブル表示時のみ) */}
369-
{categoryFilter !== 'ピン中' && (
370-
<div className={`flex items-center gap-3 px-4 py-2 border-b shrink-0 ${dk ? 'border-slate-800/60 bg-slate-900/20' : 'border-slate-100 bg-slate-50/50'}`}>
371-
<span className={`text-[10px] font-bold shrink-0 ${dk ? 'text-slate-400' : 'text-slate-500'}`}>バブル倍率</span>
372-
<input
373-
type="range"
374-
min={0.5}
375-
max={2}
376-
step={0.1}
377-
value={bubbleScale}
378-
onChange={(e) => setBubbleScale(Number(e.target.value))}
379-
className={`flex-1 h-2 rounded-full appearance-none cursor-pointer accent-indigo-500 ${dk ? 'bg-slate-700' : 'bg-slate-200'}`}
380-
/>
381-
<span className={`text-[10px] font-mono font-bold tabular-nums w-10 shrink-0 ${dk ? 'text-slate-400' : 'text-slate-600'}`}>
382-
{Math.round(bubbleScale * 100)}%
383-
</span>
384-
</div>
385-
)}
386-
387329
{/* ピン中: IndexedDB のピン留め一覧を表で表示(文字起こしハイライト風) */}
388330
{categoryFilter === 'ピン中' ? (
389331
<div className="flex-1 overflow-auto">

Frontend/src/app/components/SettingsModal.tsx

Lines changed: 69 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
import React from 'react';
22
import { motion, AnimatePresence } from 'motion/react';
3-
import { Settings, X, Moon, Sun, Palette } from 'lucide-react';
3+
import { Settings, X, Moon, Sun, Palette, SlidersHorizontal, Loader2 } from 'lucide-react';
44

55
interface SettingsModalProps {
66
isOpen: boolean;
@@ -10,6 +10,16 @@ interface SettingsModalProps {
1010
themeColor: string;
1111
};
1212
updateSettings: (newSettings: any) => void;
13+
/** 類似度フィルター ON/OFF */
14+
similarityFilterEnabled?: boolean;
15+
onSimilarityFilterEnabledChange?: (enabled: boolean) => void;
16+
/** フィルターの強さ 0〜100 */
17+
similarityFilterStrength?: number;
18+
onSimilarityFilterStrengthChange?: (value: number) => void;
19+
/** 基準語(一覧表示用) */
20+
similarityReferenceWord?: string;
21+
/** APIから基準ベクトルを取得できたか */
22+
similarityReady?: boolean;
1323
}
1424

1525
const THEME_COLORS = [
@@ -21,7 +31,18 @@ const THEME_COLORS = [
2131
{ name: 'Orange', value: 'orange', bg: 'bg-orange-500' },
2232
];
2333

24-
export const SettingsModal: React.FC<SettingsModalProps> = ({ isOpen, onClose, settings, updateSettings }) => {
34+
export const SettingsModal: React.FC<SettingsModalProps> = ({
35+
isOpen,
36+
onClose,
37+
settings,
38+
updateSettings,
39+
similarityFilterEnabled = false,
40+
onSimilarityFilterEnabledChange,
41+
similarityFilterStrength = 8,
42+
onSimilarityFilterStrengthChange,
43+
similarityReferenceWord = 'it',
44+
similarityReady = false,
45+
}) => {
2546
if (!isOpen) return null;
2647

2748
const dk = settings.darkMode;
@@ -89,7 +110,53 @@ export const SettingsModal: React.FC<SettingsModalProps> = ({ isOpen, onClose, s
89110
</div>
90111
</section>
91112

113+
{/* Similarity Filter Settings */}
114+
{onSimilarityFilterEnabledChange && (
115+
<section>
116+
<div className={`flex items-center gap-1.5 mb-3 text-[10px] font-bold uppercase tracking-widest ${dk ? 'text-slate-500' : 'text-slate-400'}`}>
117+
<SlidersHorizontal size={12} /> 用語フィルター
118+
</div>
119+
120+
<div className="flex items-center justify-between mb-3">
121+
<div className="flex items-center gap-1.5">
122+
<span className="text-xs font-bold">&ldquo;{similarityReferenceWord}&rdquo; 類似度フィルター</span>
123+
{!similarityReady && (
124+
<span className={`flex items-center gap-1 text-[9px] ${dk ? 'text-slate-500' : 'text-slate-400'}`}>
125+
<Loader2 size={10} className="animate-spin" />準備中
126+
</span>
127+
)}
128+
</div>
129+
<button
130+
onClick={() => onSimilarityFilterEnabledChange(!similarityFilterEnabled)}
131+
className={`w-10 h-5 rounded-full relative transition-colors ${similarityFilterEnabled ? 'bg-indigo-600' : (dk ? 'bg-slate-700' : 'bg-slate-200')}`}
132+
>
133+
<div className={`absolute top-0.5 w-4 h-4 rounded-full bg-white transition-all shadow-sm ${similarityFilterEnabled ? 'left-[22px]' : 'left-0.5'}`} />
134+
</button>
135+
</div>
92136

137+
{onSimilarityFilterStrengthChange && (
138+
<div className={`mb-2 transition-opacity ${similarityFilterEnabled ? 'opacity-100' : 'opacity-40 pointer-events-none'}`}>
139+
<div className="flex justify-between items-center mb-1">
140+
<span className="text-xs font-bold">強さ</span>
141+
<span className={`text-[10px] font-mono font-bold ${dk ? 'text-slate-500' : 'text-slate-400'}`}>{similarityFilterStrength}</span>
142+
</div>
143+
<input
144+
type="range"
145+
min={0}
146+
max={100}
147+
step={1}
148+
value={similarityFilterStrength}
149+
onChange={(e) => onSimilarityFilterStrengthChange(Number(e.target.value))}
150+
className="w-full h-1 rounded-lg appearance-none cursor-pointer bg-slate-700 accent-indigo-500"
151+
/>
152+
<div className={`flex justify-between mt-0.5 text-[9px] ${dk ? 'text-slate-600' : 'text-slate-400'}`}>
153+
<span>広く</span>
154+
<span>絞る</span>
155+
</div>
156+
</div>
157+
)}
158+
</section>
159+
)}
93160

94161
</div>
95162
</div>

0 commit comments

Comments
 (0)