mirror of
https://github.com/RooVetGit/Roo-Code.git
synced 2026-08-28 05:27:24 +00:00
commit
08a449eb8f
11 changed files with 502 additions and 3 deletions
|
|
@ -3,6 +3,34 @@ import delay from "delay"
|
|||
|
||||
import { ClineProvider } from "../core/webview/ClineProvider"
|
||||
|
||||
// Store panel references in both modes
|
||||
let sidebarPanel: vscode.WebviewView | undefined = undefined
|
||||
let tabPanel: vscode.WebviewPanel | undefined = undefined
|
||||
|
||||
/**
|
||||
* Get the currently active panel
|
||||
* @returns WebviewPanel或WebviewView
|
||||
*/
|
||||
export function getPanel(): vscode.WebviewPanel | vscode.WebviewView | undefined {
|
||||
return tabPanel || sidebarPanel
|
||||
}
|
||||
|
||||
/**
|
||||
* Set panel references
|
||||
*/
|
||||
export function setPanel(
|
||||
newPanel: vscode.WebviewPanel | vscode.WebviewView | undefined,
|
||||
type: "sidebar" | "tab",
|
||||
): void {
|
||||
if (type === "sidebar") {
|
||||
sidebarPanel = newPanel as vscode.WebviewView
|
||||
tabPanel = undefined
|
||||
} else {
|
||||
tabPanel = newPanel as vscode.WebviewPanel
|
||||
sidebarPanel = undefined
|
||||
}
|
||||
}
|
||||
|
||||
export type RegisterCommandOptions = {
|
||||
context: vscode.ExtensionContext
|
||||
outputChannel: vscode.OutputChannel
|
||||
|
|
@ -15,6 +43,22 @@ export const registerCommands = (options: RegisterCommandOptions) => {
|
|||
for (const [command, callback] of Object.entries(getCommandsMap(options))) {
|
||||
context.subscriptions.push(vscode.commands.registerCommand(command, callback))
|
||||
}
|
||||
|
||||
// Human Relay Dialog Command
|
||||
context.subscriptions.push(
|
||||
vscode.commands.registerCommand(
|
||||
"roo-cline.showHumanRelayDialog",
|
||||
(params: { requestId: string; promptText: string }) => {
|
||||
if (getPanel()) {
|
||||
getPanel()?.webview.postMessage({
|
||||
type: "showHumanRelayDialog",
|
||||
requestId: params.requestId,
|
||||
promptText: params.promptText,
|
||||
})
|
||||
}
|
||||
},
|
||||
),
|
||||
)
|
||||
}
|
||||
|
||||
const getCommandsMap = ({ context, outputChannel, provider }: RegisterCommandOptions) => {
|
||||
|
|
@ -65,20 +109,28 @@ const openClineInNewTab = async ({ context, outputChannel }: Omit<RegisterComman
|
|||
|
||||
const targetCol = hasVisibleEditors ? Math.max(lastCol + 1, 1) : vscode.ViewColumn.Two
|
||||
|
||||
const panel = vscode.window.createWebviewPanel(ClineProvider.tabPanelId, "Roo Code", targetCol, {
|
||||
const newPanel = vscode.window.createWebviewPanel(ClineProvider.tabPanelId, "Roo Code", targetCol, {
|
||||
enableScripts: true,
|
||||
retainContextWhenHidden: true,
|
||||
localResourceRoots: [context.extensionUri],
|
||||
})
|
||||
|
||||
// Save as tab type panel
|
||||
setPanel(newPanel, "tab")
|
||||
|
||||
// TODO: use better svg icon with light and dark variants (see
|
||||
// https://stackoverflow.com/questions/58365687/vscode-extension-iconpath).
|
||||
panel.iconPath = {
|
||||
newPanel.iconPath = {
|
||||
light: vscode.Uri.joinPath(context.extensionUri, "assets", "icons", "rocket.png"),
|
||||
dark: vscode.Uri.joinPath(context.extensionUri, "assets", "icons", "rocket.png"),
|
||||
}
|
||||
|
||||
await tabProvider.resolveWebviewView(panel)
|
||||
await tabProvider.resolveWebviewView(newPanel)
|
||||
|
||||
// Handle panel closing events
|
||||
newPanel.onDidDispose(() => {
|
||||
setPanel(undefined, "tab")
|
||||
})
|
||||
|
||||
// Lock the editor group so clicking on files doesn't open them over the panel
|
||||
await delay(100)
|
||||
|
|
|
|||
|
|
@ -19,6 +19,7 @@ import { VsCodeLmHandler } from "./providers/vscode-lm"
|
|||
import { ApiStream } from "./transform/stream"
|
||||
import { UnboundHandler } from "./providers/unbound"
|
||||
import { RequestyHandler } from "./providers/requesty"
|
||||
import { HumanRelayHandler } from "./providers/human-relay"
|
||||
|
||||
export interface SingleCompletionHandler {
|
||||
completePrompt(prompt: string): Promise<string>
|
||||
|
|
@ -72,6 +73,8 @@ export function buildApiHandler(configuration: ApiConfiguration): ApiHandler {
|
|||
return new UnboundHandler(options)
|
||||
case "requesty":
|
||||
return new RequestyHandler(options)
|
||||
case "human-relay":
|
||||
return new HumanRelayHandler(options)
|
||||
default:
|
||||
return new AnthropicHandler(options)
|
||||
}
|
||||
|
|
|
|||
139
src/api/providers/human-relay.ts
Normal file
139
src/api/providers/human-relay.ts
Normal file
|
|
@ -0,0 +1,139 @@
|
|||
// filepath: e:\Project\Roo-Code\src\api\providers\human-relay.ts
|
||||
import { Anthropic } from "@anthropic-ai/sdk"
|
||||
import { ApiHandlerOptions, ModelInfo } from "../../shared/api"
|
||||
import { ApiHandler, SingleCompletionHandler } from "../index"
|
||||
import { ApiStream } from "../transform/stream"
|
||||
import * as vscode from "vscode"
|
||||
import { ExtensionMessage } from "../../shared/ExtensionMessage"
|
||||
import { getPanel } from "../../activate/registerCommands" // Import the getPanel function
|
||||
|
||||
/**
|
||||
* Human Relay API processor
|
||||
* This processor does not directly call the API, but interacts with the model through human operations copy and paste.
|
||||
*/
|
||||
export class HumanRelayHandler implements ApiHandler, SingleCompletionHandler {
|
||||
private options: ApiHandlerOptions
|
||||
|
||||
constructor(options: ApiHandlerOptions) {
|
||||
this.options = options
|
||||
}
|
||||
countTokens(content: Array<Anthropic.Messages.ContentBlockParam>): Promise<number> {
|
||||
return Promise.resolve(0)
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a message processing flow, display a dialog box to request human assistance
|
||||
* @param systemPrompt System prompt words
|
||||
* @param messages Message list
|
||||
*/
|
||||
async *createMessage(systemPrompt: string, messages: Anthropic.Messages.MessageParam[]): ApiStream {
|
||||
// Get the most recent user message
|
||||
const latestMessage = messages[messages.length - 1]
|
||||
|
||||
if (!latestMessage) {
|
||||
throw new Error("No message to relay")
|
||||
}
|
||||
|
||||
// If it is the first message, splice the system prompt word with the user message
|
||||
let promptText = ""
|
||||
if (messages.length === 1) {
|
||||
promptText = `${systemPrompt}\n\n${getMessageContent(latestMessage)}`
|
||||
} else {
|
||||
promptText = getMessageContent(latestMessage)
|
||||
}
|
||||
|
||||
// Copy to clipboard
|
||||
await vscode.env.clipboard.writeText(promptText)
|
||||
|
||||
// A dialog box pops up to request user action
|
||||
const response = await showHumanRelayDialog(promptText)
|
||||
|
||||
if (!response) {
|
||||
// The user canceled the operation
|
||||
throw new Error("Human relay operation cancelled")
|
||||
}
|
||||
|
||||
// Return to the user input reply
|
||||
yield { type: "text", text: response }
|
||||
}
|
||||
|
||||
/**
|
||||
* Get model information
|
||||
*/
|
||||
getModel(): { id: string; info: ModelInfo } {
|
||||
// Human relay does not depend on a specific model, here is a default configuration
|
||||
return {
|
||||
id: "human-relay",
|
||||
info: {
|
||||
maxTokens: 16384,
|
||||
contextWindow: 100000,
|
||||
supportsImages: true,
|
||||
supportsPromptCache: false,
|
||||
supportsComputerUse: true,
|
||||
inputPrice: 0,
|
||||
outputPrice: 0,
|
||||
description: "Calling web-side AI model through human relay",
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Implementation of a single prompt
|
||||
* @param prompt Prompt content
|
||||
*/
|
||||
async completePrompt(prompt: string): Promise<string> {
|
||||
// Copy to clipboard
|
||||
await vscode.env.clipboard.writeText(prompt)
|
||||
|
||||
// A dialog box pops up to request user action
|
||||
const response = await showHumanRelayDialog(prompt)
|
||||
|
||||
if (!response) {
|
||||
throw new Error("Human relay operation cancelled")
|
||||
}
|
||||
|
||||
return response
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Extract text content from message object
|
||||
* @param message
|
||||
*/
|
||||
function getMessageContent(message: Anthropic.Messages.MessageParam): string {
|
||||
if (typeof message.content === "string") {
|
||||
return message.content
|
||||
} else if (Array.isArray(message.content)) {
|
||||
return message.content
|
||||
.filter((item) => item.type === "text")
|
||||
.map((item) => (item.type === "text" ? item.text : ""))
|
||||
.join("\n")
|
||||
}
|
||||
return ""
|
||||
}
|
||||
/**
|
||||
* Displays the human relay dialog and waits for user response.
|
||||
* @param promptText The prompt text that needs to be copied.
|
||||
* @returns The user's input response or undefined (if canceled).
|
||||
*/
|
||||
async function showHumanRelayDialog(promptText: string): Promise<string | undefined> {
|
||||
return new Promise<string | undefined>((resolve) => {
|
||||
// Create a unique request ID
|
||||
const requestId = Date.now().toString()
|
||||
|
||||
// Register a global callback function
|
||||
vscode.commands.executeCommand(
|
||||
"roo-cline.registerHumanRelayCallback",
|
||||
requestId,
|
||||
(response: string | undefined) => {
|
||||
resolve(response)
|
||||
},
|
||||
)
|
||||
|
||||
// Open the dialog box directly using the current panel
|
||||
vscode.commands.executeCommand("roo-cline.showHumanRelayDialog", {
|
||||
requestId,
|
||||
promptText,
|
||||
})
|
||||
})
|
||||
}
|
||||
|
|
@ -7,6 +7,7 @@ import pWaitFor from "p-wait-for"
|
|||
import * as path from "path"
|
||||
import * as vscode from "vscode"
|
||||
import simpleGit from "simple-git"
|
||||
import { setPanel } from "../../activate/registerCommands"
|
||||
|
||||
import { ApiConfiguration, ApiProvider, ModelInfo, API_CONFIG_KEYS } from "../../shared/api"
|
||||
import { CheckpointStorage } from "../../shared/checkpoints"
|
||||
|
|
@ -237,6 +238,15 @@ export class ClineProvider implements vscode.WebviewViewProvider {
|
|||
this.outputChannel.appendLine("Resolving webview view")
|
||||
this.view = webviewView
|
||||
|
||||
// Set panel reference according to webview type
|
||||
if ("onDidChangeViewState" in webviewView) {
|
||||
// Tag page type
|
||||
setPanel(webviewView, "tab")
|
||||
} else if ("onDidChangeVisibility" in webviewView) {
|
||||
// Sidebar Type
|
||||
setPanel(webviewView, "sidebar")
|
||||
}
|
||||
|
||||
// Initialize sound enabled state
|
||||
this.getState().then(({ soundEnabled }) => {
|
||||
setSoundEnabled(soundEnabled ?? false)
|
||||
|
|
@ -1558,6 +1568,25 @@ export class ClineProvider implements vscode.WebviewViewProvider {
|
|||
await this.updateGlobalState("mode", defaultModeSlug)
|
||||
await this.postStateToWebview()
|
||||
}
|
||||
break
|
||||
case "humanRelayResponse":
|
||||
if (message.requestId && message.text) {
|
||||
vscode.commands.executeCommand("roo-cline.handleHumanRelayResponse", {
|
||||
requestId: message.requestId,
|
||||
text: message.text,
|
||||
cancelled: false,
|
||||
})
|
||||
}
|
||||
break
|
||||
|
||||
case "humanRelayCancel":
|
||||
if (message.requestId) {
|
||||
vscode.commands.executeCommand("roo-cline.handleHumanRelayResponse", {
|
||||
requestId: message.requestId,
|
||||
cancelled: true,
|
||||
})
|
||||
}
|
||||
break
|
||||
}
|
||||
},
|
||||
null,
|
||||
|
|
|
|||
|
|
@ -19,6 +19,18 @@ import { McpServerManager } from "./services/mcp/McpServerManager"
|
|||
let outputChannel: vscode.OutputChannel
|
||||
let extensionContext: vscode.ExtensionContext
|
||||
|
||||
// Callback mapping of human relay response
|
||||
const humanRelayCallbacks = new Map<string, (response: string | undefined) => void>()
|
||||
|
||||
/**
|
||||
* Register a callback function for human relay response
|
||||
* @param requestId
|
||||
* @param callback
|
||||
*/
|
||||
export function registerHumanRelayCallback(requestId: string, callback: (response: string | undefined) => void): void {
|
||||
humanRelayCallbacks.set(requestId, callback)
|
||||
}
|
||||
|
||||
// This method is called when your extension is activated.
|
||||
// Your extension is activated the very first time the command is executed.
|
||||
export function activate(context: vscode.ExtensionContext) {
|
||||
|
|
@ -45,6 +57,40 @@ export function activate(context: vscode.ExtensionContext) {
|
|||
|
||||
registerCommands({ context, outputChannel, provider: sidebarProvider })
|
||||
|
||||
// Register human relay callback registration command
|
||||
context.subscriptions.push(
|
||||
vscode.commands.registerCommand(
|
||||
"roo-cline.registerHumanRelayCallback",
|
||||
(requestId: string, callback: (response: string | undefined) => void) => {
|
||||
registerHumanRelayCallback(requestId, callback)
|
||||
},
|
||||
),
|
||||
)
|
||||
|
||||
// Register human relay response processing command
|
||||
context.subscriptions.push(
|
||||
vscode.commands.registerCommand(
|
||||
"roo-cline.handleHumanRelayResponse",
|
||||
(response: { requestId: string; text?: string; cancelled?: boolean }) => {
|
||||
const callback = humanRelayCallbacks.get(response.requestId)
|
||||
if (callback) {
|
||||
if (response.cancelled) {
|
||||
callback(undefined)
|
||||
} else {
|
||||
callback(response.text)
|
||||
}
|
||||
humanRelayCallbacks.delete(response.requestId)
|
||||
}
|
||||
},
|
||||
),
|
||||
)
|
||||
|
||||
context.subscriptions.push(
|
||||
vscode.commands.registerCommand("roo-cline.unregisterHumanRelayCallback", (requestId: string) => {
|
||||
humanRelayCallbacks.delete(requestId)
|
||||
}),
|
||||
)
|
||||
|
||||
/**
|
||||
* We use the text document content provider API to show the left side for diff
|
||||
* view by creating a virtual document for the original content. This makes it
|
||||
|
|
|
|||
|
|
@ -46,6 +46,9 @@ export interface ExtensionMessage {
|
|||
| "updateCustomMode"
|
||||
| "deleteCustomMode"
|
||||
| "currentCheckpointUpdated"
|
||||
| "showHumanRelayDialog"
|
||||
| "humanRelayResponse"
|
||||
| "humanRelayCancel"
|
||||
| "browserToolEnabled"
|
||||
text?: string
|
||||
action?:
|
||||
|
|
@ -243,4 +246,22 @@ export interface ClineApiReqInfo {
|
|||
streamingFailedMessage?: string
|
||||
}
|
||||
|
||||
// Human relay related message types
|
||||
export interface ShowHumanRelayDialogMessage {
|
||||
type: "showHumanRelayDialog"
|
||||
requestId: string
|
||||
promptText: string
|
||||
}
|
||||
|
||||
export interface HumanRelayResponseMessage {
|
||||
type: "humanRelayResponse"
|
||||
requestId: string
|
||||
text: string
|
||||
}
|
||||
|
||||
export interface HumanRelayCancelMessage {
|
||||
type: "humanRelayCancel"
|
||||
requestId: string
|
||||
}
|
||||
|
||||
export type ClineApiReqCancelReason = "streaming_failed" | "user_cancelled"
|
||||
|
|
|
|||
|
|
@ -95,6 +95,8 @@ export interface WebviewMessage {
|
|||
| "checkpointRestore"
|
||||
| "deleteMcpServer"
|
||||
| "maxOpenTabsContext"
|
||||
| "humanRelayResponse"
|
||||
| "humanRelayCancel"
|
||||
| "browserToolEnabled"
|
||||
text?: string
|
||||
disabled?: boolean
|
||||
|
|
@ -119,6 +121,19 @@ export interface WebviewMessage {
|
|||
timeout?: number
|
||||
payload?: WebViewMessagePayload
|
||||
source?: "global" | "project"
|
||||
requestId?: string
|
||||
}
|
||||
|
||||
// Human relay related message types
|
||||
export interface HumanRelayResponseMessage extends WebviewMessage {
|
||||
type: "humanRelayResponse"
|
||||
requestId: string
|
||||
text: string
|
||||
}
|
||||
|
||||
export interface HumanRelayCancelMessage extends WebviewMessage {
|
||||
type: "humanRelayCancel"
|
||||
requestId: string
|
||||
}
|
||||
|
||||
export const checkoutDiffPayloadSchema = z.object({
|
||||
|
|
|
|||
|
|
@ -16,6 +16,7 @@ export type ApiProvider =
|
|||
| "mistral"
|
||||
| "unbound"
|
||||
| "requesty"
|
||||
| "human-relay"
|
||||
|
||||
export interface ApiHandlerOptions {
|
||||
apiModelId?: string
|
||||
|
|
|
|||
|
|
@ -2,6 +2,7 @@ import { useCallback, useEffect, useRef, useState } from "react"
|
|||
import { useEvent } from "react-use"
|
||||
|
||||
import { ExtensionMessage } from "../../src/shared/ExtensionMessage"
|
||||
import { ShowHumanRelayDialogMessage } from "../../src/shared/ExtensionMessage"
|
||||
|
||||
import { vscode } from "./utils/vscode"
|
||||
import { ExtensionStateContextProvider, useExtensionState } from "./context/ExtensionStateContext"
|
||||
|
|
@ -11,6 +12,7 @@ import SettingsView, { SettingsViewRef } from "./components/settings/SettingsVie
|
|||
import WelcomeView from "./components/welcome/WelcomeView"
|
||||
import McpView from "./components/mcp/McpView"
|
||||
import PromptsView from "./components/prompts/PromptsView"
|
||||
import { HumanRelayDialog } from "./components/human-relay/HumanRelayDialog"
|
||||
|
||||
type Tab = "settings" | "history" | "mcp" | "prompts" | "chat"
|
||||
|
||||
|
|
@ -28,6 +30,17 @@ const App = () => {
|
|||
const [tab, setTab] = useState<Tab>("chat")
|
||||
const settingsRef = useRef<SettingsViewRef>(null)
|
||||
|
||||
// Human Relay Dialog Status
|
||||
const [humanRelayDialogState, setHumanRelayDialogState] = useState<{
|
||||
isOpen: boolean
|
||||
requestId: string
|
||||
promptText: string
|
||||
}>({
|
||||
isOpen: false,
|
||||
requestId: "",
|
||||
promptText: "",
|
||||
})
|
||||
|
||||
const switchTab = useCallback((newTab: Tab) => {
|
||||
if (settingsRef.current?.checkUnsaveChanges) {
|
||||
settingsRef.current.checkUnsaveChanges(() => setTab(newTab))
|
||||
|
|
@ -47,10 +60,36 @@ const App = () => {
|
|||
switchTab(newTab)
|
||||
}
|
||||
}
|
||||
const mes: ShowHumanRelayDialogMessage = message as ShowHumanRelayDialogMessage
|
||||
// Processing displays human relay dialog messages
|
||||
if (mes.type === "showHumanRelayDialog" && mes.requestId && mes.promptText) {
|
||||
setHumanRelayDialogState({
|
||||
isOpen: true,
|
||||
requestId: mes.requestId,
|
||||
promptText: mes.promptText,
|
||||
})
|
||||
}
|
||||
},
|
||||
[switchTab],
|
||||
)
|
||||
|
||||
// Processing Human Relay Dialog Submission
|
||||
const handleHumanRelaySubmit = (requestId: string, text: string) => {
|
||||
vscode.postMessage({
|
||||
type: "humanRelayResponse",
|
||||
requestId,
|
||||
text,
|
||||
})
|
||||
}
|
||||
|
||||
// Handle Human Relay dialog box cancel
|
||||
const handleHumanRelayCancel = (requestId: string) => {
|
||||
vscode.postMessage({
|
||||
type: "humanRelayCancel",
|
||||
requestId,
|
||||
})
|
||||
}
|
||||
|
||||
useEvent("message", onMessage)
|
||||
|
||||
useEffect(() => {
|
||||
|
|
@ -60,6 +99,11 @@ const App = () => {
|
|||
}
|
||||
}, [shouldShowAnnouncement])
|
||||
|
||||
// Tell Extension that we are ready to receive messages
|
||||
useEffect(() => {
|
||||
vscode.postMessage({ type: "webviewDidLaunch" })
|
||||
}, [])
|
||||
|
||||
if (!didHydrateState) {
|
||||
return null
|
||||
}
|
||||
|
|
@ -80,6 +124,15 @@ const App = () => {
|
|||
hideAnnouncement={() => setShowAnnouncement(false)}
|
||||
showHistoryView={() => switchTab("history")}
|
||||
/>
|
||||
{/* Human Relay Dialog */}
|
||||
<HumanRelayDialog
|
||||
isOpen={humanRelayDialogState.isOpen}
|
||||
requestId={humanRelayDialogState.requestId}
|
||||
promptText={humanRelayDialogState.promptText}
|
||||
onClose={() => setHumanRelayDialogState((prev) => ({ ...prev, isOpen: false }))}
|
||||
onSubmit={handleHumanRelaySubmit}
|
||||
onCancel={handleHumanRelayCancel}
|
||||
/>
|
||||
</>
|
||||
)
|
||||
}
|
||||
|
|
|
|||
113
webview-ui/src/components/human-relay/HumanRelayDialog.tsx
Normal file
113
webview-ui/src/components/human-relay/HumanRelayDialog.tsx
Normal file
|
|
@ -0,0 +1,113 @@
|
|||
import * as React from "react"
|
||||
import { Button } from "../ui/button"
|
||||
import { Dialog, DialogContent, DialogDescription, DialogFooter, DialogHeader, DialogTitle } from "../ui/dialog"
|
||||
import { Textarea } from "../ui/textarea"
|
||||
import { useClipboard } from "../ui/hooks"
|
||||
import { Check, Copy, X } from "lucide-react"
|
||||
|
||||
interface HumanRelayDialogProps {
|
||||
isOpen: boolean
|
||||
onClose: () => void
|
||||
requestId: string
|
||||
promptText: string
|
||||
onSubmit: (requestId: string, text: string) => void
|
||||
onCancel: (requestId: string) => void
|
||||
}
|
||||
|
||||
/**
|
||||
* Human Relay Dialog Component
|
||||
* Displays the prompt text that needs to be copied and provides an input box for the user to paste the AI's response.
|
||||
*/
|
||||
export const HumanRelayDialog: React.FC<HumanRelayDialogProps> = ({
|
||||
isOpen,
|
||||
onClose,
|
||||
requestId,
|
||||
promptText,
|
||||
onSubmit,
|
||||
onCancel,
|
||||
}) => {
|
||||
const [response, setResponse] = React.useState("")
|
||||
const { copy } = useClipboard()
|
||||
const [isCopyClicked, setIsCopyClicked] = React.useState(false)
|
||||
|
||||
// Listen to isOpen changes, clear the input box when the dialog box is opened
|
||||
React.useEffect(() => {
|
||||
if (isOpen) {
|
||||
setResponse("")
|
||||
setIsCopyClicked(false)
|
||||
}
|
||||
}, [isOpen])
|
||||
|
||||
// Copy to clipboard and show a success message
|
||||
const handleCopy = () => {
|
||||
copy(promptText)
|
||||
setIsCopyClicked(true)
|
||||
setTimeout(() => {
|
||||
setIsCopyClicked(false)
|
||||
}, 2000)
|
||||
}
|
||||
|
||||
// Submit the response
|
||||
const handleSubmit = (e: React.FormEvent) => {
|
||||
e.preventDefault()
|
||||
if (response.trim()) {
|
||||
onSubmit(requestId, response)
|
||||
onClose()
|
||||
}
|
||||
}
|
||||
|
||||
// Cancel the operation
|
||||
const handleCancel = () => {
|
||||
onCancel(requestId)
|
||||
onClose()
|
||||
}
|
||||
|
||||
return (
|
||||
<Dialog open={isOpen} onOpenChange={(open) => !open && handleCancel()}>
|
||||
<DialogContent className="sm:max-w-[600px]">
|
||||
<DialogHeader>
|
||||
<DialogTitle>Human Relay - Please Help Copy and Paste Information</DialogTitle>
|
||||
<DialogDescription>
|
||||
Please copy the text below to the web AI, then paste the AI's response into the input box below.
|
||||
</DialogDescription>
|
||||
</DialogHeader>
|
||||
|
||||
<div className="grid gap-4 py-4">
|
||||
<div className="relative">
|
||||
<Textarea
|
||||
className="min-h-[200px] font-mono text-sm p-4 pr-12 whitespace-pre-wrap"
|
||||
value={promptText}
|
||||
readOnly
|
||||
/>
|
||||
<Button variant="ghost" size="icon" className="absolute top-2 right-2" onClick={handleCopy}>
|
||||
{isCopyClicked ? <Check className="h-4 w-4" /> : <Copy className="h-4 w-4" />}
|
||||
</Button>
|
||||
</div>
|
||||
|
||||
{isCopyClicked && <div className="text-sm text-emerald-500 font-medium">Copied to clipboard</div>}
|
||||
|
||||
<div>
|
||||
<div className="mb-2 font-medium">Please enter the AI's response:</div>
|
||||
<Textarea
|
||||
placeholder="Paste the AI's response here..."
|
||||
value={response}
|
||||
onChange={(e) => setResponse(e.target.value)}
|
||||
className="min-h-[150px]"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<DialogFooter>
|
||||
<Button variant="outline" onClick={handleCancel} className="gap-1">
|
||||
<X className="h-4 w-4" />
|
||||
Cancel
|
||||
</Button>
|
||||
<Button onClick={handleSubmit} disabled={!response.trim()} className="gap-1">
|
||||
<Check className="h-4 w-4" />
|
||||
Submit
|
||||
</Button>
|
||||
</DialogFooter>
|
||||
</DialogContent>
|
||||
</Dialog>
|
||||
)
|
||||
}
|
||||
|
|
@ -257,6 +257,7 @@ const ApiOptions = ({
|
|||
{ value: "ollama", label: "Ollama" },
|
||||
{ value: "unbound", label: "Unbound" },
|
||||
{ value: "requesty", label: "Requesty" },
|
||||
{ value: "human-relay", label: "Human Relay" },
|
||||
]}
|
||||
/>
|
||||
</div>
|
||||
|
|
@ -1378,6 +1379,32 @@ const ApiOptions = ({
|
|||
</div>
|
||||
)}
|
||||
|
||||
{selectedProvider === "human-relay" && (
|
||||
<div>
|
||||
<p
|
||||
style={{
|
||||
fontSize: "12px",
|
||||
marginTop: 5,
|
||||
color: "var(--vscode-descriptionForeground)",
|
||||
lineHeight: "1.4",
|
||||
}}>
|
||||
The API key is not required, but the user needs to help copy and paste the information to the
|
||||
web chat AI.
|
||||
</p>
|
||||
<p
|
||||
style={{
|
||||
fontSize: "12px",
|
||||
marginTop: 10,
|
||||
color: "var(--vscode-descriptionForeground)",
|
||||
lineHeight: "1.4",
|
||||
}}>
|
||||
During use, a dialog box will pop up and the current message will be copied to the clipboard
|
||||
automatically. You need to paste these to web versions of AI (such as ChatGPT or Claude), then
|
||||
copy the AI's reply back to the dialog box and click the confirm button.
|
||||
</p>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{selectedProvider === "openrouter" && (
|
||||
<ModelPicker
|
||||
apiConfiguration={apiConfiguration}
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue