Added deeplinks, added qr code login, added settings, other stuff

This commit is contained in:
Alois 2026-04-10 19:44:08 +02:00
commit 8ff8bd9528
32 changed files with 917 additions and 78 deletions

View file

@ -10,6 +10,7 @@ import { useTTP } from "@tensamin/ttp/context";
import { useCrypto } from "@tensamin/crypto/context";
import { log, toast } from "@tensamin/shared/log";
import Message from "./message";
import { cn, useIsMobile } from "@tensamin/ui/utils";
export default function InputComponent({
value,
@ -27,7 +28,7 @@ export default function InputComponent({
const { load } = useStorage();
React.useEffect(() => {
void load("chat_invert_enter_behaviour").then((shouldInvert) => {
void load("settings.reverse_enter_behavior").then((shouldInvert) => {
setInvertEnterBehavior(shouldInvert);
});
}, [load]);
@ -100,8 +101,17 @@ export default function InputComponent({
setValue("");
}
const isMobile = useIsMobile();
return (
<Card className="rounded-none rounded-t-xl border-b-0 pt-0 pb-[env(safe-area-inset-bottom)]">
<Card
className={cn(
"rounded-none border-b-0 pt-0 pb-[env(safe-area-inset-bottom)]",
isMobile
? "border-0 border-t-1 fixed w-full bottom-0 left-0 px-2"
: "rounded-t-xl",
)}
>
<CardHeader className="relative p-0 flex flex-col">
<div
aria-hidden="true"

View file

@ -258,7 +258,13 @@ export default function Screen() {
})}
</div>
</div>
<div ref={inputBoxRef}>
<div
ref={inputBoxRef}
className="fixed -bottom-15 pb-15"
style={{
width: "calc(100vw - 277px)",
}}
>
<InputComponent setValue={setValue} value={value} />
</div>
</div>

View file

@ -6,6 +6,7 @@
"exports": {
"./data": "./src/data.ts",
"./log": "./src/log.tsx",
"./settings": "./src/settings.ts",
"./features/legal/schema": "./src/features/legal/schema.ts",
"./features/conversation/schema": "./src/features/conversation/schema.ts"
},

View file

@ -1,6 +1,7 @@
import { z } from "zod";
import { legalDocsSchema } from "./features/legal/schema";
import { settingsStorageDefaults, type SettingsStorageDefaults } from "./settings";
const fileFromMessage = z.object({
name: z.string(),
@ -168,7 +169,7 @@ export const ttp = {
export type TTP = typeof ttp;
// Storage
export interface Storage {
export interface Storage extends SettingsStorageDefaults {
session_id: number;
user_id: number;
private_key: string;
@ -179,7 +180,6 @@ export interface Storage {
analytics_usage_data: boolean;
analytics_done: boolean;
legal_docs: z.infer<typeof legalDocsSchema>;
chat_invert_enter_behaviour: boolean;
}
export const storageDefaults: Storage = {
@ -192,6 +192,7 @@ export const storageDefaults: Storage = {
analytics_crash_reports: true,
analytics_usage_data: true,
analytics_done: false,
...settingsStorageDefaults,
legal_docs: {
eula: {
version: "0.0",
@ -209,5 +210,4 @@ export const storageDefaults: Storage = {
unix: 0,
},
},
chat_invert_enter_behaviour: false,
};

View file

@ -0,0 +1,76 @@
type StringKeyOf<T> = Extract<keyof T, string>;
type SettingDefinition = {
display: string;
type: string;
default?: unknown;
};
export type SettingsSchema = Record<
string,
Record<string, Record<string, SettingDefinition>>
>;
const settings = {
account: {
security: {
login_qr_code: {
display: "Login QR Code",
type: "custom",
},
},
},
accessability: {
chat: {
reverse_enter_behavior: {
display: "Reverse Enter Behavior",
type: "boolean",
default: false,
},
},
},
} as const satisfies SettingsSchema;
export default settings;
type BooleanSettingNames<T extends SettingsSchema> = {
[C in StringKeyOf<T>]: {
[P in StringKeyOf<T[C]>]: {
[S in StringKeyOf<T[C][P]>]: T[C][P][S] extends {
type: "boolean";
default: boolean;
}
? S
: never;
}[StringKeyOf<T[C][P]>];
}[StringKeyOf<T[C]>];
}[StringKeyOf<T>];
export type SettingsStorageKey<T extends SettingsSchema = typeof settings> =
`settings.${BooleanSettingNames<T>}`;
export type SettingsStorageDefaults<T extends SettingsSchema = typeof settings> =
{
[K in SettingsStorageKey<T>]: boolean;
};
function buildSettingsStorageDefaults<T extends SettingsSchema>(
schema: T,
): SettingsStorageDefaults<T> {
const defaults: Partial<SettingsStorageDefaults<T>> = {};
for (const category of Object.values(schema)) {
for (const page of Object.values(category)) {
for (const [settingName, setting] of Object.entries(page)) {
if (setting.type === "boolean" && typeof setting.default === "boolean") {
const key = `settings.${settingName}` as SettingsStorageKey<T>;
defaults[key] = setting.default;
}
}
}
}
return defaults as SettingsStorageDefaults<T>;
}
export const settingsStorageDefaults = buildSettingsStorageDefaults(settings);

View file

@ -11,12 +11,12 @@
"./utils": "./src/lib/utils.ts"
},
"scripts": {
"patch": "find src -type f \\( -name \"*.ts\" -o -name \"*.tsx\" \\) -print0 | xargs -0 sed -i 's|@/lib/utils|../lib/utils|g; s|@/cmp/|./|g' && bunx prettier --write .",
"format": "bunx prettier --write .",
"lint": "eslint src",
"build": "tsc -p tsconfig.json --noEmit"
},
"dependencies": {
"@tauri-apps/api": "^2",
"@tensamin/tauri": "workspace:*",
"@base-ui/react": "^1.3.0",
"class-variance-authority": "^0.7.1",

View file

@ -0,0 +1,30 @@
import { Switch as SwitchPrimitive } from "@base-ui/react/switch"
import { cn } from "../lib/utils"
function Switch({
className,
size = "default",
...props
}: SwitchPrimitive.Root.Props & {
size?: "sm" | "default"
}) {
return (
<SwitchPrimitive.Root
data-slot="switch"
data-size={size}
className={cn(
"peer group/switch relative inline-flex shrink-0 items-center rounded-full border border-transparent transition-all outline-none after:absolute after:-inset-x-3 after:-inset-y-2 focus-visible:border-ring focus-visible:ring-3 focus-visible:ring-ring/50 aria-invalid:border-destructive aria-invalid:ring-3 aria-invalid:ring-destructive/20 data-[size=default]:h-[18.4px] data-[size=default]:w-[32px] data-[size=sm]:h-[14px] data-[size=sm]:w-[24px] dark:aria-invalid:border-destructive/50 dark:aria-invalid:ring-destructive/40 data-checked:bg-primary data-unchecked:bg-input dark:data-unchecked:bg-input/80 data-disabled:cursor-not-allowed data-disabled:opacity-50",
className
)}
{...props}
>
<SwitchPrimitive.Thumb
data-slot="switch-thumb"
className="pointer-events-none block rounded-full bg-background ring-0 transition-transform group-data-[size=default]/switch:size-4 group-data-[size=sm]/switch:size-3 group-data-[size=default]/switch:data-checked:translate-x-[calc(100%-2px)] group-data-[size=sm]/switch:data-checked:translate-x-[calc(100%-2px)] dark:data-checked:bg-primary-foreground group-data-[size=default]/switch:data-unchecked:translate-x-0 group-data-[size=sm]/switch:data-unchecked:translate-x-0 dark:data-unchecked:bg-foreground"
/>
</SwitchPrimitive.Root>
)
}
export { Switch }

View file

@ -1,5 +1,6 @@
import { cn } from "../lib/utils";
import Controls from "@tensamin/tauri/controls";
import { isTauri } from "@tauri-apps/api/core";
export default function CreateScreen({
children,
@ -41,7 +42,13 @@ import { useIsMobile } from "../lib/utils";
export function StorageDestoryer() {
const isMobile = useIsMobile();
return (
<div className={cn("fixed p-5 bottom-0", isMobile ? "w-full" : "right-0")}>
<div
className={cn(
"fixed p-5 bottom-0",
isMobile ? "w-full" : "right-0",
isTauri() ? "py-10" : "",
)}
>
<AlertDialog>
<AlertDialogTrigger
render={