mirror of
				https://github.com/ChatGPTNextWeb/ChatGPT-Next-Web.git
				synced 2025-11-04 08:13:43 +08:00 
			
		
		
		
	feat: session-level model config
This commit is contained in:
		@@ -138,9 +138,7 @@
 | 
			
		||||
.sidebar-body {
 | 
			
		||||
  flex: 1;
 | 
			
		||||
  overflow: auto;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.chat-list {
 | 
			
		||||
  overflow-x: hidden;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.chat-item {
 | 
			
		||||
 
 | 
			
		||||
@@ -2,7 +2,7 @@
 | 
			
		||||
 | 
			
		||||
require("../polyfill");
 | 
			
		||||
 | 
			
		||||
import { useState, useEffect, StyleHTMLAttributes } from "react";
 | 
			
		||||
import { useState, useEffect } from "react";
 | 
			
		||||
 | 
			
		||||
import styles from "./home.module.scss";
 | 
			
		||||
 | 
			
		||||
@@ -10,7 +10,6 @@ import BotIcon from "../icons/bot.svg";
 | 
			
		||||
import LoadingIcon from "../icons/three-dots.svg";
 | 
			
		||||
 | 
			
		||||
import { getCSSVar, useMobileScreen } from "../utils";
 | 
			
		||||
import { Chat } from "./chat";
 | 
			
		||||
 | 
			
		||||
import dynamic from "next/dynamic";
 | 
			
		||||
import { Path } from "../constant";
 | 
			
		||||
@@ -38,6 +37,10 @@ const Settings = dynamic(async () => (await import("./settings")).Settings, {
 | 
			
		||||
  loading: () => <Loading noLogo />,
 | 
			
		||||
});
 | 
			
		||||
 | 
			
		||||
const Chat = dynamic(async () => (await import("./chat")).Chat, {
 | 
			
		||||
  loading: () => <Loading noLogo />,
 | 
			
		||||
});
 | 
			
		||||
 | 
			
		||||
export function useSwitchTheme() {
 | 
			
		||||
  const config = useAppConfig();
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -19,20 +19,6 @@
 | 
			
		||||
  cursor: pointer;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.password-input-container {
 | 
			
		||||
  max-width: 50%;
 | 
			
		||||
  display: flex;
 | 
			
		||||
  justify-content: flex-end;
 | 
			
		||||
 | 
			
		||||
  .password-eye {
 | 
			
		||||
    margin-right: 4px;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  .password-input {
 | 
			
		||||
    min-width: 80%;
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.user-prompt-modal {
 | 
			
		||||
  min-height: 40vh;
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -9,10 +9,7 @@ import CloseIcon from "../icons/close.svg";
 | 
			
		||||
import CopyIcon from "../icons/copy.svg";
 | 
			
		||||
import ClearIcon from "../icons/clear.svg";
 | 
			
		||||
import EditIcon from "../icons/edit.svg";
 | 
			
		||||
import EyeIcon from "../icons/eye.svg";
 | 
			
		||||
import EyeOffIcon from "../icons/eye-off.svg";
 | 
			
		||||
 | 
			
		||||
import { Input, List, ListItem, Modal, Popover } from "./ui-lib";
 | 
			
		||||
import { Input, List, ListItem, Modal, PasswordInput, Popover } from "./ui-lib";
 | 
			
		||||
 | 
			
		||||
import { IconButton } from "./button";
 | 
			
		||||
import {
 | 
			
		||||
@@ -24,6 +21,8 @@ import {
 | 
			
		||||
  useAccessStore,
 | 
			
		||||
  ModalConfigValidator,
 | 
			
		||||
  useAppConfig,
 | 
			
		||||
  ChatConfig,
 | 
			
		||||
  ModelConfig,
 | 
			
		||||
} from "../store";
 | 
			
		||||
import { Avatar } from "./chat";
 | 
			
		||||
 | 
			
		||||
@@ -155,26 +154,127 @@ function SettingItem(props: {
 | 
			
		||||
  );
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
function PasswordInput(props: HTMLProps<HTMLInputElement>) {
 | 
			
		||||
  const [visible, setVisible] = useState(false);
 | 
			
		||||
 | 
			
		||||
  function changeVisibility() {
 | 
			
		||||
    setVisible(!visible);
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
export function ModelConfigList(props: {
 | 
			
		||||
  modelConfig: ModelConfig;
 | 
			
		||||
  updateConfig: (updater: (config: ModelConfig) => void) => void;
 | 
			
		||||
}) {
 | 
			
		||||
  return (
 | 
			
		||||
    <div className={styles["password-input-container"]}>
 | 
			
		||||
      <IconButton
 | 
			
		||||
        icon={visible ? <EyeIcon /> : <EyeOffIcon />}
 | 
			
		||||
        onClick={changeVisibility}
 | 
			
		||||
        className={styles["password-eye"]}
 | 
			
		||||
      />
 | 
			
		||||
      <input
 | 
			
		||||
        {...props}
 | 
			
		||||
        type={visible ? "text" : "password"}
 | 
			
		||||
        className={styles["password-input"]}
 | 
			
		||||
      />
 | 
			
		||||
    </div>
 | 
			
		||||
    <>
 | 
			
		||||
      <SettingItem title={Locale.Settings.Model}>
 | 
			
		||||
        <select
 | 
			
		||||
          value={props.modelConfig.model}
 | 
			
		||||
          onChange={(e) => {
 | 
			
		||||
            props.updateConfig(
 | 
			
		||||
              (config) =>
 | 
			
		||||
                (config.model = ModalConfigValidator.model(
 | 
			
		||||
                  e.currentTarget.value,
 | 
			
		||||
                )),
 | 
			
		||||
            );
 | 
			
		||||
          }}
 | 
			
		||||
        >
 | 
			
		||||
          {ALL_MODELS.map((v) => (
 | 
			
		||||
            <option value={v.name} key={v.name} disabled={!v.available}>
 | 
			
		||||
              {v.name}
 | 
			
		||||
            </option>
 | 
			
		||||
          ))}
 | 
			
		||||
        </select>
 | 
			
		||||
      </SettingItem>
 | 
			
		||||
      <SettingItem
 | 
			
		||||
        title={Locale.Settings.Temperature.Title}
 | 
			
		||||
        subTitle={Locale.Settings.Temperature.SubTitle}
 | 
			
		||||
      >
 | 
			
		||||
        <InputRange
 | 
			
		||||
          value={props.modelConfig.temperature?.toFixed(1)}
 | 
			
		||||
          min="0"
 | 
			
		||||
          max="2"
 | 
			
		||||
          step="0.1"
 | 
			
		||||
          onChange={(e) => {
 | 
			
		||||
            props.updateConfig(
 | 
			
		||||
              (config) =>
 | 
			
		||||
                (config.temperature = ModalConfigValidator.temperature(
 | 
			
		||||
                  e.currentTarget.valueAsNumber,
 | 
			
		||||
                )),
 | 
			
		||||
            );
 | 
			
		||||
          }}
 | 
			
		||||
        ></InputRange>
 | 
			
		||||
      </SettingItem>
 | 
			
		||||
      <SettingItem
 | 
			
		||||
        title={Locale.Settings.MaxTokens.Title}
 | 
			
		||||
        subTitle={Locale.Settings.MaxTokens.SubTitle}
 | 
			
		||||
      >
 | 
			
		||||
        <input
 | 
			
		||||
          type="number"
 | 
			
		||||
          min={100}
 | 
			
		||||
          max={32000}
 | 
			
		||||
          value={props.modelConfig.max_tokens}
 | 
			
		||||
          onChange={(e) =>
 | 
			
		||||
            props.updateConfig(
 | 
			
		||||
              (config) =>
 | 
			
		||||
                (config.max_tokens = ModalConfigValidator.max_tokens(
 | 
			
		||||
                  e.currentTarget.valueAsNumber,
 | 
			
		||||
                )),
 | 
			
		||||
            )
 | 
			
		||||
          }
 | 
			
		||||
        ></input>
 | 
			
		||||
      </SettingItem>
 | 
			
		||||
      <SettingItem
 | 
			
		||||
        title={Locale.Settings.PresencePenlty.Title}
 | 
			
		||||
        subTitle={Locale.Settings.PresencePenlty.SubTitle}
 | 
			
		||||
      >
 | 
			
		||||
        <InputRange
 | 
			
		||||
          value={props.modelConfig.presence_penalty?.toFixed(1)}
 | 
			
		||||
          min="-2"
 | 
			
		||||
          max="2"
 | 
			
		||||
          step="0.1"
 | 
			
		||||
          onChange={(e) => {
 | 
			
		||||
            props.updateConfig(
 | 
			
		||||
              (config) =>
 | 
			
		||||
                (config.presence_penalty =
 | 
			
		||||
                  ModalConfigValidator.presence_penalty(
 | 
			
		||||
                    e.currentTarget.valueAsNumber,
 | 
			
		||||
                  )),
 | 
			
		||||
            );
 | 
			
		||||
          }}
 | 
			
		||||
        ></InputRange>
 | 
			
		||||
      </SettingItem>
 | 
			
		||||
 | 
			
		||||
      <SettingItem
 | 
			
		||||
        title={Locale.Settings.HistoryCount.Title}
 | 
			
		||||
        subTitle={Locale.Settings.HistoryCount.SubTitle}
 | 
			
		||||
      >
 | 
			
		||||
        <InputRange
 | 
			
		||||
          title={props.modelConfig.historyMessageCount.toString()}
 | 
			
		||||
          value={props.modelConfig.historyMessageCount}
 | 
			
		||||
          min="0"
 | 
			
		||||
          max="25"
 | 
			
		||||
          step="1"
 | 
			
		||||
          onChange={(e) =>
 | 
			
		||||
            props.updateConfig(
 | 
			
		||||
              (config) => (config.historyMessageCount = e.target.valueAsNumber),
 | 
			
		||||
            )
 | 
			
		||||
          }
 | 
			
		||||
        ></InputRange>
 | 
			
		||||
      </SettingItem>
 | 
			
		||||
 | 
			
		||||
      <SettingItem
 | 
			
		||||
        title={Locale.Settings.CompressThreshold.Title}
 | 
			
		||||
        subTitle={Locale.Settings.CompressThreshold.SubTitle}
 | 
			
		||||
      >
 | 
			
		||||
        <input
 | 
			
		||||
          type="number"
 | 
			
		||||
          min={500}
 | 
			
		||||
          max={4000}
 | 
			
		||||
          value={props.modelConfig.compressMessageLengthThreshold}
 | 
			
		||||
          onChange={(e) =>
 | 
			
		||||
            props.updateConfig(
 | 
			
		||||
              (config) =>
 | 
			
		||||
                (config.compressMessageLengthThreshold =
 | 
			
		||||
                  e.currentTarget.valueAsNumber),
 | 
			
		||||
            )
 | 
			
		||||
          }
 | 
			
		||||
        ></input>
 | 
			
		||||
      </SettingItem>
 | 
			
		||||
    </>
 | 
			
		||||
  );
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
@@ -505,44 +605,6 @@ export function Settings() {
 | 
			
		||||
              />
 | 
			
		||||
            )}
 | 
			
		||||
          </SettingItem>
 | 
			
		||||
 | 
			
		||||
          <SettingItem
 | 
			
		||||
            title={Locale.Settings.HistoryCount.Title}
 | 
			
		||||
            subTitle={Locale.Settings.HistoryCount.SubTitle}
 | 
			
		||||
          >
 | 
			
		||||
            <InputRange
 | 
			
		||||
              title={config.historyMessageCount.toString()}
 | 
			
		||||
              value={config.historyMessageCount}
 | 
			
		||||
              min="0"
 | 
			
		||||
              max="25"
 | 
			
		||||
              step="1"
 | 
			
		||||
              onChange={(e) =>
 | 
			
		||||
                updateConfig(
 | 
			
		||||
                  (config) =>
 | 
			
		||||
                    (config.historyMessageCount = e.target.valueAsNumber),
 | 
			
		||||
                )
 | 
			
		||||
              }
 | 
			
		||||
            ></InputRange>
 | 
			
		||||
          </SettingItem>
 | 
			
		||||
 | 
			
		||||
          <SettingItem
 | 
			
		||||
            title={Locale.Settings.CompressThreshold.Title}
 | 
			
		||||
            subTitle={Locale.Settings.CompressThreshold.SubTitle}
 | 
			
		||||
          >
 | 
			
		||||
            <input
 | 
			
		||||
              type="number"
 | 
			
		||||
              min={500}
 | 
			
		||||
              max={4000}
 | 
			
		||||
              value={config.compressMessageLengthThreshold}
 | 
			
		||||
              onChange={(e) =>
 | 
			
		||||
                updateConfig(
 | 
			
		||||
                  (config) =>
 | 
			
		||||
                    (config.compressMessageLengthThreshold =
 | 
			
		||||
                      e.currentTarget.valueAsNumber),
 | 
			
		||||
                )
 | 
			
		||||
              }
 | 
			
		||||
            ></input>
 | 
			
		||||
          </SettingItem>
 | 
			
		||||
        </List>
 | 
			
		||||
 | 
			
		||||
        <List>
 | 
			
		||||
@@ -578,85 +640,14 @@ export function Settings() {
 | 
			
		||||
        </List>
 | 
			
		||||
 | 
			
		||||
        <List>
 | 
			
		||||
          <SettingItem title={Locale.Settings.Model}>
 | 
			
		||||
            <select
 | 
			
		||||
              value={config.modelConfig.model}
 | 
			
		||||
              onChange={(e) => {
 | 
			
		||||
                updateConfig(
 | 
			
		||||
                  (config) =>
 | 
			
		||||
                    (config.modelConfig.model = ModalConfigValidator.model(
 | 
			
		||||
                      e.currentTarget.value,
 | 
			
		||||
                    )),
 | 
			
		||||
                );
 | 
			
		||||
              }}
 | 
			
		||||
            >
 | 
			
		||||
              {ALL_MODELS.map((v) => (
 | 
			
		||||
                <option value={v.name} key={v.name} disabled={!v.available}>
 | 
			
		||||
                  {v.name}
 | 
			
		||||
                </option>
 | 
			
		||||
              ))}
 | 
			
		||||
            </select>
 | 
			
		||||
          </SettingItem>
 | 
			
		||||
          <SettingItem
 | 
			
		||||
            title={Locale.Settings.Temperature.Title}
 | 
			
		||||
            subTitle={Locale.Settings.Temperature.SubTitle}
 | 
			
		||||
          >
 | 
			
		||||
            <InputRange
 | 
			
		||||
              value={config.modelConfig.temperature?.toFixed(1)}
 | 
			
		||||
              min="0"
 | 
			
		||||
              max="2"
 | 
			
		||||
              step="0.1"
 | 
			
		||||
              onChange={(e) => {
 | 
			
		||||
                updateConfig(
 | 
			
		||||
                  (config) =>
 | 
			
		||||
                    (config.modelConfig.temperature =
 | 
			
		||||
                      ModalConfigValidator.temperature(
 | 
			
		||||
                        e.currentTarget.valueAsNumber,
 | 
			
		||||
                      )),
 | 
			
		||||
                );
 | 
			
		||||
              }}
 | 
			
		||||
            ></InputRange>
 | 
			
		||||
          </SettingItem>
 | 
			
		||||
          <SettingItem
 | 
			
		||||
            title={Locale.Settings.MaxTokens.Title}
 | 
			
		||||
            subTitle={Locale.Settings.MaxTokens.SubTitle}
 | 
			
		||||
          >
 | 
			
		||||
            <input
 | 
			
		||||
              type="number"
 | 
			
		||||
              min={100}
 | 
			
		||||
              max={32000}
 | 
			
		||||
              value={config.modelConfig.max_tokens}
 | 
			
		||||
              onChange={(e) =>
 | 
			
		||||
                updateConfig(
 | 
			
		||||
                  (config) =>
 | 
			
		||||
                    (config.modelConfig.max_tokens =
 | 
			
		||||
                      ModalConfigValidator.max_tokens(
 | 
			
		||||
                        e.currentTarget.valueAsNumber,
 | 
			
		||||
                      )),
 | 
			
		||||
                )
 | 
			
		||||
              }
 | 
			
		||||
            ></input>
 | 
			
		||||
          </SettingItem>
 | 
			
		||||
          <SettingItem
 | 
			
		||||
            title={Locale.Settings.PresencePenlty.Title}
 | 
			
		||||
            subTitle={Locale.Settings.PresencePenlty.SubTitle}
 | 
			
		||||
          >
 | 
			
		||||
            <InputRange
 | 
			
		||||
              value={config.modelConfig.presence_penalty?.toFixed(1)}
 | 
			
		||||
              min="-2"
 | 
			
		||||
              max="2"
 | 
			
		||||
              step="0.5"
 | 
			
		||||
              onChange={(e) => {
 | 
			
		||||
                updateConfig(
 | 
			
		||||
                  (config) =>
 | 
			
		||||
                    (config.modelConfig.presence_penalty =
 | 
			
		||||
                      ModalConfigValidator.presence_penalty(
 | 
			
		||||
                        e.currentTarget.valueAsNumber,
 | 
			
		||||
                      )),
 | 
			
		||||
                );
 | 
			
		||||
              }}
 | 
			
		||||
            ></InputRange>
 | 
			
		||||
          </SettingItem>
 | 
			
		||||
          <ModelConfigList
 | 
			
		||||
            modelConfig={config.modelConfig}
 | 
			
		||||
            updateConfig={(upater) => {
 | 
			
		||||
              const modelConfig = { ...config.modelConfig };
 | 
			
		||||
              upater(modelConfig);
 | 
			
		||||
              config.update((config) => (config.modelConfig = modelConfig));
 | 
			
		||||
            }}
 | 
			
		||||
          />
 | 
			
		||||
        </List>
 | 
			
		||||
 | 
			
		||||
        {shouldShowPromptModal && (
 | 
			
		||||
 
 | 
			
		||||
@@ -1,8 +1,12 @@
 | 
			
		||||
import styles from "./ui-lib.module.scss";
 | 
			
		||||
import LoadingIcon from "../icons/three-dots.svg";
 | 
			
		||||
import CloseIcon from "../icons/close.svg";
 | 
			
		||||
import EyeIcon from "../icons/eye.svg";
 | 
			
		||||
import EyeOffIcon from "../icons/eye-off.svg";
 | 
			
		||||
 | 
			
		||||
import { createRoot } from "react-dom/client";
 | 
			
		||||
import React, { useEffect } from "react";
 | 
			
		||||
import React, { HTMLProps, useEffect, useState } from "react";
 | 
			
		||||
import { IconButton } from "./button";
 | 
			
		||||
 | 
			
		||||
export function Popover(props: {
 | 
			
		||||
  children: JSX.Element;
 | 
			
		||||
@@ -190,3 +194,26 @@ export function Input(props: InputProps) {
 | 
			
		||||
    ></textarea>
 | 
			
		||||
  );
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
export function PasswordInput(props: HTMLProps<HTMLInputElement>) {
 | 
			
		||||
  const [visible, setVisible] = useState(false);
 | 
			
		||||
 | 
			
		||||
  function changeVisibility() {
 | 
			
		||||
    setVisible(!visible);
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  return (
 | 
			
		||||
    <div className={"password-input-container"}>
 | 
			
		||||
      <IconButton
 | 
			
		||||
        icon={visible ? <EyeIcon /> : <EyeOffIcon />}
 | 
			
		||||
        onClick={changeVisibility}
 | 
			
		||||
        className={"password-eye"}
 | 
			
		||||
      />
 | 
			
		||||
      <input
 | 
			
		||||
        {...props}
 | 
			
		||||
        type={visible ? "text" : "password"}
 | 
			
		||||
        className={"password-input"}
 | 
			
		||||
      />
 | 
			
		||||
    </div>
 | 
			
		||||
  );
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -334,14 +334,14 @@ export const useChatStore = create<ChatStore>()(
 | 
			
		||||
        // get short term and unmemoried long term memory
 | 
			
		||||
        const shortTermMemoryMessageIndex = Math.max(
 | 
			
		||||
          0,
 | 
			
		||||
          n - config.historyMessageCount,
 | 
			
		||||
          n - config.modelConfig.historyMessageCount,
 | 
			
		||||
        );
 | 
			
		||||
        const longTermMemoryMessageIndex = session.lastSummarizeIndex;
 | 
			
		||||
        const oldestIndex = Math.max(
 | 
			
		||||
          shortTermMemoryMessageIndex,
 | 
			
		||||
          longTermMemoryMessageIndex,
 | 
			
		||||
        );
 | 
			
		||||
        const threshold = config.compressMessageLengthThreshold;
 | 
			
		||||
        const threshold = config.modelConfig.compressMessageLengthThreshold;
 | 
			
		||||
 | 
			
		||||
        // get recent messages as many as possible
 | 
			
		||||
        const reversedRecentMessages = [];
 | 
			
		||||
@@ -410,7 +410,7 @@ export const useChatStore = create<ChatStore>()(
 | 
			
		||||
        if (historyMsgLength > config?.modelConfig?.max_tokens ?? 4000) {
 | 
			
		||||
          const n = toBeSummarizedMsgs.length;
 | 
			
		||||
          toBeSummarizedMsgs = toBeSummarizedMsgs.slice(
 | 
			
		||||
            Math.max(0, n - config.historyMessageCount),
 | 
			
		||||
            Math.max(0, n - config.modelConfig.historyMessageCount),
 | 
			
		||||
          );
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
@@ -423,11 +423,12 @@ export const useChatStore = create<ChatStore>()(
 | 
			
		||||
          "[Chat History] ",
 | 
			
		||||
          toBeSummarizedMsgs,
 | 
			
		||||
          historyMsgLength,
 | 
			
		||||
          config.compressMessageLengthThreshold,
 | 
			
		||||
          config.modelConfig.compressMessageLengthThreshold,
 | 
			
		||||
        );
 | 
			
		||||
 | 
			
		||||
        if (
 | 
			
		||||
          historyMsgLength > config.compressMessageLengthThreshold &&
 | 
			
		||||
          historyMsgLength >
 | 
			
		||||
            config.modelConfig.compressMessageLengthThreshold &&
 | 
			
		||||
          session.sendMemory
 | 
			
		||||
        ) {
 | 
			
		||||
          requestChatStream(
 | 
			
		||||
 
 | 
			
		||||
@@ -16,8 +16,6 @@ export enum Theme {
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
const DEFAULT_CONFIG = {
 | 
			
		||||
  historyMessageCount: 4,
 | 
			
		||||
  compressMessageLengthThreshold: 1000,
 | 
			
		||||
  sendBotMessages: true as boolean,
 | 
			
		||||
  submitKey: SubmitKey.CtrlEnter as SubmitKey,
 | 
			
		||||
  avatar: "1f603",
 | 
			
		||||
@@ -34,6 +32,8 @@ const DEFAULT_CONFIG = {
 | 
			
		||||
    temperature: 1,
 | 
			
		||||
    max_tokens: 2000,
 | 
			
		||||
    presence_penalty: 0,
 | 
			
		||||
    historyMessageCount: 4,
 | 
			
		||||
    compressMessageLengthThreshold: 1000,
 | 
			
		||||
  },
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -311,3 +311,17 @@ pre {
 | 
			
		||||
    overflow: auto;
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.password-input-container {
 | 
			
		||||
  max-width: 50%;
 | 
			
		||||
  display: flex;
 | 
			
		||||
  justify-content: flex-end;
 | 
			
		||||
 | 
			
		||||
  .password-eye {
 | 
			
		||||
    margin-right: 4px;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  .password-input {
 | 
			
		||||
    min-width: 80%;
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
		Reference in New Issue
	
	Block a user