Function bodies 138 total
startObserver method · typescript · L38-L51 (14 LOC)extension/src/content/sidebar-host.ts
private startObserver(): void {
this.observer = new MutationObserver(() => {
// Debounce to avoid flooding on WHD's rapid partial DOM updates
if (this.debounceTimer !== null) clearTimeout(this.debounceTimer)
this.debounceTimer = setTimeout(() => {
this.sendTicketData()
}, OBSERVER_DEBOUNCE_MS)
})
this.observer.observe(document.body, {
childList: true,
subtree: true,
})
}getBackendUrl function · typescript · L4-L11 (8 LOC)extension/src/lib/api-client.ts
async function getBackendUrl(): Promise<string> {
return new Promise((resolve) => {
chrome.storage.sync.get(STORAGE_KEY_SETTINGS, (result) => {
const settings = result[STORAGE_KEY_SETTINGS] as { backendUrl?: string } | undefined
resolve(settings?.backendUrl ?? DEFAULT_BACKEND_URL)
})
})
}getApiToken function · typescript · L14-L21 (8 LOC)extension/src/lib/api-client.ts
async function getApiToken(): Promise<string> {
return new Promise((resolve) => {
chrome.storage.local.get(STORAGE_KEY_SECRETS, (result) => {
const secrets = result[STORAGE_KEY_SECRETS] as { apiToken?: string } | undefined
resolve(secrets?.apiToken ?? '')
})
})
}buildHeaders function · typescript · L24-L29 (6 LOC)extension/src/lib/api-client.ts
async function buildHeaders(extra: Record<string, string> = {}): Promise<Record<string, string>> {
const token = await getApiToken()
const headers: Record<string, string> = { 'Content-Type': 'application/json', ...extra }
if (token) headers['X-Extension-Token'] = token
return headers
}sendNativeCommand function · typescript · L124-L134 (11 LOC)extension/src/lib/api-client.ts
export function sendNativeCommand(action: string): Promise<NativeResponse> {
return new Promise((resolve) => {
chrome.runtime.sendNativeMessage(NATIVE_HOST, { action }, (response: NativeResponse) => {
if (chrome.runtime.lastError) {
resolve({ ok: false, error: chrome.runtime.lastError.message })
} else {
resolve(response)
}
})
})
}ApiError class · typescript · L136-L143 (8 LOC)extension/src/lib/api-client.ts
export class ApiError extends Error {
constructor(
public readonly status: number,
public readonly body: unknown
) {
super(`API error ${status}`)
}
}constructor method · typescript · L137-L142 (6 LOC)extension/src/lib/api-client.ts
constructor(
public readonly status: number,
public readonly body: unknown
) {
super(`API error ${status}`)
}Same scanner, your repo: https://repobility.com — Repobility
OptionsApp function · typescript · L5-L13 (9 LOC)extension/src/options/OptionsApp.tsx
export default function OptionsApp(): React.ReactElement {
const { resolvedTheme } = useTheme()
return (
<div className="options-shell" data-theme={resolvedTheme}>
<OptionsPage />
</div>
)
}OptionsPage function · typescript · L16-L229 (214 LOC)extension/src/options/OptionsPage.tsx
export default function OptionsPage(): React.ReactElement {
const [settings, setSettings] = useState<AppSettings>(DEFAULT_SETTINGS)
const [apiToken, setApiToken] = useState('')
const [isSaving, setIsSaving] = useState(false)
const [saveMsg, setSaveMsg] = useState('')
const [models, setModels] = useState<string[]>([])
const [selectorsExpanded, setSelectorsExpanded] = useState(false)
useEffect(() => {
storage.getSettings().then(setSettings)
apiClient.models().then(setModels).catch(() => {})
chrome.storage.local.get(STORAGE_KEY_SECRETS, (result) => {
const secrets = result[STORAGE_KEY_SECRETS] as { apiToken?: string } | undefined
setApiToken(secrets?.apiToken ?? '')
})
}, [])
const handleChange = (field: keyof AppSettings, value: string) => {
setSettings((prev) => ({ ...prev, [field]: value }))
}
const handleSelectorChange = (field: keyof SelectorConfig, value: string) => {
setSettings((prev) => {
const overrides = { ...prdebugLog function · typescript · L85-L89 (5 LOC)extension/src/shared/constants.ts
export function debugLog(...args: unknown[]): void {
if (import.meta.env.DEV) {
console.log('[AI-HD]', ...args)
}
}debugError function · typescript · L92-L96 (5 LOC)extension/src/shared/constants.ts
export function debugError(...args: unknown[]): void {
if (import.meta.env.DEV) {
console.error('[AI-HD]', ...args)
}
}App function · typescript · L9-L42 (34 LOC)extension/src/sidebar/App.tsx
export default function App(): React.ReactElement {
const mainRef = useRef<HTMLElement>(null)
const { resolvedTheme, themeSetting, cycleTheme } = useTheme()
useEffect(() => {
mainRef.current?.focus()
}, [])
return (
<div className="app-shell" data-theme={resolvedTheme}>
<ErrorBoundary>
<header className="app-header" role="banner">
<div className="brand-mark" aria-hidden="true">
AI
</div>
<div className="brand-copy">
<h1>Helpdesk Assistant</h1>
<p>AI-powered ticket responses</p>
</div>
<ThemeToggle
theme={themeSetting}
resolvedTheme={resolvedTheme}
onCycle={cycleTheme}
/>
</header>
<main ref={mainRef} className="app-main" role="main" tabIndex={-1}>
<BackendControl />
<KnowledgePanel />
<ReplyPanel />
</main>
</ErrorBoundary>
</div>
)
}BackendControl function · typescript · L10-L224 (215 LOC)extension/src/sidebar/components/BackendControl.tsx
export function BackendControl(): React.ReactElement {
const [status, setStatus] = useState<BackendStatus>('checking')
const [ollamaOk, setOllamaOk] = useState(false)
const [ollamaAction, setOllamaAction] = useState<'idle' | 'starting' | 'stopping'>('idle')
const [version, setVersion] = useState('')
const [nativeError, setNativeError] = useState('')
const [collapsed, setCollapsed] = useState(false)
const timerRef = useRef<ReturnType<typeof setTimeout> | null>(null)
const mountedRef = useRef(true)
const ticketData = useSidebarStore((s) => s.ticketData)
const isTicketPage = useSidebarStore((s) => s.isTicketPage)
const selectedModel = useSidebarStore((s) => s.selectedModel)
const clearTimer = () => {
if (timerRef.current) {
clearTimeout(timerRef.current)
timerRef.current = null
}
}
const checkHealth = useCallback(async () => {
try {
const h = await apiClient.health()
if (!mountedRef.current) return
setStatus('online'ErrorBoundary class · typescript · L12-L51 (40 LOC)extension/src/sidebar/components/ErrorBoundary.tsx
export class ErrorBoundary extends React.Component<Props, State> {
constructor(props: Props) {
super(props)
this.state = { hasError: false, error: null }
}
static getDerivedStateFromError(error: Error): State {
return { hasError: true, error }
}
private handleCopyError = () => {
if (this.state.error) {
navigator.clipboard.writeText(this.state.error.stack ?? this.state.error.message).catch(() => {})
}
}
render() {
if (this.state.hasError) {
return (
<div className="p-4 flex flex-col gap-3 error-fallback" role="alert">
<p className="title">Something went wrong</p>
<p className="detail">
{this.state.error?.message ?? 'An unexpected error occurred.'}
</p>
<p className="hint">
Try refreshing the page. If the issue persists, copy the error and report it.
</p>
<button
onClick={this.handleCopyError}
className="link-btn"
constructor method · typescript · L13-L16 (4 LOC)extension/src/sidebar/components/ErrorBoundary.tsx
constructor(props: Props) {
super(props)
this.state = { hasError: false, error: null }
}Citation: Repobility (2026). State of AI-Generated Code. https://repobility.com/research/
getDerivedStateFromError method · typescript · L18-L20 (3 LOC)extension/src/sidebar/components/ErrorBoundary.tsx
static getDerivedStateFromError(error: Error): State {
return { hasError: true, error }
}render method · typescript · L28-L50 (23 LOC)extension/src/sidebar/components/ErrorBoundary.tsx
render() {
if (this.state.hasError) {
return (
<div className="p-4 flex flex-col gap-3 error-fallback" role="alert">
<p className="title">Something went wrong</p>
<p className="detail">
{this.state.error?.message ?? 'An unexpected error occurred.'}
</p>
<p className="hint">
Try refreshing the page. If the issue persists, copy the error and report it.
</p>
<button
onClick={this.handleCopyError}
className="link-btn"
aria-label="Copy error details to clipboard"
>
Copy error details
</button>
</div>
)
}
return this.props.children
}ErrorState function · typescript · L8-L28 (21 LOC)extension/src/sidebar/components/ErrorState.tsx
export function ErrorState({ message, onRetry }: ErrorStateProps): React.ReactElement {
return (
<div
className="alert-banner error"
role="alert"
aria-live="assertive"
>
<p className="alert-title">Error</p>
<p className="alert-message">{message}</p>
{onRetry && (
<button
onClick={onRetry}
className="link-btn"
aria-label="Retry generating reply"
>
Try again
</button>
)}
</div>
)
}ImportTab function · typescript · L6-L177 (172 LOC)extension/src/sidebar/components/ImportTab.tsx
export function ImportTab(): React.ReactElement {
const {
stagedFiles, uploadStatus, progress, errorMessage, results,
addFiles, removeFile, clearFiles, startUpload, cancelUpload, resetState,
} = useKnowledgeImport()
const fileInputRef = useRef<HTMLInputElement>(null)
const [dragCount, setDragCount] = useState(0)
const isDragOver = dragCount > 0
const isUploading = uploadStatus === 'uploading'
const handleDragEnter = useCallback((e: React.DragEvent) => {
e.preventDefault()
setDragCount((c) => c + 1)
}, [])
const handleDragLeave = useCallback((e: React.DragEvent) => {
e.preventDefault()
setDragCount((c) => c - 1)
}, [])
const handleDragOver = useCallback((e: React.DragEvent) => {
e.preventDefault()
}, [])
const handleDrop = useCallback((e: React.DragEvent) => {
e.preventDefault()
setDragCount(0)
addFiles([...e.dataTransfer.files])
}, [addFiles])
const handleFileChange = useCallback((e: React.ChangeEvent<HTMLInpuInsertButton function · typescript · L8-L81 (74 LOC)extension/src/sidebar/components/InsertButton.tsx
export function InsertButton(): React.ReactElement {
const reply = useSidebarStore((s) => s.reply)
const isGenerating = useSidebarStore((s) => s.isGenerating)
const setIsInserted = useSidebarStore((s) => s.setIsInserted)
const [insertState, setInsertState] = useState<InsertState>('idle')
const [errorMsg, setErrorMsg] = useState('')
const timerRef = useRef<ReturnType<typeof setTimeout> | null>(null)
useEffect(() => {
const listener = (message: ContentToSidebarMessage) => {
if (message.type === 'INSERT_SUCCESS') {
setInsertState('success')
setIsInserted(true)
if (timerRef.current) clearTimeout(timerRef.current)
timerRef.current = setTimeout(() => setInsertState('idle'), 2000)
} else if (message.type === 'INSERT_FAILED') {
setInsertState('error')
setErrorMsg(message.payload.reason)
if (timerRef.current) clearTimeout(timerRef.current)
timerRef.current = setTimeout(() => setInsertState('idle'), 30KnowledgePanel function · typescript · L8-L109 (102 LOC)extension/src/sidebar/components/KnowledgePanel.tsx
export function KnowledgePanel(): React.ReactElement {
const [collapsed, setCollapsed] = useState(true)
const [activeTab, setActiveTab] = useState<'import' | 'manage'>('import')
const [docCounts, setDocCounts] = useState<Record<string, number>>({})
const mountedRef = useRef(true)
const timerRef = useRef<ReturnType<typeof setTimeout> | null>(null)
const totalDocs = Object.values(docCounts).reduce((sum, n) => sum + n, 0)
const clearTimer = useCallback(() => {
if (timerRef.current) {
clearTimeout(timerRef.current)
timerRef.current = null
}
}, [])
const fetchCounts = useCallback(async () => {
try {
const h = await apiClient.health()
if (mountedRef.current) setDocCounts(h.chroma_doc_counts)
} catch {
// Ignore — backend may be offline
}
}, [])
const schedulePoll = useCallback(() => {
clearTimer()
timerRef.current = setTimeout(() => {
if (mountedRef.current) {
fetchCounts().finally(() => {
formatCollectionName function · typescript · L14-L16 (3 LOC)extension/src/sidebar/components/ManageTab.tsx
function formatCollectionName(name: string): string {
return COLLECTION_LABELS[name] ?? name
}ManageTab function · typescript · L18-L76 (59 LOC)extension/src/sidebar/components/ManageTab.tsx
export function ManageTab({ docCounts, onRefresh }: ManageTabProps): React.ReactElement {
const [confirmingClear, setConfirmingClear] = useState<string | null>(null)
const [clearingCollection, setClearingCollection] = useState<string | null>(null)
const collections = Object.entries(docCounts).filter(([, count]) => count > 0)
const handleClear = useCallback(async (name: string) => {
setClearingCollection(name)
setConfirmingClear(null)
try {
await apiClient.clearCollection(name)
onRefresh()
} finally {
setClearingCollection(null)
}
}, [onRefresh])
if (collections.length === 0) {
return <p className="support-text">No documents imported yet</p>
}
return (
<div className="kb-collection-list">
{collections.map(([name, count]) => (
<div key={name} className="kb-collection-row">
<span className="kb-file-name">{formatCollectionName(name)}</span>
<span className="kb-file-size">{count} docs</span>
If a scraper extracted this row, it came from Repobility (https://repobility.com)
ModelSelector function · typescript · L6-L34 (29 LOC)extension/src/sidebar/components/ModelSelector.tsx
export function ModelSelector(): React.ReactElement {
const selectedModel = useSidebarStore((s) => s.selectedModel)
const setSelectedModel = useSidebarStore((s) => s.setSelectedModel)
const [models, setModels] = useState<string[]>([DEFAULT_MODEL])
useEffect(() => {
apiClient.models().then((list) => {
if (list.length > 0) setModels(list)
}).catch(() => {
// Keep default if models endpoint unavailable
})
}, [])
return (
<div className="control-row">
<label htmlFor="model-select">Model</label>
<select
id="model-select"
value={selectedModel}
onChange={(e) => setSelectedModel(e.target.value)}
aria-label="Select LLM model"
>
{models.map((m) => (
<option key={m} value={m}>{m}</option>
))}
</select>
</div>
)
}ReplyPanel function · typescript · L11-L110 (100 LOC)extension/src/sidebar/components/ReplyPanel.tsx
export function ReplyPanel(): React.ReactElement {
useTicketData()
const { generate } = useGenerateReply()
const ticketData = useSidebarStore((s) => s.ticketData)
const isTicketPage = useSidebarStore((s) => s.isTicketPage)
const reply = useSidebarStore((s) => s.reply)
const isGenerating = useSidebarStore((s) => s.isGenerating)
const generateError = useSidebarStore((s) => s.generateError)
const isInserted = useSidebarStore((s) => s.isInserted)
const cancelGeneration = useSidebarStore((s) => s.cancelGeneration)
const isEditingReply = useSidebarStore((s) => s.isEditingReply)
const setIsEditingReply = useSidebarStore((s) => s.setIsEditingReply)
const setReply = useSidebarStore((s) => s.setReply)
return (
<>
{/* Ticket context */}
<section className="panel" aria-label="Ticket details">
<h2 className="section-heading">Ticket context</h2>
{!isTicketPage && (
<p className="support-text">Open a WHD ticket page to begin. This SkeletonLoader function · typescript · L3-L19 (17 LOC)extension/src/sidebar/components/SkeletonLoader.tsx
export function SkeletonLoader(): React.ReactElement {
return (
<div
className="flex flex-col gap-2 p-4"
role="status"
aria-label="Generating reply, please wait"
aria-live="polite"
>
<div className="skeleton h-3 rounded w-full" />
<div className="skeleton h-3 rounded w-4/5" />
<div className="skeleton h-3 rounded w-3/4" />
<div className="skeleton h-3 rounded w-full mt-2" />
<div className="skeleton h-3 rounded w-5/6" />
<span className="sr-only">Generating reply…</span>
</div>
)
}ThemeToggle function · typescript · L33-L47 (15 LOC)extension/src/sidebar/components/ThemeToggle.tsx
export function ThemeToggle({ theme, resolvedTheme, onCycle }: ThemeToggleProps): React.ReactElement {
const Icon = ICON_MAP[theme]
return (
<button
type="button"
className="theme-toggle"
onClick={onCycle}
aria-label={`Switch theme, current: ${theme}`}
title={`Theme: ${theme} (${resolvedTheme})`}
>
<Icon />
</button>
)
}TicketContext function · typescript · L8-L21 (14 LOC)extension/src/sidebar/components/TicketContext.tsx
export function TicketContext({ ticket }: TicketContextProps): React.ReactElement {
return (
<div className="context-card">
<p className="subject" title={ticket.subject}>
{ticket.subject || <span className="text-empty">No subject</span>}
</p>
<div className="meta-row">
{ticket.category && <span>{ticket.category}</span>}
{ticket.status && <span>{ticket.status}</span>}
{ticket.requesterName && <span>{ticket.requesterName}</span>}
</div>
</div>
)
}useGenerateReply function · typescript · L6-L56 (51 LOC)extension/src/sidebar/hooks/useGenerateReply.ts
export function useGenerateReply() {
const ticketData = useSidebarStore((s) => s.ticketData)
const selectedModel = useSidebarStore((s) => s.selectedModel)
const setReply = useSidebarStore((s) => s.setReply)
const setIsGenerating = useSidebarStore((s) => s.setIsGenerating)
const setGenerateError = useSidebarStore((s) => s.setGenerateError)
const setLastResponse = useSidebarStore((s) => s.setLastResponse)
const setIsInserted = useSidebarStore((s) => s.setIsInserted)
const setAbortController = useSidebarStore((s) => s.setAbortController)
const setIsEditingReply = useSidebarStore((s) => s.setIsEditingReply)
const { settings } = useSettings()
const generate = useCallback(async () => {
if (!ticketData) return
const ctrl = new AbortController()
setAbortController(ctrl)
setIsGenerating(true)
setGenerateError(null)
setReply('')
setIsInserted(false)
setIsEditingReply(false)
try {
const response = await apiClient.generate({
formatFileSize function · typescript · L27-L31 (5 LOC)extension/src/sidebar/hooks/useKnowledgeImport.ts
function formatFileSize(bytes: number): string {
if (bytes < 1024) return `${bytes} B`
if (bytes < 1024 * 1024) return `${(bytes / 1024).toFixed(1)} KB`
return `${(bytes / (1024 * 1024)).toFixed(1)} MB`
}getExtension function · typescript · L33-L36 (4 LOC)extension/src/sidebar/hooks/useKnowledgeImport.ts
function getExtension(name: string): string {
const idx = name.lastIndexOf('.')
return idx >= 0 ? name.slice(idx).toLowerCase() : ''
}Provenance: Repobility (https://repobility.com) — every score reproducible from /scan/
useKnowledgeImport function · typescript · L38-L204 (167 LOC)extension/src/sidebar/hooks/useKnowledgeImport.ts
export function useKnowledgeImport() {
const [stagedFiles, setStagedFiles] = useState<StagedFile[]>([])
const [uploadStatus, setUploadStatus] = useState<UploadStatus>('idle')
const [progress, setProgress] = useState<UploadProgress | null>(null)
const [errorMessage, setErrorMessage] = useState<string | null>(null)
const [results, setResults] = useState<IngestUploadResponse[]>([])
const abortRef = useRef<AbortController | null>(null)
const dismissRef = useRef<ReturnType<typeof setTimeout> | null>(null)
const clearDismissTimer = useCallback(() => {
if (dismissRef.current) {
clearTimeout(dismissRef.current)
dismissRef.current = null
}
}, [])
const resetState = useCallback(() => {
clearDismissTimer()
setStagedFiles([])
setUploadStatus('idle')
setProgress(null)
setErrorMessage(null)
setResults([])
}, [clearDismissTimer])
const addFiles = useCallback((files: File[]) => {
setErrorMessage(null)
const invalid = fileuseSettings function · typescript · L5-L22 (18 LOC)extension/src/sidebar/hooks/useSettings.ts
export function useSettings() {
const [settings, setSettings] = useState<AppSettings>(DEFAULT_SETTINGS)
const [isLoading, setIsLoading] = useState(true)
useEffect(() => {
storage.getSettings().then((s) => {
setSettings(s)
setIsLoading(false)
})
}, [])
const updateSettings = async (updates: Partial<AppSettings>) => {
await storage.saveSettings(updates)
setSettings((prev) => ({ ...prev, ...updates }))
}
return { settings, isLoading, updateSettings }
}resolveTheme function · typescript · L15-L18 (4 LOC)extension/src/sidebar/hooks/useTheme.ts
function resolveTheme(setting: AppSettings['theme'], osDark: boolean): ResolvedTheme {
if (setting === 'system') return osDark ? 'dark' : 'light'
return setting
}useTheme function · typescript · L20-L43 (24 LOC)extension/src/sidebar/hooks/useTheme.ts
export function useTheme(): UseThemeReturn {
const { settings, updateSettings } = useSettings()
const [resolved, setResolved] = useState<ResolvedTheme>(() =>
resolveTheme(settings.theme, window.matchMedia(MEDIA_QUERY).matches)
)
useEffect(() => {
const mq = window.matchMedia(MEDIA_QUERY)
const apply = () => setResolved(resolveTheme(settings.theme, mq.matches))
apply()
mq.addEventListener('change', apply)
return () => mq.removeEventListener('change', apply)
}, [settings.theme])
const cycleTheme = useCallback(() => {
const next: AppSettings['theme'] =
settings.theme === 'system' ? 'light' :
settings.theme === 'light' ? 'dark' : 'system'
void updateSettings({ theme: next })
}, [settings.theme, updateSettings])
return { resolvedTheme: resolved, themeSetting: settings.theme, cycleTheme }
}useTicketData function · typescript · L5-L26 (22 LOC)extension/src/sidebar/hooks/useTicketData.ts
export function useTicketData(): void {
const setTicketData = useSidebarStore((s) => s.setTicketData)
const setIsTicketPage = useSidebarStore((s) => s.setIsTicketPage)
useEffect(() => {
// Request current ticket data on mount
chrome.runtime.sendMessage({ type: 'REQUEST_TICKET_DATA' }).catch(() => {})
const listener = (message: ContentToSidebarMessage) => {
if (message.type === 'TICKET_DATA_UPDATED') {
setTicketData(message.payload)
setIsTicketPage(true)
} else if (message.type === 'NOT_A_TICKET_PAGE') {
setTicketData(null)
setIsTicketPage(false)
}
}
chrome.runtime.onMessage.addListener(listener)
return () => chrome.runtime.onMessage.removeListener(listener)
}, [setTicketData, setIsTicketPage])
}draw_icon function · python · L13-L122 (110 LOC)scripts/generate-icons.py
def draw_icon(size: int) -> Image.Image:
"""Draw a headset icon with optional sparkle at the given size."""
# Work at 4x for anti-aliasing, then downsample
scale = 4
s = size * scale
img = Image.new("RGBA", (s, s), (0, 0, 0, 0))
draw = ImageDraw.Draw(img)
# Rounded background
margin = int(s * 0.06)
radius = int(s * 0.22)
draw.rounded_rectangle(
[margin, margin, s - margin, s - margin],
radius=radius,
fill=ACCENT,
)
cx, cy = s // 2, s // 2
if size <= 16:
# Simplified icon at 16px: just headband arc + ear cups
band_w = max(int(s * 0.07), 2)
band_r = int(s * 0.28)
band_box = [cx - band_r, cy - band_r - int(s * 0.05),
cx + band_r, cy + band_r - int(s * 0.05)]
draw.arc(band_box, start=200, end=340, fill=WHITE, width=band_w)
# Ear cups — rounded rectangles
cup_w = int(s * 0.16)
cup_h = int(s * 0.24)
cup_r = int(s * 0.05)
main function · python · L125-L131 (7 LOC)scripts/generate-icons.py
def main() -> None:
OUTPUT_DIR.mkdir(parents=True, exist_ok=True)
for size in (16, 48, 128):
icon = draw_icon(size)
out_path = OUTPUT_DIR / f"icon{size}.png"
icon.save(out_path, "PNG")
print(f"Generated {out_path} ({size}x{size})")‹ prevpage 3 / 3