Skip to content
Merged
Show file tree
Hide file tree
Changes from 3 commits
Commits
Show all changes
16 commits
Select commit Hold shift + click to select a range
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
8 changes: 5 additions & 3 deletions Architecture.md
Original file line number Diff line number Diff line change
Expand Up @@ -452,8 +452,7 @@ frontend/
│ │ │ ├── FeedbackModal.tsx # Feedback modal
│ │ │ ├── MessageContent.tsx # Message display
│ │ │ ├── MessageWindow.tsx # Chat window
│ │ │ ├── SelectField.tsx # Initialization form select field
│ │ │ └── SuggestedPrompts.tsx # Prompt suggestions
│ │ │ └── SelectField.tsx # Initialization form select field
│ │ └── utils/
│ │ ├── exportHelper.ts # Export functionality
│ │ ├── feedbackHelper.tsx # Feedback functionality
Expand All @@ -467,11 +466,14 @@ frontend/
│ │ └── letterHelper.tsx # Letter generation functionality
│ └── shared/ # Shared components and utils
│ │ ├── components/
│ │ │ ├── Navbar/
│ │ │ │ ├── Navbar.tsx # Navigation
│ │ │ │ └── NavbarMenuButton.tsx # Navigation component
│ │ │ ├── BackLink.tsx # Navigation component
│ │ │ ├── BeaverIcon.tsx # Oregon-themed icon
│ │ │ ├── DisclaimerLayout.tsx # Layout for disclaimer components
│ │ │ ├── FeatureSnippet.tsx # Features and references component
│ │ │ ├── MessageContainer.tsx # Layout for main UI component
│ │ │ ├── Navbar.tsx # Navigation
│ │ │ └── TenantFirstAidLogo.tsx # Application logo
│ │ ├── constants/
│ │ │ └── constants.ts # File of constants
Expand Down
7 changes: 4 additions & 3 deletions backend/tenantfirstaid/chat.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,9 +6,10 @@

MODEL = os.getenv("MODEL_NAME", "gemini-2.5-pro")

RESPONSE_WORD_LIMIT = 350
OREGON_LAW_CENTER_PHONE_NUMBER = "888-585-9638"
DEFAULT_INSTRUCTIONS = f"""Pretend you're a legal expert who is giving advice about housing and tenants' rights in Oregon.
Please give full, detailed answers.
Please give full, detailed answers, limit your responses to under {RESPONSE_WORD_LIMIT} words whenever possible.
Please only ask one question at a time so that the user isn't confused.
If the user is being evicted for non-payment of rent and they are too poor to pay the rent and you have confirmed in various ways that the notice is valid and there is a valid court hearing date, then tell them to call Oregon Law Center at {OREGON_LAW_CENTER_PHONE_NUMBER}.
Focus on finding technicalities that would legally prevent someone getting evicted, such as deficiencies in notice.
Expand All @@ -31,7 +32,7 @@

If the user asks questions about Section 8 or the HomeForward program, search the web for the correct answer and provide a link to the page you used, using the same format as above.

If the user asks to make/generate/create/draft a letter, you should return a formatted letter after your conversational response. Add a delimiter -----generate letter----- to separate the two content. Place this formatted letter at the end of the response. You can include <a>, <em>, and <strong> tags for additional formatting. Proof-read the letter for accuracy in content and tone.
If the user asks to make/generate/create/draft a letter, **always return a formatted letter after your conversational response**. Add a delimiter -----generate letter----- to separate the two content. Place this formatted letter at the end of the response. You can include <a>, <em>, and <strong> tags for additional formatting. Proof-read the letter for accuracy in content and tone.

You can use the following as the initial letter template:

Expand Down Expand Up @@ -66,7 +67,7 @@
[Your Name]


If they provide details replace the issue in the template.
If they provide details replace the issue in the template. If they didn't ask for a letter, don't need to generate one.
"""


