Skip to content
Open

aa #781

Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
610 changes: 610 additions & 0 deletions .yunnan-cached.html

Large diffs are not rendered by default.

34 changes: 34 additions & 0 deletions app/globals.css
Original file line number Diff line number Diff line change
Expand Up @@ -649,6 +649,40 @@ button.extension-widget-trigger:focus-visible {
line-height: 1.45;
white-space: pre;
}

/* Footer-placement widgets (e.g. ctx.ui.setFooter registrations like
@liziy/token-stats) render as a full-width multi-line block above the
status-shelf children. They intentionally bypass the 108px trigger chip
treatment since their content is meant to span the whole footer. */
.extension-footer-widgets {
display: flex;
flex: 0 0 100%;
flex-direction: column;
min-width: 0;
border-bottom: 1px solid color-mix(in srgb, var(--border) 78%, transparent);
}

.extension-footer-widget {
position: relative;
min-width: 0;
padding: 4px 12px 5px;
color: var(--text-muted);
font-family: var(--font-mono);
font-size: 11px;
line-height: 1.4;
overflow-x: hidden;
overflow-y: auto;
overscroll-behavior: contain;
white-space: pre;
}

.extension-footer-widget + .extension-footer-widget {
border-top: 1px solid color-mix(in srgb, var(--border) 50%, transparent);
}

.extension-footer-widget.is-updating {
color: var(--text);
}
::-webkit-scrollbar-thumb:hover {
background: var(--text-dim);
}
Expand Down
20 changes: 17 additions & 3 deletions app/layout.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import type { Metadata, Viewport } from "next";
import Script from "next/script";
import { Noto_Sans_Mono } from "next/font/google";
import { PwaRegistration } from "@/components/PwaRegistration";
import { THEME_INIT_SCRIPT } from "@/lib/theme";
Expand All @@ -12,6 +13,8 @@ const notoSansMono = Noto_Sans_Mono({
display: "swap",
});

const basePath = process.env.PI_WEB_BASE_PATH ?? "";

