init claude-code
This commit is contained in:
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
@@ -0,0 +1,112 @@
|
||||
import axios from 'axios'
|
||||
import { readFile, stat } from 'fs/promises'
|
||||
import type { Message } from '../../types/message.js'
|
||||
import { checkAndRefreshOAuthTokenIfNeeded } from '../../utils/auth.js'
|
||||
import { logForDebugging } from '../../utils/debug.js'
|
||||
import { errorMessage } from '../../utils/errors.js'
|
||||
import { getAuthHeaders, getUserAgent } from '../../utils/http.js'
|
||||
import { normalizeMessagesForAPI } from '../../utils/messages.js'
|
||||
import {
|
||||
extractAgentIdsFromMessages,
|
||||
getTranscriptPath,
|
||||
loadSubagentTranscripts,
|
||||
MAX_TRANSCRIPT_READ_BYTES,
|
||||
} from '../../utils/sessionStorage.js'
|
||||
import { jsonStringify } from '../../utils/slowOperations.js'
|
||||
import { redactSensitiveInfo } from '../Feedback.js'
|
||||
|
||||
type TranscriptShareResult = {
|
||||
success: boolean
|
||||
transcriptId?: string
|
||||
}
|
||||
|
||||
export type TranscriptShareTrigger =
|
||||
| 'bad_feedback_survey'
|
||||
| 'good_feedback_survey'
|
||||
| 'frustration'
|
||||
| 'memory_survey'
|
||||
|
||||
export async function submitTranscriptShare(
|
||||
messages: Message[],
|
||||
trigger: TranscriptShareTrigger,
|
||||
appearanceId: string,
|
||||
): Promise<TranscriptShareResult> {
|
||||
try {
|
||||
logForDebugging('Collecting transcript for sharing', { level: 'info' })
|
||||
|
||||
const transcript = normalizeMessagesForAPI(messages)
|
||||
|
||||
// Collect subagent transcripts
|
||||
const agentIds = extractAgentIdsFromMessages(messages)
|
||||
const subagentTranscripts = await loadSubagentTranscripts(agentIds)
|
||||
|
||||
// Read raw JSONL transcript (with size guard to prevent OOM)
|
||||
let rawTranscriptJsonl: string | undefined
|
||||
try {
|
||||
const transcriptPath = getTranscriptPath()
|
||||
const { size } = await stat(transcriptPath)
|
||||
if (size <= MAX_TRANSCRIPT_READ_BYTES) {
|
||||
rawTranscriptJsonl = await readFile(transcriptPath, 'utf-8')
|
||||
} else {
|
||||
logForDebugging(
|
||||
`Skipping raw transcript read: file too large (${size} bytes)`,
|
||||
{ level: 'warn' },
|
||||
)
|
||||
}
|
||||
} catch {
|
||||
// File may not exist
|
||||
}
|
||||
|
||||
const data = {
|
||||
trigger,
|
||||
version: MACRO.VERSION,
|
||||
platform: process.platform,
|
||||
transcript,
|
||||
subagentTranscripts:
|
||||
Object.keys(subagentTranscripts).length > 0
|
||||
? subagentTranscripts
|
||||
: undefined,
|
||||
rawTranscriptJsonl,
|
||||
}
|
||||
|
||||
const content = redactSensitiveInfo(jsonStringify(data))
|
||||
|
||||
await checkAndRefreshOAuthTokenIfNeeded()
|
||||
|
||||
const authResult = getAuthHeaders()
|
||||
if (authResult.error) {
|
||||
return { success: false }
|
||||
}
|
||||
|
||||
const headers: Record<string, string> = {
|
||||
'Content-Type': 'application/json',
|
||||
'User-Agent': getUserAgent(),
|
||||
...authResult.headers,
|
||||
}
|
||||
|
||||
const response = await axios.post(
|
||||
'https://api.anthropic.com/api/claude_code_shared_session_transcripts',
|
||||
{ content, appearance_id: appearanceId },
|
||||
{
|
||||
headers,
|
||||
timeout: 30000,
|
||||
},
|
||||
)
|
||||
|
||||
if (response.status === 200 || response.status === 201) {
|
||||
const result = response.data
|
||||
logForDebugging('Transcript shared successfully', { level: 'info' })
|
||||
return {
|
||||
success: true,
|
||||
transcriptId: result?.transcript_id,
|
||||
}
|
||||
}
|
||||
|
||||
return { success: false }
|
||||
} catch (err) {
|
||||
logForDebugging(errorMessage(err), {
|
||||
level: 'error',
|
||||
})
|
||||
return { success: false }
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,82 @@
|
||||
import { useEffect, useRef } from 'react'
|
||||
import { normalizeFullWidthDigits } from '../../utils/stringUtils.js'
|
||||
|
||||
// Delay before accepting a digit as a response, to prevent accidental
|
||||
// submissions when users start messages with numbers (e.g., numbered lists).
|
||||
// Short enough to feel instant for intentional presses, long enough to
|
||||
// cancel when the user types more characters.
|
||||
const DEFAULT_DEBOUNCE_MS = 400
|
||||
|
||||
/**
|
||||
* Detects when the user types a single valid digit into the prompt input,
|
||||
* debounces to avoid accidental submissions (e.g., "1. First item"),
|
||||
* trims the digit from the input, and fires a callback.
|
||||
*
|
||||
* Used by survey components that accept numeric responses typed directly
|
||||
* into the main prompt input.
|
||||
*/
|
||||
export function useDebouncedDigitInput<T extends string = string>({
|
||||
inputValue,
|
||||
setInputValue,
|
||||
isValidDigit,
|
||||
onDigit,
|
||||
enabled = true,
|
||||
once = false,
|
||||
debounceMs = DEFAULT_DEBOUNCE_MS,
|
||||
}: {
|
||||
inputValue: string
|
||||
setInputValue: (value: string) => void
|
||||
isValidDigit: (char: string) => char is T
|
||||
onDigit: (digit: T) => void
|
||||
enabled?: boolean
|
||||
once?: boolean
|
||||
debounceMs?: number
|
||||
}): void {
|
||||
const initialInputValue = useRef(inputValue)
|
||||
const hasTriggeredRef = useRef(false)
|
||||
const debounceRef = useRef<ReturnType<typeof setTimeout> | null>(null)
|
||||
|
||||
// Latest-ref pattern so callers can pass inline callbacks without causing
|
||||
// the effect to re-run (which would reset the debounce timer every render).
|
||||
const callbacksRef = useRef({ setInputValue, isValidDigit, onDigit })
|
||||
callbacksRef.current = { setInputValue, isValidDigit, onDigit }
|
||||
|
||||
useEffect(() => {
|
||||
if (!enabled || (once && hasTriggeredRef.current)) {
|
||||
return
|
||||
}
|
||||
|
||||
if (debounceRef.current !== null) {
|
||||
clearTimeout(debounceRef.current)
|
||||
debounceRef.current = null
|
||||
}
|
||||
|
||||
if (inputValue !== initialInputValue.current) {
|
||||
const lastChar = normalizeFullWidthDigits(inputValue.slice(-1))
|
||||
if (callbacksRef.current.isValidDigit(lastChar)) {
|
||||
const trimmed = inputValue.slice(0, -1)
|
||||
debounceRef.current = setTimeout(
|
||||
(debounceRef, hasTriggeredRef, callbacksRef, trimmed, lastChar) => {
|
||||
debounceRef.current = null
|
||||
hasTriggeredRef.current = true
|
||||
callbacksRef.current.setInputValue(trimmed)
|
||||
callbacksRef.current.onDigit(lastChar)
|
||||
},
|
||||
debounceMs,
|
||||
debounceRef,
|
||||
hasTriggeredRef,
|
||||
callbacksRef,
|
||||
trimmed,
|
||||
lastChar,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
return () => {
|
||||
if (debounceRef.current !== null) {
|
||||
clearTimeout(debounceRef.current)
|
||||
debounceRef.current = null
|
||||
}
|
||||
}
|
||||
}, [inputValue, enabled, once, debounceMs])
|
||||
}
|
||||
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
Reference in New Issue
Block a user