-
Notifications
You must be signed in to change notification settings - Fork 331
feat(askgh): allow users to select additional search scopes in repo Ask GitHub view #1436
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -7,13 +7,16 @@ import { ChatBox, ChatBoxHandle } from "@/features/chat/components/chatBox"; | |
| import { ChatBoxToolbar } from "@/features/chat/components/chatBox/chatBoxToolbar"; | ||
| import { ChatPaneDropzone } from "@/features/chat/components/chatBox/chatPaneDropzone"; | ||
| import { NotConfiguredErrorBanner } from "@/features/chat/components/notConfiguredErrorBanner"; | ||
| import { LanguageModelInfo, RepoSearchScope } from "@/features/chat/types"; | ||
| import { LanguageModelInfo, RepoSearchScope, SearchScope } from "@/features/chat/types"; | ||
| import { useCreateNewChatThread } from "@/features/chat/useCreateNewChatThread"; | ||
| import { DISABLED_MCP_SERVER_IDS_LOCAL_STORAGE_KEY } from "@/features/chat/constants"; | ||
| import { getRepoImageSrc } from '@/lib/utils'; | ||
| import { useMemo, useRef, useState } from "react"; | ||
| import { useEffect, useMemo, useRef, useState } from "react"; | ||
| import { useLocalStorage } from "usehooks-ts"; | ||
| import type { AskCommandDefinition } from '@/features/chat/commands/types'; | ||
| import { RepositoryQuery, SearchContextQuery } from "@/lib/types"; | ||
|
|
||
| const ASKGH_SELECTED_SEARCH_SCOPES_LOCAL_STORAGE_KEY = 'askGhSelectedSearchScopes'; | ||
|
|
||
| interface LandingPageProps { | ||
| languageModels: LanguageModelInfo[]; | ||
|
|
@@ -24,6 +27,8 @@ interface LandingPageProps { | |
| askCommands: AskCommandDefinition[]; | ||
| isAuthenticated: boolean; | ||
| maxImageBytes: number; | ||
| repos: RepositoryQuery[]; | ||
| searchContexts: SearchContextQuery[]; | ||
| } | ||
|
|
||
| export const LandingPage = ({ | ||
|
|
@@ -35,21 +40,49 @@ export const LandingPage = ({ | |
| askCommands, | ||
| isAuthenticated, | ||
| maxImageBytes, | ||
| repos, | ||
| searchContexts, | ||
| }: LandingPageProps) => { | ||
| const { createNewChatThread, isLoading } = useCreateNewChatThread(); | ||
| const [isContextSelectorOpen, setIsContextSelectorOpen] = useState(false); | ||
| const [disabledMcpServerIds, setDisabledMcpServerIds] = useLocalStorage<string[]>(DISABLED_MCP_SERVER_IDS_LOCAL_STORAGE_KEY, [], { initializeWithValue: false }); | ||
| const chatBoxRef = useRef<ChatBoxHandle>(null); | ||
| const isChatBoxDisabled = languageModels.length === 0; | ||
|
|
||
| const selectedSearchScopes = useMemo(() => [ | ||
| { | ||
| type: 'repo', | ||
| name: repoDisplayName ?? repoName, | ||
| value: repoName, | ||
| codeHostType: 'github' as const, | ||
| } satisfies RepoSearchScope, | ||
| ], [repoDisplayName, repoName]); | ||
| // Default scope for the current repo | ||
| const defaultRepoScope = useMemo(() => ({ | ||
| type: 'repo' as const, | ||
| name: repoDisplayName ?? repoName, | ||
| value: repoName, | ||
| codeHostType: 'github' as const, | ||
| } satisfies RepoSearchScope), [repoDisplayName, repoName]); | ||
|
|
||
| // Use local storage for selected scopes, with the current repo as default | ||
| const [selectedSearchScopes, setSelectedSearchScopes] = useLocalStorage<SearchScope[]>( | ||
| ASKGH_SELECTED_SEARCH_SCOPES_LOCAL_STORAGE_KEY, | ||
| [defaultRepoScope], | ||
| { initializeWithValue: false } | ||
| ); | ||
|
|
||
| // Ensure the current repo is always included in selected scopes when visiting this page | ||
| // This handles the case where the user visits a different repo's Ask GH page | ||
| const [hasInitialized, setHasInitialized] = useState(false); | ||
| useEffect(() => { | ||
| if (hasInitialized) { | ||
| return; | ||
| } | ||
| setHasInitialized(true); | ||
|
|
||
| // Check if the current repo is already in the selected scopes | ||
| const currentRepoIncluded = selectedSearchScopes.some( | ||
| (scope) => scope.type === 'repo' && scope.value === repoName | ||
| ); | ||
|
|
||
| // If not, add it to the scopes | ||
| if (!currentRepoIncluded) { | ||
| setSelectedSearchScopes([defaultRepoScope, ...selectedSearchScopes]); | ||
| } | ||
| }, [hasInitialized, selectedSearchScopes, repoName, defaultRepoScope, setSelectedSearchScopes]); | ||
|
Comment on lines
+60
to
+85
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win 🧩 Analysis chain🏁 Script executed: #!/usr/bin/env bash
set -euo pipefail
ast-grep outline 'packages/web/src/app/(app)/askgh/[owner]/[repo]/components/landingPage.tsx' --items all
rg -n -C 8 'initializeWithValue|hasInitialized|selectedSearchScopes|setSelectedSearchScopes' \
'packages/web/src/app/(app)/askgh/[owner]/[repo]/components/landingPage.tsx'
fd -a -i '^(package\.json|pnpm-lock\.yaml)$' . -x rg -n -C 2 'usehooks-ts'Repository: sourcebot-dev/sourcebot Length of output: 6774 🏁 Script executed: #!/usr/bin/env bash
set -euo pipefail
echo "== package manager lock section for usehooks-ts =="
python3 - <<'PY'
from pathlib import Path
for p in Path('.').rglob('pnpm-lock.yaml'):
print(f'--- {p} ---')
lines = p.read_text(encoding='utf-8').splitlines()
for i,l in enumerate(lines):
if '"usehooks-ts"' in l or '- usehooks-ts' in l or 'usehooks-ts@' in l:
for j in range(max(0,i-5), min(len(lines),i+15)):
print(f'{j+1}: {lines[j]}')
PY
echo "== local storage usage around scope merging =="
sed -n '40,145p' 'packages/web/src/app/(app)/askgh/[owner]/[repo]/components/landingPage.tsx'
echo "== dependency files mentioning usehooks-ts =="
fd -a -i '^(package\.json|pnpm-lock\.yaml|package-lock.json|yarn.lock|bun.lockb)$' . -x bash -c 'echo "--- $1 ---"; rg -n "usehooks-ts" "$1" || true' sh {}Repository: sourcebot-dev/sourcebot Length of output: 6281 🌐 Web query:
💡 Result: In the usehooks-ts library, the Citations:
🏁 Script executed: #!/usr/bin/env bash
set -euo pipefail
echo "== packages/web package.json "
sed -n '1,240p' packages/web/package.json
echo "== yarn.lock usehooks-ts resolved package info =="
sed -n '23430,23445p' yarn.lock
echo "== find installed usehooks-ts package.json =="
fd -a -i '^package.json$' . \
-x sh -c 'if rg -q "\"usehooks-ts\"" "$1/yarn.lock" && rg -q "\"usehooks-ts\"" "$1/package.json"; then echo "--- $1 ---"; fi' sh {} \;
echo "== search useLocalStorage implementation in installed usehooks-ts =="
python3 - <<'PY'
from pathlib import Path
matches=[]
for f in Path('.').rglob('useLocalStorage.ts'):
p=str(f)
if 'node_modules' not in p:
continue
text=f.read_text(errors='ignore')
if 'initializeWithValue' in text:
matches.append(p)
for p in matches[:20]:
print("---", p, "---")
lines=open(p, errors='ignore').readlines()
for i,l in enumerate(lines,1):
if 'initializeWithValue' in l or 'useEffect' in l or 'addEventListener' in l:
print(f'{i}: {l.rstrip()}')
PYRepository: sourcebot-dev/sourcebot Length of output: 11253 🌐 Web query:
💡 Result: In version 3.1.1 of the usehooks-ts library, the useLocalStorage hook includes an optional parameter called initializeWithValue within its options object to support Server-Side Rendering (SSR) [1][2]. When initializeWithValue is set to false, the hook will not attempt to read from localStorage during the initial render [1]. Instead, it returns the provided initial value, which prevents hydration mismatch errors in SSR environments where the server does not have access to the browser's localStorage API [1]. The signature of useLocalStorage in this version is: export function useLocalStorage( key: string, initialValue: T | ( => T), options: UseLocalStorageOptions = {},): [T, Dispatch<SetStateAction>, => void] By default, initializeWithValue is set to true [1]. To disable it, you can pass the option as follows: const [value, setValue] = useLocalStorage('my-key', initialValue, { initializeWithValue: false }); Citations:
🏁 Script executed: #!/usr/bin/env bash
set -euo pipefail
python3 - <<'PY'
from pathlib import Path
for f in Path('.').rglob('useLocalStorage.ts'):
p = str(f)
if 'node_modules' not in p and 'dist' not in p:
continue
text = f.read_text(errors='ignore')
lines = text.splitlines()
ok = any('initializeWithValue' in line for line in lines)
print(f'--- {p} ok={ok} line_count={len(lines)} ---')
if ok:
for i,l in enumerate(lines, 1):
if any(k in l for k in ['useStorageState', 'localStorage', 'initializeWithValue', 'addEventListener', 'useEffect']):
print(f'{i}: {l}')
# Print focused region around hook definition/body
for start in range(len(lines)):
if 'function useLocalStorage' in lines[start] or 'const useLocalStorage' in lines[start]:
end = min(len(lines), start + 160)
for j in range(start, end):
print(f'{j+1}: {lines[j]}')
break
PY
echo "== fetch upstream 3.1.1 useLocalStorage.ts =="
python3 - <<'PY'
import urllib.request
url='https://github.com/ghraw/juliencrn/usehooks-ts/v3.1.1/packages/use-local-storage/src/useLocalStorage.ts'
try:
text=urllib.request.urlopen(url, timeout=20).read().decode()
for i,l in enumerate(text.splitlines(),1):
if any(s in l for s in ['initializeWithValue', 'useStorageState', 'localStorage', 'addEventListener', 'useEffect']):
print(f'{i}: {l}')
except Exception as e:
print('FETCH_ERROR', repr(e))
PYRepository: sourcebot-dev/sourcebot Length of output: 379 🌐 Web query:
💡 Result: In the usehooks-ts library version 3.1.1, the useLocalStorage hook is designed to persist state in the browser's localStorage using JSON serialization [1]. The implementation handles initialization, reading, and synchronization through the following key mechanisms: initializeWithValue: The hook accepts an optional initializeWithValue parameter (defaulting to true) [1]. When set to true, the hook reads from localStorage during the initial state initialization [1]. If false, it ignores localStorage during the initial render and uses the provided initialValue instead, which helps prevent hydration mismatches in Server-Side Rendering (SSR) environments [1][2]. JSON.parse and Serialization: By default, the hook uses JSON.stringify for serialization and JSON.parse for deserialization [1]. The deserializer function is wrapped in a try-catch block; if JSON.parse fails, it logs an error and returns the provided initialValue as a fallback [1]. It also explicitly handles the string 'undefined' by returning the value undefined [1]. Users can override these defaults by providing custom serializer and deserializer functions in the options object [1]. useEffect and State Management: The hook utilizes useState to hold the current value and uses the localStorage API to persist updates [1]. While the provided source reflects the core logic of readValue being called during initialization via useState, the hook ensures that localStorage operations are safely executed in client-side environments by checking for the existence of the window object (or an equivalent server-side check) to avoid errors during server-side builds [1]. [1] Citations:
🏁 Script executed: #!/usr/bin/env bash
set -euo pipefail
curl -fsSL --connect-timeout 15 https://registry.npmjs.org/usehooks-ts/3.1.1 \
| jq -r '.dist.tarball' \
| xargs -r curl -fsSL --connect-timeout 15 \
| tar -xzO --no-same-owner --wildcards '*useLocalStorage.ts' \
| sed -n '1,220p'Repository: sourcebot-dev/sourcebot Length of output: 264 🏁 Script executed: #!/usr/bin/env bash
set -euo pipefail
curl -fsSL --connect-timeout 15 https://registry.npmjs.org/usehooks-ts/3.1.1 \
| jq -r '.dist.tarball' \
| xargs -r curl -fsSL --connect-timeout 15 \
| tar -tz \
| grep -E 'use-hooks-ts|useLocalStorage|package' | head -80Repository: sourcebot-dev/sourcebot Length of output: 310 🏁 Script executed: #!/usr/bin/env bash
set -euo pipefail
tmp="$(mktemp -d)"
curl -fsSL --connect-timeout 15 https://registry.npmjs.org/usehooks-ts/3.1.1 \
| jq -r '.dist.tarball' \
| xargs curl -fsSL --connect-timeout 15 -o "$tmp/package.tgz"
echo "== build/useLocalStorage references =="
python3 - <<'PY'
import tarfile, re, subprocess, tempfile, sys
path=sys.argv[1]
with tarfile.open(path, "r:gz") as t:
names=[n for n in t.getnames() if "local" in n.lower() or n.endswith(("js","cts","d.ts","d.cts"))]
for n in names:
try:
data=t.extractfile(n).read().decode(errors="ignore")
except Exception as e:
print(n, "ERR", repr(e))
continue
if "initializeWithValue" in data or "useLocalStorage" in data:
print(f"--- {n} ---")
for i,line in enumerate(data.splitlines(),1):
if any(k in line for k in ["initializeWithValue", "useLocalStorage", "useEffect", "localStorage", "useStorageState"]):
print(f"{i}: {line}")
py "$tmp/package.tgz"
echo "== JS module snippet around useLocalStorage exports =="
python3 - <<'PY'
import tarfile, sys
path=sys.argv[1]
with tarfile.open(path, "r:gz") as t:
for n in ["package/dist/index.js","package/dist/index.cts","package/dist/index.cjs","package/dist/index.d.ts","package/dist/index.d.cts"]:
try:
data=t.extractfile(n).read().decode(errors="ignore")
except Exception:
continue
if "local" in data.lower():
print(f"--- {n} ---")
for i,line in enumerate(data.splitlines(),1):
if "local" in line.lower() or "export" in line and ("useLocalStorage" in line or "useReadLocalStorage" in line):
print(f"{i}: {line}")
text=' '.join(data.split())
start=text.find("useLocalStorage")
if start!=-1:
print("--- snippet ---")
print(text[max(0,start-800):start+1600])
py "$tmp/package.tgz"Repository: sourcebot-dev/sourcebot Length of output: 376 🏁 Script executed: #!/usr/bin/env bash
set -euo pipefail
tmp="$(mktemp -d)"
curl -fsSL -- connect-timeout 15 https://registry.npmjs.org/usehooks-ts/3.1.1 \
| jq -r '.dist.tarball' \
| xargs curl -fsSL --connect-timeout 15 -o "$tmp/package.tgz"
echo "== build/useLocalStorage references =="
python3 <<'PY' "$tmp/package.tgz"
import tarfile, sys
path=sys.argv[1]
with tarfile.open(path, "r:gz") as t:
for n in t.getnames():
try:
data=t.extractfile(n).read().decode(errors="ignore")
except Exception as e:
continue
if "initializeWithValue" in data or "useLocalStorage" in data:
print(f"--- {n} ---")
for i,line in enumerate(data.splitlines(),1):
if any(k in line for k in ["initializeWithValue", "useLocalStorage", "useEffect", "localStorage", "useStorageState"]):
print(f"{i}: {line}")
PY
echo "== JS module snippet around useLocalStorage exports =="
python3 <<'PY' "$tmp/package.tgz"
import tarfile, sys
path=sys.argv[1]
with tarfile.open(path, "r:gz") as t:
for n in ["package/dist/index.js","package/dist/index.cts","package/dist/index.cjs","package/dist/index.d.ts","package/dist/index.d.cts"]:
try:
data=t.extractfile(n).read().decode(errors="ignore")
except Exception:
continue
if "local" in data.lower():
print(f"--- {n} ---")
for i,line in enumerate(data.splitlines(),1):
if "local" in line.lower() or ("export" in line and ("useLocalStorage" in line or "useReadLocalStorage" in line)):
print(f"{i}: {line}")
text=' '.join(data.split())
start=text.find("useLocalStorage")
if start!=-1:
print("--- snippet ---")
print(text[max(0,start-800):start+1600])
PYRepository: sourcebot-dev/sourcebot Length of output: 153 🏁 Script executed: #!/usr/bin/env bash
set -euo pipefail
tmp="$(mktemp -d)"
curl -L https://codeload.github.com/juliencrn/usehooks-ts/tar.gz/v3.1.1 -o "$tmp/tar.gz"
tar -xzO -f "$tmp/tar.gz" "usehooks-ts-v3.1.1/packages/use-local-storage/src/useLocalStorage.ts" | sed -n '1,240p'
echo "== installed usehooks source if present =="
if [ -f node_modules/usehooks-ts/usehooks-ts-v3.1.1/packages/use-local-storage/src/useLocalStorage.ts ]; then
sed -n '1,240p' node_modules/usehooks-ts/usehooks-ts-v3.1.1/packages/use-local-storage/src/useLocalStorage.ts
else
echo "not present"
fiRepository: sourcebot-dev/sourcebot Length of output: 577 Remove the initialization gate. With 🤖 Prompt for AI Agents |
||
|
|
||
| const imageSrc = imageUrl ? getRepoImageSrc(imageUrl, repoId) : undefined; | ||
| const displayName = repoDisplayName ?? repoName; | ||
|
|
@@ -88,7 +121,7 @@ export const LandingPage = ({ | |
| className="min-h-[50px]" | ||
| isRedirecting={isLoading} | ||
| selectedSearchScopes={selectedSearchScopes} | ||
| searchContexts={[]} | ||
| searchContexts={searchContexts} | ||
| askCommands={askCommands} | ||
| isDisabled={isChatBoxDisabled} | ||
| isAuthenticated={isAuthenticated} | ||
|
|
@@ -100,10 +133,10 @@ export const LandingPage = ({ | |
| <div className="w-full flex flex-row items-center bg-accent rounded-b-md px-2"> | ||
| <ChatBoxToolbar | ||
| languageModels={languageModels} | ||
| repos={[]} | ||
| searchContexts={[]} | ||
| repos={repos} | ||
| searchContexts={searchContexts} | ||
| selectedSearchScopes={selectedSearchScopes} | ||
| onSelectedSearchScopesChange={() => { }} | ||
| onSelectedSearchScopesChange={setSelectedSearchScopes} | ||
| isContextSelectorOpen={isContextSelectorOpen} | ||
| onContextSelectorOpenChanged={setIsContextSelectorOpen} | ||
| disabledMcpServerIds={disabledMcpServerIds} | ||
|
|
||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Current repo never auto-added
High Severity
The one-shot
hasInitializedeffect runs against the pre-hydration default ([defaultRepoScope]), so the current repo always looks included and the add path never runs. AfteruseLocalStoragehydrates with{ initializeWithValue: false }, stored scopes without this repo win, and soft navigations between Ask GH pages also skip re-checking becausehasInitializedstays true. Visiting another repo's Ask GH page therefore does not auto-include that repo in the search scope.Reviewed by Cursor Bugbot for commit bc78eef. Configure here.