export const metadata: Metadata = {
title: "Pi Web",
description: "Pi Web interface for the pi coding agent",
Expand All @@ -20,14 +23,14 @@ export const metadata: Metadata = {
icons: {
icon: [
{
url: "/icons/icon-192.png",
url: `${basePath}/icons/icon-192.png`,
sizes: "192x192",
type: "image/png",
},
],
apple: [
{
url: "/icons/apple-touch-icon.png",
url: `${basePath}/icons/apple-touch-icon.png`,
sizes: "180x180",
type: "image/png",
},
Expand Down Expand Up @@ -60,14 +63,25 @@ export default function RootLayout({
children: React.ReactNode;
}) {
return (
<html lang="en" translate="no" className={`${notoSansMono.variable} notranslate`} suppressHydrationWarning>
<html
lang="en"
translate="no"
className={`${notoSansMono.variable} notranslate`}
suppressHydrationWarning
>
<head>
<meta name="google" content="notranslate" />
<script
dangerouslySetInnerHTML={{
__html: THEME_INIT_SCRIPT,
}}
/>
{/* basePath 下,Next.js 只管 Link/router/资源路径,不管代码里裸 fetch("/api/...")
这种绝对路径(浏览器会解析到域名根 → 404)。这里用 beforeInteractive 同步脚本
patch window.fetch 和 window.EventSource,在所有业务 module 执行前把 /api/* 统一加上前缀。 */}
<Script id="pi-web-basepath-api-patch" strategy="beforeInteractive">
{`(function(){var b=${JSON.stringify(basePath)};if(!b)return;var fix=function(u){return(u==="/api"||u.indexOf("/api/")===0)?b+u:u};var of=window.fetch.bind(window);window.fetch=function(i,n){if(typeof i==="string")i=fix(i);else if(i instanceof URL)i=new URL(fix(i.pathname+i.search),i.origin);return of(i,n)};var OE=window.EventSource;if(OE){window.EventSource=function(u,c){return new OE(typeof u==="string"?fix(u):u,c)};window.EventSource.prototype=OE.prototype}var XO=window.XMLHttpRequest&&window.XMLHttpRequest.prototype.open;if(XO){window.XMLHttpRequest.prototype.open=function(){var a=[].slice.call(arguments);if(typeof a[1]==="string")a[1]=fix(a[1]);return XO.apply(this,a)}}})();`}
</Script>
</head>
<body translate="no" className="notranslate" suppressHydrationWarning>
{children}
Expand Down
49 changes: 41 additions & 8 deletions app/login/page.tsx
Original file line number Diff line number Diff line change
@@ -1,12 +1,17 @@
"use client";

import Image from "next/image";
import { useState, type FormEvent } from "react";
import { I18nProvider, useI18n } from "@/hooks/useI18n";

function safeDestination(): string {
const destination = new URLSearchParams(window.location.search).get("next");
return destination?.startsWith("/") && !destination.startsWith("//") ? destination : "/";
const internal =
destination?.startsWith("/") && !destination.startsWith("//")
? destination
: "/";
// The next param is an internal path (no basePath); window.location is a raw
// browser API and does not apply Next's basePath, so prepend it manually.
return `${process.env.NEXT_PUBLIC_BASE_PATH ?? ""}${internal}`;
}

function LoginForm() {
Expand All @@ -26,7 +31,11 @@ function LoginForm() {
body: JSON.stringify({ password }),
});
if (!response.ok) {
setError(response.status === 401 ? t("auth.invalidPassword") : t("auth.loginFailed"));
setError(
response.status === 401
? t("auth.invalidPassword")
: t("auth.loginFailed"),
);
return;
}
window.location.replace(safeDestination());
Expand All @@ -41,15 +50,23 @@ function LoginForm() {
<main className="web-login-page">
<div className="web-login-shell">
<header className="web-login-brand">
<Image src="/icons/apple-touch-icon.png" width={52} height={52} alt="" priority />
<img
src={`${process.env.NEXT_PUBLIC_BASE_PATH ?? ""}/icons/apple-touch-icon.png`}
width={52}
height={52}
alt=""
style={{ width: 52, height: 52 }}
/>
<div>
<h1>Pi Web</h1>
<p>{t("auth.prompt")}</p>
</div>
</header>
<form className="web-login-form" onSubmit={submit}>
<div className="web-login-composer">
<label className="web-login-label" htmlFor="web-login-password">{t("auth.password")}</label>
<label className="web-login-label" htmlFor="web-login-password">
{t("auth.password")}
</label>
<input
id="web-login-password"
type="password"
Expand All @@ -62,20 +79,36 @@ function LoginForm() {
disabled={busy}
/>
<button type="submit" disabled={busy || !password}>
<svg width="14" height="14" viewBox="0 0 14 14" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round" aria-hidden="true">
<svg
width="14"
height="14"
viewBox="0 0 14 14"
fill="none"
stroke="currentColor"
strokeWidth="2"
strokeLinecap="round"
strokeLinejoin="round"
aria-hidden="true"
>
<line x1="2" y1="7" x2="11" y2="7" />
<polyline points="7.5 3 12 7 7.5 11" />
</svg>
{busy ? t("auth.loggingIn") : t("auth.logIn")}
</button>
</div>
<p className="web-login-error" role="alert" aria-live="polite">{error}</p>
<p className="web-login-error" role="alert" aria-live="polite">
{error}
</p>
</form>
</div>
</main>
);
}

export default function LoginPage() {
return <I18nProvider><LoginForm /></I18nProvider>;
return (
<I18nProvider>
<LoginForm />
</I18nProvider>
);
}
56 changes: 29 additions & 27 deletions app/manifest.ts
Original file line number Diff line number Diff line change
@@ -1,31 +1,33 @@
import type { MetadataRoute } from "next";

const basePath = process.env.PI_WEB_BASE_PATH ?? "";

export default function manifest(): MetadataRoute.Manifest {
return {
id: "/",
name: "Pi Web",
short_name: "Pi Web",
description: "Local web interface for the pi coding agent",
start_url: "/",
scope: "/",
display: "standalone",
background_color: "#ffffff",
theme_color: "#1a1a1a",
categories: ["developer", "productivity"],
lang: "en",
icons: [
{
src: "/icons/icon-192.png",
sizes: "192x192",
type: "image/png",
purpose: "any",
},
{
src: "/icons/icon-512.png",
sizes: "512x512",
type: "image/png",
purpose: "any",
},
],
};
return {
id: `${basePath}/`,
name: "Pi Web",
short_name: "Pi Web",
description: "Local web interface for the pi coding agent",
start_url: `${basePath}/`,
scope: `${basePath}/`,
display: "standalone",
background_color: "#1a1a1a",
theme_color: "#1a1a1a",
categories: ["developer", "productivity"],
lang: "en",
icons: [
{
src: `${basePath}/icons/icon-192.png`,
sizes: "192x192",
type: "image/png",
purpose: "any",
},
{
src: `${basePath}/icons/icon-512.png`,
sizes: "512x512",
type: "image/png",
purpose: "any",
},
],
};
}
Loading