feat(hololake): establish clean v1 personal language shell
This commit is contained in:
parent
9e04b0354c
commit
c4f662e541
51 changed files with 16233 additions and 1 deletions
629
product-source/hololake-clean-desktop/src/App.tsx
Normal file
629
product-source/hololake-clean-desktop/src/App.tsx
Normal file
|
|
@ -0,0 +1,629 @@
|
|||
import { useEffect, useMemo, useState } from "react";
|
||||
import { check } from "@tauri-apps/plugin-updater";
|
||||
import { relaunch } from "@tauri-apps/plugin-process";
|
||||
import { Icon } from "./icons";
|
||||
import * as api from "./runtime";
|
||||
import type { KnowledgeDocument, SourceKind, SystemSnapshot } from "./types";
|
||||
|
||||
type View =
|
||||
| "channel"
|
||||
| "knowledge"
|
||||
| "market"
|
||||
| "history"
|
||||
| "portal"
|
||||
| "settings";
|
||||
const nav: [View, string, string][] = [
|
||||
["channel", "我的频道", "channel"],
|
||||
["knowledge", "光湖知识库", "book"],
|
||||
["market", "模块商城", "cube"],
|
||||
["history", "系统演化史", "history"],
|
||||
["portal", "企业门户", "building"],
|
||||
["settings", "设置", "settings"],
|
||||
];
|
||||
const labels: Record<SourceKind, string> = {
|
||||
USER_MESSAGE: "用户语言",
|
||||
PERSONA_RESPONSE: "人格回应",
|
||||
SYSTEM_CONTEXT: "系统环境",
|
||||
PROTOCOL_EVENT: "协议运行",
|
||||
AGENT_ACTION: "Agent 执行",
|
||||
TOOL_RESULT: "工具结果",
|
||||
SYSTEM_RECEIPT: "系统回执",
|
||||
};
|
||||
|
||||
export default function App() {
|
||||
const [data, setData] = useState<SystemSnapshot | null>(null),
|
||||
[view, setView] = useState<View>("channel"),
|
||||
[error, setError] = useState(""),
|
||||
[busy, setBusy] = useState(false);
|
||||
const refresh = () =>
|
||||
api
|
||||
.snapshot()
|
||||
.then(setData)
|
||||
.catch((e) => setError(String(e)));
|
||||
useEffect(() => {
|
||||
refresh();
|
||||
}, []);
|
||||
if (!data)
|
||||
return (
|
||||
<div className="boot">
|
||||
<div className="lake-mark" />
|
||||
正在进入 HoloLake…
|
||||
</div>
|
||||
);
|
||||
if (!data.channel) return <Onboarding onDone={refresh} error={error} />;
|
||||
return (
|
||||
<div className="app-shell">
|
||||
<div className="titlebar">
|
||||
<div className="traffic">
|
||||
<i />
|
||||
<i />
|
||||
<i />
|
||||
</div>
|
||||
<span>HoloLake</span>
|
||||
<span className="version">
|
||||
v{data.appVersion.replace("-browser", "")}
|
||||
</span>
|
||||
</div>
|
||||
<aside className="rail">
|
||||
<div className="brand-orbit">
|
||||
<span />
|
||||
</div>
|
||||
<nav>
|
||||
{nav.map(([id, label, icon]) => (
|
||||
<button
|
||||
key={id}
|
||||
className={view === id ? "active" : ""}
|
||||
aria-label={label}
|
||||
onClick={() => setView(id)}
|
||||
>
|
||||
<Icon name={icon} />
|
||||
<span>{label}</span>
|
||||
</button>
|
||||
))}
|
||||
</nav>
|
||||
<div className="channel-mini">
|
||||
<div className="mini-lake" />
|
||||
<div>
|
||||
<strong>{data.channel.name}</strong>
|
||||
<small>{data.channel.channelId}</small>
|
||||
</div>
|
||||
</div>
|
||||
</aside>
|
||||
<main>
|
||||
{view === "channel" && (
|
||||
<Channel
|
||||
data={data}
|
||||
refresh={refresh}
|
||||
openKnowledge={() => setView("knowledge")}
|
||||
/>
|
||||
)}{" "}
|
||||
{view === "knowledge" && <Knowledge data={data} refresh={refresh} />}{" "}
|
||||
{view === "market" && <Market />}{" "}
|
||||
{view === "history" && <History data={data} />}{" "}
|
||||
{view === "portal" && <Portal />}{" "}
|
||||
{view === "settings" && (
|
||||
<Settings
|
||||
data={data}
|
||||
refresh={refresh}
|
||||
busy={busy}
|
||||
setBusy={setBusy}
|
||||
setError={setError}
|
||||
/>
|
||||
)}
|
||||
</main>
|
||||
{error && (
|
||||
<div className="toast" role="alert">
|
||||
<span>{error}</span>
|
||||
<button onClick={() => setError("")}>关闭</button>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
function Onboarding({ onDone, error }: { onDone: () => void; error: string }) {
|
||||
const [name, setName] = useState("");
|
||||
const go = async () => {
|
||||
if (!name.trim()) return;
|
||||
await api.createChannel(name.trim());
|
||||
onDone();
|
||||
};
|
||||
return (
|
||||
<div className="onboard">
|
||||
<div className="stars" />
|
||||
<section>
|
||||
<div className="origin-light" />
|
||||
<h1>为你的频道起一个名字</h1>
|
||||
<p>
|
||||
这是属于你的私人初始化频道。系统将为它登记唯一编号,并在本机建立私人
|
||||
Git。
|
||||
</p>
|
||||
<label>
|
||||
频道名称
|
||||
<input
|
||||
value={name}
|
||||
onChange={(e) => setName(e.target.value)}
|
||||
onKeyDown={(e) => e.key === "Enter" && go()}
|
||||
placeholder="例如:我的光湖"
|
||||
autoFocus
|
||||
/>
|
||||
</label>
|
||||
<button onClick={go} disabled={!name.trim()}>
|
||||
建立我的频道
|
||||
</button>
|
||||
{error && <small>{error}</small>}
|
||||
</section>
|
||||
<footer>GH-AIOS · 光湖语言系统 · 通用人工智能操作平台</footer>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
function Channel({
|
||||
data,
|
||||
refresh,
|
||||
openKnowledge,
|
||||
}: {
|
||||
data: SystemSnapshot;
|
||||
refresh: () => void;
|
||||
openKnowledge: () => void;
|
||||
}) {
|
||||
const [text, setText] = useState("");
|
||||
const send = async () => {
|
||||
if (!text.trim()) return;
|
||||
await api.submitMessage(text.trim());
|
||||
setText("");
|
||||
refresh();
|
||||
};
|
||||
return (
|
||||
<div className="page channel-page">
|
||||
<header>
|
||||
<div>
|
||||
<h1>{data.channel?.name}</h1>
|
||||
<p>个人初始化频道</p>
|
||||
</div>
|
||||
<button className="soft" onClick={openKnowledge}>
|
||||
打开光湖知识库
|
||||
</button>
|
||||
</header>
|
||||
<div className="channel-grid">
|
||||
<div className="channel-primary">
|
||||
<section className="conversation">
|
||||
<div className="welcome">
|
||||
<div className="lake-mark" />
|
||||
<h2>频道已经准备好</h2>
|
||||
<p>
|
||||
这里保存你与人格体的语言。系统动作会被分流到下方执行记录,不再混入对话。
|
||||
</p>
|
||||
</div>
|
||||
{data.timeline
|
||||
.filter(
|
||||
(e) =>
|
||||
e.sourceKind === "USER_MESSAGE" ||
|
||||
e.sourceKind === "PERSONA_RESPONSE",
|
||||
)
|
||||
.slice(0, 8)
|
||||
.reverse()
|
||||
.map((e) => (
|
||||
<article
|
||||
className={
|
||||
e.sourceKind === "USER_MESSAGE" ? "human" : "persona"
|
||||
}
|
||||
key={e.eventId}
|
||||
>
|
||||
<span>{labels[e.sourceKind]}</span>
|
||||
<p>{e.content}</p>
|
||||
</article>
|
||||
))}
|
||||
</section>
|
||||
<ReceiptStream data={data} />
|
||||
<div className="composer">
|
||||
<textarea
|
||||
value={text}
|
||||
onChange={(e) => setText(e.target.value)}
|
||||
placeholder={`和${data.channel?.name}说点什么…`}
|
||||
onKeyDown={(e) => {
|
||||
if (e.key === "Enter" && !e.shiftKey) {
|
||||
e.preventDefault();
|
||||
send();
|
||||
}
|
||||
}}
|
||||
/>
|
||||
<button onClick={send} aria-label="发送">
|
||||
↗
|
||||
</button>
|
||||
<small>Enter 发送 · Shift + Enter 换行</small>
|
||||
</div>
|
||||
</div>
|
||||
<aside className="channel-context" aria-label="频道运行状态">
|
||||
<article>
|
||||
<h2>当前频道</h2>
|
||||
<strong>{data.channel?.name}</strong>
|
||||
<p>{data.channel?.channelId}</p>
|
||||
</article>
|
||||
<article>
|
||||
<h2>私人 Git</h2>
|
||||
<strong>本机已建立</strong>
|
||||
<p className="path">{data.channel?.privateGitPath}</p>
|
||||
</article>
|
||||
<article>
|
||||
<h2>公共更新</h2>
|
||||
<strong>
|
||||
{data.publicDistributionState === "SIGNED_UPDATE_FEED_READY_NO_RELEASE"
|
||||
? "更新入口在线 · 暂无发行"
|
||||
: data.publicDistributionState}
|
||||
</strong>
|
||||
<p>只接收签名发行,不自动执行普通 Git 拉取。</p>
|
||||
</article>
|
||||
</aside>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
function ReceiptStream({ data }: { data: SystemSnapshot }) {
|
||||
const rows = data.timeline
|
||||
.filter(
|
||||
(e) =>
|
||||
e.sourceKind !== "USER_MESSAGE" && e.sourceKind !== "PERSONA_RESPONSE",
|
||||
)
|
||||
.slice(0, 5);
|
||||
return (
|
||||
<section className="receipt-stream">
|
||||
<h2>执行记录</h2>
|
||||
{rows.length === 0 ? (
|
||||
<p className="empty">
|
||||
还没有系统动作。你的语言与执行记录会在这里分开呈现。
|
||||
</p>
|
||||
) : (
|
||||
rows.map((e) => (
|
||||
<div
|
||||
className={`receipt ${e.sourceKind.toLowerCase()}`}
|
||||
key={e.eventId}
|
||||
>
|
||||
<span className="kind">{labels[e.sourceKind]}</span>
|
||||
<strong>{e.title}</strong>
|
||||
<p>{e.content}</p>
|
||||
<time>
|
||||
{new Date(e.occurredAt).toLocaleTimeString("zh-CN", {
|
||||
hour: "2-digit",
|
||||
minute: "2-digit",
|
||||
second: "2-digit",
|
||||
})}
|
||||
</time>
|
||||
</div>
|
||||
))
|
||||
)}
|
||||
</section>
|
||||
);
|
||||
}
|
||||
|
||||
function Knowledge({
|
||||
data,
|
||||
refresh,
|
||||
}: {
|
||||
data: SystemSnapshot;
|
||||
refresh: () => void;
|
||||
}) {
|
||||
const [selected, setSelected] = useState<string | null>(
|
||||
data.documents[0]?.path ?? null,
|
||||
),
|
||||
[doc, setDoc] = useState<KnowledgeDocument | null>(null),
|
||||
[body, setBody] = useState(""),
|
||||
[query, setQuery] = useState(""),
|
||||
[newTitle, setNewTitle] = useState<string | null>(null);
|
||||
useEffect(() => {
|
||||
if (selected)
|
||||
api.readDocument(selected).then((d) => {
|
||||
setDoc(d);
|
||||
setBody(d.body);
|
||||
});
|
||||
}, [selected]);
|
||||
const list = useMemo(
|
||||
() =>
|
||||
data.documents.filter((d) =>
|
||||
d.title.toLowerCase().includes(query.toLowerCase()),
|
||||
),
|
||||
[data.documents, query],
|
||||
);
|
||||
const create = async () => {
|
||||
const title = newTitle?.trim();
|
||||
if (title) {
|
||||
await api.createDocument(title);
|
||||
setNewTitle(null);
|
||||
await refresh();
|
||||
setSelected(`${title.replace(/[\\/:*?\"<>|]/g, "-")}.md`);
|
||||
}
|
||||
};
|
||||
const save = async () => {
|
||||
if (doc) {
|
||||
await api.saveDocument(doc.path, body);
|
||||
refresh();
|
||||
}
|
||||
};
|
||||
const del = async () => {
|
||||
if (doc && confirm(`把“${doc.title}”移入回收站?`)) {
|
||||
await api.deleteDocument(doc.path);
|
||||
setSelected(null);
|
||||
setDoc(null);
|
||||
refresh();
|
||||
}
|
||||
};
|
||||
return (
|
||||
<div className="page knowledge-page">
|
||||
<header>
|
||||
<div>
|
||||
<h1>光湖知识库</h1>
|
||||
<p>预装模块 · HLP-MOD-KB-0001</p>
|
||||
</div>
|
||||
<div className="actions">
|
||||
<button
|
||||
className="soft"
|
||||
onClick={async () => {
|
||||
await api.importFolder();
|
||||
refresh();
|
||||
}}
|
||||
>
|
||||
导入文件夹
|
||||
</button>
|
||||
<button onClick={() => setNewTitle("")}>新建知识页</button>
|
||||
</div>
|
||||
</header>
|
||||
<div className="knowledge-workspace">
|
||||
<aside>
|
||||
<input
|
||||
value={query}
|
||||
onChange={(e) => setQuery(e.target.value)}
|
||||
placeholder="搜索知识库"
|
||||
/>
|
||||
{list.length === 0 ? (
|
||||
<p className="empty">暂无知识页</p>
|
||||
) : (
|
||||
list.map((d) => (
|
||||
<button
|
||||
className={selected === d.path ? "selected" : ""}
|
||||
key={d.path}
|
||||
onClick={() => setSelected(d.path)}
|
||||
>
|
||||
<strong>{d.title}</strong>
|
||||
<small>
|
||||
{new Date(d.updatedAt).toLocaleDateString("zh-CN")}
|
||||
</small>
|
||||
</button>
|
||||
))
|
||||
)}
|
||||
</aside>
|
||||
<section>
|
||||
{doc ? (
|
||||
<>
|
||||
<div className="editor-tools">
|
||||
<strong>{doc.title}</strong>
|
||||
<span />
|
||||
<button onClick={() => api.exportDocument(doc.path)}>
|
||||
下载
|
||||
</button>
|
||||
<button className="danger" onClick={del}>
|
||||
删除
|
||||
</button>
|
||||
<button onClick={save}>保存</button>
|
||||
</div>
|
||||
<textarea
|
||||
className="editor"
|
||||
value={body}
|
||||
onChange={(e) => setBody(e.target.value)}
|
||||
/>
|
||||
</>
|
||||
) : (
|
||||
<div className="welcome">
|
||||
<Icon name="book" />
|
||||
<h2>从第一条知识开始</h2>
|
||||
<p>新建页面,或导入一个外部文件夹。原文件不会被改写。</p>
|
||||
<button onClick={() => setNewTitle("")}>新建知识页</button>
|
||||
</div>
|
||||
)}
|
||||
</section>
|
||||
</div>
|
||||
{newTitle !== null && (
|
||||
<div className="modal-backdrop" onMouseDown={() => setNewTitle(null)}>
|
||||
<form
|
||||
className="modal"
|
||||
onSubmit={(e) => {
|
||||
e.preventDefault();
|
||||
create();
|
||||
}}
|
||||
onMouseDown={(e) => e.stopPropagation()}
|
||||
>
|
||||
<h2>新建知识页</h2>
|
||||
<p>名称将成为本地 Markdown 文件名,并进入私人 Git。</p>
|
||||
<label>
|
||||
知识页名称
|
||||
<input
|
||||
value={newTitle}
|
||||
onChange={(e) => setNewTitle(e.target.value)}
|
||||
placeholder="例如:第一条知识"
|
||||
autoFocus
|
||||
/>
|
||||
</label>
|
||||
<div>
|
||||
<button
|
||||
type="button"
|
||||
className="soft"
|
||||
onClick={() => setNewTitle(null)}
|
||||
>
|
||||
取消
|
||||
</button>
|
||||
<button type="submit" disabled={!newTitle.trim()}>
|
||||
创建
|
||||
</button>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
function Market() {
|
||||
return (
|
||||
<div className="page simple">
|
||||
<header>
|
||||
<div>
|
||||
<h1>模块商城</h1>
|
||||
<p>能力按使用主体分开登记</p>
|
||||
</div>
|
||||
</header>
|
||||
<section className="split-list">
|
||||
<div>
|
||||
<h2>人格体使用</h2>
|
||||
<p>动态脑、技能和执行零件将在签名审核后出现。</p>
|
||||
<span>当前没有已发布模块</span>
|
||||
</div>
|
||||
<div>
|
||||
<h2>人类使用</h2>
|
||||
<p>行业工作模块将在完成独立验收后出现。</p>
|
||||
<span>光湖知识库为预装模块</span>
|
||||
</div>
|
||||
</section>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
function History({ data }: { data: SystemSnapshot }) {
|
||||
return (
|
||||
<div className="page simple">
|
||||
<header>
|
||||
<div>
|
||||
<h1>系统演化史</h1>
|
||||
<p>事实与计划分开记录</p>
|
||||
</div>
|
||||
</header>
|
||||
<section className="timeline-list">
|
||||
{data.timeline.map((e) => (
|
||||
<article key={e.eventId}>
|
||||
<time>{new Date(e.occurredAt).toLocaleString("zh-CN")}</time>
|
||||
<div>
|
||||
<strong>
|
||||
{labels[e.sourceKind]} · {e.title}
|
||||
</strong>
|
||||
<p>{e.content}</p>
|
||||
</div>
|
||||
</article>
|
||||
))}
|
||||
</section>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
function Portal() {
|
||||
return (
|
||||
<div className="page simple">
|
||||
<header>
|
||||
<div>
|
||||
<h1>企业门户</h1>
|
||||
<p>公共四域的官方入口位于企业服务器</p>
|
||||
</div>
|
||||
</header>
|
||||
<section className="portal">
|
||||
<div className="origin-light" />
|
||||
<h2>公共内容不嵌入私人频道</h2>
|
||||
<p>
|
||||
官方通知、四域规则和团队发布由企业门户承载。HoloLake
|
||||
只读取经过签名审核的公共发行事实。
|
||||
</p>
|
||||
<a href="https://guanghu.chat" target="_blank" rel="noreferrer">
|
||||
打开企业门户 ↗
|
||||
</a>
|
||||
</section>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
function Settings({
|
||||
data,
|
||||
refresh,
|
||||
busy,
|
||||
setBusy,
|
||||
setError,
|
||||
}: {
|
||||
data: SystemSnapshot;
|
||||
refresh: () => void;
|
||||
busy: boolean;
|
||||
setBusy: (v: boolean) => void;
|
||||
setError: (v: string) => void;
|
||||
}) {
|
||||
const [bridgeName, setBridgeName] = useState("");
|
||||
const update = async () => {
|
||||
setBusy(true);
|
||||
try {
|
||||
const u = await check();
|
||||
if (!u) throw new Error("当前已是最新版本,或公共发行尚未发布。");
|
||||
if (confirm(`发现 HoloLake ${u.version},现在安装?`)) {
|
||||
await u.downloadAndInstall();
|
||||
await relaunch();
|
||||
}
|
||||
} catch (e) {
|
||||
setError(String(e));
|
||||
} finally {
|
||||
setBusy(false);
|
||||
}
|
||||
};
|
||||
return (
|
||||
<div className="page simple">
|
||||
<header>
|
||||
<div>
|
||||
<h1>设置</h1>
|
||||
<p>本机节点、公共更新与外部 AI</p>
|
||||
</div>
|
||||
</header>
|
||||
<section className="settings-list">
|
||||
<article>
|
||||
<div>
|
||||
<h2>私人 Git</h2>
|
||||
<p>{data.channel?.privateGitPath}</p>
|
||||
</div>
|
||||
<span className="status ok">已建立</span>
|
||||
</article>
|
||||
<article>
|
||||
<div>
|
||||
<h2>公共更新</h2>
|
||||
<p>{data.publicUpdateEndpoint}</p>
|
||||
</div>
|
||||
<button className="soft" onClick={update} disabled={busy}>
|
||||
{busy ? "正在检查…" : "检查更新"}
|
||||
</button>
|
||||
</article>
|
||||
<article className="bridge">
|
||||
<div>
|
||||
<h2>外部编程 AI</h2>
|
||||
<p>以来源明确的本地表达桥接入;不能取得人格或执行主控权。</p>
|
||||
</div>
|
||||
<div>
|
||||
<input
|
||||
value={bridgeName}
|
||||
onChange={(e) => setBridgeName(e.target.value)}
|
||||
placeholder="给这个 AI 入口起名"
|
||||
/>
|
||||
<button
|
||||
onClick={async () => {
|
||||
if (bridgeName.trim()) {
|
||||
await api.registerBridge(bridgeName.trim());
|
||||
setBridgeName("");
|
||||
refresh();
|
||||
}
|
||||
}}
|
||||
>
|
||||
建立入口
|
||||
</button>
|
||||
</div>
|
||||
</article>
|
||||
{data.externalAiBridges.map((b) => (
|
||||
<article key={b.bridgeId}>
|
||||
<div>
|
||||
<h2>{b.displayName}</h2>
|
||||
<p>
|
||||
{b.bridgeId} · {b.inboxPath}
|
||||
</p>
|
||||
</div>
|
||||
<span className="status">{b.state}</span>
|
||||
</article>
|
||||
))}
|
||||
</section>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
12
product-source/hololake-clean-desktop/src/icons.tsx
Normal file
12
product-source/hololake-clean-desktop/src/icons.tsx
Normal file
|
|
@ -0,0 +1,12 @@
|
|||
import type { SVGProps } from "react";
|
||||
export function Icon({name,...props}:{name:string}&SVGProps<SVGSVGElement>){
|
||||
const paths:Record<string,React.ReactNode>={
|
||||
channel:<><path d="M4 5.5h16v11H8l-4 3v-14Z"/><path d="M8 10h8M8 13h5"/></>,
|
||||
book:<><path d="M4 5.5A3.5 3.5 0 0 1 7.5 2H11v17H7.5A3.5 3.5 0 0 0 4 22V5.5ZM20 5.5A3.5 3.5 0 0 0 16.5 2H13v17h3.5A3.5 3.5 0 0 1 20 22V5.5Z"/></>,
|
||||
cube:<><path d="m12 2 8 4.5v10L12 21l-8-4.5v-10L12 2Z"/><path d="m4 6.5 8 4.5 8-4.5M12 11v10"/></>,
|
||||
history:<><path d="M3 12a9 9 0 1 0 3-6.7L3 8"/><path d="M3 3v5h5M12 7v5l3 2"/></>,
|
||||
building:<><path d="M4 21V4h10v17M14 9h6v12M8 8h2M8 12h2M8 16h2M17 13h1M17 17h1M2 21h20"/></>,
|
||||
settings:<><circle cx="12" cy="12" r="3"/><path d="M19.4 15a1.7 1.7 0 0 0 .3 1.9l.1.1-2.8 2.8-.1-.1a1.7 1.7 0 0 0-1.9-.3 1.7 1.7 0 0 0-1 1.6v.2h-4V21a1.7 1.7 0 0 0-1-1.6 1.7 1.7 0 0 0-1.9.3l-.1.1L4.2 17l.1-.1a1.7 1.7 0 0 0 .3-1.9A1.7 1.7 0 0 0 3 14H2.8v-4H3a1.7 1.7 0 0 0 1.6-1 1.7 1.7 0 0 0-.3-1.9L4.2 7 7 4.2l.1.1a1.7 1.7 0 0 0 1.9.3A1.7 1.7 0 0 0 10 3V2.8h4V3a1.7 1.7 0 0 0 1 1.6 1.7 1.7 0 0 0 1.9-.3l.1-.1L19.8 7l-.1.1a1.7 1.7 0 0 0-.3 1.9 1.7 1.7 0 0 0 1.6 1h.2v4H21a1.7 1.7 0 0 0-1.6 1Z"/></>
|
||||
};
|
||||
return <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="1.7" strokeLinecap="round" strokeLinejoin="round" aria-hidden="true" {...props}>{paths[name]}</svg>
|
||||
}
|
||||
6
product-source/hololake-clean-desktop/src/main.tsx
Normal file
6
product-source/hololake-clean-desktop/src/main.tsx
Normal file
|
|
@ -0,0 +1,6 @@
|
|||
import React from "react";
|
||||
import ReactDOM from "react-dom/client";
|
||||
import App from "./App";
|
||||
import "./styles.css";
|
||||
|
||||
ReactDOM.createRoot(document.getElementById("root")!).render(<React.StrictMode><App/></React.StrictMode>);
|
||||
53
product-source/hololake-clean-desktop/src/runtime.ts
Normal file
53
product-source/hololake-clean-desktop/src/runtime.ts
Normal file
|
|
@ -0,0 +1,53 @@
|
|||
import { invoke } from "@tauri-apps/api/core";
|
||||
import type { Channel, ExternalAiBridge, KnowledgeDocument, MutationReceipt, SystemSnapshot, TimelineEvent } from "./types";
|
||||
|
||||
const tauri = () => "__TAURI_INTERNALS__" in window;
|
||||
const now = () => new Date().toISOString();
|
||||
const key = "hololake.clean.browser-state.v1";
|
||||
|
||||
type BrowserState = { channel: Channel | null; timeline: TimelineEvent[]; documents: KnowledgeDocument[]; bridges: ExternalAiBridge[] };
|
||||
const empty: BrowserState = { channel: null, timeline: [], documents: [], bridges: [] };
|
||||
const load = (): BrowserState => JSON.parse(localStorage.getItem(key) ?? JSON.stringify(empty));
|
||||
const save = (state: BrowserState) => localStorage.setItem(key, JSON.stringify(state));
|
||||
const event = (sourceKind: TimelineEvent["sourceKind"], title: string, content: string, state: TimelineEvent["state"] = "SUCCEEDED"): TimelineEvent => ({ eventId: crypto.randomUUID(), sourceKind, title, content, occurredAt: now(), state });
|
||||
|
||||
export async function snapshot(): Promise<SystemSnapshot> {
|
||||
if (tauri()) return invoke("system_snapshot");
|
||||
const s = load();
|
||||
return { appVersion: "1.0.0-browser", channel: s.channel, timeline: s.timeline, documents: s.documents.map(({ path, title, updatedAt, sha256 }) => ({ path, title, updatedAt, sha256 })), moduleRegistryVersion: "1", publicUpdateEndpoint: "https://guanghulab.com/hololake/releases/latest.json", publicDistributionState: "SIGNED_UPDATE_FEED_READY_NO_RELEASE", externalAiBridges: s.bridges };
|
||||
}
|
||||
|
||||
export async function createChannel(name: string): Promise<Channel> {
|
||||
if (tauri()) return invoke("create_channel", { name });
|
||||
const s = load(); const channel: Channel = { channelId: `HL-CH-${crypto.randomUUID().slice(0,8).toUpperCase()}`, name, createdAt: now(), privateGitPath: "浏览器预览不写入磁盘", publicDistributionState: "SIGNED_UPDATE_FEED_READY_NO_RELEASE" };
|
||||
s.channel = channel; s.timeline.unshift(event("SYSTEM_RECEIPT", "频道已建立", `${channel.channelId} 已登记。`)); save(s); return channel;
|
||||
}
|
||||
|
||||
export async function submitMessage(content: string): Promise<MutationReceipt> {
|
||||
if (tauri()) return invoke("submit_user_message", { content });
|
||||
const s = load(); const e = event("USER_MESSAGE", "用户语言", content, "RECORDED"); s.timeline.unshift(e, event("SYSTEM_RECEIPT", "系统回执", "用户语言已分流保存;等待人格体接入,不伪造人格回应。", "WAITING")); save(s);
|
||||
return { receiptId: crypto.randomUUID(), state: "WAITING_PERSONA", message: "用户语言已记录", gitCommit: null, event: e };
|
||||
}
|
||||
|
||||
export async function readDocument(path: string): Promise<KnowledgeDocument> {
|
||||
if (tauri()) return invoke("read_knowledge_document", { path });
|
||||
const doc = load().documents.find(d => d.path === path); if (!doc) throw new Error("文档不存在"); return doc;
|
||||
}
|
||||
export async function createDocument(title: string): Promise<MutationReceipt> {
|
||||
if (tauri()) return invoke("create_knowledge_document", { title });
|
||||
const s = load(); const path = `${title.trim().replace(/[\\/:*?\"<>|]/g,"-")}.md`; const d = { path, title: title.trim(), body: `# ${title.trim()}\n\n`, updatedAt: now(), sha256: "browser-preview" }; s.documents.unshift(d); const e=event("AGENT_ACTION","Agent 执行",`已创建 ${path}`); s.timeline.unshift(e); save(s); return {receiptId:crypto.randomUUID(),state:"SUCCEEDED",message:"文档已创建",gitCommit:"browser-preview",event:e};
|
||||
}
|
||||
export async function saveDocument(path: string, body: string): Promise<MutationReceipt> {
|
||||
if (tauri()) return invoke("save_knowledge_document", { path, body });
|
||||
const s=load(); const d=s.documents.find(x=>x.path===path); if(!d) throw new Error("文档不存在"); d.body=body; d.updatedAt=now(); const e=event("SYSTEM_RECEIPT","系统回执",`${path} 已保存到私人 Git 预览层。`); s.timeline.unshift(e); save(s); return {receiptId:crypto.randomUUID(),state:"SUCCEEDED",message:"保存完成",gitCommit:"browser-preview",event:e};
|
||||
}
|
||||
export async function deleteDocument(path: string): Promise<MutationReceipt> {
|
||||
if (tauri()) return invoke("delete_knowledge_document", { path });
|
||||
const s=load(); s.documents=s.documents.filter(x=>x.path!==path); const e=event("SYSTEM_RECEIPT","系统回执",`${path} 已移入回收站。`); s.timeline.unshift(e); save(s); return {receiptId:crypto.randomUUID(),state:"SUCCEEDED",message:"已移入回收站",gitCommit:"browser-preview",event:e};
|
||||
}
|
||||
export async function importFolder(): Promise<MutationReceipt | null> { if (!tauri()) throw new Error("文件夹导入请在已安装的 HoloLake 中使用"); return invoke("import_knowledge_folder"); }
|
||||
export async function exportDocument(path: string): Promise<string | null> { if (!tauri()) throw new Error("下载请在已安装的 HoloLake 中使用"); return invoke("export_knowledge_document", { path }); }
|
||||
export async function registerBridge(displayName: string): Promise<ExternalAiBridge> {
|
||||
if (tauri()) return invoke("register_external_ai_bridge", { displayName });
|
||||
const s=load(); const b={bridgeId:`HL-AI-${crypto.randomUUID().slice(0,8).toUpperCase()}`,displayName,inboxPath:"浏览器预览",outboxPath:"浏览器预览",state:"LOCAL_EXPRESSION_ONLY"}; s.bridges.push(b); save(s); return b;
|
||||
}
|
||||
855
product-source/hololake-clean-desktop/src/styles.css
Normal file
855
product-source/hololake-clean-desktop/src/styles.css
Normal file
|
|
@ -0,0 +1,855 @@
|
|||
:root {
|
||||
font-family:
|
||||
-apple-system, BlinkMacSystemFont, "SF Pro Display", "PingFang SC",
|
||||
"Microsoft YaHei", sans-serif;
|
||||
color: #eaf2ff;
|
||||
background: #050a13;
|
||||
font-synthesis: none;
|
||||
--bg: #050a13;
|
||||
--panel: rgba(9, 23, 41, 0.78);
|
||||
--panel2: rgba(12, 30, 52, 0.68);
|
||||
--line: rgba(151, 191, 230, 0.18);
|
||||
--muted: #92a8be;
|
||||
--text: #eaf2ff;
|
||||
--gold: #f0c56b;
|
||||
--blue: #69b9f3;
|
||||
--green: #76d99c;
|
||||
--danger: #ff8f93;
|
||||
}
|
||||
* {
|
||||
box-sizing: border-box;
|
||||
}
|
||||
body {
|
||||
margin: 0;
|
||||
min-width: 760px;
|
||||
min-height: 100vh;
|
||||
overflow: hidden;
|
||||
background:
|
||||
radial-gradient(ellipse at 55% 35%, #0b3151 0, transparent 33%),
|
||||
linear-gradient(180deg, #06101f 0%, #071628 54%, #030911 100%);
|
||||
}
|
||||
button,
|
||||
input,
|
||||
textarea {
|
||||
font: inherit;
|
||||
}
|
||||
button {
|
||||
color: inherit;
|
||||
}
|
||||
.app-shell {
|
||||
height: 100vh;
|
||||
display: grid;
|
||||
grid-template: 48px 1fr/244px 1fr;
|
||||
background: linear-gradient(
|
||||
180deg,
|
||||
rgba(2, 9, 19, 0.22),
|
||||
rgba(2, 8, 15, 0.7)
|
||||
);
|
||||
position: relative;
|
||||
}
|
||||
.app-shell:after {
|
||||
content: "";
|
||||
position: fixed;
|
||||
inset: 48px 0 0 244px;
|
||||
pointer-events: none;
|
||||
opacity: 0.26;
|
||||
background-image:
|
||||
radial-gradient(circle at 12% 14%, #fff 0 1px, transparent 1.5px),
|
||||
radial-gradient(circle at 74% 8%, #badaff 0 1px, transparent 1.5px),
|
||||
radial-gradient(circle at 52% 26%, #fff 0 1px, transparent 1.5px);
|
||||
background-size:
|
||||
260px 230px,
|
||||
340px 290px,
|
||||
440px 360px;
|
||||
}
|
||||
.titlebar {
|
||||
grid-column: 1/-1;
|
||||
display: grid;
|
||||
grid-template-columns: 1fr auto 1fr;
|
||||
align-items: center;
|
||||
border-bottom: 1px solid var(--line);
|
||||
background: rgba(3, 11, 22, 0.82);
|
||||
backdrop-filter: blur(24px);
|
||||
z-index: 5;
|
||||
}
|
||||
.traffic {
|
||||
display: flex;
|
||||
gap: 8px;
|
||||
padding-left: 18px;
|
||||
}
|
||||
.traffic i {
|
||||
width: 12px;
|
||||
height: 12px;
|
||||
border-radius: 50%;
|
||||
background: #ff6259;
|
||||
}
|
||||
.traffic i:nth-child(2) {
|
||||
background: #ffbd2e;
|
||||
}
|
||||
.traffic i:nth-child(3) {
|
||||
background: #28c941;
|
||||
}
|
||||
.titlebar > span {
|
||||
font-size: 14px;
|
||||
font-weight: 560;
|
||||
}
|
||||
.titlebar .version {
|
||||
justify-self: end;
|
||||
margin-right: 18px;
|
||||
color: #738aa0;
|
||||
font-size: 11px;
|
||||
}
|
||||
.rail {
|
||||
grid-row: 2;
|
||||
padding: 26px 14px 18px;
|
||||
border-right: 1px solid var(--line);
|
||||
background: rgba(3, 11, 22, 0.68);
|
||||
backdrop-filter: blur(22px);
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
z-index: 3;
|
||||
}
|
||||
.brand-orbit {
|
||||
height: 96px;
|
||||
display: grid;
|
||||
place-items: center;
|
||||
}
|
||||
.brand-orbit span,
|
||||
.lake-mark {
|
||||
display: block;
|
||||
width: 54px;
|
||||
height: 54px;
|
||||
border: 1px solid rgba(240, 197, 107, 0.7);
|
||||
border-radius: 50%;
|
||||
box-shadow:
|
||||
0 0 22px rgba(240, 197, 107, 0.17),
|
||||
inset 0 0 18px rgba(240, 197, 107, 0.09);
|
||||
position: relative;
|
||||
}
|
||||
.brand-orbit span:before,
|
||||
.lake-mark:before {
|
||||
content: "";
|
||||
position: absolute;
|
||||
left: 10px;
|
||||
right: 10px;
|
||||
top: 26px;
|
||||
height: 1px;
|
||||
background: linear-gradient(90deg, transparent, var(--gold), transparent);
|
||||
box-shadow: 0 5px 7px rgba(240, 197, 107, 0.55);
|
||||
}
|
||||
.brand-orbit span:after,
|
||||
.lake-mark:after {
|
||||
content: "✦";
|
||||
position: absolute;
|
||||
inset: 0;
|
||||
display: grid;
|
||||
place-items: center;
|
||||
color: #f8d98c;
|
||||
text-shadow: 0 0 13px var(--gold);
|
||||
}
|
||||
nav {
|
||||
display: grid;
|
||||
gap: 6px;
|
||||
margin-top: 14px;
|
||||
}
|
||||
nav button {
|
||||
border: 0;
|
||||
background: transparent;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 13px;
|
||||
padding: 12px 14px;
|
||||
border-radius: 12px;
|
||||
color: #b8c7d7;
|
||||
text-align: left;
|
||||
font-size: 14px;
|
||||
cursor: pointer;
|
||||
}
|
||||
nav button svg {
|
||||
width: 21px;
|
||||
height: 21px;
|
||||
}
|
||||
nav button:hover {
|
||||
background: rgba(255, 255, 255, 0.045);
|
||||
color: #fff;
|
||||
}
|
||||
nav button.active {
|
||||
color: #f7dc9a;
|
||||
background: linear-gradient(
|
||||
90deg,
|
||||
rgba(153, 103, 26, 0.38),
|
||||
rgba(255, 255, 255, 0.025)
|
||||
);
|
||||
box-shadow: inset 2px 0 var(--gold);
|
||||
}
|
||||
.channel-mini {
|
||||
margin-top: auto;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 10px;
|
||||
padding: 11px;
|
||||
}
|
||||
.mini-lake {
|
||||
width: 36px;
|
||||
height: 36px;
|
||||
border-radius: 50%;
|
||||
background: radial-gradient(
|
||||
circle at 50% 58%,
|
||||
#e5c477 0 2%,
|
||||
#236b8f 5%,
|
||||
#08182c 42%,
|
||||
#020811 70%
|
||||
);
|
||||
box-shadow: 0 0 14px rgba(105, 185, 243, 0.3);
|
||||
}
|
||||
.channel-mini strong,
|
||||
.channel-mini small {
|
||||
display: block;
|
||||
}
|
||||
.channel-mini strong {
|
||||
font-size: 13px;
|
||||
}
|
||||
.channel-mini small {
|
||||
font-size: 9px;
|
||||
color: #70879d;
|
||||
margin-top: 3px;
|
||||
}
|
||||
main {
|
||||
grid-column: 2;
|
||||
grid-row: 2;
|
||||
overflow: auto;
|
||||
z-index: 1;
|
||||
}
|
||||
.page {
|
||||
width: min(1240px, 100%);
|
||||
min-height: calc(100vh - 48px);
|
||||
margin: auto;
|
||||
padding: 38px 40px 30px;
|
||||
}
|
||||
.page > header {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
margin-bottom: 25px;
|
||||
}
|
||||
.page h1 {
|
||||
font-size: 30px;
|
||||
line-height: 1.2;
|
||||
margin: 0 0 8px;
|
||||
font-weight: 650;
|
||||
letter-spacing: -0.02em;
|
||||
}
|
||||
.page header p {
|
||||
margin: 0;
|
||||
color: var(--muted);
|
||||
font-size: 13px;
|
||||
}
|
||||
.page button,
|
||||
.portal a {
|
||||
border: 1px solid rgba(240, 197, 107, 0.55);
|
||||
background: linear-gradient(
|
||||
180deg,
|
||||
rgba(194, 142, 51, 0.35),
|
||||
rgba(114, 72, 19, 0.45)
|
||||
);
|
||||
border-radius: 10px;
|
||||
padding: 10px 16px;
|
||||
font-size: 13px;
|
||||
font-weight: 580;
|
||||
cursor: pointer;
|
||||
text-decoration: none;
|
||||
}
|
||||
.page button:hover,
|
||||
.portal a:hover {
|
||||
filter: brightness(1.18);
|
||||
}
|
||||
.page button.soft {
|
||||
border-color: var(--line);
|
||||
background: rgba(255, 255, 255, 0.04);
|
||||
}
|
||||
.conversation {
|
||||
border: 1px solid var(--line);
|
||||
border-radius: 20px;
|
||||
background: linear-gradient(
|
||||
180deg,
|
||||
rgba(7, 27, 48, 0.68),
|
||||
rgba(6, 18, 34, 0.74)
|
||||
);
|
||||
overflow: hidden;
|
||||
min-height: 230px;
|
||||
}
|
||||
.channel-grid {
|
||||
display: grid;
|
||||
grid-template-columns: minmax(0, 1fr) 250px;
|
||||
gap: 18px;
|
||||
align-items: start;
|
||||
}
|
||||
.channel-primary {
|
||||
min-width: 0;
|
||||
}
|
||||
.channel-context {
|
||||
display: grid;
|
||||
gap: 12px;
|
||||
}
|
||||
.channel-context article {
|
||||
border: 1px solid var(--line);
|
||||
border-radius: 18px;
|
||||
background: rgba(6, 20, 36, 0.74);
|
||||
padding: 19px;
|
||||
}
|
||||
.channel-context h2 {
|
||||
color: #b8cadb;
|
||||
font-size: 13px;
|
||||
margin: 0 0 17px;
|
||||
}
|
||||
.channel-context strong {
|
||||
font-size: 14px;
|
||||
font-weight: 620;
|
||||
}
|
||||
.channel-context p {
|
||||
color: #7f97ad;
|
||||
font-size: 11px;
|
||||
line-height: 1.6;
|
||||
margin: 7px 0 0;
|
||||
}
|
||||
.channel-context .path {
|
||||
word-break: break-all;
|
||||
}
|
||||
.welcome {
|
||||
text-align: center;
|
||||
display: grid;
|
||||
justify-items: center;
|
||||
align-content: center;
|
||||
min-height: 210px;
|
||||
padding: 25px;
|
||||
color: var(--muted);
|
||||
}
|
||||
.welcome h2 {
|
||||
font-size: 18px;
|
||||
color: var(--text);
|
||||
margin: 14px 0 6px;
|
||||
}
|
||||
.welcome p {
|
||||
max-width: 500px;
|
||||
line-height: 1.7;
|
||||
margin: 0;
|
||||
font-size: 13px;
|
||||
}
|
||||
.welcome svg {
|
||||
width: 34px;
|
||||
color: var(--blue);
|
||||
}
|
||||
.conversation article {
|
||||
display: grid;
|
||||
grid-template-columns: 100px 1fr;
|
||||
padding: 20px 24px;
|
||||
border-top: 1px solid rgba(151, 191, 230, 0.11);
|
||||
}
|
||||
.conversation article span {
|
||||
font-size: 12px;
|
||||
color: var(--blue);
|
||||
font-weight: 650;
|
||||
}
|
||||
.conversation article.persona span {
|
||||
color: #c5a6ff;
|
||||
}
|
||||
.conversation article p {
|
||||
margin: 0;
|
||||
line-height: 1.7;
|
||||
font-size: 15px;
|
||||
}
|
||||
.receipt-stream {
|
||||
margin-top: 18px;
|
||||
padding: 18px 20px;
|
||||
border: 1px solid var(--line);
|
||||
border-radius: 18px;
|
||||
background: rgba(4, 16, 30, 0.74);
|
||||
}
|
||||
.receipt-stream h2 {
|
||||
font-size: 13px;
|
||||
font-weight: 570;
|
||||
color: #b8cadb;
|
||||
margin: 0 0 12px;
|
||||
}
|
||||
.receipt {
|
||||
display: grid;
|
||||
grid-template-columns: 90px 120px 1fr auto;
|
||||
gap: 10px;
|
||||
align-items: center;
|
||||
padding: 10px 12px;
|
||||
margin-top: 7px;
|
||||
border-radius: 10px;
|
||||
background: rgba(12, 34, 56, 0.7);
|
||||
font-size: 12px;
|
||||
}
|
||||
.receipt .kind {
|
||||
color: #63d6cc;
|
||||
font-weight: 650;
|
||||
}
|
||||
.receipt strong {
|
||||
font-weight: 600;
|
||||
}
|
||||
.receipt p {
|
||||
margin: 0;
|
||||
color: #aabbd0;
|
||||
white-space: nowrap;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
}
|
||||
.receipt time {
|
||||
color: #6f879e;
|
||||
font-variant-numeric: tabular-nums;
|
||||
}
|
||||
.receipt.agent_action .kind {
|
||||
color: var(--gold);
|
||||
}
|
||||
.receipt.system_receipt .kind {
|
||||
color: var(--green);
|
||||
}
|
||||
.empty {
|
||||
color: #7890a8;
|
||||
font-size: 13px;
|
||||
}
|
||||
.composer {
|
||||
margin-top: 18px;
|
||||
border: 1px solid rgba(179, 210, 239, 0.3);
|
||||
border-radius: 18px;
|
||||
background: rgba(10, 26, 46, 0.78);
|
||||
padding: 14px 16px 10px;
|
||||
display: grid;
|
||||
grid-template-columns: 1fr auto;
|
||||
align-items: end;
|
||||
}
|
||||
.composer textarea {
|
||||
resize: none;
|
||||
height: 62px;
|
||||
background: transparent;
|
||||
border: 0;
|
||||
outline: 0;
|
||||
color: #eaf2ff;
|
||||
font-size: 15px;
|
||||
padding: 7px;
|
||||
}
|
||||
.composer textarea::placeholder {
|
||||
color: #6f8298;
|
||||
}
|
||||
.composer button {
|
||||
border-radius: 50%;
|
||||
width: 42px;
|
||||
height: 42px;
|
||||
padding: 0;
|
||||
font-size: 22px;
|
||||
}
|
||||
.composer small {
|
||||
grid-column: 1/-1;
|
||||
color: #5f7489;
|
||||
font-size: 10px;
|
||||
padding: 5px 7px 0;
|
||||
}
|
||||
.actions {
|
||||
display: flex;
|
||||
gap: 9px;
|
||||
}
|
||||
.knowledge-workspace {
|
||||
display: grid;
|
||||
grid-template-columns: 260px 1fr;
|
||||
min-height: 650px;
|
||||
border: 1px solid var(--line);
|
||||
border-radius: 18px;
|
||||
overflow: hidden;
|
||||
background: rgba(5, 17, 31, 0.76);
|
||||
}
|
||||
.knowledge-workspace > aside {
|
||||
border-right: 1px solid var(--line);
|
||||
padding: 13px;
|
||||
background: rgba(3, 12, 24, 0.55);
|
||||
}
|
||||
.knowledge-workspace > aside > input,
|
||||
.settings-list input {
|
||||
width: 100%;
|
||||
background: rgba(255, 255, 255, 0.045);
|
||||
border: 1px solid var(--line);
|
||||
border-radius: 9px;
|
||||
color: #fff;
|
||||
padding: 10px 12px;
|
||||
outline: none;
|
||||
}
|
||||
.knowledge-workspace > aside > button {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
width: 100%;
|
||||
border: 0;
|
||||
background: transparent;
|
||||
text-align: left;
|
||||
margin-top: 7px;
|
||||
padding: 11px 12px;
|
||||
}
|
||||
.knowledge-workspace > aside > button.selected {
|
||||
background: rgba(73, 141, 194, 0.15);
|
||||
box-shadow: inset 2px 0 #74bee9;
|
||||
}
|
||||
.knowledge-workspace > aside strong {
|
||||
font-size: 13px;
|
||||
}
|
||||
.knowledge-workspace > aside small {
|
||||
color: #6e879e;
|
||||
margin-top: 5px;
|
||||
font-size: 10px;
|
||||
}
|
||||
.knowledge-workspace > section {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
min-width: 0;
|
||||
}
|
||||
.editor-tools {
|
||||
height: 55px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 8px;
|
||||
padding: 0 16px;
|
||||
border-bottom: 1px solid var(--line);
|
||||
}
|
||||
.editor-tools > span {
|
||||
flex: 1;
|
||||
}
|
||||
.editor-tools button {
|
||||
padding: 7px 11px;
|
||||
border-color: var(--line);
|
||||
background: transparent;
|
||||
}
|
||||
.editor-tools button.danger {
|
||||
color: var(--danger);
|
||||
}
|
||||
.editor {
|
||||
flex: 1;
|
||||
resize: none;
|
||||
border: 0;
|
||||
outline: 0;
|
||||
padding: 32px 38px;
|
||||
background: rgba(1, 8, 17, 0.24);
|
||||
color: #dce8f5;
|
||||
line-height: 1.8;
|
||||
font-family: "SFMono-Regular", Menlo, monospace;
|
||||
font-size: 14px;
|
||||
}
|
||||
.split-list {
|
||||
display: grid;
|
||||
grid-template-columns: 1fr 1fr;
|
||||
gap: 18px;
|
||||
}
|
||||
.split-list > div,
|
||||
.settings-list article,
|
||||
.portal {
|
||||
border: 1px solid var(--line);
|
||||
border-radius: 18px;
|
||||
background: rgba(8, 24, 42, 0.72);
|
||||
padding: 25px;
|
||||
}
|
||||
.split-list h2,
|
||||
.settings-list h2 {
|
||||
font-size: 17px;
|
||||
margin: 0 0 8px;
|
||||
}
|
||||
.split-list p,
|
||||
.settings-list p,
|
||||
.portal p {
|
||||
color: var(--muted);
|
||||
font-size: 13px;
|
||||
line-height: 1.7;
|
||||
}
|
||||
.split-list span {
|
||||
display: block;
|
||||
color: #6f879e;
|
||||
margin-top: 30px;
|
||||
font-size: 12px;
|
||||
}
|
||||
.timeline-list {
|
||||
display: grid;
|
||||
gap: 0;
|
||||
border-top: 1px solid var(--line);
|
||||
}
|
||||
.timeline-list article {
|
||||
display: grid;
|
||||
grid-template-columns: 170px 1fr;
|
||||
gap: 25px;
|
||||
padding: 20px 4px;
|
||||
border-bottom: 1px solid var(--line);
|
||||
}
|
||||
.timeline-list time {
|
||||
color: #71869b;
|
||||
font-size: 12px;
|
||||
}
|
||||
.timeline-list strong {
|
||||
font-size: 14px;
|
||||
}
|
||||
.timeline-list p {
|
||||
color: #9eb0c2;
|
||||
margin: 7px 0 0;
|
||||
font-size: 13px;
|
||||
}
|
||||
.portal {
|
||||
text-align: center;
|
||||
min-height: 360px;
|
||||
display: grid;
|
||||
justify-items: center;
|
||||
align-content: center;
|
||||
}
|
||||
.portal h2 {
|
||||
font-size: 22px;
|
||||
margin: 18px 0 2px;
|
||||
}
|
||||
.portal p {
|
||||
max-width: 570px;
|
||||
}
|
||||
.portal .origin-light {
|
||||
width: 72px;
|
||||
height: 72px;
|
||||
}
|
||||
.origin-light {
|
||||
border-radius: 50%;
|
||||
background: radial-gradient(
|
||||
circle,
|
||||
#fff7c9 0 2%,
|
||||
#f2c76c 3%,
|
||||
rgba(105, 185, 243, 0.55) 10%,
|
||||
rgba(24, 71, 105, 0.24) 38%,
|
||||
transparent 70%
|
||||
);
|
||||
box-shadow: 0 0 70px rgba(105, 185, 243, 0.16);
|
||||
}
|
||||
.settings-list {
|
||||
display: grid;
|
||||
gap: 12px;
|
||||
}
|
||||
.settings-list article {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
padding: 19px 22px;
|
||||
}
|
||||
.settings-list h2 {
|
||||
font-size: 14px;
|
||||
}
|
||||
.settings-list p {
|
||||
margin: 0;
|
||||
word-break: break-all;
|
||||
}
|
||||
.status {
|
||||
font-size: 11px;
|
||||
color: #8ebde0;
|
||||
}
|
||||
.status.ok {
|
||||
color: var(--green);
|
||||
}
|
||||
.settings-list .bridge {
|
||||
align-items: flex-end;
|
||||
}
|
||||
.bridge > div:last-child {
|
||||
display: flex;
|
||||
gap: 8px;
|
||||
min-width: 370px;
|
||||
}
|
||||
.onboard {
|
||||
height: 100vh;
|
||||
display: grid;
|
||||
place-items: center;
|
||||
background:
|
||||
radial-gradient(
|
||||
ellipse at 50% 70%,
|
||||
rgba(22, 92, 126, 0.45),
|
||||
transparent 37%
|
||||
),
|
||||
linear-gradient(#06101f, #03070e);
|
||||
position: relative;
|
||||
overflow: hidden;
|
||||
}
|
||||
.onboard section {
|
||||
z-index: 2;
|
||||
width: min(520px, 80vw);
|
||||
text-align: center;
|
||||
display: grid;
|
||||
justify-items: center;
|
||||
}
|
||||
.onboard .origin-light {
|
||||
width: 118px;
|
||||
height: 118px;
|
||||
}
|
||||
.onboard h1 {
|
||||
font-size: 32px;
|
||||
margin: 20px 0 9px;
|
||||
}
|
||||
.onboard p {
|
||||
color: #9db0c4;
|
||||
line-height: 1.8;
|
||||
margin: 0 0 26px;
|
||||
}
|
||||
.onboard label {
|
||||
width: 100%;
|
||||
text-align: left;
|
||||
font-size: 12px;
|
||||
color: #a9bed1;
|
||||
}
|
||||
.onboard input {
|
||||
display: block;
|
||||
width: 100%;
|
||||
margin: 8px 0 14px;
|
||||
padding: 15px 16px;
|
||||
border-radius: 12px;
|
||||
border: 1px solid rgba(164, 201, 235, 0.27);
|
||||
background: rgba(6, 19, 35, 0.85);
|
||||
color: #fff;
|
||||
outline: 0;
|
||||
font-size: 15px;
|
||||
}
|
||||
.onboard button {
|
||||
border: 1px solid rgba(240, 197, 107, 0.65);
|
||||
border-radius: 11px;
|
||||
background: linear-gradient(#bd8e3c, #7f561d);
|
||||
color: #fff;
|
||||
padding: 12px 22px;
|
||||
font-weight: 650;
|
||||
}
|
||||
.onboard footer {
|
||||
position: absolute;
|
||||
bottom: 24px;
|
||||
color: #5e7488;
|
||||
font-size: 11px;
|
||||
}
|
||||
.stars {
|
||||
position: absolute;
|
||||
inset: 0;
|
||||
opacity: 0.45;
|
||||
background-image:
|
||||
radial-gradient(circle at 15% 20%, #fff 0 1px, transparent 1.5px),
|
||||
radial-gradient(circle at 70% 12%, #fff 0 1px, transparent 1.5px),
|
||||
radial-gradient(circle at 42% 39%, #9fd7ff 0 1px, transparent 1.5px);
|
||||
background-size:
|
||||
270px 230px,
|
||||
390px 310px,
|
||||
520px 440px;
|
||||
}
|
||||
.boot {
|
||||
height: 100vh;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
gap: 16px;
|
||||
color: #b9cadb;
|
||||
}
|
||||
.toast {
|
||||
position: fixed;
|
||||
right: 24px;
|
||||
bottom: 24px;
|
||||
background: #18263a;
|
||||
border: 1px solid rgba(255, 143, 147, 0.45);
|
||||
border-radius: 12px;
|
||||
padding: 13px 15px;
|
||||
z-index: 10;
|
||||
max-width: 520px;
|
||||
box-shadow: 0 18px 50px rgba(0, 0, 0, 0.4);
|
||||
font-size: 12px;
|
||||
}
|
||||
.toast button {
|
||||
border: 0;
|
||||
background: transparent;
|
||||
color: #ffb1b4;
|
||||
margin-left: 15px;
|
||||
}
|
||||
.modal-backdrop {
|
||||
position: fixed;
|
||||
inset: 48px 0 0;
|
||||
background: rgba(1, 5, 11, 0.7);
|
||||
backdrop-filter: blur(8px);
|
||||
z-index: 20;
|
||||
display: grid;
|
||||
place-items: center;
|
||||
}
|
||||
.modal {
|
||||
width: min(430px, 80vw);
|
||||
border: 1px solid rgba(164, 201, 235, 0.25);
|
||||
border-radius: 18px;
|
||||
background: #09182a;
|
||||
padding: 25px;
|
||||
box-shadow: 0 28px 80px rgba(0, 0, 0, 0.55);
|
||||
}
|
||||
.modal h2 {
|
||||
font-size: 20px;
|
||||
margin: 0 0 7px;
|
||||
}
|
||||
.modal p {
|
||||
color: var(--muted);
|
||||
font-size: 13px;
|
||||
line-height: 1.6;
|
||||
margin: 0 0 20px;
|
||||
}
|
||||
.modal label {
|
||||
display: block;
|
||||
color: #aabed2;
|
||||
font-size: 12px;
|
||||
}
|
||||
.modal input {
|
||||
display: block;
|
||||
width: 100%;
|
||||
margin: 8px 0 22px;
|
||||
background: rgba(255, 255, 255, 0.04);
|
||||
border: 1px solid var(--line);
|
||||
border-radius: 10px;
|
||||
color: #fff;
|
||||
padding: 12px 13px;
|
||||
outline: none;
|
||||
}
|
||||
.modal > div {
|
||||
display: flex;
|
||||
justify-content: flex-end;
|
||||
gap: 8px;
|
||||
}
|
||||
@media (max-width: 900px) {
|
||||
.app-shell {
|
||||
grid-template-columns: 72px 1fr;
|
||||
}
|
||||
.rail {
|
||||
padding-inline: 8px;
|
||||
}
|
||||
.brand-orbit {
|
||||
height: 62px;
|
||||
}
|
||||
.brand-orbit span {
|
||||
width: 38px;
|
||||
height: 38px;
|
||||
}
|
||||
.rail nav button {
|
||||
justify-content: center;
|
||||
padding: 12px;
|
||||
}
|
||||
.rail nav span,
|
||||
.channel-mini > div:last-child {
|
||||
display: none;
|
||||
}
|
||||
.channel-mini {
|
||||
justify-content: center;
|
||||
}
|
||||
.app-shell:after {
|
||||
inset-left: 72px;
|
||||
}
|
||||
.page {
|
||||
padding: 26px 22px;
|
||||
}
|
||||
.receipt {
|
||||
grid-template-columns: 88px 1fr;
|
||||
}
|
||||
.receipt p {
|
||||
display: none;
|
||||
}
|
||||
.knowledge-workspace {
|
||||
grid-template-columns: 210px 1fr;
|
||||
}
|
||||
.split-list {
|
||||
grid-template-columns: 1fr;
|
||||
}
|
||||
.channel-grid {
|
||||
grid-template-columns: 1fr;
|
||||
}
|
||||
.channel-context {
|
||||
grid-template-columns: repeat(3, 1fr);
|
||||
}
|
||||
}
|
||||
9
product-source/hololake-clean-desktop/src/types.ts
Normal file
9
product-source/hololake-clean-desktop/src/types.ts
Normal file
|
|
@ -0,0 +1,9 @@
|
|||
export type SourceKind = "USER_MESSAGE" | "PERSONA_RESPONSE" | "SYSTEM_CONTEXT" | "PROTOCOL_EVENT" | "AGENT_ACTION" | "TOOL_RESULT" | "SYSTEM_RECEIPT";
|
||||
|
||||
export interface Channel { channelId: string; name: string; createdAt: string; privateGitPath: string; publicDistributionState: string; }
|
||||
export interface TimelineEvent { eventId: string; sourceKind: SourceKind; title: string; content: string; occurredAt: string; state: "RECORDED" | "WAITING" | "RUNNING" | "SUCCEEDED" | "FAILED"; }
|
||||
export interface KnowledgeDocument { path: string; title: string; body: string; updatedAt: string; sha256: string; }
|
||||
export interface KnowledgeSummary { path: string; title: string; updatedAt: string; sha256: string; }
|
||||
export interface SystemSnapshot { appVersion: string; channel: Channel | null; timeline: TimelineEvent[]; documents: KnowledgeSummary[]; moduleRegistryVersion: string; publicUpdateEndpoint: string; publicDistributionState: string; externalAiBridges: ExternalAiBridge[]; }
|
||||
export interface ExternalAiBridge { bridgeId: string; displayName: string; inboxPath: string; outboxPath: string; state: string; }
|
||||
export interface MutationReceipt { receiptId: string; state: string; message: string; gitCommit: string | null; event: TimelineEvent; }
|
||||
1
product-source/hololake-clean-desktop/src/vite-env.d.ts
vendored
Normal file
1
product-source/hololake-clean-desktop/src/vite-env.d.ts
vendored
Normal file
|
|
@ -0,0 +1 @@
|
|||
/// <reference types="vite/client" />
|
||||
Loading…
Reference in a new issue