Skip to content

Commit 4acfef6

Browse files
anbei.yuanCopilot
andcommitted
feat(prompts): full-text modal on click
- Backend: PromptHit now carries the full prompt text (capped at 16 KB; truncation marker appended when over). - Frontend: clicking a hit opens an inline modal with monospace pre-wrap formatting; Copy button + Open session → button. - Click row no longer jumps directly to session — must use the modal's Open button (more discoverable, no accidental nav). Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
1 parent 9a63bf8 commit 4acfef6

3 files changed

Lines changed: 65 additions & 2 deletions

File tree

crates/pawscope-server/src/api.rs

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -412,6 +412,7 @@ struct PromptHit {
412412
prompt_id: String,
413413
timestamp: Option<chrono::DateTime<chrono::Utc>>,
414414
snippet: String,
415+
text: String,
415416
}
416417

417418
pub async fn prompts_search(
@@ -493,6 +494,16 @@ pub async fn prompts_search(
493494
prompt_id: prompt.id.clone(),
494495
timestamp: prompt.timestamp,
495496
snippet: prompt.snippet.clone(),
497+
text: {
498+
let max = 16 * 1024;
499+
if prompt.text.len() <= max {
500+
prompt.text.clone()
501+
} else {
502+
let mut s = prompt.text[..max].to_string();
503+
s.push_str("\n…[truncated]");
504+
s
505+
}
506+
},
496507
});
497508
}
498509
}

web/src/api.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -162,6 +162,7 @@ export interface PromptHit {
162162
prompt_id: string;
163163
timestamp: string | null;
164164
snippet: string;
165+
text: string;
165166
}
166167
export interface PromptSearchFilters {
167168
agent?: string;

web/src/components/PromptsPanel.tsx

Lines changed: 53 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -44,14 +44,19 @@ interface Props {
4444
onOpenSession: (id: string) => void;
4545
}
4646

47+
interface PromptHitFull extends PromptHit {
48+
text: string;
49+
}
50+
4751
export function PromptsPanel({ onOpenSession }: Props) {
4852
const { t } = useT();
4953
const [q, setQ] = useState('');
5054
const [agent, setAgent] = useState<string>('');
5155
const [repo, setRepo] = useState<string>('');
5256
const [range, setRange] = useState<string>('');
53-
const [hits, setHits] = useState<PromptHit[]>([]);
57+
const [hits, setHits] = useState<PromptHitFull[]>([]);
5458
const [loading, setLoading] = useState(false);
59+
const [modal, setModal] = useState<PromptHitFull | null>(null);
5560
const debounceRef = useRef<ReturnType<typeof setTimeout> | null>(null);
5661

5762
const filters: PromptSearchFilters = useMemo(() => {
@@ -169,7 +174,7 @@ export function PromptsPanel({ onOpenSession }: Props) {
169174
{hits.map((h) => (
170175
<li
171176
key={`${h.session_id}::${h.prompt_id}`}
172-
onClick={() => onOpenSession(h.session_id)}
177+
onClick={() => setModal(h)}
173178
className="px-6 py-3 hover:bg-slate-900/60 cursor-pointer transition-colors"
174179
>
175180
<div className="flex items-center gap-2 mb-1.5">
@@ -199,6 +204,52 @@ export function PromptsPanel({ onOpenSession }: Props) {
199204
</ul>
200205
)}
201206
</div>
207+
{modal && (
208+
<div
209+
className="fixed inset-0 z-50 bg-black/60 backdrop-blur-sm flex items-center justify-center p-4"
210+
onClick={() => setModal(null)}
211+
>
212+
<div
213+
className="bg-slate-900 border border-slate-700 rounded-lg shadow-2xl max-w-3xl w-full max-h-[80vh] flex flex-col"
214+
onClick={(e) => e.stopPropagation()}
215+
>
216+
<header className="px-5 py-3 border-b border-slate-800 flex items-center gap-2">
217+
<span
218+
className={`px-1.5 py-0.5 rounded border text-[10px] uppercase tracking-wider ${
219+
AGENT_BADGE[modal.agent] ?? 'bg-slate-500/10 text-slate-300 border-slate-700'
220+
}`}
221+
>
222+
{modal.agent}
223+
</span>
224+
{modal.repo && <span className="text-[11px] text-slate-400 truncate max-w-xs">{modal.repo}</span>}
225+
{modal.branch && <span className="text-[11px] text-slate-500">· {modal.branch}</span>}
226+
<span className="ml-auto text-[11px] text-slate-500 font-mono">{relTime(modal.timestamp)}</span>
227+
<button
228+
onClick={() => setModal(null)}
229+
className="ml-2 text-slate-500 hover:text-slate-200"
230+
></button>
231+
</header>
232+
<div className="px-5 py-4 overflow-y-auto flex-1">
233+
<pre className="text-sm text-slate-200 whitespace-pre-wrap font-mono leading-relaxed break-words">
234+
{modal.text || modal.snippet}
235+
</pre>
236+
</div>
237+
<footer className="px-5 py-2.5 border-t border-slate-800 flex items-center gap-2">
238+
<span className="text-[11px] text-slate-500 font-mono truncate flex-1">
239+
{modal.session_id}
240+
</span>
241+
<button
242+
onClick={() => navigator.clipboard.writeText(modal.text || modal.snippet)}
243+
className="px-2.5 py-1 text-xs text-slate-300 bg-slate-800 hover:bg-slate-700 rounded"
244+
>Copy</button>
245+
<button
246+
onClick={() => { onOpenSession(modal.session_id); setModal(null); }}
247+
className="px-2.5 py-1 text-xs text-slate-100 bg-emerald-700 hover:bg-emerald-600 rounded"
248+
>Open session →</button>
249+
</footer>
250+
</div>
251+
</div>
252+
)}
202253
</div>
203254
);
204255
}

0 commit comments

Comments
 (0)