Expand Down
54 changes: 3 additions & 51 deletions frontend/src/About.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -15,20 +15,20 @@ export default function About() {
volunteer-built program by{" "}
<a
href="https://www.codepdx.org/"
className="text-blue-600 underline"
className="text-blue-link hover:text-blue-dark"
>
Code PDX
</a>{" "}
and{" "}
<a
href="https://www.qiu-qiulaw.com/"
className="text-blue-600 underline"
className="text-blue-link hover:text-blue-dark"
>
Qiu Qiu Law
</a>
.
</p>
<p className="mb-6 text-gray-700">
<p className="mb-6">
It&apos;s called "Tenant First Aid" because it&apos;s like emergency
help for renters facing eviction—quick, clear, and focused on what to
do right now. Just like medical first aid helps stabilize someone
Expand All @@ -40,13 +40,6 @@ export default function About() {
<p>Michael Zhang</p>
<p>Attorney, licensed in Oregon and Washington</p>
<p>{CONTACT_EMAIL}</p>
<h2 className="text-2xl font-semibold mt-6 mb-2">Features</h2>
<ul className="list-disc list-inside">
<li>Instant answers to common rental questions</li>
<li>Guidance on tenant rights and landlord obligations</li>
<li>Easy-to-use chat interface</li>
<li>Available 24/7</li>
</ul>
<h2 className="text-2xl font-semibold mt-6 mb-2">How It Works</h2>
<p className="mb-6">
Simply type your question or describe your situation, and Tenant First
Expand All @@ -58,47 +51,6 @@ export default function About() {
Tenant First Aid does not store any personal data. All interactions
are processed in real-time and not saved for future use.
</p>
<h2 className="text-2xl font-semibold mt-6 mb-2">Quick Facts:</h2>
<ul className="list-disc list-inside mb-6">
<li>Uses Google's `gemini-2.5-pro` model</li>
<li>
Reference library:
<ul className="list-none pl-6 mt-1">
<li>
<a
href="https://www.oregonlegislature.gov/bills_laws/ors/ors090.html"
className="text-blue-600 underline"
>
ORS 90 (as amended 2023)
</a>
</li>
<li>
<a
href="https://www.oregonlegislature.gov/bills_laws/ors/ors105.html"
className="text-blue-600 underline"
>
ORS 105
</a>
</li>
<li>
<a
href="https://eugene.municipal.codes/EC/8.425"
className="text-blue-600 underline"
>
Eugene Code Section 8.425
</a>
</li>
<li>
<a
href="https://www.portland.gov/code/30/all"
className="text-blue-600 underline"
>
Portland City Code Title 30
</a>
</li>
</ul>
</li>
</ul>
<h2 className="text-2xl font-semibold mt-6 mb-2">
Legal Disclaimer & Privacy Notice
</h2>
Expand Down
2 changes: 1 addition & 1 deletion frontend/src/App.tsx
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import { BrowserRouter as Router, Route, Routes } from "react-router-dom";
import Chat from "./Chat";
import About from "./About";
import Navbar from "./shared/components/Navbar";
import Navbar from "./shared/components/Navbar/Navbar";
import Disclaimer from "./Disclaimer";
import PrivacyPolicy from "./PrivacyPolicy";
import Letter from "./Letter";
Expand Down
34 changes: 21 additions & 13 deletions frontend/src/Chat.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import useMessages from "./hooks/useMessages";
import { useLetterContent } from "./hooks/useLetterContent";
import ChatDisclaimer from "./pages/Chat/components/ChatDisclaimer";
import MessageContainer from "./shared/components/MessageContainer";
import FeatureSnippet from "./shared/components/FeatureSnippet";

export default function Chat() {
const { addMessage, messages, setMessages } = useMessages();
Expand All @@ -11,20 +12,27 @@ export default function Chat() {

return (
<div className="flex pt-16 h-screen items-center justify-center">
<div className="flex-1 h-full sm:h-auto items-center transition-all duration-300">
<MessageContainer isOngoing={isOngoing} letterContent={letterContent}>
<div
className={`flex flex-col ${letterContent === "" ? "flex-1" : "flex-1/3"}`}
>
<MessageWindow
messages={messages}
addMessage={addMessage}
setMessages={setMessages}
isOngoing={isOngoing}
/>
<div className="h-full w-full flex flex-col lg:flex-row gap-4 transition-all duration-300 md:px-4 max-w-[1400px]">
<div className="my-auto w-full flex">
<MessageContainer isOngoing={isOngoing} letterContent={letterContent}>
<div
className={`flex flex-col ${letterContent === "" ? "flex-1" : "flex-1/3"}`}
>
<MessageWindow
messages={messages}
addMessage={addMessage}
setMessages={setMessages}
isOngoing={isOngoing}
/>
</div>
</MessageContainer>
</div>
<div className="flex flex-col m-auto lg:h-[620px] lg:max-w-[300px] rounded-lg bg-paper-background">
<FeatureSnippet />
<div className="p-4">
<ChatDisclaimer isOngoing={isOngoing} />
</div>
</MessageContainer>
<ChatDisclaimer isOngoing={isOngoing} />
</div>
</div>
</div>
);
Expand Down
9 changes: 6 additions & 3 deletions frontend/src/Disclaimer.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@ export default function Disclaimer() {
contact us at{" "}
<a
href={`mailto:${CONTACT_EMAIL}`}
className="underline text-blue-600"
className="underline text-blue-link hover:text-blue-dark"
aria-label="contact-email"
>
{CONTACT_EMAIL}
Expand All @@ -51,7 +51,10 @@ export default function Disclaimer() {
<h3 className="text-xl font-semibold my-4">2. Privacy</h3>
<p>
Please refer to our{" "}
<Link to="/privacy-policy" className="underline text-blue-600">
<Link
to="/privacy-policy"
className="underline text-blue-link hover:text-blue-dark"
>
Privacy Policy
</Link>{" "}
for information about how we collect, use and disclose information
Expand Down Expand Up @@ -408,7 +411,7 @@ export default function Disclaimer() {
by sending an email to{" "}
<a
href={`mailto:${CONTACT_EMAIL}`}
className="underline text-blue-600"
className="underline text-blue-link hover:text-blue-dark"
aria-label="contact-email"
>
{CONTACT_EMAIL}
Expand Down
53 changes: 33 additions & 20 deletions frontend/src/Letter.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ import MessageContainer from "./shared/components/MessageContainer";
import useHousingContext from "./hooks/useHousingContext";
import { buildChatUserMessage } from "./pages/Chat/utils/formHelper";
import { ILocation } from "./contexts/HousingContext";
import FeatureSnippet from "./shared/components/FeatureSnippet";

export default function Letter() {
const { addMessage, messages, setMessages } = useMessages();
Expand All @@ -19,7 +20,7 @@ export default function Letter() {
const { org, loc } = useParams();
const [startStreaming, setStartStreaming] = useState(false);
const streamLocationRef = useRef<ILocation | null>(null);
const [isLoading, setIsLoading] = useState(true);
const [isGenerating, setIsGenerating] = useState(true);
const dialogRef = useRef<HTMLDialogElement>(null);
const LOADING_DISPLAY_DELAY_MS = 1000;
const { housingLocation, housingType, tenantTopic, issueDescription } =
Expand Down Expand Up @@ -99,7 +100,7 @@ export default function Letter() {
if (messages.length > 1 && messages[1]?.content !== "") {
// Include 1s delay for smoother transition
const timeoutId = setTimeout(
() => setIsLoading(false),
() => setIsGenerating(false),
LOADING_DISPLAY_DELAY_MS,
);
return () => clearTimeout(timeoutId);
Expand All @@ -114,26 +115,38 @@ export default function Letter() {
<>
<div className="flex pt-16 h-screen items-center justify-center">
<LetterGenerationDialog ref={dialogRef} />
<div className="flex-1 h-full sm:h-auto items-center transition-all duration-300">
<MessageContainer isOngoing={isOngoing} letterContent={letterContent}>
<div
className={`flex flex-col ${letterContent === "" ? "flex-1" : "flex-1/3"}`}
<div className="h-full w-full flex flex-col lg:flex-row gap-4 transition-all duration-300 sm:px-4 max-w-[1400px]">
<div className="my-auto w-full flex">
<MessageContainer
isOngoing={isOngoing}
letterContent={letterContent}
>
{isLoading ? (
<div className="flex flex-1 items-center justify-center animate-pulse text-lg">
Generating letter...
</div>
) : (
<MessageWindow
messages={messages}
addMessage={addMessage}
setMessages={setMessages}
isOngoing={isOngoing}
/>
)}
<div
className={`flex flex-col ${letterContent === "" ? "flex-1" : "flex-1/3"}`}
>
{isGenerating ? (
<div className="h-full flex items-center justify-center">
<div className="animate-pulse text-lg">
Generating Letter...
</div>
</div>
) : (
<MessageWindow
messages={messages}
addMessage={addMessage}
setMessages={setMessages}
isOngoing={isOngoing}
/>
)}
</div>
</MessageContainer>
</div>
<div className="flex flex-col m-auto lg:h-[620px] lg:max-w-[300px] rounded-lg bg-paper-background">
<FeatureSnippet />
<div className="p-4">
<LetterDisclaimer isOngoing={isOngoing} />
</div>
</MessageContainer>
<LetterDisclaimer isOngoing={isOngoing} />
</div>
</div>
</div>
</>
Expand Down
8 changes: 4 additions & 4 deletions frontend/src/PrivacyPolicy.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -91,14 +91,14 @@ export default function PrivacyPolicy() {
(Google:{" "}
<Link
to="https://policies.google.com/privacy"
className="underline text-blue-600"
className="text-blue-link hover:text-blue-dark"
>
Privacy Policy
</Link>{" "}
and{" "}
<Link
to="https://policies.google.com/terms"
className="underline text-blue-600"
className="text-blue-link hover:text-blue-dark"
>
Terms of Service
</Link>
Expand Down Expand Up @@ -214,7 +214,7 @@ export default function PrivacyPolicy() {
interacting with the Services, or emailing us at{" "}
<a
href={`mailto:${CONTACT_EMAIL}`}
className="underline text-blue-600"
className="text-blue-link hover:text-blue-dark"
aria-label="contact-email"
>
{CONTACT_EMAIL}
Expand All @@ -239,7 +239,7 @@ export default function PrivacyPolicy() {
at:{" "}
<a
href={`mailto:${CONTACT_EMAIL}`}
className="underline text-blue-600"
className="text-blue-link hover:text-blue-dark"
aria-label="contact-email"
>
{CONTACT_EMAIL}
Expand Down
11 changes: 11 additions & 0 deletions frontend/src/contexts/HousingContext.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ export interface IHousingContextType {
handleIssueDescription: (
event: React.ChangeEvent<HTMLTextAreaElement>,
) => void;
handleFormReset: () => void;
}

const HousingContext = createContext<IHousingContextType | null>(null);
Expand Down Expand Up @@ -59,6 +60,14 @@ export default function HousingContextProvider({ children }: Props) {
[],
);

const handleFormReset = useCallback(() => {
setCity(null);
setHousingLocation({ city: null, state: null });
setHousingType(null);
setTenantTopic(null);
setIssueDescription("");
}, []);

const housingContextObject = useMemo(
() => ({
housingLocation,
Expand All @@ -71,6 +80,7 @@ export default function HousingContextProvider({ children }: Props) {
handleHousingChange,
handleTenantTopic,
handleIssueDescription,
handleFormReset,
}),
[
housingLocation,
Expand All @@ -83,6 +93,7 @@ export default function HousingContextProvider({ children }: Props) {
handleHousingChange,
handleTenantTopic,
handleIssueDescription,
handleFormReset,
],
);

Expand Down
Loading
Loading