完善folder页面的web接口实现

This commit is contained in:
xintaofei
2026-03-25 15:27:43 +08:00
parent ac09d3db9e
commit 218055ab01
18 changed files with 569 additions and 37 deletions

View File

@@ -29,7 +29,7 @@ import {
DropdownMenuItem,
DropdownMenuTrigger,
} from "@/components/ui/dropdown-menu"
import { cn } from "@/lib/utils"
import { cn, randomUUID } from "@/lib/utils"
import { matchShortcutEvent } from "@/lib/keyboard-shortcuts"
import { useShortcutSettings } from "@/hooks/use-shortcut-settings"
import { readFileBase64 } from "@/lib/api"
@@ -245,7 +245,7 @@ function isTextLikeFile(file: File): boolean {
function buildClipboardResourceUri(name: string): string {
const normalizedName = name.trim() || "clipboard-resource"
return `clipboard://${encodeURIComponent(normalizedName)}-${crypto.randomUUID()}`
return `clipboard://${encodeURIComponent(normalizedName)}-${randomUUID()}`
}
function buildDataUri(base64Data: string, mimeType: string | null): string {
@@ -491,7 +491,7 @@ export function MessageInput({
setAttachments((prev) => [
...prev,
...resources.map((resource) => ({
id: `resource-embedded:${crypto.randomUUID()}`,
id: `resource-embedded:${randomUUID()}`,
type: "resource" as const,
kind: "embedded" as const,
uri: resource.uri,
@@ -530,7 +530,7 @@ export function MessageInput({
for (const file of files) {
const path = getFilePath(file)
const name = file.name || `resource-${crypto.randomUUID()}`
const name = file.name || `resource-${randomUUID()}`
const mimeType = file.type || mimeTypeFromPath(name)
if (path) {
const uri = toFileUri(path)
@@ -596,7 +596,7 @@ export function MessageInput({
: (mimeTypeFromPath(file.name) ?? "image/png")
const base64Data = await blobToBase64(file)
return {
id: `image:${Date.now()}:${index}:${crypto.randomUUID()}`,
id: `image:${Date.now()}:${index}:${randomUUID()}`,
type: "image" as const,
data: base64Data,
uri: null,
@@ -615,7 +615,7 @@ export function MessageInput({
paths.map(async (path, index) => {
const data = await readFileBase64(path, DRAG_DROP_IMAGE_MAX_BYTES)
return {
id: `image:${Date.now()}:${index}:${crypto.randomUUID()}`,
id: `image:${Date.now()}:${index}:${randomUUID()}`,
type: "image" as const,
data,
uri: toFileUri(path),

View File

@@ -17,7 +17,7 @@ import { useAcpActions } from "@/contexts/acp-connections-context"
import { useFolderContext } from "@/contexts/folder-context"
import { useTabContext } from "@/contexts/tab-context"
import { useSessionStats } from "@/contexts/session-stats-context"
import { cn } from "@/lib/utils"
import { cn, randomUUID } from "@/lib/utils"
import { useConnectionLifecycle } from "@/hooks/use-connection-lifecycle"
import { useMessageQueue, type QueuedMessage } from "@/hooks/use-message-queue"
import { MessageListView } from "@/components/message/message-list-view"
@@ -103,7 +103,7 @@ function buildOptimisticUserTurnFromDraft(
blocks.push({ type: "text", text })
return {
id: `optimistic-${crypto.randomUUID()}`,
id: `optimistic-${randomUUID()}`,
role: "user",
blocks,
timestamp: new Date().toISOString(),
@@ -762,7 +762,7 @@ const ConversationTabView = memo(function ConversationTabView({
(answer: string) => {
if (connStatus !== "connected") return
const optimisticTurn: MessageTurn = {
id: `optimistic-${crypto.randomUUID()}`,
id: `optimistic-${randomUUID()}`,
role: "user",
blocks: [{ type: "text", text: answer }],
timestamp: new Date().toISOString(),

View File

@@ -3,6 +3,7 @@
import { useCallback, useState } from "react"
import { Eye, EyeOff } from "lucide-react"
import { useTranslations } from "next-intl"
import { randomUUID } from "@/lib/utils"
import { Button } from "@/components/ui/button"
import { Input } from "@/components/ui/input"
import {
@@ -72,7 +73,7 @@ export function AddGitAccountDialog({
}
const account: GitHubAccount = {
id: crypto.randomUUID(),
id: randomUUID(),
server_url: trimmedUrl,
username: trimmedUser,
scopes: [],

View File

@@ -4,6 +4,7 @@ import { useCallback, useState } from "react"
import { ExternalLink, Eye, EyeOff, Loader2 } from "lucide-react"
import { openUrl } from "@/lib/platform"
import { useTranslations } from "next-intl"
import { randomUUID } from "@/lib/utils"
import { Button } from "@/components/ui/button"
import { Input } from "@/components/ui/input"
import {
@@ -91,7 +92,7 @@ export function AddGitHubAccountDialog({
}
const account: GitHubAccount = {
id: crypto.randomUUID(),
id: randomUUID(),
server_url: serverUrl.trim() || "https://github.com",
username: result.username ?? "unknown",
scopes: result.scopes,

View File

@@ -11,6 +11,7 @@ import {
} from "react"
import { useTranslations } from "next-intl"
import { subscribe } from "@/lib/platform"
import { randomUUID } from "@/lib/utils"
import { inferLiveToolName } from "@/lib/tool-call-normalization"
import {
acpConnect,
@@ -536,7 +537,7 @@ function connectionsReducer(
const updated = { ...conn, status: action.status }
if (action.status === "prompting") {
updated.liveMessage = {
id: crypto.randomUUID(),
id: randomUUID(),
role: "assistant",
content: [],
startedAt: Date.now(),

View File

@@ -9,6 +9,7 @@ import {
useState,
type ReactNode,
} from "react"
import { randomUUID } from "@/lib/utils"
import {
ExternalLink,
Eye,
@@ -147,7 +148,7 @@ async function saveGenericAccount(
(a) => a.username === creds.username && extractHost(a.server_url) === host
)
if (!isDuplicate) {
const newId = crypto.randomUUID()
const newId = randomUUID()
await saveAccountToken(newId, creds.password)
await updateGitHubAccounts({
accounts: [
@@ -283,7 +284,7 @@ export function GitCredentialProvider({ children }: { children: ReactNode }) {
)
if (!isDuplicate) {
const newAccount = {
id: crypto.randomUUID(),
id: randomUUID(),
server_url: serverUrl,
username: result.username ?? "unknown",
scopes: result.scopes,

View File

@@ -2,6 +2,7 @@
import { useCallback, useEffect, useRef, useState } from "react"
import type { PromptDraft } from "@/lib/types"
import { randomUUID } from "@/lib/utils"
export interface QueuedMessage {
id: string
@@ -31,7 +32,7 @@ export function useMessageQueue(): UseMessageQueueReturn {
const enqueue = useCallback((draft: PromptDraft, modeId: string | null) => {
const item: QueuedMessage = {
id: crypto.randomUUID(),
id: randomUUID(),
draft,
modeId,
}

View File

@@ -852,7 +852,14 @@ export async function openFolderWindow(path: string): Promise<void> {
}
export async function openCommitWindow(folderId: number): Promise<void> {
return getTransport().call("open_commit_window", { folderId })
if (getTransport().isDesktop()) {
return getTransport().call("open_commit_window", { folderId })
}
const result = await getTransport().call<{ path: string }>(
"open_commit_window",
{ folderId },
)
window.location.href = result.path
}
export type SettingsSection =
@@ -871,10 +878,21 @@ export async function openSettingsWindow(
section?: SettingsSection,
options?: OpenSettingsWindowOptions
): Promise<void> {
return getTransport().call("open_settings_window", {
section: section ?? null,
agentType: options?.agentType ?? null,
})
if (getTransport().isDesktop()) {
return getTransport().call("open_settings_window", {
section: section ?? null,
agentType: options?.agentType ?? null,
})
}
// Web mode: get navigation path from backend and navigate
const result = await getTransport().call<{ path: string }>(
"open_settings_window",
{
section: section ?? null,
agentType: options?.agentType ?? null,
},
)
window.location.href = result.path
}
export async function listOpenFolders(): Promise<FolderHistoryEntry[]> {

View File

@@ -4,3 +4,21 @@ import { twMerge } from "tailwind-merge"
export function cn(...inputs: ClassValue[]) {
return twMerge(clsx(inputs))
}
/**
* Generate a UUID v4. Uses `crypto.randomUUID()` when available (secure
* contexts), otherwise falls back to `crypto.getRandomValues()`.
*/
export function randomUUID(): string {
if (typeof crypto !== "undefined" && typeof crypto.randomUUID === "function") {
return crypto.randomUUID()
}
// Fallback for non-secure contexts (HTTP over LAN)
const bytes = new Uint8Array(16)
crypto.getRandomValues(bytes)
// Set version 4 and variant bits
bytes[6] = (bytes[6] & 0x0f) | 0x40
bytes[8] = (bytes[8] & 0x3f) | 0x80
const hex = Array.from(bytes, (b) => b.toString(16).padStart(2, "0")).join("")
return `${hex.slice(0, 8)}-${hex.slice(8, 12)}-${hex.slice(12, 16)}-${hex.slice(16, 20)}-${hex.slice(20)}`
}